Commit Graph
19 Commits
Author SHA1 Message Date
momentum devandClaude Fable 5 b3a47d775d feat(019): discovery on its own thread + tuner heartbeat (honest stale alert)
Telemetry: discovery took 6-30s ON the single scheduler, freezing ingest/arm
so the trigger read stale bars — the real 'late edge detection'. Now discovery
runs on its own daemon thread with a dedicated DB connection; the watchlist it
mutates is a lock-guarded Watchlist (list-like drop-in, no torn reads); the
scheduler discover step auto-disables (single-writer). Loop is now 3 fast
threads (entries/exits/discovery) + slow scheduler for tolerant work.

Also: nightly_tune writes a heartbeat every run, so the boot alert distinguishes
'tuner not running' (real) from 'ran, no profitable ORB config, kept params'
(expected — the strategy has no edge). Kills the misleading Telegram alarm.
543 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:44:10 -05:00
momentum devandClaude Fable 5 5896d291d8 feat(018): sub-second exits on a dedicated thread + fast decision pipeline
Exits move off the single-threaded scheduler onto their own daemon thread at
1s (mirrors the fast trigger thread). Dedicated research connection (shared
facade isn't thread-safe); scheduler exits step auto-disabled when the thread
runs (single-caller invariant — no racing instance state, no double-submit);
manage() already idempotent (stop ratchets >1c up only). 3 driver tests + 539
suite green. On fast small-caps this is the 'get out fast' the owner asked for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:32:46 -05:00
momentum devandClaude Fable 5 680ddc2174 loop: faster decision pipeline — ingest 20->5s, arm 20->8s, watch 15->8s, trigger 4->2s
Owner: 'make all decisions as fast as possible.' The real entry-detection
ceiling was ingest — bars refreshed every 20s, so the 4s trigger thread read
data up to 20s stale and detected breakouts late. Ingest is incremental+cheap,
safe at 5s. Fresher bars + faster arm (trigger levels) + 2s trigger check +
the 4s exits shipped earlier = the whole ingest->detect->decide->exit loop
tightened. One batched data call per cycle so Alpaca rate limits are fine.
Sub-second exit management on a dedicated thread is the next (code) step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 22:04:29 -05:00
momentum devandClaude Fable 5 c3828eff15 exits: manage every 4s not 12s — kill the give-back on fast small-cap round-trips
Owner's real complaint: late exits gave back gains. Exits ran on the slow
scheduler at 12s while entries run on the 4s fast thread — exits were 3x
slower than entries. A small-cap can spike and round-trip inside 12s. Drop
TRADING_EXIT_MANAGE_INTERVAL_SECONDS to 4s (config, reversible, both compose
files). Deeper fix (exit mgmt on the sub-second trigger thread) planned
separately. Entry chase-tail (24% fill >5% above break) is the next
investigation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:53:57 -05:00
momentum devandClaude Opus 4.8 acdf0019c3 Regime gate: document TRADING_REGIME_GATE_P_MIN in both compose anchors
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:21:49 -05:00
momentum devandClaude Opus 4.8 0ac09d3574 Regime gate LIVE (plan 011): gate fresh entries on the session-level metalabel
Wires the regime signal into the money path — fresh entries are BLOCKED on
sessions the walk-forward model scores unfavorable. Session-level: evaluated
once per day, cached. Enabled in both compose templates (paper).

Correctness (blocker 1 resolved): the LIVE feature set drops tape_regime (the
only non-strictly-open feature — AND measured 0.000 importance) + breadth_gap
(also 0.000). The 5 remaining features are all non-look-ahead at entry time:
breadth_rvol = median(relative_volume=vol_upto/avg_vol_20d) over the session's
setups so far (the SAME up-to-trigger calc the live bot runs, NOT day-end),
spy_* lagged to prior close, trailing from PRIOR sessions. The live single-
session fit reproduces the batch meta_v1 verdict exactly on the last 6 sessions.

Safety: fail-open everywhere — disabled, unfit model (<60 prior sessions),
missing breadth_rvol (pre-setup), or ANY exception returns None and never
blocks. The gate can only SUBTRACT trades on bad-regime days.

- research/regime.py: live_session_favorable() (refit on priors <today, predict
  today; writes regime_scores['meta_live'] for forward validation) + LIVE_FEATURE_COLS
  + the breadth_rvol-missing fail-open guard.
- trading_execution.py: TRADING_REGIME_GATE_ENABLED / _P_MIN settings + _regime_reject()
  (cached per session) + a REGIME gate after SESSION in _evaluate_entry_gates.
- docker-compose.yml + truenas-compose.yml: TRADING_REGIME_GATE_ENABLED=1.
- tests/unit/test_regime_gate_live.py: block / pass / fail-open(x3) / scored-once-per-day.

KNOWN train/serve skew (documented, validate forward via meta_live vs EOD label):
model trains on full-day breadth_rvol, live uses setups-so-far. Small edge on a
~266-session sample; paper only. Suite: 504 passed, 15 pre-existing loopback fails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 14:11:50 -05:00
momentum devandClaude Opus 4.8 092d6072e8 Regime gate (plan 011): deploy-wire the shadow pipeline + on-demand admin trigger + prod backtest
The session-level metalabel P(our ORB profits today) — the one lever that
survives out-of-sample. Backtested on 266 prod sessions (walk-forward):

  trade-always (trained subset): -0.111R/session, 36% hit
  meta_v1 GATED (trades best ~32% of days): -0.004R/session, 48% hit
  delta +0.107R in-sample, +0.128R OOS (recent 80 sessions), improves across folds

It does NOT make the bot profitable — it takes a bleeding strategy to roughly
flat by sitting out the worst ~2/3 of sessions. threshold_v1 rule gates too
little to matter; meta_v1 (walk-forward GBM) is the signal. Leaky full-fit
(+0.61R) >> walk-forward confirms the discipline was load-bearing; the OOS
holding is the evidence the +0.078R is real, not look-ahead.

Changes:
- research/regime.py: batch minute-bar load per session (64k per-fire round-trips
  -> ~266 queries; fixed a >5min build to ~3min) + incremental label pass (skip
  already-labeled sessions; --rebuild forces full). Nightly cost now ~one new day.
- docker-compose.yml + truenas-compose.yml: scheduler runs build-features + score
  (both models) before `experiments run`, nightly. ALL-SHADOW — does NOT gate live
  trading (that stays owner-gated Step 5).
- admin_api.py + admin.html: POST /api/regime/run (detached) + GET /api/regime/status
  + a Run/Status button — the panel had no way to trigger this.
- plans/011 + docs/research/regime_notes.md (6-book distillation) + plans 008/009 docs.

Prod state (shadow, rollback = DROP regime_features/regime_scores + DELETE
experiments WHERE id LIKE 'regime%'): 266 feature rows, 532 scores, 2 experiments
registered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 14:05:34 -04:00
momentum devandClaude Fable 5 97dcef4992 compose(root): sync managed-exit config to match truenas template
Root anchor lacked the TRADING_EXIT_* block the truenas template already has, so
a fresh root deploy ran no-trail defaults (scratches v1 winners at breakeven).
prior_low trail after +1R (counterfactual: v1 win 17%->24%) + step0.25 + profit
tiers + BE@1R. Only the loop acts on these.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 22:34:27 -04:00
momentum devandClaude Fable 5 5b885a719f ingest(009): nightly SPY/QQQ ingest + catalyst enrichment ON as feature source
Step 3 of plan 009 (config + code; no live-path change):

- research/ingestion/market_context.py: pulls SPY/QQQ full-session minute bars
  (SIP, post-close embargo-safe) + daily bars via the existing upsert helpers, so
  the labeler's market-context features have a FORWARD source (index ETFs never
  enter the gapper universe the live loop ingests). Mirrors sip_backfill's shape.
