feat: Phase 2 — Pattern Reliability, Contre-thèses & Calibration probabiliste

Sprint 2.1 — Pattern Reliability Score
- database.py: get_pattern_reliability() — win_rate × log(n+1) sur trades matures (≥35% horizon)
- database.py: get_all_pattern_reliability_map() pour injection rapide dans les prompts
- ai_analyzer.py: inject reliability_map dans suggest_patterns (patterns fiables mis en avant)
- auto_cycle.py: charge reliability_map avant suggestion et le passe au suggéreur
- routers/analytics.py: GET /api/analytics/reliability
- PatternEditor.tsx: ReliabilityBadge sur chaque card + usePatternReliability hook
- useApi.ts: usePatternReliability, useCalibration hooks

Sprint 2.2 — Contre-thèses & Invalidation Triggers
- database.py: migration ALTER TABLE — counter_thesis, invalidation_trigger, invalidation_probability
- database.py: save_custom_pattern() persiste les 3 nouveaux champs
- ai_analyzer.py: counter_thesis + invalidation_trigger + invalidation_probability dans le JSON schema
- auto_cycle.py: détection automatique des triggers d'invalidation contre les news (keyword match)
- routers/analytics.py: GET /api/analytics/invalidation-alerts
- PatternEditor.tsx: affichage contre-thèse dans les cards + champs dans le formulaire
- PatternEditor.tsx: affichage dans AiSuggestModal (suggestions IA)
- routers/patterns.py: PatternRequest inclut les 3 nouveaux champs

Sprint 2.3 — Calibration probabiliste & Demi-vie KB
- database.py: migration — predicted_probability sur pattern_score_history
- database.py: save_pattern_scores() stocke probability du pattern à chaque scoring run
- database.py: get_calibration_data() — Brier score + buckets de calibration par décile
- database.py: expires_at + confidence_decay_days sur knowledge_base
- database.py: decay_kb_confidence() — decay automatique + archivage à 0
- auto_cycle.py: decay_kb_confidence() appelé au début de chaque cycle (non-bloquant)
- routers/analytics.py: GET /api/analytics/calibration + POST /api/analytics/kb/decay
- frontend/src/pages/Analytics.tsx: nouvelle page — tableau fiabilité + calibration Brier
- App.tsx + Sidebar.tsx: route /analytics + entrée menu

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-17 16:50:53 +02:00
parent 9a6b6f70b1
commit f09c5b8ee7
11 changed files with 718 additions and 14 deletions

View File

@@ -87,6 +87,15 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
ai_score_news_batch, _chat, DEFAULT_ANALYSIS_TEMPLATE,
)
# KB confidence decay (non-blocking)
try:
from services.database import decay_kb_confidence
_decayed = decay_kb_confidence()
if _decayed:
logger.info(f"[Cycle {run_id[:16]}] KB decay: {_decayed} entrée(s) mise(s) à jour")
except Exception as _e:
logger.warning(f"[Cycle] KB decay failed (non-blocking): {_e}")
# Check AI key
ai_key = get_config("openai_api_key") or ""
if not ai_key:
@@ -157,6 +166,37 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
geo_score_val = int(geo_score_obj.get("score") or 0)
summary["geo_score"] = geo_score_val
# ── Invalidation trigger detection ────────────────────────────────────
try:
from services.database import get_custom_patterns as _gcp
_active_patterns = _gcp()
_news_headlines = " ".join(
(n.get("title", "") + " " + (n.get("summary", "") or "")).lower()
for n in news[:15]
)
_triggered = []
for _pat in _active_patterns:
_trigger = (_pat.get("invalidation_trigger") or "").lower().strip()
if not _trigger:
continue
# Simple keyword matching: split trigger into words and check majority match
_words = [w for w in _trigger.split() if len(w) > 3]
if _words and sum(1 for w in _words if w in _news_headlines) >= max(1, len(_words) // 2):
_triggered.append({
"pattern_id": _pat["id"],
"pattern_name": _pat["name"],
"trigger": _pat["invalidation_trigger"],
"probability": _pat.get("invalidation_probability"),
})
if _triggered:
logger.warning(
f"[Cycle {run_id[:16]}] ⚠ INVALIDATION TRIGGERS FIRED for "
f"{len(_triggered)} pattern(s): {[t['pattern_name'] for t in _triggered]}"
)
summary["invalidation_alerts"] = _triggered
except Exception as _ie:
logger.debug(f"[Cycle] Invalidation check failed (non-blocking): {_ie}")
quotes = get_all_quotes()
gauges = get_macro_gauges()
@@ -167,12 +207,21 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
# ── Step 2: Suggest new patterns ──────────────────────────────────────
logger.info(f"[Cycle {run_id[:16]}] Step 2: suggesting patterns")
_reliability_map = {}
try:
from services.database import get_all_pattern_reliability_map
_reliability_map = get_all_pattern_reliability_map()
if _reliability_map:
logger.info(f"[Cycle {run_id[:16]}] Reliability map: {len(_reliability_map)} patterns")
except Exception as _re:
logger.warning(f"[Cycle] Reliability map failed (non-blocking): {_re}")
try:
from services.data_fetcher import get_economic_calendar
calendar = get_economic_calendar()
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,
)
except Exception as e:
logger.warning(f"[Cycle] Suggestion step failed: {e}")