feat: Phase 1 — delta temporel + decay news + cycle_meta dans prompts IA

- database.py: get_last_completed_cycle_ts() pour mesurer le delta entre cycles
- auto_cycle.py: calcul delta_minutes + cycle_meta dict transmis aux fonctions IA
- ai_analyzer.py: apply_news_decay() (halflife par catégorie), partition_news_by_age()
  (3 buckets: inter_cycle / recent_24h / older), _build_temporal_news_block() pour
  le prompt suggestion; cycle_meta injecté aussi dans score_patterns_with_context()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-20 16:36:54 +02:00
parent fff0c15316
commit d5e31bc897
3 changed files with 211 additions and 5 deletions

View File

@@ -174,6 +174,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
save_pattern_scores, log_macro_regime, log_geo_alert, log_trade_entries,
add_cycle_run, update_cycle_run, save_reasoning_trace,
get_latest_portfolio_lessons, log_system_event,
get_last_completed_cycle_ts,
)
from services.data_fetcher import fetch_geo_news, get_all_quotes, get_macro_gauges, score_macro_scenarios
from services.geo_analyzer import compute_geo_risk_score
@@ -206,6 +207,37 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
_current_status["running"] = True
_current_status["last_run_id"] = run_id
# ── Cycle meta — timing context ───────────────────────────────────────
_now = datetime.utcnow()
_last_cycle_ts_str = get_last_completed_cycle_ts()
_delta_minutes: float = 180.0 # default 3h if no prior cycle
if _last_cycle_ts_str:
try:
_last_dt = datetime.fromisoformat(_last_cycle_ts_str.replace("Z", ""))
_delta_minutes = max(1.0, (_now - _last_dt).total_seconds() / 60)
except Exception:
pass
_interval_hours = float(get_config("auto_cycle_interval_hours") or "3")
if _delta_minutes < 90:
_calib_label = "Court terme (<2h) — signaux très récents"
elif _delta_minutes < 720:
_calib_label = f"Moyen terme ({_delta_minutes/60:.0f}h) — signaux potentiellement pas encore pricés"
else:
_calib_label = f"Long terme ({_delta_minutes/60:.0f}h) — marchés ont eu le temps d'intégrer"
cycle_meta = {
"current_cycle_ts": _now.isoformat(),
"last_cycle_ts": _last_cycle_ts_str,
"delta_minutes": round(_delta_minutes, 1),
"interval_hours": _interval_hours,
"calibration_label": _calib_label,
}
logger.info(
f"[Cycle {run_id[:16]}] Cycle meta: delta={_delta_minutes:.0f}min depuis dernier cycle"
f" ({_last_cycle_ts_str[:16] if _last_cycle_ts_str else 'premier cycle'})"
)
# ── Step 0: Load portfolio lessons + Super Contexte ──────────────────
portfolio_lessons = get_latest_portfolio_lessons()
@@ -341,11 +373,15 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
try:
from services.data_fetcher import get_economic_calendar
calendar = get_economic_calendar()
# Apply decay to news before suggestion (adds decayed_score + age_hours)
from services.ai_analyzer import apply_news_decay as _apply_decay
news = _apply_decay(news)
suggestions = suggest_patterns_from_market_context(
news, quotes, calendar, macro_regime=macro_regime, geo_score=geo_score_obj,
portfolio_lessons=portfolio_lessons,
reliability_map=_reliability_map or None,
iv_context=iv_context,
cycle_meta=cycle_meta,
)
except Exception as e:
logger.warning(f"[Cycle] Suggestion step failed: {e}")
@@ -468,6 +504,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
portfolio_lessons=portfolio_lessons,
iv_context=iv_context,
risk_context=risk_cluster_context,
cycle_meta=cycle_meta,
)
scored_with_id = [s for s in scored if s.get("pattern_id")]
scored_without_id = [s for s in scored if not s.get("pattern_id")]