- both compose scheduler blocks: run it after premarket_eval, guarded || echo.
- NEWS_ENRICH_ENABLED on: added to docker-compose x-app-env anchor (loop +
  scheduler inherit it); truenas-compose + .env.example already had it. Catalyst
  is a FEATURE source only, never a live gate; enabled-but-down never blocks
  trading. Enrichment OUTPUT is verified by the orchestrator (needs live Ollama).

Compose statically validated (yaml.safe_load). Ingest self-checked via a
fake-client :memory: round-trip through _preload_spy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:27:49 -04:00
momentum devandClaude Fable 5 07570fb0e4 operator-plane: fix dashboard POST NameError (import os/hmac)
Every POST endpoint (approve/reject/exit/watch) crashed with NameError:
do_POST references os.environ + hmac.compare_digest but neither was imported.
Adds the imports, a non-loopback regression test for the token gate (no token
-> routes; token set -> 401 without/with-wrong header, 200 with correct), and
plumbs DASHBOARD_TOKEN + PG_SKIP_SCHEMA=1 into both compose dashboard services
(+ .env.example docs for DASHBOARD_TOKEN / COMMAND_BUS_ENABLED).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 00:04:01 -04:00
momentum devandClaude Fable 5 1036c19448 experiment-os: wire harness into both compose schedulers
Plan 006 step 5. python -m research.experiments run && adjudicate after
nightly_tune in both scheduler blocks (guarded with FAILED echoes). ALL-SHADOW:
emits verdicts, never touches live gates/sizing/exits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 23:49:31 -04:00
momentum devandClaude Fable 5 3e2d690b21 flywheel: schedule eod_replay + nightly_tune in both compose scheduler blocks
The tuner was fiction in production: the 20:00 ET scheduler ran only
premarket_eval (+ eod_reconcile), so learned_params.json sat 26 days stale and
the whole-market dataset never grew. Both scheduler loops now run, in order,
after the eval/reconcile pass:

  python eod_replay.py    || echo "[scheduler] eod_replay FAILED"
  python nightly_tune.py  || echo "[scheduler] nightly_tune FAILED"

