diff --git a/GeoOptions Intelligence — Documentation Système.htm b/GeoOptions Intelligence — Documentation Système.htm new file mode 100644 index 0000000..4df6401 --- /dev/null +++ b/GeoOptions Intelligence — Documentation Système.htm @@ -0,0 +1,971 @@ + + + + + +GeoOptions Intelligence — Documentation Système + + + + + +
+ +

GeoOptions Intelligence

+
Architecture du Système & Logique des Prompts IA
+
+ Version 2.0  ·  Juin 2026  ·  FastAPI + React + GPT-4o +
+
+ + + +

1. Concept Général

+ +

+ GeoOptions Intelligence est un cockpit de trading d'options dont +l'intelligence centrale repose sur un cycle itératif : collecter les +signaux macro-géopolitiques, identifier les patterns d'opportunité, +scorer leur pertinence en temps réel, puis apprendre des performances +passées pour affiner les cycles suivants. +

+ +

1.1 Philosophie fondamentale

+ +

+ Le système est construit autour d'une idée simple : les options sur dérivés géopolitiques suivent des patterns répétables. + Une escalade tarifaire US-Chine génère systématiquement de la +volatilité sur les semi-conducteurs. Un choc pétrolier bénéficie aux +stratégies long sur l'énergie. Le défi n'est pas de créer ces patterns, mais de les détecter assez tôt pour agir avant le consensus. +

+ +

+ L'IA (GPT-4o) joue le rôle d'un stratège senior externe + qui lit les news, connaît les régimes macro, et enrichit la base de +patterns — mais ce sont des règles humaines explicites qui décident si +un trade est logué, si une analyse est valide, et si les leçons passées +sont fiables. +

+ +
+
Principe directeur
+ L'IA propose, les règles quantitatives filtrent, l'historique de performance valide. Le cycle répète et apprend. +
+ +

