Phase 1 — Catégorisation:
- database.py: ADD COLUMN category + signal_direction on custom_patterns (migration);
save_custom_pattern persists category/signal_direction; new helpers:
get_unclassified_patterns(), update_pattern_classification(),
get_patterns_with_last_score()
- ai_analyzer.py: PATTERN_CATEGORIES dict (8 categories: géopolitique, macro_monétaire,
technique, commodités_supply, risk_off, flux_saisonnier, géo_économique, crédit_stress);
classify_patterns_batch() → GPT-4o-mini batch classification
- suggest schema: added category + signal_direction fields so new patterns are
classified from birth
- auto_cycle.py: Step 3.1 classifies all unclassified patterns after each suggestion
Phase 2 — Convergence layer (post-scoring, no extra AI call):
- ai_analyzer.py: _compute_convergence() groups scored patterns by (underlying, signal_direction);
conviction_bonus = min(20, +5 per additional agreeing pattern); adds conviction_score,
conviction_bonus, convergence_count, convergence_underlying, convergence_partners to each result;
called at end of score_patterns_with_context(), re-sorts by conviction_score
- auto_cycle.py: logs convergence summary after scoring; propagates category/signal_direction
to scored results for display
Phase optionnelle — Convergence in suggestion prompt:
- ai_analyzer.py: suggest_patterns_from_market_context() accepts convergence_block param;
injected into prompt so AI knows which underlyings have multi-pattern agreement
- auto_cycle.py: before suggestion, loads last-cycle scores via get_patterns_with_last_score(),
calls _compute_convergence() to build convergence block, passes to suggester
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auto_cycle.py: _scheduler_loop now distinguishes weekday (interval_hours)
from weekend (weekend_cycle_times UTC slots or sleep until Monday);
_parse_weekend_times() and _next_weekend_slot() helpers;
get_status() exposes weekend_cycle_enabled + weekend_cycle_times
- cycle.py: CycleConfigRequest adds weekend_cycle_enabled + weekend_cycle_times;
update_cycle_config validates HH:MM format and persists to config DB
- Config.tsx: weekend scheduling section with enable toggle + time picker
(06:00/08:00/12:00/18:00/22:00/00:00 UTC presets, multi-select);
weekendEnabled + weekendTimes state synced from cycle status
Default: enabled with 08:00 + 22:00 UTC (covers news scan + Globex open Sunday)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- portfolio_context.py: add _safe_float() helper (converts NaN/Inf → None);
use .squeeze().dropna() on yfinance closes before computing moves;
guard division by checking closes.iloc[-2] != 0
- cycle.py: add _sanitize_floats() recursive sanitizer applied to the full
snapshot before FastAPI serializes it — catches any remaining NaN from
iv_rank, technical indicators, or other sources
Fixes 500 on GET /api/cycle/contexts/{run_id} when yfinance returns NaN
weekend data for portfolio positions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auto_cycle.py: detect weekend/market session, build cycle_meta with day_of_week/is_weekend/market_note;
IVGate skips iv_rank>=99 on weekends to avoid artificial weekend option premium cascade;
inject portfolio context (open trades + price moves + concentration) before AI scoring;
pass portfolio_context_block + run_id to both AI scorer and suggester
- ai_analyzer.py: _build_temporal_news_block injects market session banner (WEEKEND warning,
pre/after-market note, or open session label) so AI knows markets are closed and defers execution to Monday
- iv_engine.py: add WHEAT/EUR/USD ticker aliases; skip saving IV snapshots on weekends to protect history;
resolve aliases before slash-format conversion in _resolve_ticker
- technical_indicators.py: fix pandas MultiIndex from yfinance>=0.2 (droplevel+squeeze);
use period proportional to lookback instead of fixed period=1d
- database.py: asset_class ticker-based fallback (_asset_class_from_ticker); one-time backfill migration
for all NULL asset_class rows; ai_call_logs table + save/get helpers; normalize_ticker public function
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Portfolio context (portfolio_context.py):
- get_open_trades_with_moves(): fetches open trades + 1d/5d yfinance price moves
- get_portfolio_concentration(): counts by asset_class
- build_portfolio_context_block(): formatted prompt block with strict AI instructions
(no double positions, flag contradictions, avoid overweight classes)
AI call logging:
- ai_call_logs table in DB (run_id, call_type, system/user prompt, response, tokens, ms)
- _chat() now accepts log_meta dict → saves call to DB non-blocking after each call
- suggest and score_batch calls pass run_id + call_type for full traceability
auto_cycle.py:
- Builds portfolio context before snapshot and both AI calls
- Context snapshot now includes portfolio_open_positions key
SystemLogs.tsx:
- "Contexte IA" tab gains sub-tabs: Contexte / Appels IA
- AiCallRow: expandable with 3 panes (user prompt / system prompt / response)
shows model, tokens breakdown, duration, call type badge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _normalize_asset_class() now accepts ticker param and infers class from
a full ticker→class lookup table (energy/metals/agri/indices/equities/forex)
- init_db() runs one-time UPDATE to backfill all NULL asset_class rows in
trade_entry_prices and skipped_trades using known ticker lists
- log_trade_entries and log_skipped_trade pass ticker to normalizer
- Frontend _normalizeAssetClass() gets same ticker lookup + pattern fallbacks
for =F futures, NSE: prefixed equities, =X currency pairs
- All 3 filter calls now pass t.underlying as second argument
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Direction (Ouvert) : t.direction n'existe pas dans trade_entry_prices → utilisait
undefined, excluait tout. Remplacé par _isBearishStr(t.strategy) comme Fermés.
Asset class (Ouvert, Fermés, Non loggés) : l'IA retournait parfois "commodities",
"currencies", "fx", "equity" au lieu des clés canoniques. Double correction :
- Frontend : _normalizeAssetClass() mappe les variantes → energy|metals|agriculture|
indices|equities|forex dans les 3 sections filtrées
- Backend database.py : _normalize_asset_class() appliqué à l'INSERT dans
trade_entry_prices et skipped_trades (nouveaux trades normalisés au stockage)
- Prompt ai_analyzer.py : suggested_trades[].asset_class contraint à l'enum explicite
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 4 — Price Discovery Status (la pièce maîtresse) :
- price_discovery.py (nouveau) : capture_price_snapshots() sauve les prix des tickers
liés à chaque news scorée (energy→BZ=F/NG=F, metals→GC=F/HG=F, indices→^GSPC/IWM)
- compute_absorptions() mesure combien du mouvement attendu s'est déjà produit
(status: not_yet_priced <30% / partially_priced 30-80% / fully_priced >80%)
- build_price_discovery_block() → bloc prompt avec opportunités classées
- database.py : table news_price_snapshots + save/get/purge fonctions
- auto_cycle.py : capture après ai_score_news_batch, compute avant suggestions,
block injecté dans suggestion + scoring prompts + context snapshot
- ai_analyzer.py : param price_discovery_block dans suggest + score
Phase 5 — Replay historique :
- cycle.py : POST /api/cycle/contexts/{run_id}/replay — recharge le snapshot historique
et relance suggest_patterns_from_market_context avec le contexte original
- useApi.ts : hook useReplayCycle
- SystemLogs.tsx : bouton "Rejouer ce cycle" dans onglet Contexte IA avec champ
notes, résultats inline (liste des patterns générés), section price_discovery
ouverte par défaut en rouge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 2 — Données macro FRED :
- fred_fetcher.py (nouveau) : 7 séries FRED (CPI, NFP, UNRATE, FEDFUNDS, GDP, ICSA,
spread 10Y-2Y) avec détection direction bullish/bearish et block prompt formaté
- ai_analyzer.py : param fred_block dans suggest + score, injecté dans les deux prompts
- auto_cycle.py : fetch FRED non-bloquant avant la suggestion
Context log — Snapshot du contexte complet :
- database.py : table cycle_context_snapshots + save/get/list fonctions
- auto_cycle.py : sauvegarde le snapshot (meta, news partitionnées, FRED, tech, IV, quotes)
- cycle.py : GET /api/cycle/contexts + GET /api/cycle/contexts/{run_id}
- useApi.ts : hooks useCycleContextSnapshots + useCycleContextSnapshot
- SystemLogs.tsx : onglet "Contexte IA" avec liste de cycles et visualiseur JSON
par section (cycle_meta, macro, news, FRED, tech) avec accordéon
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
get_trade_entry_prices filtre WHERE status='open', les trades fermés n'y
apparaissaient jamais. Réalisé branche maintenant sur useClosedTrades(90)
avec calcul EUR = capital * pnl_realized% / 100 et affichage avg %.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dashboard: P&L card séparé en deux colonnes (Ouvertes/Réalisées) pour Simulé et Portfolio
- Dashboard: closed trades P&L locked from pnl_realized, ne fluctue plus après fermeture
- Journal Ouvert: filtres ticker/stratégie + classe d'actif + direction (haussier/baissier)
- Journal Fermés: mêmes filtres + filtre P&L (gagnants/perdants) + bouton supprimer par ligne
- Journal Non loggés: filtres ticker + classe d'actif + raison de skip
- Backend: DELETE /api/journal/trades/{id} + delete_trade() dans database.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Partie basse de la card : 5 indicateurs clés expliquant le régime
(VIX, S&P vs 200j MA, Pente 10Y-3M, Cuivre, Or) avec couleur
contextuelle et hint court, puis chips des signaux déclencheurs
du régime dominant issus de scenarios.reasons[dominant].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Card PnL : area chart historique depuis pnl_snapshots (gradient couleur
selon PnL positif/négatif, axes date + %, tooltip)
- Card Risque : bloc VaR 95% (Historique / CVaR / Monte Carlo ×1.5)
depuis le dernier var_snapshot, avec horodatage du calcul
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- get_pnl_snapshot(id) : détail complet d'un snapshot avec trades parsés
- diff_pnl_snapshots(a, b) : diff positions entre deux snapshots (nouvelles /
fermées / évolution PnL par position + delta portfolio)
- GET /api/var/pnl/snapshots/{id} : détail snapshot
- GET /api/var/pnl/diff?a=&b= : calcul du diff
Frontend PositionHistory.tsx :
- Timeline scrollable des snapshots avec sparkline PnL
- Clic snapshot → détail des positions à ce moment (prix entrée, prix actuel,
PnL %, PnL €, régime macro)
- Boutons A/B par snapshot → sélection de deux points à comparer
- Vue diff A→B : nouvelles positions, fermées, évolution PnL par trade,
delta portfolio (capital, PnL %, PnL €)
- Route /position-history + nav sidebar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remplace http://localhost:8000 par des URLs relatives (/api/...)
pour passer par le proxy Vite/Nginx comme toutes les autres pages.
Cause du NetworkError en prod Docker.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cycleTrades inclut tous les trades du scoring run (anciens patterns
re-scorés inclus). On filtre par cyclePatternIds pour ne compter
que les trades issus de patterns ajoutés pendant ce cycle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GeoRadar:
- Filtre période : Aujourd'hui / 7j / 30j / Toutes
- Tri : Impact ↓/↑ · Récentes/Anciennes
- Compteur de news filtrées
Dashboard:
- Remplace Super Contexte par "Top Trades" (top 5 par PnL%)
avec ticker, stratégie, date d'entrée, PnL%
- Card "Scores Patterns" : ajoute la date d'ajout (created_at)
sous le nom de chaque pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For patterns added in the cycle but below log threshold, display the
recommended_trade (underlying + strategy + score) from scoreMap instead
of just the pattern name. Pattern name moves to secondary small italic line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PnL simulated: Investi ~ et P&L € calculés avec entry_price quand
capital_invested est null (label ~ pour indiquer estimation)
- Dernier Cycle: rename trades loggés -> trades ajoutés + mini mention
des non-loggés quand patternsAdded > tradesLogged
- Trades du cycle: supprime le grand header +N Trades (info déplacée
dans Dernier Cycle); layout inversé — trade en primaire, pattern
en secondaire petit en dessous; trades non loggés affichés en gris
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: get_status() now resolves scoring_run_id by querying
trade_entry_prices within the cycle time window, fixing the mismatch
between cycle run_id and the id actually written to trades
- Dashboard: Trades du cycle filters by scoring_run_id (no stale fallback)
- Dashboard: Pattern du cycle shows only patterns added in last cycle
(created_at >= started_at), renamed from Top Patterns
- Dashboard: Dernier Cycle now shows 4 stats (patterns/scorés/loggés/fermés)
+ IA commentary snippet
- Dashboard: P&L simulated mode bottom half shows open/closed/capital/profit
- Dashboard: Régime Macro shows top 4 scenario score bars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PnL card: rename to P&L, add Simulé/Portfolio toggle (localStorage),
simulated shows target/stop alert counts, portfolio shows invested € + P&L €
- Risk card: add asset class allocation horizontal bars (bullish=color/bearish=red)
from simPortfolioRisk.concentration, conflicts badge
- Dernier Cycle: full bilan layout — patterns added / scored / trades logged
in 3 mini-stat boxes
- Trades du cycle: show direction, ticker, strategy, PnL%, score for each trade
- Top Patterns: replaces Meilleur Pattern — shows top 4 scored with rank + ticker
- Top News: replaces Patterns Actifs — useGeoNews sorted by impact_score,
top 3 with title + impact bar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract TradeCard/TradeRow/IBKRTicket + TradeIdeasTab self-contained
component to frontend/src/components/TradeIdeas.tsx
- Dashboard: remove trade ideas section, MtM section, all related hooks/
state/computations; replace Signaux Géo mini-card with Trades du cycle;
add News géo link on Risque Géopolitique card
- JournalDeBord: add Idées de trade tab (left of Ouverts) using TradeIdeasTab
- Config: reorganize into 5 tabs — IA & Analyse, Auto-Cycle & Logging,
Sources, Journal & Sortie, Profils de risque; expose min_score_threshold
as a prominent slider in the Auto-Cycle tab
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dashboard: insert 2 rows of 4 mini-cards between top row and trade ideas
- Row 1: PnL Simulé, Risque Simulé, Dernier Cycle, Régime Macro
- Row 2: Super Contexte, Signaux Géo, Meilleur Pattern, Patterns Actifs
- All cards link to underlying pages via react-router Link
Journal: add 'Non loggés' tab exposing trades suggested by cycle
but skipped because no risk profile was matched
- New skipped_trades table (auto-created on backend restart)
- log_trade_entries() persists each skip with score/gain/asset_class
- GET /api/journal/skipped-trades + useSkippedTrades hook
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- portfolio_risk.py: add _infer_asset_class() with ticker→asset_class map
covering energy/metals/agri/indices/forex/rates futures, ETFs, forex pairs,
exchange prefixes (NSE:). Fallback applied when JOIN finds no match (orphaned
pattern_id after re-seed). Fixes "unknown 100%" shown in screenshot.
- RiskDashboard.tsx: add Portefeuille Réel / Simulé toggle at top.
New SimRiskPanel component with KPI row + concentration bars + conflict cards
+ AI recommendations — all visible inline in Risk Dashboard.
Red badge on Simulé tab when danger alerts exist.
- JournalDeBord.tsx: remove standalone Risque Sim. tab (moved to Risk Dashboard).
Replace with a red banner in summary cards when conflicts are detected,
pointing user to Risk Dashboard → Simulé.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auto_cycle.py: replace _log with logger (NameError at lines 484/489)
- auto_cycle.py: normalize underlying via _normalize_ticker before _resolve_ticker
so WHEAT→ZW=F→WEAT and EUR/USD→EURUSD=X→FXE reach the IV watchlist correctly
- iv_engine.py: _resolve_ticker now strips slash-format forex (EUR/USD→EURUSD=X)
before _PROXY lookup, fixing yfinance 500/404 spam from get_atm_iv
- database.py: _fetch in log_trade_entries uses _normalize_ticker (not _normalize_yf_ticker)
so commodity aliases like WHEAT→ZW=F are applied at price-fetch time
- ai_analyzer.py: max_workers=1 for batch scorer — parallel workers both slept and
retried simultaneously after 429, causing repeated bursts; sequential fixes the pattern
- journal.py + JournalDeBord.tsx: add price_warning field (no_price_data/no_entry_price/
no_live_price) with visible ⚠ badge and amber color on affected ticker/price cells
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ticker normalization (_normalize_ticker):
- EUR/USD slash-format → EURUSD=X (was passed raw to yfinance → 500/404 spam)
- bare 6-char forex pairs EURUSD/USDJPY etc → append =X
- commodity alias table: WHEAT→ZW=F, CORN→ZC=F, WTI→CL=F, BRENT→BZ=F,
GOLD→GC=F, SILVER→SI=F, NATGAS→NG=F, SUGAR→SB=F, + 15 others
- also normalize underlying at log_trade_entries time so stored tickers
are already canonical before MtM lookups
GPT-4o 429 rate limit:
- _chat() retries up to 3× on rate_limit errors, respects retry-after hint
from error message (e.g. "try again in 12.37s"), falls back to 2^n×5s
- batch scorer: parallel workers 4→2 to halve the token burst per cycle
(2 concurrent batches × ~6K tokens vs 4 × ~6K = 24K burst at 30K limit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB: 9 new columns on trade_entry_prices (status, closed_at, close_reason,
close_note, pnl_realized, close_price, target_pct, stop_loss_pct, signal_threshold)
via ALTER TABLE migration; close_trade(), get_closed_trades(),
update_trade_exit_params() helpers; exit_defaults config key
- Backend: PATCH /trades/{id}/close, PATCH /trades/{id}/exit-params,
GET/PUT /exit-defaults, GET /closed-trades with win-rate/avg-PnL stats;
trade-mtm now computes alert_type (target_reached|stop_loss) per trade
- Journal: new "Fermés" tab with closed trades table + stats banner (win rate,
avg PnL, total PnL, best trade); open trades show Cible/Stop progress bar +
🎯/🛑 alert badges + 1-click close modal (price, reason, note)
- Config: new "Paramètres de sortie" panel — target_pct, stop_loss_pct,
signal_reversal_mode, signal_reversal_threshold with live sliders
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hook ai_score_news_batch() into /api/geo/news so every news fetch is enriched
by GPT-4o-mini: corrected impact_score, ai_dir_energy/metals/indices, ai_resolution
(ceasefire/peace deal flag), ai_insight (1 French sentence). Gracefully no-ops
when OpenAI is not configured.
Add _compute_ai_alignment() in geo_analyzer: for each pattern compares the news
AI directional signals against the pattern's expected_move direction and produces
a -25..+25 bonus injected into similarity/relevance scores. Contra-signals
(e.g. peace deal → oil bearish while pattern expects oil spike) are flagged.
Frontend GeoRadar: PatternRelevanceCard shows AI alignment badge (green = aligned,
red = contra-signal) + base relevance diff + AI insights. NewsCard shows ai_insight,
directional arrows per asset class (⛽↑ 🥇↓) and resolution badge when expanded.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace 1-day change_pct with weighted blend (20% 1d / 50% 5d / 30% 10d)
for all 20 scored signals. Add Bayesian prior from rolling 25-day history
(weight 15%→45%) and a 10-point persistence threshold before regime switch.
Bootstrap on first load: replays last 20 trading days via yf.download batch
(45d) to pre-populate _regime_history, so stability is visible immediately.
Frontend: adds 'stable Xj' badge and history depth indicator on regime banner.
Doc: updates v4.0→v4.1, rewrites Étape 1 Régime Macro and glossary entry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds full Interactive Brokers order ticket to both the Dashboard cockpit
and the Journal de Bord MtM expanded rows. Each ticket shows the
underlying, computed strike in dollars, estimated expiry date (nearest
Friday), per-leg BUY/SELL CALL/PUT breakdown, order type LIMIT, budget
and target.
Also adds Strike and DTE columns to the MtM table and persists
strike_guidance + expiry_days_at_entry in trade_entry_prices.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- DB: add system_logs table (level/source/cycle_id/ticker/message) and
iv_watchlist table (ticker/added_by/is_active); seed builtin 18 tickers
- DBLogHandler attached at startup — all WARNING+ logs auto-persist to DB
- log_system_event() helper for structured manual events
- New router /api/logs: GET with filters (level, source, cycle_id, ticker,
date range), GET /sources, GET /cycles for dropdowns, DELETE /clear
- iv_watchlist now read from DB instead of hardcoded constant; options_vol
watchlist/refresh/bootstrap endpoints all use get_watchlist_tickers()
- New endpoints: POST/DELETE /options-vol/watchlist-tickers/{ticker} to
add/remove tickers; adding triggers background 1-year bootstrap
- auto_cycle: after log_trade_entries(), auto-detect new underlying proxies
not yet in watchlist, add them and bootstrap their IV history
Frontend:
- New page SystemLogs (/logs): log table with level/source/cycle/ticker/date
filters, color-coded rows, expandable JSON details, auto-refresh 30s
- Options Lab: WatchlistManager section — add ticker input, chip list with
builtin/auto/manual color coding, remove button for non-builtins
- Sidebar: Logs Système nav link (ScrollText icon)
- useApi: useSystemLogs, useLogSources, useLogCycles, useClearLogs,
useWatchlistTickers, useAddWatchlistTicker, useRemoveWatchlistTicker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>