6 Commits
Author SHA1 Message Date
Claude 37f39299ad spread_pct P0: real decision-time bid/ask spread as an observe-only metric
GO_LIVE flags `spread_pct` as a not-yet-real field. The reason it was null is
structural: OHLCV bars carry no NBBO, so `minute_bars.spread_pct` can never be
populated from the bars endpoint. The execution-relevant figure is the spread at
the moment a setup is evaluated — a wide book quietly eats R on the thin sub-$20
names this system trades.

This makes that metric real, shadow-first (measure before gating), mirroring how
the catalyst score was introduced:

- `strategy/evaluation/liquidity.py`: pure `compute_spread_pct(bid, ask)` →
  (ask-bid)/mid, or None for missing/crossed/non-positive quotes (None never
  collapses to a real 0.0). Unit-tested.
- `AlpacaPaperClient.get_latest_quotes`: batched latest-NBBO fetch, mirrors
  `get_latest_trades`.
- `Watcher`: optional injected `spread_provider` (same shape as
  `catalyst_score_provider`); default None == prior behavior byte-for-byte.
  Computed spread is logged onto the criteria_evaluated and signal_ready events.
  Observe-only — it does NOT gate ready/blocked.
- `run_live_paper`: TTL-cached spread provider off the client; degrades to None
  on any fault, SPREAD_PCT_ENABLED=0 to silence.

The dead `scanner.spread_pct_max` knob is intentionally left for a later
promotion, once the logged data shows spread separates outcomes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019psgRHdtTXz6TCnnqPiqGw
2026-06-29 21:47:41 +00: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
Claude 4173acc9d5 docs: add future-hardening roadmap to deploy README
Document reliability, security, and CI/operational improvements for the
Kubernetes package as an actionable backlog (not yet implemented). Notably
flags the loop-heartbeat liveness gap: /api/health proves only the dashboard
thread is alive, so a hung trading loop would go undetected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCVMm2AbmZvvxwLTX6UpNR
2026-06-29 21:35:38 +00:00
Claude 767bd75a8d Wire catalyst score into the live watcher and arming rank
Completes the Phase 2 loop: the catalyst score now actually reaches the runtime
decision, not just the pure functions. Gated behind its own flag so enabling the
Phase 1 advisory/dashboard never silently changes trade scoring.

- OllamaConfig.catalyst_score_enabled (NEWS_CATALYST_SCORE_ENABLED, default off).
- Watcher accepts an optional catalyst_score_provider (symbol -> 0..1 | None) and
  passes it into evaluate_setup; a None provider reproduces today's behavior. The
  provider is injected so runtime/ stays free of research/ imports.
- run_live_paper: one TTL-cached catalyst provider shared by the watcher and the
  execution service; the watcher gets the score provider only when the blend is
  enabled. step_arm gains a gated graded ranking multiplier (bullish catalyst
  boosts by 1+gain*score; a confirmed dilutive offering is sunk below the armed
  cut), falling back to the existing binary news boost when off.
- Add the enrich task to the run_pass pipeline order so a single --once pass also
  runs enrichment (previously only the continuous loop did).
- .env.example documents the new flags; watcher tests cover the blend + the
  no-op None-provider case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M5HTMkaadGDnAPtXe2hWwg
2026-06-19 18:45:18 +00:00
Claude e3cb2b8e5f Add local-LLM (Ollama) news/catalyst scoring
Leverage a local Ollama model to interpret news catalysts for the small-cap
momentum strategy — distinguishing bullish catalysts (FDA approval, M&A) from
dilutive offerings the technicals alone can't see. Advisory-first, then automate.

Phase 1 (advisory; no change to trade behavior):
- research/ingestion/news_enrichment.py: stdlib urllib call to Ollama with
  structured JSON output, classifying each headline into a catalyst type,
  sentiment, conviction and dilution flag. Graceful degradation (None on any
  failure) so trading is never blocked.
- news_catalyst_cache table + populate the previously-dead news_events.sentiment.
- Runs on the existing interval Scheduler (off the hot path), gated by
  OllamaConfig; deduped by the raw_news_items payload hash.
- Surface the advisory on the dashboard approval queue (projection + api + UI).

Phase 2 (automation; ships gated OFF):
- Optional catalyst_score blended into calculate_setup_quality (None reproduces
  the exact legacy weights); pure pass-through via evaluate_setup.
- Dilution veto in TradingExecutionService (slow + fast paths) emitting a
  catalyst_dilution_veto risk event, behind NEWS_DILUTION_VETO_ENABLED.

Config (OllamaConfig.from_env), .env.example docs, and tests for parsing,
persistence/dedupe, the projection, the score blend, the veto, and an
end-to-end enrich->advisory flow (Ollama mocked). Default model moves to
qwen2.5:7b-instruct (best small-model JSON, fits a 12GB GPU).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M5HTMkaadGDnAPtXe2hWwg
2026-06-19 17:29:24 +00:00
Claude e91979a622 Add Kubernetes image + Helm package with in-cluster Postgres
Containerizes the momentum monolith and packages every run mode for
deployment in a single Kubernetes namespace, with PostgreSQL hosted in
the cluster.

- Dockerfile (python:3.11-slim, non-root, writable /app/data) + .dockerignore
- GitHub Actions workflow publishing to ghcr.io/fez5587/momentum-system
- Helm chart (deploy/helm/momentum):
  * PostgreSQL StatefulSet + PVC (single source of truth; schema
    auto-creates from storage/pg_schema.sql on first connect)
  * App Deployment running run_live_paper.py with the embedded dashboard
    (replicas: 1, Recreate; the dashboard's write endpoints need the
    in-process TradingExecutionService) + Service/Ingress on 8010, plus a
    wait-for-postgres initContainer
  * CronJobs for journal / eod-replay / nightly-tune (US/Eastern via
    spec.timeZone) sharing the app data PVC
  * ConfigMap (tuning env) + Secret (DATABASE_URL/broker keys, existingSecret
    supported); DATABASE_URL built from the in-cluster Postgres service
  * Optional Grafana provisioned against the in-cluster Postgres
- deploy/README.md with install, secrets, and constraints

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