1.2 Les trois mémoires du système

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
MémoireContenuDurée de vieUsage
Rapport PortfolioAnalyse post-mortem des trades matures : gagnants, perdants, leçons clés~7–30 jours (jusqu'au prochain rapport)Injecté dans les prompts de suggestion et scoring du cycle suivant
Super ContexteSynthèse évolutive : régimes, patterns récurrents, erreurs, corrélations macroPermanent — s'enrichit à chaque cycle (max 1×/6h)Contexte de fond pour tout le raisonnement IA
Base de Connaissances (KB)Entrées structurées par catégorie (régimes, patterns, erreurs) avec score de confiancePermanent — géré manuellement ou auto-ajouté par synthèseConsulté lors de la synthèse du Super Contexte
+ + + +
+

2. Workflow d'un Cycle Complet

+ +

+ Un cycle se déclenche automatiquement (toutes les N heures) ou +manuellement. La durée typique est de 2 à 5 minutes pour les étapes +synchrones, plus 1 à 3 minutes en background selon la disponibilité des +données. +

+ +
+ +
+
0
+
+
Chargement du contexte mémoriel DB
+
Charge le dernier Rapport Portfolio et le Super Contexte depuis la base de données. Ces données enrichiront tous les prompts IA du cycle courant.
+
+
+ +
+ +
+
1
+
+
Collecte des données de marché GPT-4o-mini
+
+ Récupère les news géopolitiques (RSS, GDELT), les prix de marché + (yfinance), les indicateurs macro (VIX, pente de courbe, DXY, Brent, +or, cuivre, S&P vs moyenne 200j) et le calendrier économique. +
Appel IA : GPT-4o-mini score chaque news (impact 0–100, classification géopolitique) en batch. +
+
+
+ +
+ +
+
2
+
+
Suggestion de nouveaux patterns GPT-4o
+
+ GPT-4o reçoit le contexte complet (news + prix + macro + +calendrier + Super Contexte + leçons portfolio) et propose des nouvelles + thèses de trading structurées. +
Retourne : nom, description, thèse macro, trades suggérés, keywords, probabilité, horizon cible. +
+
+
+ +
+ +
+
3
+
+
Filtre anti-doublons (Jaccard) Règle
+
+ Chaque pattern suggéré est comparé aux patterns existants par +similarité de keywords (distance de Jaccard). Si la similarité dépasse +le seuil configuré (défaut : 30%), le pattern est rejeté comme doublon. +Seuls les patterns vraiment nouveaux sont sauvegardés. +
+
+
+ +
+ +
+
4
+
+
Scoring de TOUS les patterns GPT-4o
+
+ GPT-4o score l'ensemble des patterns (nouveaux + existants) en +les confrontant au contexte marché actuel. Retourne pour chaque pattern : + score de pertinence (0–100), niveau de confiance, catalyseur clé, +résumé, trade recommandé avec stratégie (call spread, put, straddle...). +
+
+
+ +
+ +
+
5
+
+
Logging et journal DB
+
+ Sauvegarde les scores en historique, log les prix d'entrée des +trades passant les filtres EV/score, enregistre l'alerte géo et le +régime macro. Traces de raisonnement (input/output de chaque appel IA) +persistées pour audit. +
+
+
+ +
+ +
+
6
+
+
Commentaire de cycle GPT-4o
+
+ Synthèse narrative courte (4–6 phrases) expliquant pourquoi les +patterns scorent ainsi aujourd'hui, quel est le risque principal, et +quelle recommandation tactique pour le prochain cycle. Affiché dans +l'interface. +
+
+
+ +
Thread background (non-bloquant)
+ +
+
7
+
+
Rapport Portfolio automatique GPT-4o Background
+
+ Déclenché seulement si ≥ 2 trades matures avec |P&L| > +0.1% sont disponibles. GPT-4o analyse les performances réelles des +trades matures et génère un rapport structuré (headline, analyse +gagnants/perdants, leçons clés, angles morts). Ce rapport sera injecté +au cycle suivant. +
+
+
+ +
+ +
+
8
+
+
Mise à jour du Super Contexte GPT-4o Background
+
+ Déclenché seulement si le dernier Super Contexte a plus de 6 +heures. GPT-4o agrège tous les rapports portfolio, tous les trades (90j) + et la base de connaissances pour produire une synthèse évolutive riche. + Nouvelles entrées KB auto-ajoutées. +
+
+
+ +
+ +
+
Résumé des appels IA par cycle
+ Synchrones (systématiques) : 1× GPT-4o-mini (scoring news) + 3× GPT-4o (suggestion patterns, scoring patterns, commentaire)
+ Background (conditionnels) : 0–2× GPT-4o selon la disponibilité de trades matures et la fraîcheur du Super Contexte +
+ + + +
+

3. Anatomie des Prompts IA — Ce qu'on Inclut et Pourquoi

+ +

+ Chaque prompt est construit pour donner à GPT-4o exactement ce dont il + a besoin pour être pertinent — sans surcharge inutile qui diluerait le +signal ou augmenterait le coût. +

+ + +

3.1 Scoring des news (GPT-4o-mini)

+ +
+
+ Prompt : Classification et scoring d'impact des news + GPT-4o-mini +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Titre + source + date de chaque news — le minimum nécessaire pour scorer l'impact
  • +
  • Liste des catégories cibles (énergie, métaux, indices, forex, agriculture) — ancre le scoring sur nos actifs réels
  • +
  • Schéma de sortie strict (score 0–100, catégorie, keywords) — pour être parsé de manière fiable
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • Corps complet des articles — le titre suffit pour scorer l'impact géopolitique ; le corps alourdit le contexte sans gain mesurable
  • +
  • Historique des scores passés — scorer une news est une tâche stateless ; l'historique n'apporterait rien
  • +
  • Données de marché — inutiles pour déterminer l'impact géopolitique d'une news
  • +
+
+ +
+
Pourquoi GPT-4o-mini ici ?
+ Le scoring des news est une tâche de classification répétitive sur + des centaines d'items. GPT-4o-mini est 10× moins cher que GPT-4o pour +une qualité de classification identique sur des tâches structurées +courtes. On garde GPT-4o pour le raisonnement stratégique complexe. +
+
+
+ + +

3.2 Suggestion de nouveaux patterns (GPT-4o)

+ +
+
+ Prompt : Identification de nouvelles thèses de trading + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Top N news scorées avec leur impact — les signaux géopolitiques qui motivent les thèses
  • +
  • Régime macro dominant (stagflation, crise géo, expansion...) + scores de tous les scénarios — ancre les suggestions dans la réalité macro du moment
  • +
  • Prix clés (VIX, DXY, Brent, or, cuivre) — oriente vers des actifs avec momentum
  • +
  • Calendrier économique à venir — évite de suggérer des thèses juste avant un catalyseur qui les invaliderait
  • +
  • Super Contexte (narrative + priorités stratégiques + erreurs récurrentes) — le "cerveau" accumulé qui guide le style de suggestions
  • +
  • Leçons du dernier rapport portfolio (headline + leçons clés) — ancre dans ce qui a marché ou échoué récemment
  • +
  • Schéma de sortie complet (nom, description, thèse macro, keywords, trades suggérés, horizon, probabilité)
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • Liste des patterns existants — le filtre +Jaccard (étape 3) gère les doublons algorithmiquement ; l'inclure dans +le prompt créerait du bruit et biaiserait GPT-4o vers des variations des + patterns actuels
  • +
  • Historique complet des scores — on injecte seulement le résumé des leçons (rapport portfolio) pas les données brutes ; plus compact, plus signal
  • +
  • Données de trades individuels — trop granulaires pour la suggestion ; les leçons agrégées suffisent
  • +
  • Prix d'entrée et P&L des positions ouvertes — crée un biais d'ancrage ; on veut des suggestions basées sur le marché actuel, pas sur nos positions
  • +
+
+
+
+ + +

3.3 Scoring des patterns (GPT-4o)

+ +
+
+ Prompt : Évaluation de pertinence de chaque pattern dans le contexte actuel + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Tous les patterns (nom, description, thèse, + keywords, trades suggérés) — GPT-4o doit évaluer chacun dans le même +appel pour maintenir une cohérence relative des scores
  • +
  • Contexte marché complet (régime macro, prix, news) — le même contexte que la suggestion, pour cohérence
  • +
  • Super Contexte — les erreurs récurrentes et + priorités stratégiques permettent de pénaliser les patterns qui +répètent des patterns d'échec connus
  • +
  • Biais par classe d'actif (calculé depuis le régime dominant) — oriente les scores vers les classes qui profitent du régime
  • +
  • Score de risque géopolitique global — affecte les scores des patterns directionnels risqués
  • +
  • Schéma de sortie structuré (score, confiance, buckets d'évaluation, trade recommandé avec détails option)
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • Scores historiques de chaque pattern — +créerait un biais d'ancrage ; un pattern qui scorait 80 il y a 3 +semaines ne devrait pas nécessairement scorer 80 aujourd'hui si le +régime a changé
  • +
  • P&L réel des trades logués — même raison : GPT-4o doit évaluer le marché aujourd'hui, pas être influencé par nos performances passées
  • +
  • Identité des traders — non pertinent pour l'évaluation macro
  • +
+
+
+
+ + +

3.4 Commentaire de cycle (GPT-4o)

+ +
+
+ Prompt : Narration du cycle pour le trader + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Top 5 patterns scorés (nom, score, résumé) — ce que le cycle a retenu comme pertinent maintenant
  • +
  • Régime dominant + scores macro — pour expliquer pourquoi ces patterns scorent ainsi
  • +
  • Indicateurs macro clés (VIX, pente de courbe, DXY, Brent, or, cuivre) — les chiffres bruts pour ancrer la narration
  • +
  • Top 5 news à fort impact — les catalyseurs qui expliquent le contexte du cycle
  • +
  • Trades logués ces 7 derniers jours (pattern, underlying, stratégie) — pour commenter l'alignement positions/contexte
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • P&L des trades — le commentaire doit rester sur le contexte marché, pas sur les performances (risque de biais confirmationiste)
  • +
  • Toutes les news — seules les top 5 à fort impact sont pertinentes pour une synthèse narrative
  • +
  • Super Contexte complet — le commentaire est une analyse ponctuelle du cycle actuel, pas une synthèse mémorielle
  • +
+
+
+
+ + +

3.5 Rapport Portfolio automatique (GPT-4o)

+ +
+
+ Prompt : Post-mortem de performance sur trades matures + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Uniquement les trades MATURES avec P&L réel — seule source fiable d'apprentissage (voir section 4)
  • +
  • Classification explicite (matures gagnants / matures perdants / immatures à ne pas juger)
  • +
  • Score d'entrée de chaque trade — permet de corréler "le système a bien scoré ET le trade a marché" vs les faux positifs
  • +
  • Indicateur de maturité de chaque trade (% de l'horizon écoulé) — contexte pour interpréter le P&L
  • +
  • Règle de timing explicite dans le system prompt — interdit de tirer des conclusions sur les trades immatures
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • Trades immatures comme base d'apprentissage — un trade ouvert depuis 2 jours sur un horizon de 60 jours ne dit rien de fiable sur la qualité de la thèse
  • +
  • Contexte macro actuel — le rapport analyse le passé (pourquoi les matures ont marché), pas le présent
  • +
  • Données de marché en temps réel — hors sujet pour un post-mortem
  • +
+
+
+
+ + +

3.6 Synthèse Super Contexte (GPT-4o)

+ +
+
+ Prompt : Mise à jour de la base de raisonnement évolutive + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • 10 derniers rapports portfolio (headline, stats, analyse gagnants/perdants, leçons) — la matière première de l'apprentissage
  • +
  • Tous les trades (90j) classés par maturité avec P&L — la base empirique
  • +
  • Base de connaissances existante (par catégorie : régimes, patterns, erreurs) — évite de repartir de zéro et préserve les insights validés
  • +
  • Règle de timing dans le system prompt — même garde-fou que pour le rapport portfolio
  • +
  • Instruction de conservation des insights existants si peu de matures disponibles — évite que la synthèse "oublie" les leçons validées à cause d'un manque de données récentes
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • News du jour — le Super Contexte est une mémoire long terme, pas une analyse quotidienne
  • +
  • Prix de marché actuels — même raison ; les régimes et corrélations sont déduits des performances, pas des prix instantanés
  • +
  • Plus de 10 rapports — au-delà de 10 +rapports, le prompt dépasserait la fenêtre utile ; les rapports les plus + anciens ont aussi moins de pertinence dans un marché qui évolue
  • +
+
+
+
+ + + +
+

4. La Maturité des Trades — Pourquoi C'est Central

+ +

+ C'est l'une des décisions d'architecture les plus importantes du +système. Un P&L observé sur une option après 3 jours est du bruit +statistique. Après 70% de son horizon, c'est un signal. Mélanger les +deux dans les prompts IA pollue l'apprentissage. +

+ +

4.1 Définition des stades

+ +
+
0–10%
+
10–35%
+
35–75%
+
>75%
+
+
+ Trop tôt (0–10%) — Signal nul, ne pas analyser + Début (10–35%) — Signal faible, surveiller + Mature (35–75%) — Signal fiable, apprendre + Fin d'horizon (>75%) — Signal fort, leçons définitives +
+ +

4.2 Impact sur chaque étape du système

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ComposantTraitement des immaturesTraitement des matures
Rapport PortfolioListés pour transparence, étiquetés "ne pas juger", pas dans les calculs P&LBase exclusive des analyses, leçons et statistiques
Super ContexteListés avec étiquette, exclus des insights de performanceMatures gagnants et perdants analysés pour régimes et patterns
Journal de BordBadge gris/orange + barre de progression courteBadge vert/bleu + P&L affiché comme signal fiable
Gate rapport portfolioAu moins 2 trades matures avec |P&L| > 0,1% requis pour déclencher la génération GPT-4o
+ +
+
Risque évité
+ Sans ce filtre de maturité, GPT-4o pourrait tirer des leçons d'un +trade ouvert depuis 2 heures qui montre −5% (normal sur une option). Il +conclurait "la stratégie straddle est mauvaise sur le secteur énergie" +et dégraderait les cycles suivants. Le filtre de maturité protège +l'intégrité de l'apprentissage. +
+ + + +

5. Règles de Filtrage des Trades Logués

+ +

+ Tous les patterns scorés ne génèrent pas un trade dans le Journal de Bord. Deux filtres quantitatifs contrôlent l'entrée. +

+ +

5.1 Les deux filtres

+ + + + + + + + + + + + + + + + + + + + +
FiltreFormuleParamètreLogique
Score minimumscore ≥ min_scoreConfigurable (défaut : 0)Rejette les patterns jugés peu pertinents par GPT-4o dans le contexte actuel
EV nette minimump × G − (1−p) ≥ min_evConfigurable (défaut : 0.0)Filtre les trades à espérance mathématique négative (p = probabilité, G = gain/perte max)
+ +

5.2 Les profils de risque

+ +

+ Les profils de risque définissent des frontières d'acceptation nommées (Conservateur, Standard, Agressif...). Un trade est logué s'il passe au moins un + profil activé. Chaque profil définit un score minimum et un gain +minimum attendu, et affiche la formule EV nette à la frontière pour +vérifier la cohérence mathématique. +

+ +
+
Intérêt de l'approche multi-profil
+ Un même pattern peut être rejeté par le profil Conservateur (score 60, + gain 50% requis) mais accepté par le profil Agressif (score 40, gain +200% requis). Cela permet de logger des opportunités à fort potentiel +sans abaisser la barre globale. +
+ + + +

6. La Gate de 6 Heures du Super Contexte

+ +

+ Le Super Contexte est une synthèse mémorielle fondée sur des +performances de trades à 30–90 jours. Le relancer toutes les 3 heures (à + chaque cycle) serait non seulement coûteux, mais contre-productif : un +nouveau trade immature ne devrait jamais remettre en cause des +conclusions issues de mois d'observations. +

+ + + + + + + + + + + + + + + + + + + + + + +
ScénarioComportementRaison
Dernier Super Contexte < 6hSynthèse ignorée, le SC existant est utiliséPréserve les insights solides, évite le bruit des trades immatures
Dernier Super Contexte > 6hNouvelle synthèse GPT-4o déclenchéeIntègre les nouvelles données de performance accumulées
Trigger manuel avec ?force=trueSynthèse forcée quelle que soit l'anciennetéPermet une mise à jour immédiate si l'utilisateur le décide
+ +
+
Analogie
+ Le Super Contexte est comme un livre de règles qui s'enrichit après +chaque trimestre d'opérations. On ne réécrit pas le livre parce qu'un +trade ouvert ce matin montre −2%. +
+ + + +

7. Filtre Anti-doublon par Distance de Jaccard

+ +

+ GPT-4o, s'il reçoit les mêmes news deux cycles de suite, risque de +suggérer des patterns très proches. La distance de Jaccard mesure le +chevauchement entre les keywords de deux patterns. +

+ +
+
Formule
+ Jaccard(A, B) = |A ∩ B| / |A ∪ B| +
Où A et B sont les ensembles de keywords (en minuscules) de deux patterns. +

+ Si la similarité maximale entre le candidat et n'importe quel pattern existant dépasse le seuil (défaut 30%), le candidat est rejeté. +
+ + + + + + + + + + + + + + + + + + + + + + +
Seuil configuréComportementUsage recommandé
10% (strict)Presque tout passe, peu de déduplicationsBase de données de patterns vide ou très petite
30% (défaut)Équilibre entre nouveauté et redondanceUsage normal avec >20 patterns existants
60–80% (permissif)Seuls les quasi-doublons exacts sont rejetésVeut maximiser le volume de patterns générés
+ + + +
+

8. Architecture des Données

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table / CollectionContenuConsulté par
custom_patternsPatterns créés manuellement ou par l'IA (nom, thèse, keywords, trades suggérés)Scoring, suggestion (filtre Jaccard)
pattern_scoresHistorique de chaque scoring run (score, confiance, catalyseur, trade recommandé)Cockpit, Rapport IA, Journal
trade_entry_pricesUn enregistrement par trade logué : prix d'entrée, score à l'entrée, P&L courantJournal de Bord, Rapport Portfolio, Super Contexte
ai_reportsRapports portfolio générés par GPT-4o (JSON + stats + winners/losers)Super Contexte (10 derniers), Rapport IA page
reasoning_statesVersions successives du Super Contexte (narrative + synthesis JSON)Cycle (contexte mémoriel), Super Contexte page
kb_entriesBase de connaissances : entrées par catégorie avec confiance (0–100)Synthèse Super Contexte, Super Contexte page
reasoning_tracesInput/output de chaque appel IA (suggestion + scoring) — audit completDébogage, traçabilité des décisions
cycle_runsMétadonnées de chaque cycle (statut, patterns added/scored, commentary, régime)Page Config, historique cycles
configClés API, paramètres auto-cycle, seuils EV/scoreStartup, chaque cycle
+ + + +

9. Synthèse des Choix de Conception

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChoixAlternative rejetéeRaison
GPT-4o-mini pour le scoring des newsGPT-4o pour toutClassification répétitive → coût 10× réduit sans perte qualité
Filtre Jaccard pour déduplier (pas GPT-4o)Demander à GPT-4o si un pattern est un doublonAlgorithmique = déterministe, gratuit, rapide
Gate 6h sur le Super ContexteResynthétiser à chaque cycleProtège l'intégrité mémorielle du bruit court terme
Rapport portfolio en background threadBloquer le cycle jusqu'à la fin du rapportLe rapport sert au cycle suivant — pas besoin qu'il soit prêt maintenant
Filtres EV et score configurables par profilUn seul seuil global fixePermet plusieurs appétits de risque sans modifier le code
Injecter Super Contexte dans la suggestionLaisser GPT-4o repartir de zéroLe SC contient des mois d'apprentissage validé ; en partir sans le contexte serait un gaspillage
Exclure les P&L du prompt de scoringDonner à GPT-4o les performances passées pour scorerÉvite le biais d'ancrage et la circularité (un pattern qui a +bien scoré dans le passé ne mérite pas nécessairement un bon score +aujourd'hui)
Cleanup des cycles "running" au démarrageLaisser l'état incohérentUn kill du processus laisse la DB en état "running" pour toujours ; le cleanup au startup garantit la cohérence
+ +
+ +
+
En résumé
+ Le système applique un principe de séparation claire entre signal et bruit + : l'IA reçoit ce qui lui permet de raisonner (contexte actuel + mémoire + validée), pas ce qui peut la biaiser (performances récentes non +matures, historique de scores bruts). Les règles quantitatives (Jaccard, + maturité, EV, gate 6h) protègent la qualité des outputs IA en amont et +en aval de chaque appel. +
+ +

+ GeoOptions Intelligence v2.0  ·  Document généré le 17 juin 2026  ·  FastAPI + React + SQLite + GPT-4o +

+ + + + \ No newline at end of file diff --git a/GeoOptions_Intelligence_Documentation.html b/GeoOptions_Intelligence_Documentation.html new file mode 100644 index 0000000..5967908 --- /dev/null +++ b/GeoOptions_Intelligence_Documentation.html @@ -0,0 +1,888 @@ + + + + + +GeoOptions Intelligence — Documentation Système + + + + + +
+ +

GeoOptions Intelligence

+
Architecture du Système & Logique des Prompts IA
+
+ Version 2.0  ·  Juin 2026  ·  FastAPI + React + GPT-4o +
+
+ + + +

1. Concept Général

+ +

+ GeoOptions Intelligence est un cockpit de trading d'options dont l'intelligence centrale repose sur un cycle itératif : collecter les signaux macro-géopolitiques, identifier les patterns d'opportunité, scorer leur pertinence en temps réel, puis apprendre des performances passées pour affiner les cycles suivants. +

+ +

1.1 Philosophie fondamentale

+ +

+ Le système est construit autour d'une idée simple : les options sur dérivés géopolitiques suivent des patterns répétables. Une escalade tarifaire US-Chine génère systématiquement de la volatilité sur les semi-conducteurs. Un choc pétrolier bénéficie aux stratégies long sur l'énergie. Le défi n'est pas de créer ces patterns, mais de les détecter assez tôt pour agir avant le consensus. +

+ +

+ L'IA (GPT-4o) joue le rôle d'un stratège senior externe qui lit les news, connaît les régimes macro, et enrichit la base de patterns — mais ce sont des règles humaines explicites qui décident si un trade est logué, si une analyse est valide, et si les leçons passées sont fiables. +

+ +
+
Principe directeur
+ L'IA propose, les règles quantitatives filtrent, l'historique de performance valide. Le cycle répète et apprend. +
+ +

1.2 Les trois mémoires du système

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
MémoireContenuDurée de vieUsage
Rapport PortfolioAnalyse post-mortem des trades matures : gagnants, perdants, leçons clés~7–30 jours (jusqu'au prochain rapport)Injecté dans les prompts de suggestion et scoring du cycle suivant
Super ContexteSynthèse évolutive : régimes, patterns récurrents, erreurs, corrélations macroPermanent — s'enrichit à chaque cycle (max 1×/6h)Contexte de fond pour tout le raisonnement IA
Base de Connaissances (KB)Entrées structurées par catégorie (régimes, patterns, erreurs) avec score de confiancePermanent — géré manuellement ou auto-ajouté par synthèseConsulté lors de la synthèse du Super Contexte
+ + + +
+

2. Workflow d'un Cycle Complet

+ +

+ Un cycle se déclenche automatiquement (toutes les N heures) ou manuellement. La durée typique est de 2 à 5 minutes pour les étapes synchrones, plus 1 à 3 minutes en background selon la disponibilité des données. +

+ +
+ +
+
0
+
+
Chargement du contexte mémoriel DB
+
Charge le dernier Rapport Portfolio et le Super Contexte depuis la base de données. Ces données enrichiront tous les prompts IA du cycle courant.
+
+
+ +
+ +
+
1
+
+
Collecte des données de marché GPT-4o-mini
+
+ Récupère les news géopolitiques (RSS, GDELT), les prix de marché (yfinance), les indicateurs macro (VIX, pente de courbe, DXY, Brent, or, cuivre, S&P vs moyenne 200j) et le calendrier économique. +
Appel IA : GPT-4o-mini score chaque news (impact 0–100, classification géopolitique) en batch. +
+
+
+ +
+ +
+
2
+
+
Suggestion de nouveaux patterns GPT-4o
+
+ GPT-4o reçoit le contexte complet (news + prix + macro + calendrier + Super Contexte + leçons portfolio) et propose des nouvelles thèses de trading structurées. +
Retourne : nom, description, thèse macro, trades suggérés, keywords, probabilité, horizon cible. +
+
+
+ +
+ +
+
3
+
+
Filtre anti-doublons (Jaccard) Règle
+
+ Chaque pattern suggéré est comparé aux patterns existants par similarité de keywords (distance de Jaccard). Si la similarité dépasse le seuil configuré (défaut : 30%), le pattern est rejeté comme doublon. Seuls les patterns vraiment nouveaux sont sauvegardés. +
+
+
+ +
+ +
+
4
+
+
Scoring de TOUS les patterns GPT-4o
+
+ GPT-4o score l'ensemble des patterns (nouveaux + existants) en les confrontant au contexte marché actuel. Retourne pour chaque pattern : score de pertinence (0–100), niveau de confiance, catalyseur clé, résumé, trade recommandé avec stratégie (call spread, put, straddle...). +
+
+
+ +
+ +
+
5
+
+
Logging et journal DB
+
+ Sauvegarde les scores en historique, log les prix d'entrée des trades passant les filtres EV/score, enregistre l'alerte géo et le régime macro. Traces de raisonnement (input/output de chaque appel IA) persistées pour audit. +
+
+
+ +
+ +
+
6
+
+
Commentaire de cycle GPT-4o
+
+ Synthèse narrative courte (4–6 phrases) expliquant pourquoi les patterns scorent ainsi aujourd'hui, quel est le risque principal, et quelle recommandation tactique pour le prochain cycle. Affiché dans l'interface. +
+
+
+ +
Thread background (non-bloquant)
+ +
+
7
+
+
Rapport Portfolio automatique GPT-4o Background
+
+ Déclenché seulement si ≥ 2 trades matures avec |P&L| > 0.1% sont disponibles. GPT-4o analyse les performances réelles des trades matures et génère un rapport structuré (headline, analyse gagnants/perdants, leçons clés, angles morts). Ce rapport sera injecté au cycle suivant. +
+
+
+ +
+ +
+
8
+
+
Mise à jour du Super Contexte GPT-4o Background
+
+ Déclenché seulement si le dernier Super Contexte a plus de 6 heures. GPT-4o agrège tous les rapports portfolio, tous les trades (90j) et la base de connaissances pour produire une synthèse évolutive riche. Nouvelles entrées KB auto-ajoutées. +
+
+
+ +
+ +
+
Résumé des appels IA par cycle
+ Synchrones (systématiques) : 1× GPT-4o-mini (scoring news) + 3× GPT-4o (suggestion patterns, scoring patterns, commentaire)
+ Background (conditionnels) : 0–2× GPT-4o selon la disponibilité de trades matures et la fraîcheur du Super Contexte +
+ + + +
+

3. Anatomie des Prompts IA — Ce qu'on Inclut et Pourquoi

+ +

+ Chaque prompt est construit pour donner à GPT-4o exactement ce dont il a besoin pour être pertinent — sans surcharge inutile qui diluerait le signal ou augmenterait le coût. +

+ + +

3.1 Scoring des news (GPT-4o-mini)

+ +
+
+ Prompt : Classification et scoring d'impact des news + GPT-4o-mini +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Titre + source + date de chaque news — le minimum nécessaire pour scorer l'impact
  • +
  • Liste des catégories cibles (énergie, métaux, indices, forex, agriculture) — ancre le scoring sur nos actifs réels
  • +
  • Schéma de sortie strict (score 0–100, catégorie, keywords) — pour être parsé de manière fiable
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • Corps complet des articles — le titre suffit pour scorer l'impact géopolitique ; le corps alourdit le contexte sans gain mesurable
  • +
  • Historique des scores passés — scorer une news est une tâche stateless ; l'historique n'apporterait rien
  • +
  • Données de marché — inutiles pour déterminer l'impact géopolitique d'une news
  • +
+
+ +
+
Pourquoi GPT-4o-mini ici ?
+ Le scoring des news est une tâche de classification répétitive sur des centaines d'items. GPT-4o-mini est 10× moins cher que GPT-4o pour une qualité de classification identique sur des tâches structurées courtes. On garde GPT-4o pour le raisonnement stratégique complexe. +
+
+
+ + +

3.2 Suggestion de nouveaux patterns (GPT-4o)

+ +
+
+ Prompt : Identification de nouvelles thèses de trading + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Top N news scorées avec leur impact — les signaux géopolitiques qui motivent les thèses
  • +
  • Régime macro dominant (stagflation, crise géo, expansion...) + scores de tous les scénarios — ancre les suggestions dans la réalité macro du moment
  • +
  • Prix clés (VIX, DXY, Brent, or, cuivre) — oriente vers des actifs avec momentum
  • +
  • Calendrier économique à venir — évite de suggérer des thèses juste avant un catalyseur qui les invaliderait
  • +
  • Super Contexte (narrative + priorités stratégiques + erreurs récurrentes) — le "cerveau" accumulé qui guide le style de suggestions
  • +
  • Leçons du dernier rapport portfolio (headline + leçons clés) — ancre dans ce qui a marché ou échoué récemment
  • +
  • Schéma de sortie complet (nom, description, thèse macro, keywords, trades suggérés, horizon, probabilité)
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • Liste des patterns existants — le filtre Jaccard (étape 3) gère les doublons algorithmiquement ; l'inclure dans le prompt créerait du bruit et biaiserait GPT-4o vers des variations des patterns actuels
  • +
  • Historique complet des scores — on injecte seulement le résumé des leçons (rapport portfolio) pas les données brutes ; plus compact, plus signal
  • +
  • Données de trades individuels — trop granulaires pour la suggestion ; les leçons agrégées suffisent
  • +
  • Prix d'entrée et P&L des positions ouvertes — crée un biais d'ancrage ; on veut des suggestions basées sur le marché actuel, pas sur nos positions
  • +
+
+
+
+ + +

3.3 Scoring des patterns (GPT-4o)

+ +
+
+ Prompt : Évaluation de pertinence de chaque pattern dans le contexte actuel + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Tous les patterns (nom, description, thèse, keywords, trades suggérés) — GPT-4o doit évaluer chacun dans le même appel pour maintenir une cohérence relative des scores
  • +
  • Contexte marché complet (régime macro, prix, news) — le même contexte que la suggestion, pour cohérence
  • +
  • Super Contexte — les erreurs récurrentes et priorités stratégiques permettent de pénaliser les patterns qui répètent des patterns d'échec connus
  • +
  • Biais par classe d'actif (calculé depuis le régime dominant) — oriente les scores vers les classes qui profitent du régime
  • +
  • Score de risque géopolitique global — affecte les scores des patterns directionnels risqués
  • +
  • Schéma de sortie structuré (score, confiance, buckets d'évaluation, trade recommandé avec détails option)
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • Scores historiques de chaque pattern — créerait un biais d'ancrage ; un pattern qui scorait 80 il y a 3 semaines ne devrait pas nécessairement scorer 80 aujourd'hui si le régime a changé
  • +
  • P&L réel des trades logués — même raison : GPT-4o doit évaluer le marché aujourd'hui, pas être influencé par nos performances passées
  • +
  • Identité des traders — non pertinent pour l'évaluation macro
  • +
+
+
+
+ + +

3.4 Commentaire de cycle (GPT-4o)

+ +
+
+ Prompt : Narration du cycle pour le trader + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Top 5 patterns scorés (nom, score, résumé) — ce que le cycle a retenu comme pertinent maintenant
  • +
  • Régime dominant + scores macro — pour expliquer pourquoi ces patterns scorent ainsi
  • +
  • Indicateurs macro clés (VIX, pente de courbe, DXY, Brent, or, cuivre) — les chiffres bruts pour ancrer la narration
  • +
  • Top 5 news à fort impact — les catalyseurs qui expliquent le contexte du cycle
  • +
  • Trades logués ces 7 derniers jours (pattern, underlying, stratégie) — pour commenter l'alignement positions/contexte
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • P&L des trades — le commentaire doit rester sur le contexte marché, pas sur les performances (risque de biais confirmationiste)
  • +
  • Toutes les news — seules les top 5 à fort impact sont pertinentes pour une synthèse narrative
  • +
  • Super Contexte complet — le commentaire est une analyse ponctuelle du cycle actuel, pas une synthèse mémorielle
  • +
+
+
+
+ + +

3.5 Rapport Portfolio automatique (GPT-4o)

+ +
+
+ Prompt : Post-mortem de performance sur trades matures + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • Uniquement les trades MATURES avec P&L réel — seule source fiable d'apprentissage (voir section 4)
  • +
  • Classification explicite (matures gagnants / matures perdants / immatures à ne pas juger)
  • +
  • Score d'entrée de chaque trade — permet de corréler "le système a bien scoré ET le trade a marché" vs les faux positifs
  • +
  • Indicateur de maturité de chaque trade (% de l'horizon écoulé) — contexte pour interpréter le P&L
  • +
  • Règle de timing explicite dans le system prompt — interdit de tirer des conclusions sur les trades immatures
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • Trades immatures comme base d'apprentissage — un trade ouvert depuis 2 jours sur un horizon de 60 jours ne dit rien de fiable sur la qualité de la thèse
  • +
  • Contexte macro actuel — le rapport analyse le passé (pourquoi les matures ont marché), pas le présent
  • +
  • Données de marché en temps réel — hors sujet pour un post-mortem
  • +
+
+
+
+ + +

3.6 Synthèse Super Contexte (GPT-4o)

+ +
+
+ Prompt : Mise à jour de la base de raisonnement évolutive + GPT-4o +
+
+ +
+
✓ Ce qui est inclus
+
    +
  • 10 derniers rapports portfolio (headline, stats, analyse gagnants/perdants, leçons) — la matière première de l'apprentissage
  • +
  • Tous les trades (90j) classés par maturité avec P&L — la base empirique
  • +
  • Base de connaissances existante (par catégorie : régimes, patterns, erreurs) — évite de repartir de zéro et préserve les insights validés
  • +
  • Règle de timing dans le system prompt — même garde-fou que pour le rapport portfolio
  • +
  • Instruction de conservation des insights existants si peu de matures disponibles — évite que la synthèse "oublie" les leçons validées à cause d'un manque de données récentes
  • +
+
+ +
+
✗ Ce qui est exclu
+
    +
  • News du jour — le Super Contexte est une mémoire long terme, pas une analyse quotidienne
  • +
  • Prix de marché actuels — même raison ; les régimes et corrélations sont déduits des performances, pas des prix instantanés
  • +
  • Plus de 10 rapports — au-delà de 10 rapports, le prompt dépasserait la fenêtre utile ; les rapports les plus anciens ont aussi moins de pertinence dans un marché qui évolue
  • +
+
+
+
+ + + +
+

4. La Maturité des Trades — Pourquoi C'est Central

+ +

+ C'est l'une des décisions d'architecture les plus importantes du système. Un P&L observé sur une option après 3 jours est du bruit statistique. Après 70% de son horizon, c'est un signal. Mélanger les deux dans les prompts IA pollue l'apprentissage. +

+ +

4.1 Définition des stades

+ +
+
0–10%
+
10–35%
+
35–75%
+
>75%
+
+
+ Trop tôt (0–10%) — Signal nul, ne pas analyser + Début (10–35%) — Signal faible, surveiller + Mature (35–75%) — Signal fiable, apprendre + Fin d'horizon (>75%) — Signal fort, leçons définitives +
+ +

4.2 Impact sur chaque étape du système

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ComposantTraitement des immaturesTraitement des matures
Rapport PortfolioListés pour transparence, étiquetés "ne pas juger", pas dans les calculs P&LBase exclusive des analyses, leçons et statistiques
Super ContexteListés avec étiquette, exclus des insights de performanceMatures gagnants et perdants analysés pour régimes et patterns
Journal de BordBadge gris/orange + barre de progression courteBadge vert/bleu + P&L affiché comme signal fiable
Gate rapport portfolioAu moins 2 trades matures avec |P&L| > 0,1% requis pour déclencher la génération GPT-4o
+ +
+
Risque évité
+ Sans ce filtre de maturité, GPT-4o pourrait tirer des leçons d'un trade ouvert depuis 2 heures qui montre −5% (normal sur une option). Il conclurait "la stratégie straddle est mauvaise sur le secteur énergie" et dégraderait les cycles suivants. Le filtre de maturité protège l'intégrité de l'apprentissage. +
+ + + +

5. Règles de Filtrage des Trades Logués

+ +

+ Tous les patterns scorés ne génèrent pas un trade dans le Journal de Bord. Deux filtres quantitatifs contrôlent l'entrée. +

+ +

5.1 Les deux filtres

+ + + + + + + + + + + + + + + + + + + + +
FiltreFormuleParamètreLogique
Score minimumscore ≥ min_scoreConfigurable (défaut : 0)Rejette les patterns jugés peu pertinents par GPT-4o dans le contexte actuel
EV nette minimump × G − (1−p) ≥ min_evConfigurable (défaut : 0.0)Filtre les trades à espérance mathématique négative (p = probabilité, G = gain/perte max)
+ +

5.2 Les profils de risque

+ +

+ Les profils de risque définissent des frontières d'acceptation nommées (Conservateur, Standard, Agressif...). Un trade est logué s'il passe au moins un profil activé. Chaque profil définit un score minimum et un gain minimum attendu, et affiche la formule EV nette à la frontière pour vérifier la cohérence mathématique. +

+ +
+
Intérêt de l'approche multi-profil
+ Un même pattern peut être rejeté par le profil Conservateur (score 60, gain 50% requis) mais accepté par le profil Agressif (score 40, gain 200% requis). Cela permet de logger des opportunités à fort potentiel sans abaisser la barre globale. +
+ + + +

6. La Gate de 6 Heures du Super Contexte

+ +

+ Le Super Contexte est une synthèse mémorielle fondée sur des performances de trades à 30–90 jours. Le relancer toutes les 3 heures (à chaque cycle) serait non seulement coûteux, mais contre-productif : un nouveau trade immature ne devrait jamais remettre en cause des conclusions issues de mois d'observations. +

+ + + + + + + + + + + + + + + + + + + + + + +
ScénarioComportementRaison
Dernier Super Contexte < 6hSynthèse ignorée, le SC existant est utiliséPréserve les insights solides, évite le bruit des trades immatures
Dernier Super Contexte > 6hNouvelle synthèse GPT-4o déclenchéeIntègre les nouvelles données de performance accumulées
Trigger manuel avec ?force=trueSynthèse forcée quelle que soit l'anciennetéPermet une mise à jour immédiate si l'utilisateur le décide
+ +
+
Analogie
+ Le Super Contexte est comme un livre de règles qui s'enrichit après chaque trimestre d'opérations. On ne réécrit pas le livre parce qu'un trade ouvert ce matin montre −2%. +
+ + + +

7. Filtre Anti-doublon par Distance de Jaccard

+ +

+ GPT-4o, s'il reçoit les mêmes news deux cycles de suite, risque de suggérer des patterns très proches. La distance de Jaccard mesure le chevauchement entre les keywords de deux patterns. +

+ +
+
Formule
+ Jaccard(A, B) = |A ∩ B| / |A ∪ B| +
Où A et B sont les ensembles de keywords (en minuscules) de deux patterns. +

+ Si la similarité maximale entre le candidat et n'importe quel pattern existant dépasse le seuil (défaut 30%), le candidat est rejeté. +
+ + + + + + + + + + + + + + + + + + + + + + +
Seuil configuréComportementUsage recommandé
10% (strict)Presque tout passe, peu de déduplicationsBase de données de patterns vide ou très petite
30% (défaut)Équilibre entre nouveauté et redondanceUsage normal avec >20 patterns existants
60–80% (permissif)Seuls les quasi-doublons exacts sont rejetésVeut maximiser le volume de patterns générés
+ + + +
+

8. Architecture des Données

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table / CollectionContenuConsulté par
custom_patternsPatterns créés manuellement ou par l'IA (nom, thèse, keywords, trades suggérés)Scoring, suggestion (filtre Jaccard)
pattern_scoresHistorique de chaque scoring run (score, confiance, catalyseur, trade recommandé)Cockpit, Rapport IA, Journal
trade_entry_pricesUn enregistrement par trade logué : prix d'entrée, score à l'entrée, P&L courantJournal de Bord, Rapport Portfolio, Super Contexte
ai_reportsRapports portfolio générés par GPT-4o (JSON + stats + winners/losers)Super Contexte (10 derniers), Rapport IA page
reasoning_statesVersions successives du Super Contexte (narrative + synthesis JSON)Cycle (contexte mémoriel), Super Contexte page
kb_entriesBase de connaissances : entrées par catégorie avec confiance (0–100)Synthèse Super Contexte, Super Contexte page
reasoning_tracesInput/output de chaque appel IA (suggestion + scoring) — audit completDébogage, traçabilité des décisions
cycle_runsMétadonnées de chaque cycle (statut, patterns added/scored, commentary, régime)Page Config, historique cycles
configClés API, paramètres auto-cycle, seuils EV/scoreStartup, chaque cycle
+ + + +

9. Synthèse des Choix de Conception

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ChoixAlternative rejetéeRaison
GPT-4o-mini pour le scoring des newsGPT-4o pour toutClassification répétitive → coût 10× réduit sans perte qualité
Filtre Jaccard pour déduplier (pas GPT-4o)Demander à GPT-4o si un pattern est un doublonAlgorithmique = déterministe, gratuit, rapide
Gate 6h sur le Super ContexteResynthétiser à chaque cycleProtège l'intégrité mémorielle du bruit court terme
Rapport portfolio en background threadBloquer le cycle jusqu'à la fin du rapportLe rapport sert au cycle suivant — pas besoin qu'il soit prêt maintenant
Filtres EV et score configurables par profilUn seul seuil global fixePermet plusieurs appétits de risque sans modifier le code
Injecter Super Contexte dans la suggestionLaisser GPT-4o repartir de zéroLe SC contient des mois d'apprentissage validé ; en partir sans le contexte serait un gaspillage
Exclure les P&L du prompt de scoringDonner à GPT-4o les performances passées pour scorerÉvite le biais d'ancrage et la circularité (un pattern qui a bien scoré dans le passé ne mérite pas nécessairement un bon score aujourd'hui)
Cleanup des cycles "running" au démarrageLaisser l'état incohérentUn kill du processus laisse la DB en état "running" pour toujours ; le cleanup au startup garantit la cohérence
+ +
+ +
+
En résumé
+ Le système applique un principe de séparation claire entre signal et bruit : l'IA reçoit ce qui lui permet de raisonner (contexte actuel + mémoire validée), pas ce qui peut la biaiser (performances récentes non matures, historique de scores bruts). Les règles quantitatives (Jaccard, maturité, EV, gate 6h) protègent la qualité des outputs IA en amont et en aval de chaque appel. +
+ +

+ GeoOptions Intelligence v2.0  ·  Document généré le 17 juin 2026  ·  FastAPI + React + SQLite + GPT-4o +

+ + + diff --git a/GeoOptions_Roadmap_Dev.html b/GeoOptions_Roadmap_Dev.html new file mode 100644 index 0000000..09ab06b --- /dev/null +++ b/GeoOptions_Roadmap_Dev.html @@ -0,0 +1,900 @@ + + + + + +GeoOptions Intelligence — Roadmap de Développement + + + + + +
+ +

GeoOptions Intelligence

+
Roadmap de Développement — Vers le Niveau Hedge Fund
+
+ Basé sur l'audit expert · Juin 2026 · 4 Phases · ~6 mois +
+
+ + + +

État actuel vs Cibles

+ +

L'audit positionne le système à 6,5/10 au niveau "fonds professionnel" avec des notes excellentes sur l'architecture IA et la discipline de recherche, mais des lacunes importantes sur le côté purement options (volatilité implicite, structure de marché, sizing). Ce roadmap adresse chacune.

+ +
+
Trading d'Options
5/10
→ Cible : 8/10 (Phase 1 + 2)
+
Gestion du risque
6/10
→ Cible : 8.5/10 (Phase 3)
+
Niveau Fonds Pro
6.5/10
→ Cible : 8.5/10 (Phase 1→3)
+
Architecture IA
8.5/10
→ Cible : 9.5/10 (Phase 2 + 4)
+
+ + + +

Vue d'ensemble — 6 mois

+ +
+
+
1
+
Phase 1
IV & Vol
+
S1–S6
+
+
+
2
+
Phase 2
Fiabilité & Thèses
+
S7–S12
+
+
+
3
+
Phase 3
Portefeuille & Risk
+
S13–S20
+
+
+
4
+
Phase 4
Moteur Quantitatif
+
S21–S26
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PhaseThèmeDuréeLacune adresséeImpact note globale
1Volatilité implicite & structure du marché options6 semainesTrading options 5/10 → 7.5/10+1.5 pts niveau pro
2Fiabilité historique, contre-thèses & calibration6 semainesArchitecture IA 8.5/10 → 9.5/10+0.8 pts niveau pro
3Portefeuille, corrélations & position sizing8 semainesGestion risque 6/10 → 8.5/10+1.2 pts niveau pro
4Moteur probabiliste & apprentissage automatique6 semainesCalibration statistique / MLNiveau recherche quant
+ + + +
+ +
+
+
🔴 Phase 1 — Volatilité Implicite & Structure Marché Options
+
Semaines 1–6 · PRIORITÉ CRITIQUE · Lacune la plus impactante
+
+
+ +
+
Pourquoi c'est la priorité absolue
+ "Tu trades des options. IV Rank, term structure, skew sont les paramètres fondamentaux de valorisation d'une option. Sans eux, une bonne thèse géopolitique peut quand même produire un mauvais trade : si l'IV est déjà au 95e percentile, tu paies 3× trop cher pour la convexité." — Audit expert +
+ + +
+
+
+
Sprint 1.1
+
IV Rank & IV Percentile par underlying
+
+
+ data + backend + frontend + S1 – S2 +
+
+
+
+
Livrables
+
    +
  • Endpoint GET /api/options/iv/{ticker} retournant IV actuelle, IV Rank, IV Percentile (52 semaines)
  • +
  • Service iv_engine.py : récupère les options chains yfinance, calcule IV par Black-Scholes inverse (ou utilise la mid-price des ATM options)
  • +
  • Table DB iv_history : stockage journalier de l'IV par underlying (pour calculer Rank/Percentile)
  • +
  • Affichage dans Journal de Bord : badge "IV Rank 78%" coloré (vert <25%, orange 25–75%, rouge >75%)
  • +
  • Affichage dans Cockpit : section "Options Atmosphere" avec IV des actifs clés
  • +
+
+
+
Pourquoi ces choix
+
    +
  • IV Rank = (IV actuelle − IV min 52s) / (IV max 52s − IV min 52s) → signal cheap/cher universel
  • +
  • IV Percentile = % jours où IV était inférieure → plus robuste aux pics extrêmes
  • +
  • yfinance donne accès aux options chains gratuitement sur les actions US et ETFs
  • +
  • Stockage journalier nécessaire : sans historique, pas de Rank/Percentile
  • +
+
Contraintes
+
    +
  • yfinance : données options limitées aux actions US/ETF (pas les futures)
  • +
  • Calcul BS inverse peut être instable sur les options très OTM — utiliser uniquement les ATM proches
  • +
+
+
+
+ + +
+
+
+
Sprint 1.2
+
Term Structure & Skew
+
+
+ data + backend + frontend + S3 – S4 +
+
+
+
+
Livrables
+
    +
  • Term structure : IV à 30j, 60j, 90j, 180j — affiché comme courbe dans une nouvelle page "Options Lab"
  • +
  • Indicateur contango/backwardation : structure = "contango" | "backwardation" | "flat"
  • +
  • Put/Call skew : IV put 25-delta − IV call 25-delta (proxy de protection de marché)
  • +
  • Injection dans le prompt de scoring : "IV Rank=78%, structure=backwardation, skew=+4pts → IV élevée, marché achète des puts"
  • +
  • Règle dans le prompt : si IV Rank > 80%, pénaliser les stratégies acheteuses de vol (long straddle, long call)
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Contango (IV court terme < long terme) = marché calme, bon pour vendre de la vol courte
  • +
  • Backwardation (IV court terme > long terme) = stress, bon pour acheter de la protection courte
  • +
  • Skew positif (puts plus chers que calls) = marché se protège → biais baissier implicite
  • +
  • Injecter dans le scoring change concrètement les recommandations de stratégie d'option
  • +
+
Lié à
+
    +
  • Sprint 1.1 (infrastructure IV)
  • +
  • Modifie le prompt de scoring (étape 4 du cycle)
  • +
+
+
+
+ + +
+
+
+
Sprint 1.3
+
Options Flow (proxy Open Interest)
+
+
+ data + backend + S5 – S6 +
+
+
+
+
Livrables
+
    +
  • Calcul du Put/Call Open Interest Ratio par underlying (via yfinance options chains)
  • +
  • Détection des strikes avec OI inhabituel (OI > 2σ de la moyenne sur 20j)
  • +
  • Indicateur "Dealer Gamma" simplifié : net gamma des market makers estimé depuis l'OI puts/calls ATM
  • +
  • Signal injecté dans le prompt : "OI puts/calls = 1.8 → smart money défensif" ou "gros OI call sur strike 550 → mur de résistance potentiel"
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Options flow = ce que font réellement les institutionnels, pas ce qu'ils disent
  • +
  • Gamma exposure élevée des dealers → ils vont hedger → amortit les mouvements de prix
  • +
  • yfinance permet d'accéder à l'OI sans abonnement premium (limité aux US)
  • +
+
Contraintes
+
    +
  • Gamma dealer exact nécessite des données tick-by-tick (abonnement SpotGamma ~$50/mois si souhaité plus tard)
  • +
  • Proxy OI est une approximation — le labeler "estimation" dans l'UI
  • +
+
+
+
+ +
+
+ + + +
+ +
+
+
🟠 Phase 2 — Fiabilité Historique, Contre-Thèses & Calibration
+
Semaines 7–12 · PRIORITÉ HAUTE · Renforce l'intégrité de l'apprentissage
+
+
+ +
+
Enjeu
+ "Tous les patterns sont traités pareil. Or un pattern avec 28 trades à 68% de win rate mérite beaucoup plus de crédit qu'un pattern avec 3 trades à 100% (biais de petit échantillon)." — Audit expert +
+ + +
+
+
+
Sprint 2.1
+
Pattern Reliability Score
+
+
+ backend + frontend + prompt + S7 – S8 +
+
+
+
+
Livrables
+
    +
  • Vue DB pattern_reliability : pour chaque pattern, calcule trades_count, win_rate, avg_pnl_pct, max_pnl, max_loss sur les trades matures uniquement
  • +
  • Score de fiabilité composite = win_rate × log(trades_count + 1) (pénalise les petits échantillons)
  • +
  • Endpoint GET /api/patterns/reliability
  • +
  • Affichage dans la page Patterns : tableau avec colonnes Trades / Win Rate / Avg P&L / Fiabilité
  • +
  • Badge dans Journal de Bord : "⭐ 68% sur 28 trades" ou "🔬 2 trades — données insuffisantes"
  • +
  • Injection dans le prompt de suggestion : tableau de fiabilité des 10 meilleurs patterns → GPT-4o ne suggère pas de variantes d'un pattern déjà fiable si le contexte n'a pas changé
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Un pattern nouveau n'a aucune track record → sa "fiabilité" = 0 jusqu'à accumulation de données
  • +
  • Le multiplicateur log(n+1) évite qu'un pattern à 100% sur 2 trades écrase un pattern à 65% sur 30 trades
  • +
  • Injecter dans le prompt de suggestion : GPT-4o peut adapter ses thèses en connaissant ce qui a empiriquement fonctionné
  • +
  • Critique = base uniquement sur trades MATURES pour éviter le bruit (cohérence avec le principe fondamental)
  • +
+
+
+
+ + +
+
+
+
Sprint 2.2
+
Contre-Thèses & Événements Invalidants
+
+
+ backend + frontend + prompt + S9 – S10 +
+
+
+
+
Livrables
+
    +
  • Champs counter_thesis, invalidation_trigger, invalidation_probability ajoutés à la table custom_patterns
  • +
  • Modification du prompt de suggestion : GPT-4o doit retourner pour chaque pattern {"thesis": ..., "counter_thesis": ..., "invalidation_trigger": ..., "invalidation_prob": 0-100}
  • +
  • Affichage dans la page Patterns : onglet "Thèse / Contre-thèse / Invalidation" avec couleur de probabilité d'invalidation
  • +
  • Système de surveillance : si une news matche le invalidation_trigger d'un trade ouvert → alerte dans le Journal de Bord
  • +
  • Intégration dans le scoring : patterns avec forte probabilité d'invalidation imminente → score pénalisé
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Force GPT-4o à raisonner en "steel man" de la position opposée → réduit le biais de confirmation
  • +
  • Le trigger d'invalidation permet une gestion dynamique : si le trigger se réalise, sortir du trade
  • +
  • Exemple : thèse = escalade Taiwan; contre-thèse = bluff diplomatique; invalidation = annonce négociations US-Chine
  • +
+
Impact Super Contexte
+
    +
  • Le rapport portfolio pourra distinguer "thèse correcte mais invalidation s'est réalisée" vs "thèse incorrecte dès le départ"
  • +
+
+
+
+ + +
+
+
+
Sprint 2.3
+
Calibration Probabiliste & Demi-Vie des Insights
+
+
+ backend + frontend + S11 – S12 +
+
+
+
+
Livrables
+
    +
  • Stockage de la probabilité prédite (0–100) dans pattern_scores — issue du champ probability du pattern à la date de scoring
  • +
  • À maturité du trade : comparaison probabilité prédite vs résultat binaire (P&L > 0)
  • +
  • Courbe de calibration : "quand je prédis 75%, le taux de réussite réel est X%"
  • +
  • Score de Brier affiché dans une page Analytics
  • +
  • Demi-vie des insights KB : champ expires_at + confidence_decay_days sur les entrées KB. Les insights de plus de 90j sans confirmation voient leur confiance baisser automatiquement
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Si le système annonce "80%" et n'a raison qu'à 52% → il est surconfiant → recalibrer le prompt
  • +
  • Protège contre la dérive du Super Contexte : une leçon apprise en 2024 peut ne plus être valide en 2026 si le régime a changé
  • +
  • Score de Brier = mesure standard de calibration probabiliste (0 = parfait, 1 = nul, 0.25 = aléatoire)
  • +
+
Adresse le risque expert
+
    +
  • Protège la boucle SC contre l'auto-confirmation : les insights expirent s'ils ne sont pas reconfirmés empiriquement
  • +
+
+
+
+ +
+
+ + + +
+ +
+
+
🔵 Phase 3 — Portefeuille, Corrélations & Position Sizing
+
Semaines 13–20 · PRIORITÉ HAUTE · Raisonnement portefeuille vs trade par trade
+
+
+ +
+
Enjeu
+ "Tu peux avoir 3 trades différents (pétrole, défense, short Europe) qui dépendent tous d'une seule variable : guerre au Moyen-Orient. En réalité, tu n'as qu'un seul risque concentré. Un Risk Engine révèle ça." — Audit expert +
+ + +
+
+
+
Sprint 3.1
+
Vue Portefeuille Consolidée
+
+
+ backend + frontend + S13 – S14 +
+
+
+
+
Livrables
+
    +
  • Nouvelle page "Portefeuille Global" : vue consolidée de toutes les positions ouvertes par classe d'actif
  • +
  • Exposition nette par classe d'actif (énergie, métaux, indices US, Europe, Asie, forex)
  • +
  • P&L agrégé et décomposé (par classe, par régime d'entrée, par pattern)
  • +
  • Graphique d'évolution du P&L portefeuille dans le temps
  • +
  • Indicateur de concentration : si >40% du portefeuille dans une classe → alerte
  • +
+
+
+
Pourquoi ces choix
+
    +
  • La vue par trade masque les concentrations : 5 trades en énergie = 1 gros pari sur le pétrole
  • +
  • Décomposer par régime d'entrée permet de voir "ai-je sur-tradé en régime stagflation ?"
  • +
+
+
+
+ + +
+
+
+
Sprint 3.2
+
Risk Cluster Engine
+
+
+ backend + frontend + prompt + S15 – S16 +
+
+
+
+
Livrables
+
    +
  • Classification des trades ouverts par "facteur de risque" : géopolitique, inflation, récession, liquidité, dollar
  • +
  • Calcul de l'exposition nette par facteur (en nb de trades ET en P&L potentiel)
  • +
  • Heatmap des corrélations entre patterns (corrélation P&L historique entre paires de patterns)
  • +
  • Alerte "Concentration Risque" : si exposition > seuil sur un facteur → alerte dans le Cockpit
  • +
  • Injection dans le prompt de scoring : "Attention : 60% du portefeuille est déjà exposé au risque géopolitique Asie — pénaliser les nouveaux patterns dépendants de ce facteur"
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Le Risk Cluster révèle le risque "caché" commun à des trades apparemment indépendants
  • +
  • Injecter dans le scoring modifie concrètement les recommandations selon l'état du portefeuille
  • +
  • Corrélation P&L historique : deux patterns corrélés à 0.8 ne sont pas deux opportunités indépendantes
  • +
+
+
+
+ + +
+
+
+
Sprint 3.3
+
Position Sizing — Kelly Fractionnel
+
+
+ backend + frontend + S17 – S18 +
+
+
+
+
Livrables
+
    +
  • Calcul du Kelly fractionnel par trade : f* = (p × G − (1−p)) / G où p = probabilité estimée, G = gain max
  • +
  • Kelly fractionnel : recommander 25–50% du Kelly théorique (standard institutionnel)
  • +
  • Ajustement par Risk Cluster : si le facteur de risque est déjà saturé → diviser le sizing par 2
  • +
  • Volatility targeting : ajuster la taille pour maintenir une contribution constante au risque portefeuille (ex : chaque trade cible 1% de VaR)
  • +
  • Affichage dans Journal de Bord : colonne "Sizing suggéré" avec explication (Kelly brut / ajusté)
  • +
+
+
+
Pourquoi ces choix
+
    +
  • "Un signal moyen avec un sizing exceptionnel gagne de l'argent. L'inverse non." — Audit expert
  • +
  • Kelly fractionnel (25–50%) est la norme : le Kelly complet sur-optimise et produit des drawdowns brutaux
  • +
  • VaR contribution constante = technique de risk parity adaptée au sizing d'options
  • +
+
+
+
+ + +
+
+
+
Sprint 3.4
+
Tableau de Bord Risque Global
+
+
+ frontend + S19 – S20 +
+
+
+
+
Livrables
+
    +
  • Page "Risk Dashboard" : vue unique de tout le risque portefeuille
  • +
  • Jauge de concentration par facteur (géo, inflation, récession, liquidité)
  • +
  • Expected Drawdown du portefeuille estimé (basé sur corrélations historiques)
  • +
  • Score de "diversification effective" : 1 (positions toutes corrélées) → N (positions parfaitement indépendantes)
  • +
  • Recommandation automatique : "Portefeuille sur-concentré sur risque géo Asie — les 2 prochains trades devraient être sur d'autres facteurs"
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Un Risk Committee sérieux regarde ce tableau avant chaque trade
  • +
  • La recommandation automatique ferme la boucle : le Risk Engine alimente directement les suggestions du prochain cycle
  • +
+
+
+
+ +
+
+ + + +
+ +
+
+
🟣 Phase 4 — Moteur Probabiliste & Apprentissage Automatique
+
Semaines 21–26 · NIVEAU RECHERCHE · Vers le niveau quant fund
+
+
+ +
+
Enjeu
+ "Un fonds institutionnel voudrait : IA → hypothèse → Modèle statistique → validation. Aujourd'hui tu as LLM → score. Il manque la validation quantitative indépendante de GPT." — Audit expert +
+ + +
+
+
+
Sprint 4.1
+
Bayesian Updating des Scores
+
+
+ backend + frontend + S21 – S22 +
+
+
+
+
Livrables
+
    +
  • Chaque pattern a un prior bayésien (distribution Beta sur le win rate) mis à jour à chaque trade mature
  • +
  • Score composite = α × score_GPT + (1−α) × score_bayesien où α décroît avec le nombre de trades (confiance croissante dans l'historique)
  • +
  • Intervalles de confiance affichés : "Win rate estimé : 65% ± 12%" (beta distribution)
  • +
  • Recalibration automatique du facteur alpha selon le score de Brier du système (Phase 2.3)
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Beta(α,β) est le prior conjugué naturel pour un taux de succès → mise à jour simple et analytique
  • +
  • Plus le pattern a de trades → plus l'historique prime sur l'opinion de GPT-4o
  • +
  • Adresse directement le manque de calibration statistique identifié par l'expert
  • +
+
+
+
+ + +
+
+
+
Sprint 4.2
+
Détection Automatique de Régimes (Clustering)
+
+
+ backend + frontend + S23 – S24 +
+
+
+
+
Livrables
+
    +
  • Clustering non supervisé (K-Means ou DBSCAN) sur les vecteurs macro journaliers (VIX, pente, DXY, Brent, or) pour auto-détecter les régimes latents
  • +
  • Comparaison régime détecté vs régime nommé (stagflation, expansion...) → mesure d'alignement
  • +
  • Auto-découverte de sous-régimes : "stagflation avec stress dollar" vs "stagflation avec fuite vers l'or"
  • +
  • Affichage dans Régime Macro : "Cluster 3 (similaire à Oct 2022, Nov 2023)" avec performance historique des patterns dans ce cluster
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Les régimes nommés à la main sont une approximation — le clustering découvre les régimes réels dans les données
  • +
  • "Les marchés ne sont pas stationnaires" — le clustering aide à identifier quand le régime a changé
  • +
  • Librairie sklearn (déjà dans l'environnement Python probable) — pas de dépendance lourde
  • +
+
+
+
+ + +
+
+
+
Sprint 4.3
+
Embeddings de Patterns & Similarité Sémantique
+
+
+ backend + API + S25 +
+
+
+
+
Livrables
+
    +
  • Embedding de chaque pattern via l'API OpenAI (text-embedding-3-small) stocké en DB
  • +
  • Remplacement du filtre Jaccard par similarité cosinus sur embeddings → détection de doublons sémantiques, pas juste des mots identiques
  • +
  • Recherche sémantique : "trouver les patterns similaires à cette thèse" dans la page Patterns
  • +
+
+
+
Pourquoi ces choix
+
    +
  • Jaccard rate "Taiwan Tensions" et "Détroit de Taiwan Risk" comme différents — embeddings les détecte comme proches
  • +
  • text-embedding-3-small = $0.002/1M tokens, quasi gratuit
  • +
+
+
+
+ + +
+
+
+
Sprint 4.4
+
Tableau de Bord Analytique Complet
+
+
+ frontend + S26 +
+
+
+
+
Livrables
+
    +
  • Page Analytics avec : courbe de calibration, Brier score, win rate par régime, performance par pattern, drawdown historique
  • +
  • Comparaison score GPT vs résultat réel sur 6 mois : "le modèle sur-performe dans les régimes de crise, sous-performe en régime d'expansion"
  • +
  • Export CSV de toutes les métriques pour analyse externe
  • +
+
+
+
+ +
+
+ + + +
+

Récapitulatif Exécutif

+ + + + + + + + + + + + + + + + + + + +
SprintFeatureDuréeDépendancesImpact
1.1IV Rank & Percentile2s★★★★★
1.2Term Structure & Skew2s1.1★★★★★
1.3Options Flow (proxy OI)2s1.1★★★★☆
2.1Pattern Reliability Score2shistorique trades★★★★★
2.2Contre-thèses & Invalidation2s★★★★☆
2.3Calibration + Demi-vie KB2s2.1★★★★☆
3.1Vue Portefeuille Consolidée2s★★★★☆
3.2Risk Cluster Engine2s3.1★★★★★
3.3Position Sizing (Kelly)2s2.1, 3.2★★★★★
3.4Risk Dashboard2s3.1, 3.2, 3.3★★★☆☆
4.1Bayesian Updating2s2.1, 2.3★★★★☆
4.2Clustering de Régimes2sdonnées macro★★★☆☆
4.3Embeddings Patterns1s★★★☆☆
4.4Analytics Dashboard1stoutes phases★★★☆☆
+ +

Décisions de démarrage immédiates

+ + + + + + + + + + + + + + + + + + + +
QuestionRecommandation
Source des données IV ?yfinance pour les sous-jacents US/ETF (gratuit). Pour les futures (Brent, or), explorer Yahoo Finance "BZ=F", "GC=F" — options disponibles mais limitées.
Stocker l'IV en DB ou calculer à la volée ?Stocker journalièrement. IV Rank et Percentile ont besoin de 52 semaines d'historique — impossible à calculer à la volée sur yfinance sans cet historique.
Kelly sur quel capital de référence ?Laisser l'utilisateur définir un capital de référence dans la Config (ex : 50 000€) — le sizing est exprimé en % et en € notionnel.
Embeddings : OpenAI ou local ?OpenAI text-embedding-3-small pour démarrer ($0.002/1M tokens, négligeable). Migration vers un modèle local (sentence-transformers) possible en Phase 4 si nécessaire.
+ +
+
Projection des notes post-roadmap
+ Trading d'Options : 5/10 → 8.5/10 (Phase 1 + 2)
+ Gestion du Risque : 6/10 → 8.5/10 (Phase 3)
+ Niveau Fonds Pro : 6.5/10 → 8.5/10 (Phase 1 → 3)
+ Architecture IA : 8.5/10 → 9.5/10 (Phase 2 + 4) +
+ +

+ GeoOptions Intelligence v2.0 — Roadmap de développement · Basé sur audit expert · Juin 2026 +

+ + + diff --git a/backend/main.py b/backend/main.py index f73f254..5b5da35 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,6 +1,6 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from routers import market_data, geopolitical, options, backtest, ai, portfolio, config, patterns, journal, cycle as cycle_router, profiles as profiles_router, reasoning as reasoning_router, knowledge as knowledge_router +from routers import market_data, geopolitical, options, backtest, ai, portfolio, config, patterns, journal, cycle as cycle_router, profiles as profiles_router, reasoning as reasoning_router, knowledge as knowledge_router, options_vol as options_vol_router from services.database import init_db, get_config, cleanup_stale_running_cycles import os import uvicorn @@ -71,6 +71,7 @@ app.include_router(cycle_router.router) app.include_router(profiles_router.router) app.include_router(reasoning_router.router) app.include_router(knowledge_router.router) +app.include_router(options_vol_router.router) @app.get("/") diff --git a/backend/routers/options_vol.py b/backend/routers/options_vol.py new file mode 100644 index 0000000..fcb66df --- /dev/null +++ b/backend/routers/options_vol.py @@ -0,0 +1,157 @@ +""" +Options volatility endpoints — IV Rank, Term Structure, Skew, Options Flow. +""" +from fastapi import APIRouter, BackgroundTasks, HTTPException +from typing import List, Optional +import logging + +router = APIRouter(prefix="/api/options-vol", tags=["options-vol"]) +logger = logging.getLogger(__name__) + +# Simple in-memory cache to avoid hammering yfinance on every page load +_iv_cache: dict = {} +_CACHE_TTL_SECONDS = 3600 # 1h + + +def _is_stale(key: str) -> bool: + import time + entry = _iv_cache.get(key) + if not entry: + return True + return (time.time() - entry["ts"]) > _CACHE_TTL_SECONDS + + +def _set_cache(key: str, data: dict): + import time + _iv_cache[key] = {"data": data, "ts": time.time()} + + +@router.get("/snapshot/{ticker}") +def get_iv_snapshot(ticker: str, force: bool = False): + """Full IV snapshot for one ticker: IV, Rank, Percentile, Term Structure, Skew, Flow.""" + key = f"snapshot:{ticker.upper()}" + if not force and not _is_stale(key): + return _iv_cache[key]["data"] + + from services.iv_engine import get_full_iv_snapshot + data = get_full_iv_snapshot(ticker) + _set_cache(key, data) + return data + + +@router.get("/batch") +def get_iv_batch(tickers: str = "SPY,QQQ,GLD,USO,UNG,XLE,TLT"): + """ + IV snapshot for multiple tickers (comma-separated). + Returns a dict {ticker: snapshot}. + Cached 1h per ticker. + """ + ticker_list = [t.strip().upper() for t in tickers.split(",") if t.strip()][:12] + result = {} + from services.iv_engine import get_full_iv_snapshot + + for ticker in ticker_list: + key = f"snapshot:{ticker}" + if not _is_stale(key): + result[ticker] = _iv_cache[key]["data"] + else: + snap = get_full_iv_snapshot(ticker) + _set_cache(key, snap) + result[ticker] = snap + + return {"snapshots": result, "count": len(result)} + + +@router.get("/watchlist") +def get_iv_watchlist(): + """ + IV for the core IV watchlist (portfolio-relevant tickers). + Returns a summary list sorted by IV Rank descending. + """ + from services.iv_engine import IV_WATCHLIST, get_atm_iv, _resolve_ticker + from services.database import get_iv_rank_percentile, save_iv_snapshot + from datetime import date + + results = [] + today = date.today().isoformat() + + for ticker in IV_WATCHLIST: + key = f"watchlist:{ticker}" + if not _is_stale(key): + results.append(_iv_cache[key]["data"]) + continue + + iv = get_atm_iv(ticker, 30) + if not iv: + continue + + proxy = _resolve_ticker(ticker) + save_iv_snapshot(proxy, today, iv) + rank_data = get_iv_rank_percentile(proxy, iv) + + item = { + "ticker": ticker, + "iv_current_pct": round(iv * 100, 1), + "iv_rank": rank_data.get("iv_rank"), + "iv_percentile": rank_data.get("iv_percentile"), + "history_days": rank_data.get("history_days", 0), + "signal": ( + "sell_vol" if (rank_data.get("iv_rank") or 0) > 80 + else "buy_vol" if (rank_data.get("iv_rank") or 100) < 20 + else "neutral" + ), + } + _set_cache(key, item) + results.append(item) + + results.sort(key=lambda x: -(x.get("iv_rank") or 0)) + return {"items": results, "count": len(results)} + + +@router.get("/history/{ticker}") +def get_iv_history_endpoint(ticker: str, days: int = 90): + """Historical IV for a ticker (used to render IV chart).""" + from services.database import get_iv_history + from services.iv_engine import _resolve_ticker + proxy = _resolve_ticker(ticker) + history = get_iv_history(proxy, days) + return {"ticker": ticker, "proxy": proxy, "history": history, "count": len(history)} + + +@router.post("/refresh-watchlist") +def refresh_watchlist(background_tasks: BackgroundTasks): + """ + Trigger a background refresh of IV data for all watchlist tickers. + Call this once daily to build up the 52-week IV history. + """ + def _refresh(): + from services.iv_engine import IV_WATCHLIST, get_full_iv_snapshot + logger.info(f"[IVRefresh] Refreshing IV for {len(IV_WATCHLIST)} tickers") + for ticker in IV_WATCHLIST: + try: + snap = get_full_iv_snapshot(ticker) + key = f"snapshot:{ticker}" + import time + _iv_cache[key] = {"data": snap, "ts": time.time()} + logger.debug(f"[IVRefresh] {ticker}: IV={snap.get('iv_current_pct')}% IVR={snap.get('iv_rank')}") + except Exception as e: + logger.warning(f"[IVRefresh] {ticker} failed: {e}") + logger.info("[IVRefresh] Done") + + background_tasks.add_task(_refresh) + return {"status": "refresh started", "tickers": len(__import__("services.iv_engine", fromlist=["IV_WATCHLIST"]).IV_WATCHLIST)} + + +@router.get("/for-trade/{underlying}") +def get_iv_for_trade(underlying: str): + """ + IV snapshot specifically for a trade's underlying. + Used by JournalDeBord to show the IV context at trade time. + """ + from services.iv_engine import get_full_iv_snapshot + key = f"snapshot:{underlying.upper()}" + if not _is_stale(key): + return _iv_cache[key]["data"] + data = get_full_iv_snapshot(underlying) + _set_cache(key, data) + return data diff --git a/backend/services/ai_analyzer.py b/backend/services/ai_analyzer.py index b75d9cb..f82e2e2 100644 --- a/backend/services/ai_analyzer.py +++ b/backend/services/ai_analyzer.py @@ -326,6 +326,7 @@ def score_patterns_with_context( category_filter: str = None, macro_regime: Optional[Dict] = None, portfolio_lessons: Optional[Dict] = None, + iv_context: str = "", ) -> List[Dict[str, Any]]: """Score all patterns with rich context (news, prices, IV) using GPT-4o.""" if not get_client(): @@ -607,7 +608,7 @@ Leçons : {' | '.join(str(l)[:80] for l in lessons[:3])} prompt_header = f"""CONTEXTE GLOBAL: - Score risque géopolitique: {geo_score.get('score', 50)}/100 ({geo_score.get('level', 'medium')}) - Top risques: {geo_score.get('top_risks', [])} -{macro_section}{lessons_header} +{macro_section}{lessons_header}{iv_context} TEMPLATE DE NOTATION: {scoring_template} diff --git a/backend/services/auto_cycle.py b/backend/services/auto_cycle.py index 443bf01..335c2ae 100644 --- a/backend/services/auto_cycle.py +++ b/backend/services/auto_cycle.py @@ -231,6 +231,25 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]: summary["patterns_added"] = added_count + # ── Step 3.5: Collect IV context ───────────────────────────────────── + iv_context = "" + try: + from services.iv_engine import get_iv_context_for_prompt, IV_WATCHLIST + # Collect underlyings from current trade journal + default watchlist + from services.database import get_mtm_trades_with_traces + _mtm = get_mtm_trades_with_traces(days=90) + _trade_tickers = list({ + (t.get("underlying") or "").upper() + for t in _mtm.get("all_trades", []) + if t.get("underlying") + }) + _iv_tickers = (_trade_tickers + IV_WATCHLIST[:6])[:10] + iv_context = get_iv_context_for_prompt(_iv_tickers) + if iv_context: + logger.info(f"[Cycle {run_id[:16]}] IV context collected for {len(_iv_tickers)} tickers") + except Exception as _e: + logger.warning(f"[Cycle] IV context collection failed (non-blocking): {_e}") + # ── Step 4: Score ALL patterns ──────────────────────────────────────── # Verify all patterns have IDs before scoring (guard against stale data) patterns_with_id = [p for p in existing if p.get("id")] @@ -251,6 +270,7 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]: category_filter=None, macro_regime=macro_regime, portfolio_lessons=portfolio_lessons, + iv_context=iv_context, ) 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")] diff --git a/backend/services/database.py b/backend/services/database.py index bbc35e7..9cdf0ec 100644 --- a/backend/services/database.py +++ b/backend/services/database.py @@ -284,9 +284,21 @@ def init_db(): trades_analyzed INTEGER DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')) )""") + c.execute("""CREATE TABLE IF NOT EXISTS iv_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ticker TEXT NOT NULL, + recorded_date TEXT NOT NULL, + iv_current REAL, + iv_30d REAL, + iv_60d REAL, + iv_90d REAL, + created_at TEXT DEFAULT (datetime('now')) + )""") + try: c.execute("CREATE INDEX IF NOT EXISTS idx_kb_category ON knowledge_base(category, status)") c.execute("CREATE INDEX IF NOT EXISTS idx_rs_version ON reasoning_state(version DESC)") + c.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_iv_history_ticker_date ON iv_history(ticker, recorded_date)") except Exception: pass @@ -1501,3 +1513,63 @@ def delete_kb_entry(entry_id: int) -> bool: conn.commit() conn.close() return cur.rowcount > 0 + + +# ── IV History ──────────────────────────────────────────────────────────────── + +def save_iv_snapshot(ticker: str, recorded_date: str, iv_current: float, + iv_30d=None, iv_60d=None, iv_90d=None) -> None: + conn = get_conn() + conn.execute( + """INSERT OR REPLACE INTO iv_history + (ticker, recorded_date, iv_current, iv_30d, iv_60d, iv_90d) + VALUES (?, ?, ?, ?, ?, ?)""", + (ticker.upper(), recorded_date, iv_current, iv_30d, iv_60d, iv_90d), + ) + conn.commit() + conn.close() + + +def get_iv_rank_percentile(ticker: str, current_iv: float, days: int = 252) -> Dict[str, Any]: + conn = get_conn() + rows = conn.execute( + """SELECT iv_current FROM iv_history + WHERE ticker=? AND iv_current IS NOT NULL AND iv_current > 0 + ORDER BY recorded_date DESC LIMIT ?""", + (ticker.upper(), days), + ).fetchall() + conn.close() + + if not rows: + return {"iv_rank": None, "iv_percentile": None, "history_days": 0} + + hist = [r["iv_current"] for r in rows] + iv_min = min(hist) + iv_max = max(hist) + + iv_rank = ( + round((current_iv - iv_min) / (iv_max - iv_min) * 100, 1) + if iv_max > iv_min else 50.0 + ) + iv_percentile = round(sum(1 for v in hist if v < current_iv) / len(hist) * 100, 1) + + return { + "iv_rank": iv_rank, + "iv_percentile": iv_percentile, + "history_days": len(hist), + "iv_min_52w": round(iv_min * 100, 1), + "iv_max_52w": round(iv_max * 100, 1), + } + + +def get_iv_history(ticker: str, days: int = 90) -> List[Dict]: + conn = get_conn() + rows = conn.execute( + """SELECT recorded_date, iv_current, iv_30d, iv_60d, iv_90d + FROM iv_history WHERE ticker=? AND iv_current IS NOT NULL + ORDER BY recorded_date DESC LIMIT ?""", + (ticker.upper(), days), + ).fetchall() + conn.close() + return [dict(r) for r in rows] + diff --git a/backend/services/iv_engine.py b/backend/services/iv_engine.py new file mode 100644 index 0000000..2a0a057 --- /dev/null +++ b/backend/services/iv_engine.py @@ -0,0 +1,467 @@ +""" +Implied Volatility engine — IV Rank, IV Percentile, Term Structure, Skew, Options Flow. + +Supports ETFs and US equities via yfinance options chains. +Futures (CL=F, GC=F, etc.) don't have options in yfinance → mapped to ETF proxies. +""" +import logging +from datetime import date, datetime, timedelta +from typing import Dict, List, Optional, Any + +import yfinance as yf + +logger = logging.getLogger(__name__) + +# ── Ticker mapping: futures/indices → optionable ETF/stock proxies ──────────── +# Futures don't have listed options in yfinance; map to liquid ETF equivalents. +_PROXY: Dict[str, str] = { + "CL=F": "USO", # WTI oil → US Oil Fund + "BZ=F": "BNO", # Brent → US Brent Oil Fund + "NG=F": "UNG", # Natural Gas ETF + "GC=F": "GLD", # Gold → SPDR Gold + "SI=F": "SLV", # Silver ETF + "HG=F": "COPX", # Copper miners ETF + "PL=F": "PPLT", # Platinum ETF + "ZC=F": "CORN", # Corn ETF + "ZW=F": "WEAT", # Wheat ETF + "ZS=F": "SOYB", # Soybean ETF + "KC=F": "JO", # Coffee ETF + "SB=F": "CANE", # Sugar ETF + "^GSPC": "SPY", # S&P 500 → SPY + "^NDX": "QQQ", # NASDAQ 100 → QQQ + "^DJI": "DIA", # Dow → DIA + "^STOXX50E": "FEZ", # Euro Stoxx → FEZ + "^N225": "EWJ", # Nikkei → iShares Japan + "^VIX": "VIXY", # VIX → VIXY (approximate) + "EURUSD=X": "FXE", # EUR/USD ETF + "USDJPY=X": "FXY", # Yen ETF + "GBP=X": "FXB", # GBP ETF + "USDCHF=X": "FXF", # CHF ETF + "UUP": "UUP", # Already an ETF +} + +# Core optionable tickers to track IV for (portfolio-relevant) +IV_WATCHLIST = [ + "SPY", "QQQ", "GLD", "SLV", "USO", "BNO", "UNG", + "XLE", "UUP", "TLT", "GDX", "EWJ", "FEZ", + "XOM", "CVX", "LMT", "RTX", "BA", +] + + +def _resolve_ticker(ticker: str) -> str: + """Return the optionable proxy ticker for a given symbol.""" + return _PROXY.get(ticker.upper(), ticker.upper()) + + +def _get_current_price(t: yf.Ticker) -> Optional[float]: + try: + info = t.fast_info + price = getattr(info, "last_price", None) or getattr(info, "regular_market_price", None) + if price: + return float(price) + except Exception: + pass + try: + hist = t.history(period="2d", interval="1d") + if not hist.empty: + return float(hist["Close"].dropna().iloc[-1]) + except Exception: + pass + return None + + +def get_atm_iv(ticker: str, target_days: int = 30) -> Optional[float]: + """ + Get the ATM implied volatility for a ticker at the given target DTE. + Returns IV as a decimal (0.25 = 25%). Returns None if unavailable. + """ + proxy = _resolve_ticker(ticker) + try: + t = yf.Ticker(proxy) + price = _get_current_price(t) + if not price: + return None + + expirations = t.options + if not expirations: + return None + + today = date.today() + target_date = today + timedelta(days=target_days) + + # Find expiration closest to target_days + best_exp = min( + expirations, + key=lambda e: abs((datetime.strptime(e, "%Y-%m-%d").date() - target_date).days), + ) + + chain = t.option_chain(best_exp) + calls = chain.calls[chain.calls["impliedVolatility"] > 0] + puts = chain.puts[chain.puts["impliedVolatility"] > 0] + + if calls.empty and puts.empty: + return None + + # ATM = strike closest to current price + all_strikes = sorted(set(calls["strike"].tolist() + puts["strike"].tolist())) + if not all_strikes: + return None + atm_strike = min(all_strikes, key=lambda s: abs(s - price)) + + ivs = [] + c = calls[calls["strike"] == atm_strike]["impliedVolatility"] + p = puts[puts["strike"] == atm_strike]["impliedVolatility"] + if not c.empty and float(c.iloc[0]) > 0.01: + ivs.append(float(c.iloc[0])) + if not p.empty and float(p.iloc[0]) > 0.01: + ivs.append(float(p.iloc[0])) + + if not ivs: + return None + return round(sum(ivs) / len(ivs), 4) + + except Exception as e: + logger.debug(f"[IV] {proxy}: {e}") + return None + + +def get_term_structure(ticker: str) -> Dict[str, Any]: + """ + Get IV at 30d, 60d, 90d, 180d expirations. + Returns structure type: contango | backwardation | flat. + """ + proxy = _resolve_ticker(ticker) + result: Dict[str, Any] = { + "ticker": ticker, + "proxy": proxy, + "iv_30d": None, + "iv_60d": None, + "iv_90d": None, + "iv_180d": None, + "structure": None, + } + targets = {"iv_30d": 30, "iv_60d": 60, "iv_90d": 90, "iv_180d": 180} + + try: + t = yf.Ticker(proxy) + price = _get_current_price(t) + if not price: + return result + + expirations = t.options + if not expirations: + return result + + today = date.today() + + for field, days in targets.items(): + target_date = today + timedelta(days=days) + best_exp = min( + expirations, + key=lambda e: abs((datetime.strptime(e, "%Y-%m-%d").date() - target_date).days), + ) + # Only use if within ±20 days of target + actual_days = abs((datetime.strptime(best_exp, "%Y-%m-%d").date() - target_date).days) + if actual_days > 20: + continue + + try: + chain = t.option_chain(best_exp) + calls = chain.calls[chain.calls["impliedVolatility"] > 0.01] + puts = chain.puts[chain.puts["impliedVolatility"] > 0.01] + all_strikes = sorted(set(calls["strike"].tolist() + puts["strike"].tolist())) + if not all_strikes: + continue + atm = min(all_strikes, key=lambda s: abs(s - price)) + ivs = [] + c = calls[calls["strike"] == atm]["impliedVolatility"] + p = puts[puts["strike"] == atm]["impliedVolatility"] + if not c.empty: + ivs.append(float(c.iloc[0])) + if not p.empty: + ivs.append(float(p.iloc[0])) + if ivs: + result[field] = round(sum(ivs) / len(ivs), 4) + except Exception: + continue + + # Determine structure from 30d vs 90d + iv30 = result.get("iv_30d") + iv90 = result.get("iv_90d") + if iv30 and iv90: + diff = iv90 - iv30 + if diff > 0.015: + result["structure"] = "contango" # vol increases with time → calm + elif diff < -0.015: + result["structure"] = "backwardation" # vol decreases with time → stress + else: + result["structure"] = "flat" + + except Exception as e: + logger.debug(f"[TermStructure] {proxy}: {e}") + + return result + + +def get_skew(ticker: str, target_days: int = 30) -> Dict[str, Any]: + """ + Put/Call skew: IV of 25-delta put minus IV of 25-delta call. + Positive skew = market buying put protection (bearish hedging). + """ + proxy = _resolve_ticker(ticker) + result: Dict[str, Any] = { + "ticker": ticker, + "proxy": proxy, + "put_skew": None, + "skew_pct": None, + "interpretation": None, + } + try: + t = yf.Ticker(proxy) + price = _get_current_price(t) + if not price: + return result + + expirations = t.options + if not expirations: + return result + + today = date.today() + target_date = today + timedelta(days=target_days) + best_exp = min( + expirations, + key=lambda e: abs((datetime.strptime(e, "%Y-%m-%d").date() - target_date).days), + ) + + chain = t.option_chain(best_exp) + calls = chain.calls[chain.calls["impliedVolatility"] > 0.01].copy() + puts = chain.puts[chain.puts["impliedVolatility"] > 0.01].copy() + + if calls.empty or puts.empty: + return result + + # Approximate 25-delta strikes: ~85–90% of price for puts, ~110–115% for calls + otm_put_strike = price * 0.90 + otm_call_strike = price * 1.10 + + put_row = puts.iloc[(puts["strike"] - otm_put_strike).abs().argsort()[:1]] + call_row = calls.iloc[(calls["strike"] - otm_call_strike).abs().argsort()[:1]] + + if put_row.empty or call_row.empty: + return result + + iv_put = float(put_row["impliedVolatility"].iloc[0]) + iv_call = float(call_row["impliedVolatility"].iloc[0]) + + put_skew = iv_put - iv_call + result["put_skew"] = round(put_skew, 4) + result["skew_pct"] = round(put_skew * 100, 1) + result["iv_put_25d"] = round(iv_put * 100, 1) + result["iv_call_25d"] = round(iv_call * 100, 1) + + if put_skew > 0.03: + result["interpretation"] = "Marché achète des puts — protection baissière élevée" + elif put_skew < -0.02: + result["interpretation"] = "Marché achète des calls — biais haussier spéculatif" + else: + result["interpretation"] = "Skew équilibré — pas de biais directionnel fort" + + except Exception as e: + logger.debug(f"[Skew] {proxy}: {e}") + + return result + + +def get_options_flow(ticker: str) -> Dict[str, Any]: + """ + Estimate options flow from open interest changes. + Returns Put/Call OI ratio and unusual strike detection. + """ + proxy = _resolve_ticker(ticker) + result: Dict[str, Any] = { + "ticker": ticker, + "proxy": proxy, + "pc_oi_ratio": None, + "total_call_oi": None, + "total_put_oi": None, + "unusual_strikes": [], + "flow_bias": None, + "gamma_bias": None, + } + try: + t = yf.Ticker(proxy) + price = _get_current_price(t) + if not price: + return result + + expirations = t.options + if not expirations: + return result + + today = date.today() + # Aggregate OI across the next 90 days of expirations + total_call_oi = 0 + total_put_oi = 0 + strike_oi: Dict[float, Dict[str, int]] = {} + + for exp in expirations[:6]: # limit to first 6 expirations + exp_date = datetime.strptime(exp, "%Y-%m-%d").date() + if (exp_date - today).days > 90: + break + try: + chain = t.option_chain(exp) + total_call_oi += int(chain.calls["openInterest"].fillna(0).sum()) + total_put_oi += int(chain.puts["openInterest"].fillna(0).sum()) + + # Aggregate per strike + for _, row in chain.calls.iterrows(): + s = float(row["strike"]) + strike_oi.setdefault(s, {"call": 0, "put": 0}) + strike_oi[s]["call"] += int(row.get("openInterest") or 0) + for _, row in chain.puts.iterrows(): + s = float(row["strike"]) + strike_oi.setdefault(s, {"call": 0, "put": 0}) + strike_oi[s]["put"] += int(row.get("openInterest") or 0) + except Exception: + continue + + if total_call_oi + total_put_oi == 0: + return result + + result["total_call_oi"] = total_call_oi + result["total_put_oi"] = total_put_oi + pc_ratio = total_put_oi / total_call_oi if total_call_oi > 0 else None + result["pc_oi_ratio"] = round(pc_ratio, 2) if pc_ratio else None + + # Detect unusual strikes (OI > 1.5× median strike OI) + all_oi = [v["call"] + v["put"] for v in strike_oi.values() if v["call"] + v["put"] > 0] + if all_oi: + import statistics + median_oi = statistics.median(all_oi) + threshold = median_oi * 3 + unusual = [ + { + "strike": round(s, 2), + "call_oi": d["call"], + "put_oi": d["put"], + "total_oi": d["call"] + d["put"], + "type": "call" if d["call"] > d["put"] else "put", + "pct_otm": round((s - price) / price * 100, 1), + } + for s, d in strike_oi.items() + if d["call"] + d["put"] > threshold + ] + result["unusual_strikes"] = sorted(unusual, key=lambda x: -x["total_oi"])[:5] + + # Net gamma bias: are dealers long or short gamma at current price? + atm_calls_oi = sum(d["call"] for s, d in strike_oi.items() if abs(s - price) / price < 0.03) + atm_puts_oi = sum(d["put"] for s, d in strike_oi.items() if abs(s - price) / price < 0.03) + if atm_calls_oi + atm_puts_oi > 0: + # Dealers are typically short what they sell (opposite of OI) + # More call OI = dealers are short calls = short gamma (amplifies moves) + # More put OI = dealers are short puts = long gamma (dampens moves) + if atm_puts_oi > atm_calls_oi * 1.3: + result["gamma_bias"] = "Dealers long gamma (amortit les mouvements)" + elif atm_calls_oi > atm_puts_oi * 1.3: + result["gamma_bias"] = "Dealers short gamma (amplifie les mouvements)" + else: + result["gamma_bias"] = "Gamma neutre" + + if pc_ratio: + if pc_ratio > 1.3: + result["flow_bias"] = "Défensif — smart money achète des puts" + elif pc_ratio < 0.7: + result["flow_bias"] = "Spéculatif — OI calls dominant" + else: + result["flow_bias"] = "Équilibré" + + except Exception as e: + logger.debug(f"[OptionsFlow] {proxy}: {e}") + + return result + + +def get_full_iv_snapshot(ticker: str) -> Dict[str, Any]: + """ + Full IV snapshot for a ticker: current IV, rank/percentile, term structure, skew, flow. + Calls DB for historical rank/percentile. + """ + from services.database import get_iv_rank_percentile, save_iv_snapshot + + proxy = _resolve_ticker(ticker) + today = date.today().isoformat() + + iv_current = get_atm_iv(ticker, target_days=30) + term = get_term_structure(ticker) + skew = get_skew(ticker, target_days=30) + flow = get_options_flow(ticker) + + rank_data: Dict[str, Any] = {} + if iv_current: + # Save to history first, then calculate rank + save_iv_snapshot(proxy, today, iv_current, term.get("iv_30d"), term.get("iv_60d"), term.get("iv_90d")) + rank_data = get_iv_rank_percentile(proxy, iv_current) + + return { + "ticker": ticker, + "proxy": proxy, + "iv_current_pct": round(iv_current * 100, 1) if iv_current else None, + "iv_rank": rank_data.get("iv_rank"), + "iv_percentile": rank_data.get("iv_percentile"), + "history_days": rank_data.get("history_days", 0), + "iv_min_52w_pct": rank_data.get("iv_min_52w"), + "iv_max_52w_pct": rank_data.get("iv_max_52w"), + "term_structure": term, + "skew": skew, + "options_flow": flow, + "fetched_at": datetime.utcnow().isoformat(), + } + + +def get_iv_context_for_prompt(tickers: List[str]) -> str: + """ + Build a compact IV context string to inject into GPT-4o scoring prompts. + """ + lines = [] + for ticker in tickers[:8]: + proxy = _resolve_ticker(ticker) + iv = get_atm_iv(ticker, 30) + if not iv: + continue + from services.database import get_iv_rank_percentile, save_iv_snapshot + today = date.today().isoformat() + save_iv_snapshot(proxy, today, iv, None, None, None) + rank = get_iv_rank_percentile(proxy, iv) + + iv_rank = rank.get("iv_rank") + hist_days = rank.get("history_days", 0) + + skew_data = get_skew(ticker, 30) + skew_pct = skew_data.get("skew_pct") + + term = get_term_structure(ticker) + structure = term.get("structure") + + rank_str = f"IVR={iv_rank:.0f}%" if iv_rank is not None and hist_days >= 30 else "IVR=N/A (historique insuffisant)" + skew_str = f"skew={skew_pct:+.1f}pts" if skew_pct is not None else "" + term_str = f"structure={structure}" if structure else "" + + flag = "" + if iv_rank and iv_rank > 80: + flag = " ⚠️ IV ÉLEVÉE — préférer vendre de la vol" + elif iv_rank and iv_rank < 20: + flag = " ✓ IV BASSE — bon moment pour acheter de la convexité" + + parts = [f"{ticker}/{proxy}", f"IV={iv*100:.1f}%", rank_str] + if skew_str: + parts.append(skew_str) + if term_str: + parts.append(term_str) + if flag: + parts.append(flag) + + lines.append(" " + " | ".join(parts)) + + if not lines: + return "" + return "=== VOLATILITÉ IMPLICITE ===\n" + "\n".join(lines) + "\n⚠️ RÈGLE: Si IVR>80%, pénaliser les stratégies acheteuses de vol (long call/put, straddle). Si IVR<20%, favoriser l'achat de convexité.\n" diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts index 3d125b7..1b132ee 100644 --- a/frontend/src/hooks/useApi.ts +++ b/frontend/src/hooks/useApi.ts @@ -573,3 +573,43 @@ export const useDeleteKbEntry = () => { onSuccess: () => qc.invalidateQueries({ queryKey: ['knowledge-entries'] }), }) } + +// ── Options Volatility ──────────────────────────────────────────────────────── +export const useIvSnapshot = (ticker: string) => + useQuery({ + queryKey: ['iv-snapshot', ticker], + queryFn: () => api.get(`/options-vol/snapshot/${encodeURIComponent(ticker)}`).then(r => r.data), + enabled: !!ticker, + staleTime: 60 * 60_000, // 1h — IV doesn't change that fast + }) + +export const useIvWatchlist = () => + useQuery({ + queryKey: ['iv-watchlist'], + queryFn: () => api.get('/options-vol/watchlist').then(r => r.data), + staleTime: 60 * 60_000, + }) + +export const useIvBatch = (tickers: string) => + useQuery({ + queryKey: ['iv-batch', tickers], + queryFn: () => api.get('/options-vol/batch', { params: { tickers } }).then(r => r.data), + enabled: !!tickers, + staleTime: 60 * 60_000, + }) + +export const useIvHistory = (ticker: string, days = 90) => + useQuery({ + queryKey: ['iv-history', ticker, days], + queryFn: () => api.get(`/options-vol/history/${encodeURIComponent(ticker)}`, { params: { days } }).then(r => r.data), + enabled: !!ticker, + staleTime: 60 * 60_000, + }) + +export const useIvForTrade = (underlying: string) => + useQuery({ + queryKey: ['iv-for-trade', underlying], + queryFn: () => api.get(`/options-vol/for-trade/${encodeURIComponent(underlying)}`).then(r => r.data), + enabled: !!underlying, + staleTime: 60 * 60_000, + }) diff --git a/frontend/src/pages/JournalDeBord.tsx b/frontend/src/pages/JournalDeBord.tsx index 940dcb3..949d2aa 100644 --- a/frontend/src/pages/JournalDeBord.tsx +++ b/frontend/src/pages/JournalDeBord.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react' import { BookOpen, TrendingUp, TrendingDown, Activity, AlertTriangle, RefreshCw, Zap, CheckCircle, XCircle, Brain, Trash2, Search, X, ChevronDown, ChevronUp } from 'lucide-react' import clsx from 'clsx' -import { useJournalSummary, useMacroHistory, useGeoHistory, useTradeMtm, useCycleHistory, useCycleStatus, useTriggerCycle, useTradePostmortem, useAnalyzePostmortem, api } from '../hooks/useApi' +import { useJournalSummary, useMacroHistory, useGeoHistory, useTradeMtm, useCycleHistory, useCycleStatus, useTriggerCycle, useTradePostmortem, useAnalyzePostmortem, useIvForTrade, api } from '../hooks/useApi' import { useQueryClient } from '@tanstack/react-query' const SCENARIO_META: Record = { @@ -61,6 +61,27 @@ function ScoreDelta({ entry, latest }: { entry: number | null; latest: number | // ── Section 1 : Historique des régimes macro ────────────────────────────────── +function IvRankCell({ underlying }: { underlying: string }) { + const { data, isLoading } = useIvForTrade(underlying) + if (isLoading) return + const rank = data?.iv_rank + const iv = data?.iv_current_pct + if (rank == null) return + + const color = rank >= 80 ? 'text-red-400' : rank >= 50 ? 'text-amber-400' : rank >= 20 ? 'text-emerald-400' : 'text-blue-400' + const signal = rank >= 80 ? '↓vol' : rank < 20 ? '↑vol' : '' + + return ( +
+ + {rank}% + + {iv != null ? `IV ${iv}%` : ''} + {signal && {signal}} +
+ ) +} + function MacroHistorySection({ days }: { days: number }) { const { data, isLoading, refetch, isFetching } = useMacroHistory(days) const history: any[] = (data as any)?.history ?? [] @@ -395,6 +416,7 @@ function TradeMtmSection({ days }: { days: number }) { Prix entrée Prix actuel Maturité + IV Rank P&L th. @@ -483,6 +505,9 @@ function TradeMtmSection({ days }: { days: number }) { )} + + + diff --git a/frontend/src/pages/OptionsLab.tsx b/frontend/src/pages/OptionsLab.tsx index e3040fe..51e151e 100644 --- a/frontend/src/pages/OptionsLab.tsx +++ b/frontend/src/pages/OptionsLab.tsx @@ -1,341 +1,356 @@ import { useState } from 'react' -import { usePnlCurve } from '../hooks/useApi' -import axios from 'axios' +import { useIvWatchlist, useIvSnapshot, useIvHistory } from '../hooks/useApi' +import { Activity, TrendingUp, TrendingDown, Minus, RefreshCw, ChevronDown, ChevronUp } from 'lucide-react' import clsx from 'clsx' -import { - LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, - CartesianGrid, ReferenceLine, Legend, -} from 'recharts' -import { TrendingUp, FlaskConical, Calculator } from 'lucide-react' -import type { PnLPoint } from '../types' -const STRATEGIES = [ - { key: 'long_call', label: 'Long Call', desc: 'Pari haussier, gain illimité, perte limitée à la prime' }, - { key: 'long_put', label: 'Long Put', desc: 'Pari baissier, gain important, perte limitée à la prime' }, - { key: 'bull_call_spread', label: 'Bull Call Spread', desc: 'Haussier modéré, coût réduit, gain plafonné' }, - { key: 'bear_put_spread', label: 'Bear Put Spread', desc: 'Baissier modéré, coût réduit, gain plafonné' }, - { key: 'straddle', label: 'Long Straddle', desc: 'Pari sur la volatilité, direction neutre' }, -] +// ── Helpers ─────────────────────────────────────────────────────────────────── -const WATCHLIST_QUICK = [ - 'GLD', 'USO', 'WEAT', 'UNG', 'SPY', 'QQQ', 'GDX', 'COPX', - 'XLE', 'FXE', 'UUP', 'XOM', 'LMT', -] - -interface Greeks { - price: number; delta: number; gamma: number; theta: number; vega: number; rho: number - underlying_price: number; sigma: number +function ivRankColor(rank: number | null | undefined): string { + if (rank == null) return 'text-slate-500' + if (rank >= 80) return 'text-red-400' + if (rank >= 50) return 'text-amber-400' + if (rank >= 20) return 'text-emerald-400' + return 'text-blue-400' } -interface SpreadResult { - strategy: string; net_debit: number; max_loss: number; max_gain: number | null - breakeven?: number; breakevens?: number[]; legs: Array<{type: string; strike: number; premium: number}> - underlying_price: number; sigma: number +function ivRankBg(rank: number | null | undefined): string { + if (rank == null) return 'bg-dark-700/30 border-slate-700/30' + if (rank >= 80) return 'bg-red-900/20 border-red-700/30' + if (rank >= 50) return 'bg-amber-900/10 border-amber-700/20' + if (rank >= 20) return 'bg-emerald-900/10 border-emerald-700/20' + return 'bg-blue-900/20 border-blue-700/30' } -export default function OptionsLab() { - const [strategy, setStrategy] = useState('long_call') - const [symbol, setSymbol] = useState('GLD') - const [strike, setStrike] = useState(200) - const [strikeHigh, setStrikeHigh] = useState(210) - const [expiry, setExpiry] = useState(90) - const [optionType, setOptionType] = useState('call') - const [quantity, setQuantity] = useState(1) - const [result, setResult] = useState(null) - const [pnlData, setPnlData] = useState([]) - const [loading, setLoading] = useState(false) +function ivSignalLabel(rank: number | null | undefined) { + if (rank == null) return null + if (rank >= 80) return { text: 'Vendre de la vol', icon: , cls: 'text-red-400' } + if (rank < 20) return { text: 'Acheter de la vol', icon: , cls: 'text-blue-400' } + return { text: 'Neutre', icon: , cls: 'text-slate-500' } +} - const compute = async () => { - setLoading(true) - try { - let res: Greeks | SpreadResult - if (strategy === 'long_call' || strategy === 'long_put') { - const type = strategy === 'long_call' ? 'call' : 'put' - const r = await axios.get('/api/options/price', { - params: { symbol, strike, expiry_days: expiry, option_type: type } - }) - res = r.data as Greeks - const pnl = await axios.get('/api/options/pnl-curve', { - params: { symbol, strike, expiry_days: expiry, option_type: type, quantity, premium_paid: res.price } - }) - setPnlData(pnl.data as PnLPoint[]) - } else if (strategy === 'bull_call_spread') { - const r = await axios.get('/api/options/strategy/bull-call-spread', { - params: { symbol, strike_low: strike, strike_high: strikeHigh, expiry_days: expiry } - }) - res = r.data as SpreadResult - setPnlData([]) - } else if (strategy === 'bear_put_spread') { - const r = await axios.get('/api/options/strategy/bear-put-spread', { - params: { symbol, strike_high: strikeHigh, strike_low: strike, expiry_days: expiry } - }) - res = r.data as SpreadResult - setPnlData([]) - } else { - const r = await axios.get('/api/options/strategy/straddle', { - params: { symbol, strike, expiry_days: expiry } - }) - res = r.data as SpreadResult - setPnlData([]) - } - setResult(res) - } catch (e) { - console.error(e) - } - setLoading(false) +function StructureBadge({ structure }: { structure: string | null | undefined }) { + if (!structure) return null + const map: Record = { + contango: { label: 'Contango ↗', cls: 'text-emerald-400 bg-emerald-900/20 border-emerald-700/30' }, + backwardation: { label: 'Backwardation ↘', cls: 'text-red-400 bg-red-900/20 border-red-700/30' }, + flat: { label: 'Flat →', cls: 'text-slate-400 bg-slate-800 border-slate-700/30' }, } + const s = map[structure] || { label: structure, cls: 'text-slate-400 bg-slate-800 border-slate-700' } + return {s.label} +} - const isGreeks = result && 'delta' in result - const isSpread = result && 'net_debit' in result - +function IvBar({ rank }: { rank: number | null | undefined }) { + if (rank == null) return
+ const color = rank >= 80 ? 'bg-red-500' : rank >= 50 ? 'bg-amber-500' : rank >= 20 ? 'bg-emerald-500' : 'bg-blue-500' return ( -
-
-

- Options Lab -

-

- Pricer Black-Scholes · Greeks · Stratégies · Courbe P&L -

-
- -
- {/* Left: strategy builder */} -
- {/* Strategy select */} -
-
Stratégie
-
- {STRATEGIES.map(s => ( - - ))} -
-
- - {/* Parameters */} -
-
Paramètres
-
-
- -
- {WATCHLIST_QUICK.map(s => ( - - ))} -
- setSymbol(e.target.value.toUpperCase())} - className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" - placeholder="Ex: GLD, USO, SPY" - /> -
- -
- - setStrike(Number(e.target.value))} - className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" - /> -
- - {(strategy === 'bull_call_spread' || strategy === 'bear_put_spread') && ( -
- - setStrikeHigh(Number(e.target.value))} - className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" - /> -
- )} - -
- -
- {[30, 60, 90, 180].map(d => ( - - ))} -
- setExpiry(Number(e.target.value))} - className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" - /> -
- - {(strategy === 'long_call' || strategy === 'long_put') && ( -
- - setQuantity(Number(e.target.value))} - min={1} - className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" - /> -
- )} - - -
-
-
- - {/* Right: results */} -
- {/* Greeks / Spread summary */} - {isGreeks && ( -
-
Prix & Greeks — {symbol} {strike} {strategy === 'long_call' ? 'Call' : 'Put'} {expiry}j
-
- {[ - { label: 'Prime', value: `$${result.price.toFixed(4)}`, highlight: true }, - { label: 'Coût (1 contrat)', value: `$${(result.price * 100).toFixed(2)}` }, - { label: 'Spot sous-jacent', value: `$${result.underlying_price?.toFixed(2)}` }, - { label: 'Vol. Réalisée', value: `${((result.sigma ?? 0) * 100).toFixed(1)}%` }, - ].map(({ label, value, highlight }) => ( -
-
{label}
-
{value}
-
- ))} -
-
- {[ - { label: 'Delta (Δ)', value: result.delta, desc: 'Sensibilité au prix' }, - { label: 'Gamma (Γ)', value: result.gamma, desc: 'Variation du delta' }, - { label: 'Theta (Θ)', value: result.theta, desc: 'Déclin temporel/j' }, - { label: 'Vega (ν)', value: result.vega, desc: 'Sensibilité à IV' }, - { label: 'Rho (ρ)', value: result.rho, desc: 'Sensibilité aux taux' }, - ].map(({ label, value, desc }) => ( -
-
{label}
-
0, - 'text-red-400': value < 0, - 'text-slate-400': value === 0, - })}> - {value?.toFixed(4)} -
-
{desc}
-
- ))} -
-
- )} - - {isSpread && ( -
-
Résumé stratégie — {result.strategy}
-
- {[ - { label: 'Débit net', value: `$${result.net_debit.toFixed(4)}`, color: 'text-red-400' }, - { label: 'Perte max', value: `$${result.max_loss.toFixed(2)}`, color: 'text-red-400' }, - { label: 'Gain max', value: result.max_gain != null ? `$${result.max_gain.toFixed(2)}` : '∞', color: 'text-emerald-400' }, - { label: 'Seuil renta.', value: `$${(result.breakeven ?? (result.breakevens?.[0]) ?? 0).toFixed(2)}`, color: 'text-yellow-400' }, - ].map(({ label, value, color }) => ( -
-
{label}
-
{value}
-
- ))} -
-
-
Jambes de la stratégie
- {result.legs.map((leg, i) => ( -
- - {leg.type.toUpperCase()} - - Strike: ${leg.strike} - Prime: ${leg.premium.toFixed(4)} -
- ))} -
-
- )} - - {/* P&L Chart */} - {pnlData.length > 0 && ( -
-
Courbe P&L à l'expiration
- - - - `$${v.toFixed(0)}`} /> - `$${v.toFixed(0)}`} /> - [`$${v.toFixed(2)}`, 'P&L']} - labelFormatter={v => `Prix sous-jacent: $${Number(v).toFixed(2)}`} - /> - - - - - -
- )} - - {!result && !loading && ( -
-
- -
Configurer et calculer une stratégie
-
Les résultats apparaîtront ici
-
-
- )} -
-
+
+
+
+ ) +} + +function IvSparkline({ history }: { history: { recorded_date: string; iv_current: number }[] }) { + if (!history || history.length < 3) return null + const vals = [...history].reverse().map(h => h.iv_current * 100) + const max = Math.max(...vals) + const min = Math.min(...vals) + const range = max - min || 1 + const W = 100, H = 28 + const points = vals.map((v, i) => { + const x = (i / (vals.length - 1)) * W + const y = H - ((v - min) / range) * (H - 2) - 1 + return `${x.toFixed(1)},${y.toFixed(1)}` + }).join(' ') + return ( + + + + + ) +} + +// ── Expanded detail panel for one ticker ───────────────────────────────────── +function TickerDetail({ ticker }: { ticker: string }) { + const { data: snap, isLoading } = useIvSnapshot(ticker) + const { data: histData } = useIvHistory(ticker, 90) + + if (isLoading) return
+ if (!snap) return null + + const ts = (snap.term_structure || {}) as any + const skew = (snap.skew || {}) as any + const flow = (snap.options_flow || {}) as any + const history: any[] = histData?.history || [] + + return ( +
+ + {/* Term Structure */} +
+
Term Structure
+
+ {([['30j', ts.iv_30d], ['60j', ts.iv_60d], ['90j', ts.iv_90d], ['180j', ts.iv_180d]] as [string, number | undefined][]).map(([label, iv]) => + iv ? ( +
+ {label} +
+
+
+ {(iv * 100).toFixed(1)}% +
+ ) : null + )} + {ts.structure &&
} +
+
+ + {/* Skew */} +
+
Skew Put/Call
+ {skew.put_skew != null ? ( +
+
+ Put 25Δ + {skew.iv_put_25d}% +
+
+ Call 25Δ + {skew.iv_call_25d}% +
+
3 ? 'text-red-400' : skew.skew_pct < -2 ? 'text-blue-400' : 'text-slate-400')}> + {skew.skew_pct > 0 ? '+' : ''}{skew.skew_pct} pts +
+ {skew.interpretation && ( +
{skew.interpretation}
+ )} +
+ ) : ( +
Données insuffisantes
+ )} +
+ + {/* Options Flow */} +
+
Options Flow
+ {flow.pc_oi_ratio != null ? ( +
+
+ P/C OI ratio + 1.3 ? 'text-red-400' : flow.pc_oi_ratio < 0.7 ? 'text-blue-400' : 'text-slate-300')}> + {flow.pc_oi_ratio} + +
+ {flow.flow_bias &&
{flow.flow_bias}
} + {flow.gamma_bias &&
{flow.gamma_bias}
} + {flow.unusual_strikes?.length > 0 && ( +
+
Strikes inhabituels
+ {flow.unusual_strikes.slice(0, 2).map((s: any) => ( +
+ {s.strike} {s.type.toUpperCase()} OI={s.total_oi.toLocaleString()} + ({s.pct_otm > 0 ? '+' : ''}{s.pct_otm}%) +
+ ))} +
+ )} +
+ ) : ( +
Pas d'OI disponible
+ )} +
+ + {/* History sparkline */} + {history.length > 4 && ( +
+ IV 90j + +
+ min {snap.iv_min_52w_pct}% + {' · '}max {snap.iv_max_52w_pct}% + {' · '}{snap.history_days}j d'historique +
+
+ )} +
+ ) +} + +// ── Watchlist Row ───────────────────────────────────────────────────────────── +function WatchlistRow({ item }: { item: any }) { + const [expanded, setExpanded] = useState(false) + const signal = ivSignalLabel(item.iv_rank) + + return ( +
+
setExpanded(!expanded)} + > +
+
{item.ticker}
+ {item.proxy && item.proxy !== item.ticker && ( +
↳ {item.proxy}
+ )} +
+ +
+ + {item.iv_current_pct != null ? `${item.iv_current_pct}%` : '—'} + +
IV actuelle
+
+ +
+
+ IV Rank + + {item.iv_rank != null ? `${item.iv_rank}%` : 'N/A'} + +
+ +
+ +
+
Pctile
+
+ {item.iv_percentile != null ? `${item.iv_percentile}%` : '—'} +
+
+ +
+ {signal && ( +
+ {signal.icon} {signal.text} +
+ )} + {item.history_days > 0 && ( +
{item.history_days}j historique
+ )} +
+ +
+ {expanded ? : } +
+
+ + {expanded && ( +
+ +
+ )} +
+ ) +} + +// ── Main page ───────────────────────────────────────────────────────────────── +export default function OptionsLab() { + const { data, isLoading, refetch, isFetching } = useIvWatchlist() + const items: any[] = data?.items || [] + + const sellVol = items.filter(i => (i.iv_rank ?? 50) >= 80) + const buyVol = items.filter(i => i.iv_rank != null && i.iv_rank < 20) + const neutral = items.filter(i => i.iv_rank != null && i.iv_rank >= 20 && i.iv_rank < 80) + const noData = items.filter(i => i.iv_rank == null) + + return ( +
+
+
+

