diff --git a/backend/routers/cycle.py b/backend/routers/cycle.py index 6299c69..2aefce2 100644 --- a/backend/routers/cycle.py +++ b/backend/routers/cycle.py @@ -44,6 +44,8 @@ class CycleConfigRequest(BaseModel): 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") @@ -67,6 +69,14 @@ def update_cycle_config(req: CycleConfigRequest): 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() diff --git a/backend/services/auto_cycle.py b/backend/services/auto_cycle.py index b5951ec..8ccee10 100644 --- a/backend/services/auto_cycle.py +++ b/backend/services/auto_cycle.py @@ -982,8 +982,11 @@ def get_status() -> Dict[str, Any]: sim_threshold = float(get_config("auto_cycle_similarity_threshold") or "0.30") min_ev = float(get_config("min_ev_threshold") or "0.0") min_score = int(get_config("min_score_threshold") or "0") + retention_days = int(get_config("journal_retention_days") or "90") + maturity_pct = int(get_config("maturity_threshold_pct") or "35") except Exception: interval_hours, enabled, sim_threshold, min_ev, min_score = 3.0, False, 0.30, 0.0, 0 + retention_days, maturity_pct = 90, 35 recent = get_cycle_runs(limit=1) last = recent[0] if recent else None @@ -995,6 +998,8 @@ def get_status() -> Dict[str, Any]: "similarity_threshold": sim_threshold, "min_ev_threshold": min_ev, "min_score_threshold": min_score, + "journal_retention_days": retention_days, + "maturity_threshold_pct": maturity_pct, "last_cycle": last, "scheduler_alive": bool(_cycle_thread and _cycle_thread.is_alive()), } diff --git a/backend/services/database.py b/backend/services/database.py index dea1dd0..66a07e2 100644 --- a/backend/services/database.py +++ b/backend/services/database.py @@ -349,6 +349,18 @@ def init_db(): except Exception: pass + # Seed default config values if not already set + for _key, _val in [ + ("journal_retention_days", "90"), + ("maturity_threshold_pct", "35"), + ]: + existing = c.execute("SELECT value FROM config WHERE key=?", (_key,)).fetchone() + if not existing: + c.execute( + "INSERT OR IGNORE INTO config (key, value, updated_at) VALUES (?, ?, datetime('now'))", + (_key, _val) + ) + conn.commit() conn.close() @@ -991,7 +1003,8 @@ def log_trade_entries(run_id: str, scored_patterns: List[Dict[str, Any]], quotes _log.info(f"[TradeLog] NEW trade: pattern='{pattern_name}' {underlying} {strategy} score={eff_score} gain={exp_move:.0f}% profile='{matched}' price={entry_price}") _log.info(f"[TradeLog] Done — inserted={inserted_count} updated={updated_count} skipped_no_profile={skipped_no_profile}") - conn.execute("DELETE FROM trade_entry_prices WHERE entry_date < date('now', '-90 days')") + _retention = int(get_config("journal_retention_days") or "90") + conn.execute(f"DELETE FROM trade_entry_prices WHERE entry_date < date('now', '-{_retention} days')") conn.commit() conn.close() @@ -1062,18 +1075,13 @@ def get_cycle_run(run_id: str) -> Optional[Dict[str, Any]]: def _trade_maturity(days_held: int, horizon_days: int) -> Dict[str, Any]: """ Classify a trade's maturity based on elapsed time vs planned horizon. - Returns status, label, weight (0-1 for lesson extraction), and color hint. - - Thresholds (percentage of horizon elapsed): - < 10% → trop_tot : P&L is pure noise, never evaluate - 10-35% → debut : early signal, very low weight - 35-75% → mature : reliable signal, full weight - > 75% → fin_horizon : approaching expiry, full weight + watch flag + Thresholds read from config (maturity_threshold_pct, default 35%). """ h = max(horizon_days or 90, 1) d = max(days_held or 0, 0) ratio = d / h pct = round(ratio * 100, 1) + mature_threshold = float(get_config("maturity_threshold_pct") or "35") / 100.0 if ratio < 0.10: return { @@ -1081,7 +1089,7 @@ def _trade_maturity(days_held: int, horizon_days: int) -> Dict[str, Any]: "weight": 0.0, "color": "slate", "ratio_pct": pct, "readable": f"{d}j / {h}j ({pct}% écoulé — bruit statistique)", } - elif ratio < 0.35: + elif ratio < mature_threshold: return { "status": "debut", "label": "Début", "emoji": "📊", "weight": 0.25, "color": "yellow", "ratio_pct": pct, @@ -1708,6 +1716,7 @@ def get_pattern_reliability(pattern_id: str = None) -> List[Dict]: conn.close() today = _date.today() + mature_threshold = float(get_config("maturity_threshold_pct") or "35") / 100.0 by_pattern: Dict[str, list] = {} for row in rows: r = dict(row) @@ -1718,8 +1727,7 @@ def get_pattern_reliability(pattern_id: str = None) -> List[Dict]: days_held = 0 horizon = r.get("horizon_days") or 30 ratio = days_held / horizon if horizon else 0 - # Only mature trades (≥35% of horizon elapsed) - if ratio < 0.35: + if ratio < mature_threshold: continue by_pattern.setdefault(r["pattern_id"], []).append(r) diff --git a/frontend/src/pages/Config.tsx b/frontend/src/pages/Config.tsx index ab46311..9b7d832 100644 --- a/frontend/src/pages/Config.tsx +++ b/frontend/src/pages/Config.tsx @@ -359,6 +359,8 @@ export default function Config() { const [cycleSimilarity, setCycleSimilarity] = useState(0.30) const [minEv, setMinEv] = useState(0.0) const [minScore, setMinScore] = useState(0) + const [retentionDays, setRetentionDays] = useState(90) + const [maturityThreshold, setMaturityThreshold] = useState(35) useEffect(() => { if (cs) { setCycleEnabled(cs.enabled ?? false) @@ -366,6 +368,8 @@ export default function Config() { setCycleSimilarity(cs.similarity_threshold ?? 0.30) setMinEv(cs.min_ev_threshold ?? 0.0) setMinScore(cs.min_score_threshold ?? 0) + setRetentionDays(cs.journal_retention_days ?? 90) + setMaturityThreshold(cs.maturity_threshold_pct ?? 35) } }, [cs]) @@ -676,6 +680,41 @@ export default function Config() { Toutes les N heures : suggère de nouveaux patterns, filtre les doublons, score tout, log les prix et génère un commentaire IA sur les performances.
+