Order matters: replay grows the dataset first, the tuner fits after. 20:00 ET is
after close, so replaying "today" is complete data — NOT look-ahead (replay is a
labeling job, not a live gate). Deletes eod_replay.sh (dead cron-era wrapper
pointing at a nonexistent venv; compose is the runner).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 22:46:06 -04:00
momentum devandClaude Fable 5 23b3d01a00 money-path: nightly cage-count reconcile (broker vs event store) + schedule it
Plan 003 step 5 (completes plan 003). research/eod_reconcile.py diffs broker
truth (open positions, today's live orders) against event-store truth (today's
submits/fills/closes) and emits a loud eod_reconcile event + Telegram alert on
any drift — the duplicate-resting-order-after-restart class (2026-07-06) caught
automatically instead of by hand. Read-only (reports, never auto-fixes). Wired
into both scheduler blocks after premarket_eval. Pure diff core is unit-tested.

Suite: 373 passed (+22 plan-003 tests), 15 fail = WSL-loopback dashboard only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 22:29:00 -04:00
momentum devandClaude Fable 5 cf431c6c2b delete: standalone grafana/ stack, telemetry/ husk, dead workflow, stale uv.lock
grafana/ pointed at the decommissioned pre-migration DB with committed dev
credentials (rotation note in plans/002-deletions.log); its dashboards
provisioning MOVED to deploy/compose/grafana/dashboards (docker-compose.yml
bind-mount re-pointed — the in-compose grafana service keeps working).
docker-publish.yml fired on a nonexistent branch pushing a wrong image name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:02:46 -04:00
momentum devandClaude Fable 5 caee1d5655 backup: nightly pg_dump service on the postgres:18 image
Production Postgres had NO automated backup (pg_dump appeared only in the
one-time migration script). New db-backup service in both compose files fires
20:10 ET Mon-Fri: pg_dump | gzip with pipefail + gunzip -t + tmp-then-rename
(a mid-stream death can't masquerade as a backup), prunes past
BACKUP_KEEP_DAYS, writes last_backup_ok on success. postgres:18 image =
version-matched client (app image has none). TrueNAS host is pull-only, so
its copy inlines the script. Restore: gunzip -c dump | psql $DATABASE_URL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:39:26 -04:00
momentum devandClaude Opus 4.8 befcec2965 Post-migration DB health: healthcheck DDL storm + broken-pipe fixes
The loop healthcheck opened a fresh connection every 60s with schema
auto-apply enabled; the CREATE-IF-NOT-EXISTS lock bursts cancelled
minute_bars autovacuum every single cycle (visible as 'canceling
autovacuum task' each minute post-migration), so the 19M-row table
never got analyzed, snapshot queries crawled, browsers timed out, and
the dashboard log filled with double BrokenPipeError tracebacks.

- healthchecks now set PG_SKIP_SCHEMA=1 (both compose files)
- api/main.py _send_json swallows BrokenPipe/ConnectionReset (client
  hung up; nothing to tell it)
- live DB: ALTER SYSTEM max_wal_size=4GB, shared_buffers=1GB,
  effective_cache_size=3GB, checkpoint_completion_target=0.9 (the
  checkpoint-every-24s warning under migration load on NAS storage)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:41:12 -04:00
momentum devandClaude Opus 4.8 f0fb91a069 Admin panel (:8767): config, profiles, service control, paper/live switch
New admin service — LAN-only, token-authed (ADMIN_TOKEN), card-based UI
matching the owner's preferred dashboard style. Capabilities:
- health: docker containers via /var/run/docker.sock (stdlib unix-socket
  HTTP client), DB heartbeat, Alpaca account/clock, Ollama, feed
- config: whitelisted strategy knobs (TRADING_/TRIGGER_/WATCHER_/...)
  edited via data/overrides.env, which every entrypoint now loads with
  override=True at boot (runtime/env_overrides.py) — so changes apply on
  container restart without touching the deployment YAML; secrets are
  presence-only and rejected server-side if edited
- profiles: named knob presets (seeded: validated-core, conservative,
  squeeze-hunter) applied in one click
- restart buttons per container; paper/live switch guarded behind
  ALPACA_LIVE_* keys existing
Verified live in docker: auth 401s, status/config/profiles/write paths,
secret-edit rejection, page serves. Added to both compose files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:37:18 -04:00
momentum devandClaude Opus 4.8 8864041066 Docker stack modernized: full service set, PG 18, validated end-to-end
Brings the pre-migration compose file up to the current architecture:
python 3.13 image with ffmpeg + faster-whisper/yt-dlp (transcripts),
postgres 16 -> 18 (prod parity), and the real service set — loop
(--no-dashboard, heartbeat healthcheck), dashboard_api.py on :8765,
telegram qabot, youtube transcripts, and a 20:00-ET weekday scheduler
replacing the premarket-eval systemd timer. Jobs (journal/eod-replay/
nightly-tune) and Grafana keep their profiles. Self-contained by
default (in-stack DATABASE_URL); deploy/docker/README.md documents the
pg_dump migration, the transition mode against the LAN DB, and the
cutover checklist. Validated: image builds, compose config passes, and
a smoke container served the dashboard (HTTP 200) against the live
LAN Postgres.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 13:06:27 -04:00
Claude a76c0b5376 Add Docker Compose stack for local development
Single-machine equivalent of the Helm package: builds the app from the
Dockerfile, runs PostgreSQL with a persistent volume, and starts the trading
loop with its embedded dashboard on :8010.

- postgres:16 with healthcheck + named volume; app waits on it healthy
- DATABASE_URL overridden to the in-network postgres service (self-contained);
  .env is optional (dry mode without ALPACA_* keys)
- journal / eod-replay / nightly-tune under a "jobs" profile, run on demand;
  they share the app-data volume so learned_params.json reaches the loop
- Grafana under a "monitoring" profile with a compose-specific datasource
  override pointed at the postgres service
- deploy/README.md documents the Compose workflow

Validated with `docker compose config` across all profiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCVMm2AbmZvvxwLTX6UpNR
2026-06-29 21:40:19 +00:00