Files
OpenFin/backend/routers/cycle.py
OpenSquared abee090881 feat: expandable inline rows in Journal + journal/maturity params in Config
- JournalDeBord: trade rows now expand inline (full-width) instead of
  PostmortemPanel appearing below the whole table. Click anywhere on a
  row to toggle. Period selector extended to 15/30/60/90j.
- Config: added Rétention Journal (30/60/90/180j) and Seuil Maturité
  (20/30/35/50%) controls, wired to the Appliquer button.
- Backend: journal_retention_days and maturity_threshold_pct read from
  config table; seeded at startup with defaults 90d / 35%. get_status()
  now returns both values so Config page can initialise correctly.
- cycle.py: CycleConfigRequest accepts and validates both new params.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:08:16 +02:00

85 lines
3.4 KiB
Python

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional
from services.database import get_cycle_runs, get_cycle_run, set_config, get_config
from services.auto_cycle import get_status, trigger_manual, restart_scheduler
router = APIRouter(prefix="/api/cycle", tags=["cycle"])
@router.get("/status")
def cycle_status():
"""Current scheduler state + last cycle summary."""
return get_status()
@router.get("/history")
def cycle_history(limit: int = 20):
"""List recent cycle runs."""
runs = get_cycle_runs(limit=limit)
# Parse commentary JSON for frontend
import json
for r in runs:
if r.get("commentary"):
try:
r["commentary_parsed"] = json.loads(r["commentary"])
except Exception:
r["commentary_parsed"] = {"commentary": r["commentary"]}
return {"runs": runs, "count": len(runs)}
@router.post("/trigger")
def trigger_cycle():
"""Manually trigger one cycle immediately (non-blocking)."""
key = get_config("openai_api_key") or ""
if not key:
raise HTTPException(400, "Clé OpenAI non configurée")
trigger_manual()
return {"triggered": True, "message": "Cycle lancé en arrière-plan"}
class CycleConfigRequest(BaseModel):
enabled: Optional[bool] = None
interval_hours: Optional[float] = None
similarity_threshold: Optional[float] = None
min_ev_threshold: Optional[float] = None
min_score_threshold: Optional[int] = None
journal_retention_days: Optional[int] = None
maturity_threshold_pct: Optional[int] = None
@router.post("/config")
def update_cycle_config(req: CycleConfigRequest):
"""Update auto-cycle + filter configuration and restart scheduler."""
if req.enabled is not None:
set_config("auto_cycle_enabled", "true" if req.enabled else "false")
if req.interval_hours is not None:
if not (0.5 <= req.interval_hours <= 24):
raise HTTPException(400, "interval_hours must be between 0.5 and 24")
set_config("auto_cycle_hours", str(req.interval_hours))
if req.similarity_threshold is not None:
if not (0.0 <= req.similarity_threshold <= 1.0):
raise HTTPException(400, "similarity_threshold must be between 0 and 1")
set_config("auto_cycle_similarity_threshold", str(req.similarity_threshold))
if req.min_ev_threshold is not None:
if not (0.0 <= req.min_ev_threshold <= 1.0):
raise HTTPException(400, "min_ev_threshold must be between 0 and 1")
set_config("min_ev_threshold", str(req.min_ev_threshold))
if req.min_score_threshold is not None:
if not (0 <= req.min_score_threshold <= 100):
raise HTTPException(400, "min_score_threshold must be between 0 and 100")
set_config("min_score_threshold", str(req.min_score_threshold))
if req.journal_retention_days is not None:
if not (7 <= req.journal_retention_days <= 365):
raise HTTPException(400, "journal_retention_days must be between 7 and 365")
set_config("journal_retention_days", str(req.journal_retention_days))
if req.maturity_threshold_pct is not None:
if not (5 <= req.maturity_threshold_pct <= 75):
raise HTTPException(400, "maturity_threshold_pct must be between 5 and 75")
set_config("maturity_threshold_pct", str(req.maturity_threshold_pct))
# Restart scheduler to pick up changes
restart_scheduler()
return get_status()