Initial commit — GeoOptions Intelligence Cockpit v2.0
Stack: FastAPI + React/TypeScript + SQLite + GPT-4o Features: Radar géopolitique, Marchés, Régime Macro, Journal de Bord MTM, Rapport IA, Super Contexte (base de raisonnement évolutive), Boucle feedback IA. Deploy: Docker + docker-compose + nginx pour openfin.open-squared.tech Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
88
backend/routers/profiles.py
Normal file
88
backend/routers/profiles.py
Normal file
@@ -0,0 +1,88 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user