diff --git a/backend/services/ai_analyzer.py b/backend/services/ai_analyzer.py index e91f2ed..6093ec4 100644 --- a/backend/services/ai_analyzer.py +++ b/backend/services/ai_analyzer.py @@ -1173,14 +1173,14 @@ Retourne UNIQUEMENT ce JSON: "strategy": "", "underlying": "", "rationale": "", - "asset_class": "", + "asset_class": "", "expected_move_pct": }}, {{ "strategy": "", "underlying": "", "rationale": "", - "asset_class": "", + "asset_class": "", "expected_move_pct": }} ] diff --git a/backend/services/database.py b/backend/services/database.py index 8757dd6..a1c0677 100644 --- a/backend/services/database.py +++ b/backend/services/database.py @@ -1027,6 +1027,26 @@ def _normalize_yf_ticker(ticker: str) -> str: return t +def _normalize_asset_class(cls: str) -> str: + """Map AI-returned asset_class variants to canonical keys used in the UI.""" + if not cls: + return "" + c = cls.lower().strip() + if any(k in c for k in ("energy", "oil", "gas", "petrol", "brent", "wti")): + return "energy" + if any(k in c for k in ("metal", "gold", "silver", "copper", "mining", "precious")): + return "metals" + if any(k in c for k in ("agri", "grain", "corn", "wheat", "soy", "crop", "coton", "coffee", "cocoa")): + return "agriculture" + if any(k in c for k in ("index", "indic", "indices", "spx", "nasdaq", "dow", "s&p", "russell", "cac", "dax")): + return "indices" + if any(k in c for k in ("equit", "stock", "action", "share", "sector", "xle", "xlf", "xlk")): + return "equities" + if any(k in c for k in ("forex", "currency", "fx", "devise", "change", "eur", "usd", "jpy", "dxy")): + return "forex" + return c + + def log_trade_entries(run_id: str, scored_patterns: List[Dict[str, Any]], quotes: Dict[str, Any]): """ For each scored pattern's trade_rankings, record entry price if the trade @@ -1191,7 +1211,7 @@ def log_trade_entries(run_id: str, scored_patterns: List[Dict[str, Any]], quotes ) updated_count += 1 else: - _trade_asset_class = ( + _trade_asset_class = _normalize_asset_class( trade.get("asset_class") or sp.get("asset_class") or _orig.get("asset_class") or @@ -1387,7 +1407,7 @@ def log_skipped_trade(run_id: str, pattern_id: str, pattern_name: str, expected_move_pct, skip_reason, skip_detail, asset_class) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", (run_id, pattern_id, pattern_name, underlying, strategy, score, - expected_move_pct, skip_reason, skip_detail, asset_class) + expected_move_pct, skip_reason, skip_detail, _normalize_asset_class(asset_class)) ) conn.commit() conn.close() diff --git a/frontend/src/pages/JournalDeBord.tsx b/frontend/src/pages/JournalDeBord.tsx index d8cc811..c8e42e5 100644 --- a/frontend/src/pages/JournalDeBord.tsx +++ b/frontend/src/pages/JournalDeBord.tsx @@ -239,6 +239,19 @@ function _isBearishStr(strategy: string) { return /put|bear|short|sell|vente|baissier/i.test(strategy ?? '') } +// Normalize AI-returned asset_class variants to canonical CATEGORIES keys +function _normalizeAssetClass(cls: string | null | undefined): string { + if (!cls) return '' + const c = cls.toLowerCase().trim() + if (/energy|oil|gas|petrol|brent|wti/.test(c)) return 'energy' + if (/metal|gold|silver|copper|mining|precious/.test(c)) return 'metals' + if (/agri|grain|corn|wheat|soy|crop|coton|coffee|cocoa/.test(c)) return 'agriculture' + if (/index|indic|indices|spx|nasdaq|dow|s&p|russell|cac|dax/.test(c)) return 'indices' + if (/equit|stock|action|share|sector|xle|xlf|xlk/.test(c)) return 'equities' + if (/forex|currency|fx|devise|change|eur|usd|jpy|dxy/.test(c)) return 'forex' + return c +} + interface FilterBarProps { search: string; onSearch: (v: string) => void assetClass: string; onAssetClass: (v: string) => void @@ -330,7 +343,7 @@ function ClosedTradesSection({ days }: { days: number }) { const trades = allTrades.filter((t: any) => { const q = search.toLowerCase() if (q && !`${t.underlying} ${t.strategy ?? ''} ${t.pattern_name ?? ''}`.toLowerCase().includes(q)) return false - if (assetClass !== 'all' && t.asset_class !== assetClass) return false + if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class) !== assetClass) return false const bearish = _isBearishStr(t.strategy ?? '') if (direction === 'bullish' && bearish) return false if (direction === 'bearish' && !bearish) return false @@ -900,9 +913,10 @@ function TradeMtmSection({ days }: { days: number }) { if ((t.latest_score ?? t.score_at_entry ?? 0) < minScoreFilter) return false const q = search.toLowerCase() if (q && !`${t.underlying} ${t.strategy ?? ''} ${t.pattern_name ?? ''}`.toLowerCase().includes(q)) return false - if (assetClass !== 'all' && t.asset_class !== assetClass) return false - if (direction === 'bullish' && t.direction !== 'bullish') return false - if (direction === 'bearish' && t.direction !== 'bearish') return false + if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class) !== assetClass) return false + const bearish = _isBearishStr(t.strategy ?? '') + if (direction === 'bullish' && bearish) return false + if (direction === 'bearish' && !bearish) return false return true }) @@ -1631,7 +1645,7 @@ function SkippedTradesSection({ days }: { days: number }) { const trades = allTrades.filter((t: any) => { const q = search.toLowerCase() if (q && !`${t.underlying} ${t.strategy ?? ''} ${t.pattern_name ?? ''}`.toLowerCase().includes(q)) return false - if (assetClass !== 'all' && t.asset_class !== assetClass) return false + if (assetClass !== 'all' && _normalizeAssetClass(t.asset_class) !== assetClass) return false if (reasonFilter !== 'all' && t.skip_reason !== reasonFilter) return false return true })