feat: Pattern Lab — historical backtest engine for pattern discovery

- Remove all built-in patterns (no proof of legitimacy); seed_builtin_patterns is now a no-op
- DB: add backtest_lab_runs table + backtest_hits/runs_count columns on patterns
- services/pattern_lab.py: build_historical_context (yfinance + RSI/MA200),
  run_ai_backtest (GPT-4o as historical analyst), evaluate_outcomes (actual moves at T+horizon)
- routers/pattern_lab.py: POST /run, POST /evaluate/{id}, GET /runs, DELETE /runs/{id},
  POST /save-pattern (promotes hit pattern to library with reliability counters)
- PatternLab.tsx: 34 preset events 2015-2025 (macro/geo/credit/fx/commodities/volatility/tech),
  3-panel layout — preset selector + wizard + run history, market data table,
  AI pattern cards with hit/miss outcome display, Save to Library button
- useApi.ts: usePatternLabRuns, useRunPatternLab, useEvaluatePatternLab, useSaveLabPattern, useDeleteLabRun
- Sidebar + App.tsx: /pattern-lab route + FlaskConical nav link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-22 17:55:48 +02:00
parent a68a08d9af
commit cbf989502c
8 changed files with 1148 additions and 31 deletions

View File

@@ -83,12 +83,39 @@ def init_db():
"ALTER TABLE custom_patterns ADD COLUMN signal_direction TEXT",
# Pattern Explorer — taxonomy tree path
"ALTER TABLE custom_patterns ADD COLUMN taxonomy_path TEXT DEFAULT '[]'",
# Pattern Lab — backtest reliability tracking
"ALTER TABLE custom_patterns ADD COLUMN backtest_hits INTEGER DEFAULT 0",
"ALTER TABLE custom_patterns ADD COLUMN backtest_runs_count INTEGER DEFAULT 0",
# Remove all built-in patterns (no proof of legitimacy)
"DELETE FROM custom_patterns WHERE source = 'builtin'",
]:
try:
c.execute(_sql)
except Exception:
pass
# Pattern Lab — historical backtest runs
c.execute("""CREATE TABLE IF NOT EXISTS backtest_lab_runs (
id TEXT PRIMARY KEY,
preset_id TEXT,
theme TEXT NOT NULL,
analysis_date TEXT NOT NULL,
horizon_days INTEGER NOT NULL,
assets TEXT NOT NULL,
theme_hint TEXT,
context_snapshot TEXT,
ai_result TEXT,
outcome TEXT,
status TEXT DEFAULT 'pending',
error_msg TEXT,
created_at TEXT DEFAULT (datetime('now')),
evaluated_at TEXT
)""")
try:
c.execute("CREATE INDEX IF NOT EXISTS idx_blr_date ON backtest_lab_runs(analysis_date DESC)")
except Exception:
pass
# Phase 4.2 — Régime clusters (K-Means sur gauges macro)
c.execute("""CREATE TABLE IF NOT EXISTS regime_clusters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -885,37 +912,8 @@ def update_position_notes(pos_id: str, notes: str):
# ── Custom Patterns ────────────────────────────────────────────────────────────
def seed_builtin_patterns(builtin_patterns: List[Dict[str, Any]]):
"""Seed built-in patterns into DB (idempotent). Always updates taxonomy_path."""
conn = get_conn()
existing = {r[0] for r in conn.execute("SELECT id FROM custom_patterns").fetchall()}
for p in builtin_patterns:
taxonomy_json = json.dumps(p.get("taxonomy_path", []))
if p["id"] not in existing:
conn.execute("""INSERT INTO custom_patterns (
id, name, description, triggers, keywords, historical_instances,
suggested_trades, asset_class, expected_move_pct, probability,
horizon_days, source, is_active, taxonomy_path, updated_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,'builtin',1,?,datetime('now'))""", (
p["id"],
p.get("name", ""),
p.get("description", ""),
json.dumps(p.get("triggers", [])),
json.dumps(p.get("keywords", [])),
json.dumps(p.get("historical_instances", [])),
json.dumps(p.get("suggested_trades", [])),
p.get("asset_class", "indices"),
p.get("expected_move_pct", 0),
p.get("probability", 0.5),
p.get("horizon_days", 30),
taxonomy_json,
))
else:
conn.execute(
"UPDATE custom_patterns SET taxonomy_path=? WHERE id=? AND source='builtin'",
(taxonomy_json, p["id"])
)
conn.commit()
conn.close()
"""No-op: built-in patterns removed in favour of Pattern Lab backtested patterns."""
pass
def save_custom_pattern(pattern: Dict[str, Any]) -> str: