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>
This commit is contained in:
OpenSquared
2026-06-18 10:08:16 +02:00
parent 8446876eb0
commit abee090881
5 changed files with 94 additions and 25 deletions

View File

@@ -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)