- PatternExplorer.tsx: new page with two views:
• Tree View — 6-root taxonomy (geopolitical, monetary_policy, economic,
commodity, risk_off, market_structure) with collapsible sub-nodes;
pattern cards appear on node selection
• Instrument Lens — search any ticker (e.g. GLD, FXE) to see every
pattern + scenario that references it, with matching trades highlighted
- geo_analyzer.py: PATTERN_TAXONOMY tree constant + taxonomy_path on all
patterns; 15 new documented patterns P009-P023 (BoJ YCC, SVB crisis,
Taiwan semis, OPEC cuts, Fed pivot, Debt ceiling, Iran nuclear, DPRK,
VIX backwardation, ECB surprise, Extreme Fear contrarian, S. China Sea,
European energy, CPI hot print, Flash crash)
- patterns.py: GET /api/patterns/taxonomy, GET /api/patterns/by-instrument
- database.py: taxonomy_path migration + seed_builtin_patterns updates path
- App.tsx: /patterns → PatternExplorer, /patterns/edit → PatternEditor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
from fastapi import APIRouter, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
from typing import Optional, List, Dict, Any
|
|
from services.database import (
|
|
save_custom_pattern, get_custom_patterns, delete_custom_pattern, toggle_pattern_active
|
|
)
|
|
from services.geo_analyzer import PATTERN_TAXONOMY
|
|
|
|
router = APIRouter(prefix="/api/patterns", tags=["patterns"])
|
|
|
|
|
|
class PatternRequest(BaseModel):
|
|
id: Optional[str] = None
|
|
name: str
|
|
description: str
|
|
triggers: List[str]
|
|
keywords: List[str]
|
|
historical_instances: Optional[List[Dict[str, Any]]] = []
|
|
suggested_trades: Optional[List[Dict[str, Any]]] = []
|
|
asset_class: str
|
|
expected_move_pct: float
|
|
probability: float
|
|
horizon_days: int
|
|
ai_quality_score: Optional[int] = None
|
|
ai_evaluation: Optional[Dict[str, Any]] = None
|
|
source: Optional[str] = "custom"
|
|
counter_thesis: Optional[str] = None
|
|
invalidation_trigger: Optional[str] = None
|
|
invalidation_probability: Optional[float] = None
|
|
|
|
|
|
@router.get("/all")
|
|
def list_all():
|
|
"""Return all active patterns from DB (builtin + custom)."""
|
|
return get_custom_patterns()
|
|
|
|
|
|
@router.get("/builtin")
|
|
def list_builtin():
|
|
return [p for p in get_custom_patterns() if p.get("source") == "builtin"]
|
|
|
|
|
|
@router.get("/custom")
|
|
def list_custom():
|
|
return [p for p in get_custom_patterns() if p.get("source") != "builtin"]
|
|
|
|
|
|
@router.post("/custom")
|
|
def create_pattern(req: PatternRequest):
|
|
data = req.model_dump()
|
|
data["source"] = "custom"
|
|
pat_id = save_custom_pattern(data)
|
|
return {"id": pat_id, "status": "saved"}
|
|
|
|
|
|
@router.put("/custom/{pat_id}")
|
|
def update_pattern(pat_id: str, req: PatternRequest):
|
|
data = req.model_dump()
|
|
data["id"] = pat_id
|
|
save_custom_pattern(data)
|
|
return {"id": pat_id, "status": "updated"}
|
|
|
|
|
|
@router.delete("/custom/{pat_id}")
|
|
def delete_pattern(pat_id: str):
|
|
delete_custom_pattern(pat_id)
|
|
return {"status": "deleted"}
|
|
|
|
|
|
@router.put("/toggle/{pat_id}")
|
|
def toggle_pattern(pat_id: str):
|
|
"""Enable or disable a pattern (works for builtin and custom)."""
|
|
new_state = toggle_pattern_active(pat_id)
|
|
return {"id": pat_id, "is_active": new_state}
|
|
|
|
|
|
@router.get("/taxonomy")
|
|
def get_taxonomy():
|
|
"""Return the taxonomy tree + all patterns (frontend builds node→pattern mapping)."""
|
|
patterns = get_custom_patterns()
|
|
return {"tree": PATTERN_TAXONOMY, "patterns": patterns}
|
|
|
|
|
|
@router.get("/by-instrument")
|
|
def get_by_instrument(ticker: str = Query(..., description="Ticker / underlying to search")):
|
|
"""Return all patterns that have a suggested_trade matching the given ticker."""
|
|
ticker_lower = ticker.strip().lower()
|
|
result = []
|
|
for p in get_custom_patterns():
|
|
trades = p.get("suggested_trades") or []
|
|
matching = [
|
|
t for t in trades
|
|
if ticker_lower in (t.get("underlying") or "").lower()
|
|
]
|
|
if matching:
|
|
result.append({**p, "matching_trades": matching})
|
|
return result
|