Files
OpenFin/backend/routers/patterns.py
OpenSquared 5ff5006dec feat: add purge-all for Pattern Library + clarify Trade Ideas in Data Management
Backend: DELETE /api/patterns/purge-all — removes all custom + backtested patterns
(source != 'builtin'), leaves Pattern Lab run history intact.

Frontend Config > Data Management:
- New PurgeButton for Pattern Library (custom_patterns table)
- Updated note: Trade Ideas are computed live from the Pattern Library (no separate table),
  so purging patterns resets trade idea generation on next cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 18:42:11 +02:00

110 lines
3.3 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("/purge-all")
def purge_all_patterns():
"""Delete ALL custom/backtested patterns from the pattern library."""
from services.database import get_conn
conn = get_conn()
conn.execute("DELETE FROM custom_patterns WHERE source != 'builtin'")
n = conn.total_changes
conn.commit()
conn.close()
return {"deleted": n}
@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