+ Options Lab +

+

+ IV Rank · IV Percentile · Term Structure · Skew · Options Flow +

+
+ +
+ + {/* Légende */} +
+
+
+ + IV Rank > 80% — Vendre de la vol +
+
Credit spreads, iron condors, covered calls. La prime est élevée → avantage au vendeur.
+
+
+
+ + IV Rank < 20% — Acheter de la convexité +
+
Long calls, long puts, straddles, strangles. La prime est cheap → bon moment pour acheter du gamma.
+
+
+
+ Contango = IV courte < longue (marché calme, bon pour vendre du court terme) · + Backwardation = IV courte > longue (stress, bon pour acheter de la protection courte) · + Skew + = puts plus chers que calls (biais baissier institutionnel) +
+ + {isLoading ? ( +
+ {[...Array(6)].map((_, i) =>
)} +
+ ) : items.length === 0 ? ( +
+ +
Aucune donnée IV disponible
+
+ Les ETFs US sont requis (SPY, QQQ, GLD...). +
L'IV Rank nécessite de l'historique — les données s'accumulent à chaque actualisation. +
+ +
+ ) : ( +
+ {sellVol.length > 0 && ( +
+
+ Vendre de la vol — IVR > 80% ({sellVol.length}) +
+
{sellVol.map(item => )}
+
+ )} + {buyVol.length > 0 && ( +
+
+ Acheter de la vol — IVR < 20% ({buyVol.length}) +
+
{buyVol.map(item => )}
+
+ )} + {neutral.length > 0 && ( +
+
+ Zone neutre — IVR 20–80% ({neutral.length}) +
+
{neutral.map(item => )}
+
+ )} + {noData.length > 0 && ( +
+
Sans historique ({noData.length})
+
{noData.map(item => )}
+
+ )} +
+ )}
) }