from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import Optional from services.database import get_risk_profiles, upsert_risk_profile, delete_risk_profile, _compute_trade_score router = APIRouter(prefix="/api/profiles", tags=["profiles"]) class RiskProfileRequest(BaseModel): id: Optional[int] = None name: str min_score: int min_gain_pct: float color: Optional[str] = "#3b82f6" enabled: Optional[bool] = True sort_order: Optional[int] = 0 @router.get("") def list_profiles(): """List all risk profiles ordered by sort_order.""" profiles = get_risk_profiles() # Annotate each profile with the EV breakeven info result = [] for p in profiles: # At the exact frontier: score = min_score, gain = min_gain_pct _, ev_net, trade_score = _compute_trade_score(p["min_score"], p["min_gain_pct"]) result.append({ **p, "ev_net_at_frontier": round(ev_net, 3), "trade_score_at_frontier": trade_score, }) return {"profiles": result} @router.post("") def create_profile(req: RiskProfileRequest): """Create a new risk profile.""" if not (0 <= req.min_score <= 100): raise HTTPException(400, "min_score must be between 0 and 100") if req.min_gain_pct < 0: raise HTTPException(400, "min_gain_pct must be >= 0") pid = upsert_risk_profile(req.model_dump()) profiles = get_risk_profiles() return {"id": pid, "profiles": profiles} @router.put("/{profile_id}") def update_profile(profile_id: int, req: RiskProfileRequest): """Update an existing risk profile.""" if not (0 <= req.min_score <= 100): raise HTTPException(400, "min_score must be between 0 and 100") data = req.model_dump() data["id"] = profile_id upsert_risk_profile(data) return {"profiles": get_risk_profiles()} @router.delete("/{profile_id}") def remove_profile(profile_id: int): """Delete a risk profile.""" profiles = get_risk_profiles() if len([p for p in profiles if p["enabled"]]) <= 1: # Allow deletion but warn pass delete_risk_profile(profile_id) return {"profiles": get_risk_profiles()} @router.get("/preview") def preview_score(score: int = 50, gain_pct: float = 100.0): """ Preview the trade metrics for a given (score, gain_pct) pair. Useful for the Config UI slider simulation. """ ev_gross, ev_net, trade_score = _compute_trade_score(score, gain_pct) profiles = get_risk_profiles(enabled_only=True) from services.database import _matches_profile matched = _matches_profile(score, gain_pct, profiles) return { "score": score, "gain_pct": gain_pct, "ev_gross": ev_gross, "ev_net": ev_net, "trade_score": trade_score, "matched_profile": matched, "accepted": matched is not None, }