feat: time-aware trade maturity classification
- Add _trade_maturity() helper: classifies trades by % of horizon elapsed (trop_tot <10%, debut 10-35%, mature 35-75%, fin_horizon >75%) - Fix horizon_days fallback chain in log_trade_entries (default 30→90) - journal.py: enrich each MTM trade with maturity dict + horizon_days - reasoning.py: portfolio report segments trades by maturity; GPT-4o draws lessons only from matures (≥35% elapsed), never from trop_tot - auto_cycle.py: 90d window, maturity-aware prompt with timing rules - JournalDeBord.tsx: maturity badge with emoji, label, progress bar and day counter (Xj / Yj Z%) replacing plain days_held column Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -889,7 +889,13 @@ def log_trade_entries(run_id: str, scored_patterns: List[Dict[str, Any]], quotes
|
||||
|
||||
ticker_key = underlying.upper()
|
||||
entry_price = price_map.get(ticker_key)
|
||||
horizon = int(trade.get("horizon_days") or sp.get("horizon_days") or 30)
|
||||
horizon = int(
|
||||
trade.get("horizon_days") or
|
||||
sp.get("horizon_days") or
|
||||
sp.get("recommended_trade", {}).get("expiry_days") or
|
||||
_orig.get("horizon_days") or
|
||||
90
|
||||
)
|
||||
|
||||
existing_row = conn.execute(
|
||||
"SELECT id FROM trade_entry_prices WHERE pattern_id=? AND underlying=? AND strategy=?",
|
||||
@@ -975,6 +981,48 @@ def get_cycle_run(run_id: str) -> Optional[Dict[str, Any]]:
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
h = max(horizon_days or 90, 1)
|
||||
d = max(days_held or 0, 0)
|
||||
ratio = d / h
|
||||
pct = round(ratio * 100, 1)
|
||||
|
||||
if ratio < 0.10:
|
||||
return {
|
||||
"status": "trop_tot", "label": "Trop tôt", "emoji": "🕐",
|
||||
"weight": 0.0, "color": "slate", "ratio_pct": pct,
|
||||
"readable": f"{d}j / {h}j ({pct}% écoulé — bruit statistique)",
|
||||
}
|
||||
elif ratio < 0.35:
|
||||
return {
|
||||
"status": "debut", "label": "Début", "emoji": "📊",
|
||||
"weight": 0.25, "color": "yellow", "ratio_pct": pct,
|
||||
"readable": f"{d}j / {h}j ({pct}% écoulé — signal précoce)",
|
||||
}
|
||||
elif ratio < 0.75:
|
||||
return {
|
||||
"status": "mature", "label": "Signal fiable", "emoji": "✅",
|
||||
"weight": 1.0, "color": "emerald", "ratio_pct": pct,
|
||||
"readable": f"{d}j / {h}j ({pct}% écoulé — signal fiable)",
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "fin_horizon", "label": "Fin d'horizon", "emoji": "⏰",
|
||||
"weight": 1.0, "color": "orange", "ratio_pct": pct,
|
||||
"readable": f"{d}j / {h}j ({pct}% écoulé — surveiller de près)",
|
||||
}
|
||||
|
||||
|
||||
def get_trade_entry_prices(days: int = 30) -> List[Dict[str, Any]]:
|
||||
conn = get_conn()
|
||||
rows = conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user