Track experiment cycles with SQS scoring (0-100), JSONL journal, and
auto-generated leaderboard to prevent duplicate experiments and enable
data-driven strategy decisions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove 5 non-alpha features (earnings surprise, risk penalty, parse confidence,
direction clarity, LM sentiment) from composite score to eliminate double-counting
with hard gates and noise sources. Redistribute weights to 5 alpha features.
Add default-deny for unknown event types, no-follow-through early exit (D+1),
kill switch log-only mode, macro regime size scaler. Remove SUE gate (Gate 8).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix kill switch reset: remove unreachable drawdown recovery condition
(equity can't change while trading is halted), reset peak_equity and
drawdown_pct to 0 on cooldown expiry
- Raise veto_oneoff_penalty threshold 0.5 → 0.7 (was blocking 67% of
candidates due to high median oneoff_penalty in dataset)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Flip scoring weights so event/document quality is primary signal (55%)
and market confirmation is secondary (35%). Add research mode with
kill-switch cooldown/reset, veto gates for bad events, reduced portfolio
risk, and 4 diagnostic analysis scripts.
Phase A: Research mode kill-switch reset, risk reduction (0.5%/trade,
max 4 positions), bullish-only direction for all event types.
Phase B: 2 new sub-scorers (parse_confidence, direction_clarity),
4 veto gates (oneoff risk, parse confidence, unknown/bearish direction).
Phase C: signal_quality, event_type_decomposition, kill_switch_impact,
concurrent_position analysis scripts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
equity was computed as cash + unrealized_pnl where unrealized_pnl =
(close - entry) × shares. Since cash already had entry cost subtracted,
this double-counted the cost basis:
buggy: equity = (initial - entry×shares) + (close - entry)×shares
= initial + close×shares − 2×entry×shares ← WRONG
correct: equity = cash + market_value
= (initial - entry×shares) + close×shares
= initial + (close − entry)×shares ← RIGHT
This caused drawdown to spike to ~73% the instant a position opened
(e.g. TSLA $330 × 222 shares → equity appeared to drop from 100k to
27k), falsely triggering the kill switch at 25% and blocking all
subsequent entries.
Before fix: 3 trades, +0.08% return, 39.2% max drawdown (fake)
After fix: 10 trades, -2.63% return, 4.24% max drawdown (real)
Also: when bar data is missing, positions now use entry_price as
fallback market value instead of treating the position as worthless.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- BacktestRunner.run() now iterates all NYSE trading days (not just candidate
days) via SnapshotStore.all_trading_days() so stop/target/time exits are
checked every day, not only on days with new candidates
- Record initial DailyPortfolioState before simulation loop starts so
total_return_pct is computed relative to the true initial equity (100k),
not the first post-entry equity snapshot
- SnapshotStore._fetch_event_metadata() now synthesises event_timestamp from
event_date + 21:00 UTC when filed_at_utc is NULL (transparent enrichment at
loader boundary, not silent substitution in selector)
- SnapshotStore._async_load() maps event_close → entry_price_est when the
column is absent, and derives score from abs(reaction_day_return) when the
Parquet snapshot has no score column
- Add --snapshot-dir CLI flag to BacktestRunner to override the default
parquet_dir base path (needed for non-standard snapshot locations)
- Fix integration test assertion: total_trading_days >= 2 (was == 2)
- Add configs/experiments/realdata_test_v1.json for real Phase 3 snapshot runs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `think: False` and `num_ctx: 8192` to Ollama payload:
Qwen3.5 extended thinking mode generated 1300+ internal reasoning
tokens before each response, adding 30-60s latency per LLM call.
Disabling it reduces parse time from 600s timeout to ~13s.
- Rewrite OllamaClient to use sync httpx.Client inside asyncio.to_thread():
Async httpx inside an active asyncpg SQLAlchemy session context on
Python 3.13 hung indefinitely. Synchronous httpx in a thread pool
completely isolates Ollama I/O from the asyncio event loop.
- Fix filing_poller to set issuer_id/symbol_id on Document records:
Missing FK caused feature_builder to reject all events with
event_no_symbol warning. Now looks up IssuerMaster/SymbolMaster
by ticker before creating Document rows.
- Update test_llm_client to mock _sync_call instead of _client attr.
- Raise ollama_timeout default to 600s for large document processing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- filing_poller: add --start-date/--end-date CLI args for historical backfill
(defaults to 7 days ago when omitted)
- OracleClient.get/post: apply with_retry(max_attempts=3) so transient
connection errors, timeouts, and 5xx responses are automatically retried
with exponential backoff (0.1s→0.2s→fail)
- financial_features: new compute_financial_features() extracting latest_eps,
latest_gross_margin, latest_operating_margin, eps_growth_qoq,
revenue_growth_qoq from FinancialDataResponse
- feature_builder: wire FinancialService into build_features_for_event(),
persisting financial_v1 FeatureSnapshot (non-fatal if unavailable)
- tests: 94 pass (81→89 unit + 5 replay); +8 new tests covering financial
features and retry success path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>