From frankfurter.app rates and macro feeds to a paper-traded position. What runs every morning at 9:50, what runs Mondays at 9:30, what reports back to the dashboard. Three deep-dives below cover the ML core, the trade-signal pipeline, and the drift-triggered re-tuning.
FX RATES (frankfurter.app/ECB) MACRO FEEDS (yfinance) ─── DAILY 9:50 AM (LaunchAgent) ───
│ │
│ JPY/USD, SGD/USD, EUR, GBP, PHP │ DXY, VIX, US 10Y Treasury,
│ + 24,076 historical rows │ gold, oil, yield-spreads
▼ ▼
data/fx_historical.parquet data/exogenous/*.parquet
│ │
└──────────┬───────────┘
▼
┌────────────────────────────────────────┐
│ ingest_local_fx() — auto-fetch gaps │
│ create_features() — 38 features │
└────────────────────────────────────────┘
│
▼
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ STEP 1 — ML CORE ┃
┃ SARIMA + Prophet + LightGBM → ensemble ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
│
▼
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ STEP 2 — REGIME GATE + KELLY SIZING ┃
┃ ADX < 18 → SKIP | half-Kelly cap 20% ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
│
▼
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ STEP 3 — PAPER TRADE EXECUTION + SLACK ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
│
┌────────────────────┴────────────────────┐
▼ ▼
data/mock_pineapple.db data/daily_forecasts/
pipeline_runs · forecasts · YYYY-MM-DD_signals.json
signals · trades · portfolio
─── WEEKLY MONDAY 9:30 AM (LaunchAgent) ───
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ STEP 4 — DRIFT WATCH + RE-TUNE ┃
┃ walk-forward MAPE → 1.5× × 2 weeks → Optuna ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
└─► trained_models/YYYY-WXX/manifest.json + dashboard regen
─── ALWAYS-ON ───
Streamlit dashboard/app.py @ localhost:8504
Validated MAPE: JPY 1.43%, SGD 1.00% on 6-cutoff walk-forward (2023-2025). Paper trading: JPY Sharpe 1.49 live; SGD Sharpe 2.24 in rolling sim, 3 trades. Tests: 549/551 passing. Backtest speed: >30 min → ~12s per pair (ProcessPool). Daily run: ~90 seconds per pair, unattended.