Development task list organized by phase. Check off tasks as they are completed. Each task should be small enough to complete in a single session.
Convention:
[ ]Not started[~]In progress[x]Complete[!]Blocked (note reason)
Goal: Working end-to-end prototype with quant signal generation, sentiment integration, and basic backtesting. No ML yet, no live trading.
- Create Python virtual environment (
python -m venv .venv) - Install base dependencies (
pip install -r requirements.txt)- Note: pandas-ta pins numba==0.61.2; on Python 3.12 this is fine — do NOT add
numba>=0.65.0to requirements.txt or pip resolution fails. - Note: torch>=2.6 fails on Windows 10 (WinError 1114, c10.dll init). Fix: install CPU wheel
pip install "torch==2.5.0" --index-url https://download.pytorch.org/whl/cpuafter the regular install. Documented in requirements.txt.
- Note: pandas-ta pins numba==0.61.2; on Python 3.12 this is fine — do NOT add
- Verify FinBERT model downloads correctly via HuggingFace transformers
- Model cached (109M params, labels: positive/negative/neutral). Sanity-check inference confirmed correct sentiment.
- Fix applied:
use_safetensors=Trueindownload_finbert.py— transformers >=5.x blockstorch.loadon torch <2.6 (CVE-2025-32434); safetensors bypasses this. - Fix applied: removed stale
hf.cached_downloadreference inverify_setup.py— removed in huggingface_hub 1.x.
- Create
.envfrom.env.exampleand add OpenAI API key - Set up MLflow locally (
mlflow uishould serve onlocalhost:5000) — DB initialized atdata/mlflow.db - Configure git hooks for black/ruff —
.pre-commit-config.yamlcreated; runpre-commit install - Verify yfinance data fetch works for all tickers in
config/watchlist.yaml- All 8 US stocks: PASS. 4GLD.DE (gold ETF): PASS. DFNS.DE (defense ETF): no data — replaced with
ITA(iShares Aerospace & Defense ETF, US-listed, history from 2006).
- All 8 US stocks: PASS. 4GLD.DE (gold ETF): PASS. DFNS.DE (defense ETF): no data — replaced with
- Note: VS Code shows "package not installed" hints — select
.venvas the Python interpreter (Ctrl+Shift+P → "Python: Select Interpreter" →.venv).
- Implement
MarketDataProviderclass wrapping yfinance with cachingsrc/data/market_data.py— fetch_ohlcv, fetch_batch (1.5s throttle), is_cache_stale- Cache:
data/raw/{ticker}/daily.parquet; stale only on weekdays when last date < yesterday - Error handling: falls back to stale cache on yfinance failure; raises DataFetchError only when no cache exists
- Implement
FeatureStoreusing Parquet files indata/features/src/data/feature_store.py— save_features (upsert), load_features (date filtering), get_latest, update_sentiment, exists, list_tickers- Column prefix conventions:
tech_,sent_,deriv_map to FeatureVector fields
- Add
get_earnings_calendar()method to MarketDataProvider- Phase 1 stub: uses
yf.Ticker.calendar; returns[]on any error
- Phase 1 stub: uses
- Create
NewsDataProviderstub (Phase 1 may skip live news; use Alpha Vantage free tier later)src/data/news_data.py— get_headlines and get_macro_news both return[]; interface ready for Alpha Vantage / Finnhub in Phase 3
- Write unit tests for data providers (mock the yfinance calls)
tests/test_data_providers.py— 46 tests, all passing; yfinance fully mocked, file I/O uses tmp_path
- Validate data quality: check for NaN values, missing dates, suspicious price gaps
- MarketDataProvider._validate(): NaN forward-fill + warning, >20% price gap warning, missing business-day gap warning (>5 days), zero-volume warning
- Implement abstract base classes in
src/plugins/base.py:IndicatorPluginSmoothingPluginDataEnricherSignalFilter- Supporting types:
SmoothResult,ParamSpec
- Implement
PluginRegistryinsrc/plugins/registry.pywith discovery logicdiscover_plugins(config_path), getters,list_available(),get_filters_by_stage()
- Load plugin config from
config/plugins.yaml- 6 indicators + finbert enricher defined; Kalman/LLM validator commented for Phase 4/3
- Write tests for plugin registry: register, retrieve, list, error on missing
tests/test_plugin_registry.py— 14 tests; stubs defined inline; monkeypatched_instantiatefor discover tests
- Document the plugin contract in
.claude/skills/plugin-author/SKILL.md(already drafted)
- Implement
SMACrossoverIndicatorwithcompute()andnormalize()methods - Implement
RSIIndicator - Implement
MACDIndicator - Implement
BollingerBandIndicator - Implement
DonchianChannelIndicator - Implement
VolumeIndicator - Unit tests for each indicator with known input/output pairs
- Verify each indicator's
get_tunable_params()returns valid ParamSpec entries
- Implement
FinBERTEnricheras aDataEnricherpluginsrc/plugins/enrichers/finbert.py— enrich, batch_enrich, analyze_batch, analyze_batch_cached- Zero-arg instantiation for registry compatibility;
news_providerinjectable for testing
- Cache model on first load to avoid re-downloading
- HuggingFace auto-caches to
~/.cache/huggingface/on firstfrom_pretrainedcall - Per-headline inference cache in
data/models/finbert_cache/(SHA256-keyed JSON files)
- HuggingFace auto-caches to
- Implement batch processing for efficiency (process 64 headlines at a time)
analyze_batch()processes in chunks ofbatch_size=64;max_length=128truncation
- Compute rolling features:
sentiment_ma_5d,sentiment_ma_20d,sentiment_momentum- Also computes:
sentiment_score(latest daily),news_volume_ratio,negative_news_ratio - Confidence-weighted aggregation per day before rolling windows
- Also computes:
- Test on sample headlines to verify sentiment scores are reasonable
tests/plugins/enrichers/test_finbert_enricher.py— 4 live inference tests inTestLiveSampleInference- Skipped automatically if model not cached; run
scripts/download_finbert.pyto enable
- Decide on news source: skip historical news for Phase 1 backtests; use it live in Phase 3
- Phase 1:
NewsDataProviderstub returns[]→ all sentiment features are 0.0 (intentional) - Phase 3: implement
NewsDataProviderwith Alpha Vantage / Finnhub; no changes to enricher needed
- Phase 1:
- See
.claude/skills/finbert-integration/SKILL.mdfor implementation guidance
- Implement
RegimeDetectorclass with HMM (hmmlearn) + ADX dual approachsrc/signals/regime_detector.py—fit(),detect(),detect_series(),save(),load()- Layer 1 of the cascade; not a plugin — instantiated directly before QuantEngine
- Train HMM on log returns + realized volatility, n_components=3
GaussianHMM(n_components=3, covariance_type="full")trained on[log_return_1d, realized_vol_20d]- Uses last
lookback_days=504rows; raisesValueErrorif insufficient data
- Implement
detect()returningRegimeTypeenum- Uses HMM forward-backward posteriors; returns
RegimeTypefor the most recent row - Also implements
detect_series(df)→pd.Series[RegimeType]for bulk/backtest use
- Uses HMM forward-backward posteriors; returns
- Add ADX as the fast classifier (ADX > 25 = trending, ADX < 20 = ranging)
_compute_adx()usespandas_ta.adx(length=14)— robust to column-name variation
- Implement reconciliation logic combining HMM and ADX outputs
- Priority: VOLATILE (uncertainty > 0.40) → RANGING (ADX < 20) → HMM direction (bull/bear/sideways)
- All 5 reconciliation branches unit-tested directly in
TestReconciliation
- Persist trained HMM models in
data/models/hmm/per cluster (or per stock if individual)fit()auto-saves todata/models/hmm/{ticker}.pklvia joblibsave(path)/classmethod load(path)for explicit persistence
- Test regime stability: regimes should not flicker daily
TestRegimeStability::test_no_single_day_flicker_in_trending_seriesverifies zero isolated flips- HMM transition matrix naturally penalises rapid state changes
- Implement
QuantEngineclass that uses plugin registrysrc/signals/quant_engine.py—generate_signal(),generate_series(),should_exit()- Plugin-agnostic: iterates
IndicatorPlugininstances from registry; never hardcodes names
- Load active indicators from registry on init
registry.get_all_indicators()called in__init__; params loaded viaget_default_params()
- Apply regime-specific weights (initial defaults from architecture doc)
config/cluster_params/cluster_default.yaml— 4 regimes × 7 keys (6 indicators + sentiment)- Weights validated/normalized to sum to 1.0 on load; missing regimes filled with equal spread
- Compute composite score = Σ(weight × normalized_score)
_weighted_composite()— sums weight × normalized score, clips to [-1, +1]- Added
output_column: strclass attribute toIndicatorPluginbase + all 6 plugins
- Implement multi-timeframe confirmation (daily + weekly alignment check)
_weekly_confirms()— weekly SMA(4) vs SMA(10) viaresample("W-FRI"); requires ≥20 weekly barsmulti_timeframe_boost=1.15fromconfig/settings.yaml; confidence never exceeds 1.0
- Generate
TradeSignaldataclass with direction, confidence, regime- All required fields populated;
stop_loss_pct,take_profit_pct,bet_size,llm_approvedleft None
- All required fields populated;
- Wire FinBERT sentiment as one of the inputs to the composite
sentiment_score: float = 0.0parameter ongenerate_signal(); weight from cluster_default.yaml- Phase 1: always 0.0 (stub). Phase 3: orchestrator passes real FinBERT score
- See
.claude/skills/quant-engine-dev/SKILL.mdfor design patterns
- Install PyBroker and verify import works
- PyBroker 1.2.11 confirmed installed;
--verifyflag in run_backtest.py prints version and exits
- PyBroker 1.2.11 confirmed installed;
- Write
scripts/run_backtest.pythat runs the quant engine on historical data- Pre-computes signals via
generate_series()(forward-only, no lookahead), feeds into PyBroker - HMM fitted on warmup window (default: start - 730 days) before backtest begins
- Pre-computes signals via
- Configure realistic transaction costs (0.05% commission, 0.05% slippage)
FeeMode.ORDER_PERCENTwithfee_amount=0.001(0.1% per order, baked commission + slippage)buy_delay=1, sell_delay=1— fills on next bar's open for realistic execution
- Run backtest on AAPL 2020-2024, compute Sharpe, max drawdown, win rate
python scripts/run_backtest.py --ticker AAPL --start 2020-01-01 --end 2024-12-31- Metrics: sharpe, max_drawdown_pct, win_rate, total_return_pct, profit_factor, calmar, sortino
- Visualize equity curve in a notebook
notebooks/backtest_analysis.ipynb— dual-panel equity + drawdown, monthly heatmap, trade P&L
- Compare results against buy-and-hold benchmark
_compute_benchmark()computes B&H Sharpe, return, max drawdown; printed side-by-side
- Log all backtest runs to MLflow
sqlite:///data/mlflow.db, experimentargus_backtests; params + metrics + CSV artifacts logged
- See
.claude/skills/backtest-runner/SKILL.mdfor backtesting workflow
- All tests pass (
pytest tests/) - Backtest produces sensible (not random) results — Sharpe should be in
[-1.0, +2.0]range - No lookahead bias detected (manual code review)
- Pipeline can run end-to-end on at least one ticker
- Document any deviations from the architecture doc as new ADRs
- Demo run reviewed before proceeding to Phase 2
Goal: ML meta-model filters quant signals; per-stock tuning is automated; walk-forward validation prevents overfitting.
- Implement triple-barrier labeling per López de Prado AFML Chapter 3
- Function signature:
triple_barrier_labels(prices, signals, tp_pct, sl_pct, max_holding_days) - Returns labels:
+1(TP hit),-1(SL hit),0(timeout) - Test on synthetic data with known outcomes
- See
.claude/skills/ml-meta-labeler/SKILL.mdfor full workflow
- Implement
MetaLabelModelclass using XGBoost - Build feature assembly: quant features + sentiment + regime (one-hot) + quant prediction
- Train binary classifier: did the quant signal lead to a profitable trade?
- Apply Platt scaling via
CalibratedClassifierCV - Compute calibration curve and Brier score
- Save trained model to
data/models/meta_model/with versioning - Log training run to MLflow with all hyperparameters
- Implement
PurgedKFoldCVper AFML Chapter 7 (or usetimeseriescvlibrary) - Embargo gap = max signal dependency horizon (e.g., 5 days for 5-day forward returns)
- Use this CV split for all meta-model training
- Verify it produces non-overlapping folds with proper purging
- Implement
StockClustererclass with K-Means and tslearn DTW options - Feature extraction per stock: Hurst exponent, mean ADX, lag-1 autocorr, volatility
- Choose
kvia silhouette score (target k=4..8) - Persist cluster assignments to
config/cluster_assignments.yaml - Test re-clustering: same input data should produce stable assignments
- Implement
BayesianTunerwrapping Optuna - Define search space from each plugin's
get_tunable_params() - Objective: maximize OOS Sharpe ratio
- Use TPE sampler with 100 trials per cluster
- Log every trial to MLflow
- Implement parameter stability check across adjacent windows
- Implement
WalkForwardOptimizerwith rolling 252/126 day windowssrc/tuning/walk_forward.py— step size =out_of_sample_days(non-overlapping OOS); enforces ≥3 windows; defaults pulled fromconfig/settings.yaml::tuning.{in_sample_days,out_of_sample_days}.
- Optimize on in-sample, evaluate on out-of-sample
_run_window()invokesBayesianTuner.tune()on the IS slice, then evaluates the top-k IS candidates on OOS for feeding PBO. Per-window stability check delegates toBayesianTuner.stability_check(20% drift threshold).
- Concatenate all OOS results for aggregate metrics
WFOResult.aggregate_oos_sharpe= mean of per-window OOS Sharpes; full per-window series preserved inWFOResult.windows[i].oos_sharpe. CPCV (below) provides the stronger overfitting-aware aggregate called out by the architecture doc.
- Compute Probability of Backtest Overfitting (PBO) via CPCV
CombinatorialPurgedCV(from Task 2.3) wired intoWalkForwardOptimizer.optimize()and runs on the full dataset with the tunedbest_params. Result populatesWFOResult.cpcv_pbo(additive — coexists with the fast per-windowpbo). CPCV failures are non-fatal and setcpcv_pbo=None. Knobs underconfig/settings.yaml::tuning.cpcv(enabled,n_groups,n_test_groups,embargo_days; defaults 10/2/5 → 45 paths).
- Output validated parameters to
config/cluster_params/cluster_{id}.yamlscripts/tune_clusters.py— per-cluster WFO; aggregates OOS Sharpe,pbo, andcpcv_pboacross the cluster's tickers; writesmetadata(incl.cpcv_pbo,pbo_pass,pbo_source,is_stable,n_windows) +indicators.paramsto YAML atomically. Promotion gate preferscpcv_pbowhen available, falls back to per-windowpbo. QuantEngine already readsindicators.paramsvia_load_plugin_params.
- Implement
PromotionGateclass with criteria from architecture docsrc/tuning/promotion_gate.py—PromotionDecisiondataclass +evaluate/promote/demote/check_demotion/log_decision/resolve_params_path/is_promoted/list_promotedmethods. Thresholds loaded fromconfig/settings.yaml::tuning.promotion. Orchestration wrapper inscripts/tune_individual.pyruns WFO per ticker and calls the gate.
- Check: history ≥ 2500 bars, individual Sharpe > cluster × 1.20, param drift < 20%, PBO < 0.40
- All four criteria enforced in
evaluate()(in order: history → Sharpe lift → stability → PBO). PBO prefersWFOResult.cpcv_pboand falls back to per-windowpbo. Cluster Sharpe ≤ 0 edge case requires an absolute lift ≥ 0.10 instead of a ratio.
- All four criteria enforced in
- Write promoted parameters to
config/stock_overrides/{ticker}.yaml- Atomic temp-file +
shutil.movewrite; carries cluster regime weights forward unchanged and embeds stock-specificindicators.params(viaBayesianTuner.unpack_params_static). Metadata captures PBO/CPCV-PBO/stability/decision reasons for post-hoc auditing.resolve_params_path()gives downstream code the overrides → cluster → default fallback chain.
- Atomic temp-file +
- Implement demotion logic: revert if rolling 60-day Sharpe drops > 0.30 below cluster
check_demotion()(NaN/inf-safe predicate readingtuning.promotion.demotion_sharpe_gap) paired withdemote()(deletes the override file). Phase 3's daily pipeline will invoke these once paper-trading fills supply the rolling live Sharpe.
- Log all promotion/demotion decisions to
config/promotion_log.yamllog_decision()— read / modify / atomic-write append under thedecisions:key, preserving every prior entry.scripts/tune_individual.pylogs every evaluated ticker (promoted, kept, or skipped-for-history).
- Meta-model improves Sharpe ratio over raw quant signals (verified across multiple stocks)
- Walk-forward backtest shows positive OOS Sharpe on majority of clusters
- PBO < 0.40 for production parameter sets
- At least 2-3 stocks promoted to individual params (validates the promotion gate works)
- All anti-overfitting checks pass (see architecture doc Section 12)
Goal: Close the three gaps between the stated product goal (technicals + financials + news) and the current Phase-2 implementation: activate real news data for FinBERT, add fundamentals as a first-class signal pillar with per-stock tunable weight and ETF-aware zeroing, add event-driven exits, and refactor the feature assembler off its hardcoded column list.
Why this becomes Phase 3 (review output, April 2026): the v1.1 architecture lists fundamentals as an ingested data source but never feeds them into the QuantEngine composite. News is stubbed to zero (ADR-0010). Exit logic is confidence-only. FeatureAssembler hardcodes FEATURE_COLUMNS, blocking dynamic enrichment. See ADR-0011, ADR-0012, ADR-0013.
Supersedes the Phase-1 stub in src/data/news_data.py and unblocks the already-built FinBERTEnricher (Phase 1.5).
- Implement
NewsDataProvider.get_headlines(ticker, start, end)with Alpha VantageNEWS_SENTIMENT - Finnhub
/company-newsfallback on rate-limit or empty-result errors -
(ticker, date)-keyed on-disk cache underdata/raw/news/with 12-hour TTL -
get_macro_news()equivalent via FRED + Alpha Vantage economic news (optional; stub acceptable if out of scope) - Unit tests with recorded fixture responses for both providers (success + rate-limit error paths)
- Integration smoke test: call
FinBERTEnricher.enrich("AAPL", …)end-to-end; assert non-zero sentiment features for a day with known news - Update ADR-0010 status to "Superseded by ADR-0013" once merged
- Reference: ADR-0013
Adds fundamentals as a first-class signal pillar with per-stock / per-cluster tunable weight and hard ETF exclusion. See ADR-0011.
- Implement
FundamentalsDataProviderinsrc/data/fundamentals_data.py(Alpha VantageOVERVIEW,INCOME_STATEMENT,CASH_FLOW,EARNINGS; yfinance fallback) - Cache raw statements in
data/features/fundamentals/{ticker}/{fiscal_period}.parquet - Monthly refresh + on-demand refresh within 7 days of a scheduled earnings date
- ETF short-circuit: return empty DataFrame for any ticker in
config/watchlist.yaml::etfs:(no API call) - Add
FundamentalIndicatorPluginabstract base tosrc/plugins/base.py- Contract:
compute(fundamentals_df, price_df) -> pd.Series,normalize(values) -> pd.Series - Attribute
applies_to_etfs: bool = False
- Contract:
- Register
FundamentalIndicatorPlugintype insrc/plugins/registry.py - Implement initial fundamentals plugins under
src/plugins/fundamentals/:-
pe_zscore.py— trailing P/E vs sector 5-year z-score -
fcf_yield.py— free cash flow / market cap -
earnings_growth.py— YoY EPS growth from last 4 quarters -
earnings_surprise.py— most recent actual EPS vs consensus estimate
-
- Wire
fundamental_scoreaggregation channel intoQuantEngine._weighted_composite(mean of enabled plugins' normalised outputs) - Apply applicability multiplier
f_fund:- ETFs force
f_fund = 0(detect fromwatchlist.yaml::etfs:) - Optional per-stock
fundamentals_weight_overrideinwatchlist.yaml - Re-normalise remaining regime weights to sum to 1.0 when
f_fund = 0
- ETFs force
- Add
fundamentals_weightper regime toconfig/cluster_params/cluster_default.yaml(TRENDING_UP: 0.15, TRENDING_DOWN: 0.10, RANGING: 0.20, VOLATILE: 0.05) - Expose
fundamentals_weightas aParamSpecinget_tunable_params()soBayesianTunercan search it ([0.0, 0.30]) - Unit tests:
FundamentalIndicatorPlugincontract, ETF zero-weight path, re-normalisation math - Reference: ADR-0011
Adds discrete event-based exits alongside the existing confidence-based exit. See ADR-0012. The RiskManager that consumes these filters is scheduled in Phase 4.3, but the filters themselves land here and are unit-testable in isolation.
- Replace Phase-1 stub
MarketDataProvider.get_earnings_calendar()with real Alpha VantageEARNINGS_CALENDARclient - Cache earnings calendar in
data/features/events/earnings_calendar.parquetwith 24-hour TTL - Add
EventFilterabstract base tosrc/plugins/base.py- Contract:
should_exit(position, bar, context) -> (bool, Optional[str])
- Contract:
- Register
EventFiltertype insrc/plugins/registry.py - Implement filters in
src/risk/event_filter.py:-
EarningsBlackoutFilter(default window: T-2 to T+1) -
NewsShockFilter(default:|sentiment| > 0.75ANDnews_volume_ratio > 3) -
AtrStopFilter(default:2.0 × ATR_14stop,4.0 × ATR_14take-profit)
-
- Add
exit_reason: Optional[str]field toTradeSignal(src/models/trade_signal.py) - Add
exits:block toconfig/settings.yamlwith per-filter thresholds +enabled: booltoggles - Unit tests for each filter against synthetic earnings / news-shock / price-gap inputs
- Reference: ADR-0012
src/signals/feature_assembler.py currently hardcodes FEATURE_COLUMNS (~13 columns). Adding fundamentals or richer sentiment will silently drop features. Refactor to a schema registry built from active plugins.
- Remove hardcoded
FEATURE_COLUMNSconstant - Build schema at runtime from active
IndicatorPlugin,DataEnricher, andFundamentalIndicatorPlugininstances (each contributes itsoutput_columns) - Persist the training-time schema alongside each model artifact (
data/models/meta_model/v*_*.pkl.schema.json) - At inference time, assert assembled-feature schema matches training-time schema; raise with a clear error on mismatch
- Contract test: registering a new plugin propagates to assembled features without code changes to
FeatureAssembler
- Publish ADR-0013 (already drafted — ensure index is updated)
- Document Alpha Vantage free-tier rate-limit strategy and fallback logic
- Add
ALPHA_VANTAGE_API_KEYandFINNHUB_API_KEYto.env.example
- Feature matrix for a single sample stock (e.g., AAPL) shows non-zero technical, sentiment, AND fundamental columns on a recent bar
- Feature matrix for an ETF (e.g.,
ITA) showsfundamental_score == 0.0on every bar, regardless of fundamentals-provider behaviour - Manual override test: setting
fundamentals_weight_override: 0.0on a stock inwatchlist.yamlreproduces the ETF behaviour - Backtest A/B regression:
scripts/run_backtest.pyon AAPL 2020–2024 with fundamentals off vs on — fundamentals-on must not degrade Sharpe by more than 10% (overfitting guard) - Event-filter ablation: disabling
EarningsBlackoutFilterincreases max drawdown on AAPL earnings dates - All Phase 3 unit and integration tests pass
- ADRs 0011, 0012, 0013 reviewed and moved from Proposed to Accepted
Goal: LLM validation gate, risk management (consuming Phase-3 event filters), paper trading, monitoring.
Note (April 2026 renumber): This phase was previously Phase 3. Its scope is unchanged; it is renumbered to make room for the Phase 3 signal-pillar completion work above. The
RiskManagerin 4.3 now integrates theEventFilterinstances built in Phase 3.3.
Note: Alpaca offers free unlimited paper trading via their API. No paid service needed.
- Create Alpaca account at alpaca.markets and get paper trading API keys
- Add Alpaca keys to
.env - Implement
OrderManagerclass wrappingalpaca-trade-api - Test order submission, status tracking, and position queries on paper account
- Implement order types: market, limit, stop-loss, take-profit
- Add rate limiting to respect Alpaca API limits
- Implement
LLMValidatorclass as aSignalFilterplugin - Use OpenAI GPT-4o-mini with structured JSON output
- Build context: recent news (7 days), upcoming earnings, sector context
- Cache responses per ticker per day to avoid redundant API calls
- Implement APPROVE/VETO decision logic
- Track API costs in MLflow (tokens used per call)
- Write tests with mocked OpenAI responses
- Implement
RiskManagerclass with position sizing logic - ATR-based base size with confidence and Kelly-fraction scaling
- Regime adjustment (volatile = smaller size)
- Populate
TradeSignal.stop_loss_pctandtake_profit_pctusing ATR multipliers -
evaluate_exits(open_positions, bar)— iterate Phase-3.3EventFilterinstances + confidence exit; setTradeSignal.exit_reasonto the first triggered rule - Portfolio-level checks: max position %, max sector exposure, kill switches
- Daily/total drawdown monitoring with kill switch activation
- Implement
TradingPipelineclass insrc/pipeline.pywiring all components (Regime → FeatureAssembler → QuantEngine → MetaLabelModel → LLMValidator → RiskManager) -
run_daily()method: ingest → features → signals → validate → execute -
scripts/run_daily.pythin CLI wrapper aroundTradingPipeline.run_daily - Error handling and retry logic for API failures
- Idempotency: re-running on the same day should not produce duplicate trades
- Logging: structured logs to
data/results/logs/
- Create Telegram bot via BotFather, get bot token
- Implement
AlertServiceusingpython-telegram-bot - Send alerts on: trade entry, trade exit (include
exit_reason), LLM veto, drawdown breach, daily summary - Format messages with markdown for readability
- Test alert delivery before going live
- Set up local Grafana (Docker) or use simple Streamlit dashboard
- Track key metrics: open positions, daily P&L, cumulative return, drawdown, exit-reason distribution
- Visualize: regime distribution, signal generation rate, LLM veto rate
- Pull data from MLflow + local SQLite/Parquet logs
- Set up
cron(Linux/Mac) or Task Scheduler (Windows) to run pipeline daily pre-market - Alternative: use
APSchedulerorPrefectfor in-process scheduling - Verify timezone handling (US market hours regardless of local timezone)
- Paper trading runs daily for at least 5 consecutive days without errors
- LLM veto rate is in expected range (10-30% of signals)
- All alerts deliver successfully
- Risk limits enforced (no trades exceed max position %)
- Drawdown kill switch tested manually
- Event-exit attribution in trade log matches expected causes (earnings / news / ATR)
- Begin formal 3-month paper trading validation period
Note (April 2026 renumber): This was previously Phase 4. Renumbered only; contents and priority order are unchanged.
These are pluggable enhancements documented in docs/architecture.md Section 14. Implement as separate plugins per the priority order in the dependency map. Do not start until Phase 4 has been running successfully for at least 3 months.
- Kalman filter smoothing plugin (P0 — highest impact)
- Cross-asset correlation enricher (P1)
- Fractional differentiation for ML features (P1)
- Adaptive exit management using Kalman velocity (P2)
- Options flow data enricher (P2)
- Attention-based dynamic indicator weighting (P3)
- Self-hosted LLM (FinGPT/Llama) to eliminate API costs (P3)
- RL-based position sizing — DDPG/TD3 (P4 — needs 6+ months of trade data)
Use this section for ad-hoc notes, blockers, or questions to discuss:
- (empty)