feat: instrument picker + Pattern Lab instrument scan mode

- instruments.ts: 90 IB-options-tradable instruments in 12 categories
  (US Indices, Europe, Asia, EM, Sectors, Forex, Bonds, Metals, Energy,
   Agriculture, Crypto, Volatility) — EUR/CHF, Cotton, etc. all included

- PatternExplorer: replace text input in Instrument Lens with categorised
  grid picker (category pill filters + search + custom ticker fallback)

- PatternLab: add Instrument Scan tab alongside Event Presets
  - Pick any instrument from the shared categorised picker
  - Set period (start/end date) + horizon per pattern
  - AI scans the full period: identifies 4-6 key pattern instances each with
    their own entry date, expected move, strategy
  - 'Evaluate outcomes' fetches actual price at T+horizon per pattern
  - 'Save pattern' promotes any instance to the Pattern Library

- backend/services/pattern_lab.py: run_instrument_scan() + evaluate_instrument_outcomes()
  (per-pattern analysis_date vs shared date in event mode)
- backend/routers/pattern_lab.py: POST /instrument-scan + POST /evaluate-instrument/{id}
- useApi.ts: useInstrumentScan + useEvaluateInstrumentScan hooks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-22 18:20:22 +02:00
parent cbf989502c
commit 303ecc2a3a
6 changed files with 850 additions and 80 deletions

View File

@@ -39,6 +39,14 @@ class SavePatternRequest(BaseModel):
asset_class: Optional[str] = "indices"
class InstrumentScanRequest(BaseModel):
ticker: str
instrument_name: str
start_date: str # YYYY-MM-DD
end_date: str # YYYY-MM-DD
horizon_days: int = 90
# ── Helpers ────────────────────────────────────────────────────────────────────
def _get_run(run_id: str) -> dict:
@@ -165,6 +173,82 @@ def delete_run(run_id: str):
return {"deleted": run_id}
@router.post("/instrument-scan")
async def instrument_scan(req: InstrumentScanRequest):
"""Scan an instrument's historical price action to extract pattern instances."""
from services.pattern_lab import run_instrument_scan
openai_key = get_config("openai_api_key") or ""
if not openai_key:
raise HTTPException(400, "OpenAI API key not configured")
run_id = f"LAB_INS_{uuid.uuid4().hex[:8].upper()}"
now = datetime.utcnow().isoformat()
conn = get_conn()
conn.execute("""INSERT INTO backtest_lab_runs
(id, preset_id, theme, analysis_date, horizon_days, assets, theme_hint, status, created_at)
VALUES (?,?,?,?,?,?,?,'running',?)""", (
run_id,
"instrument_scan",
f"Instrument Scan: {req.instrument_name}",
req.start_date,
req.horizon_days,
json.dumps([req.ticker]),
json.dumps({"type": "instrument", "ticker": req.ticker,
"instrument_name": req.instrument_name,
"start_date": req.start_date, "end_date": req.end_date}),
now,
))
conn.commit()
conn.close()
try:
ai_result = await run_instrument_scan(
req.ticker, req.instrument_name,
req.start_date, req.end_date,
req.horizon_days, openai_key,
)
conn = get_conn()
conn.execute("""UPDATE backtest_lab_runs SET
ai_result=?, status='done'
WHERE id=?""", (json.dumps(ai_result), run_id))
conn.commit()
conn.close()
return {"run_id": run_id, "status": "done", "ai_result": ai_result}
except Exception as e:
conn = get_conn()
conn.execute("UPDATE backtest_lab_runs SET status='error', error_msg=? WHERE id=?",
(str(e), run_id))
conn.commit()
conn.close()
raise HTTPException(500, f"Instrument scan failed: {e}")
@router.post("/evaluate-instrument/{run_id}")
def evaluate_instrument_run(run_id: str):
"""Evaluate outcomes for an instrument scan (per-pattern dates)."""
from services.pattern_lab import evaluate_instrument_outcomes
run = _get_run(run_id)
if run["status"] not in ("done", "evaluated"):
raise HTTPException(400, f"Run status is '{run['status']}', must be 'done' first")
outcomes = evaluate_instrument_outcomes(run)
conn = get_conn()
conn.execute("""UPDATE backtest_lab_runs SET
outcome=?, status='evaluated', evaluated_at=datetime('now')
WHERE id=?""", (json.dumps(outcomes), run_id))
conn.commit()
conn.close()
return {"run_id": run_id, "outcomes": outcomes}
@router.post("/save-pattern")
def save_pattern_from_run(req: SavePatternRequest):
"""Promote one AI-suggested pattern (with its outcome) to the pattern library."""