feat: new cockpit
This commit is contained in:
2
GUIDE.md
2
GUIDE.md
@@ -1,4 +1,4 @@
|
||||
# GeoOptions Intelligence — Guide d'utilisation
|
||||
# OpenFin Intelligence — Guide d'utilisation
|
||||
|
||||
## Démarrage
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GeoOptions Intelligence — Documentation Système</title>
|
||||
<title>OpenFin Intelligence — Documentation Système</title>
|
||||
<style>
|
||||
/* ── Base ── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
@@ -178,7 +178,7 @@
|
||||
══════════════════════════════════════════════════════════════════════════ -->
|
||||
<div class="cover">
|
||||
<div class="logo">⚡</div>
|
||||
<h1>GeoOptions Intelligence</h1>
|
||||
<h1>OpenFin Intelligence</h1>
|
||||
<div class="subtitle">Architecture du Système & Logique des Prompts IA</div>
|
||||
<div class="meta">
|
||||
Version 2.0 · Juin 2026 · FastAPI + React + GPT-4o
|
||||
@@ -192,7 +192,7 @@
|
||||
<h2>1. Concept Général</h2>
|
||||
|
||||
<p class="section-intro">
|
||||
GeoOptions Intelligence est un cockpit de trading d'options dont
|
||||
OpenFin 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
|
||||
@@ -963,7 +963,7 @@ en aval de chaque appel.
|
||||
</div>
|
||||
|
||||
<p style="margin-top:40px; font-size:9pt; color:#94a3b8; text-align:center;">
|
||||
GeoOptions Intelligence v2.0 · Document généré le 17 juin 2026 · FastAPI + React + SQLite + GPT-4o
|
||||
OpenFin Intelligence v2.0 · Document généré le 17 juin 2026 · FastAPI + React + SQLite + GPT-4o
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GeoOptions Intelligence — Manuel Complet v5.0</title>
|
||||
<title>OpenFin Intelligence — Manuel Complet v5.0</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: 'Georgia', 'Times New Roman', serif; font-size: 10.5pt; line-height: 1.65; color: #1a1a2e; background: #fff; max-width: 960px; margin: 0 auto; padding: 40px 48px; }
|
||||
@@ -101,7 +101,7 @@ li { margin-bottom: 4px; font-size: 9.5pt; }
|
||||
<!-- COVER PAGE -->
|
||||
<div class="cover">
|
||||
<div style="font-size:9pt;color:#7a8aaa;font-family:'Courier New',monospace;margin-bottom:16px;letter-spacing:2px;">DOCUMENTATION TECHNIQUE & GUIDE TRADER</div>
|
||||
<h1 style="font-size:28pt;">GeoOptions Intelligence</h1>
|
||||
<h1 style="font-size:28pt;">OpenFin Intelligence</h1>
|
||||
<div class="subtitle">Manuel Complet du Système — Architecture, Algorithmes & Guide Trader</div>
|
||||
<div style="margin-top:24px;display:flex;justify-content:center;gap:10px;flex-wrap:wrap;">
|
||||
<span class="badge badge-p1">Phase 1 · IV & Structure</span>
|
||||
@@ -213,7 +213,7 @@ li { margin-bottom: 4px; font-size: 9.5pt; }
|
||||
<h2 id="s1">1. Vue d'Ensemble du Système</h2>
|
||||
|
||||
<h3 id="s1-1">1.1 Ce que fait le système</h3>
|
||||
<p>GeoOptions Intelligence est un cockpit de trading d'options à usage personnel, conçu pour un trader individuel opérant sur Interactive Brokers avec des positions de l'ordre de 1 000 € par trade et un horizon moyen de 3 mois. Le système fusionne en temps quasi-réel trois flux d'information hétérogènes — données de marché financières (prix, volatilité implicite, open interest), signaux géopolitiques (flux RSS de presse internationale, scores de tension par région) et analyse macroéconomique (régime macro dominant parmi 8 états : Goldilocks, Désinflation, Soft Landing, Reflation, Stagflation, Choc Inflationniste, Récession, Crise de Liquidité) — pour générer des suggestions de patterns de trading options avec probabilités de succès, tailles de position ajustées au risque, et contre-thèses d'invalidation. L'ensemble tourne sur un VPS Linux sous FastAPI (backend) et React (frontend), avec SQLite comme base de données persistante et GPT-4o comme moteur de raisonnement.</p>
|
||||
<p>OpenFin Intelligence est un cockpit de trading d'options à usage personnel, conçu pour un trader individuel opérant sur Interactive Brokers avec des positions de l'ordre de 1 000 € par trade et un horizon moyen de 3 mois. Le système fusionne en temps quasi-réel trois flux d'information hétérogènes — données de marché financières (prix, volatilité implicite, open interest), signaux géopolitiques (flux RSS de presse internationale, scores de tension par région) et analyse macroéconomique (régime macro dominant parmi 8 états : Goldilocks, Désinflation, Soft Landing, Reflation, Stagflation, Choc Inflationniste, Récession, Crise de Liquidité) — pour générer des suggestions de patterns de trading options avec probabilités de succès, tailles de position ajustées au risque, et contre-thèses d'invalidation. L'ensemble tourne sur un VPS Linux sous FastAPI (backend) et React (frontend), avec SQLite comme base de données persistante et GPT-4o comme moteur de raisonnement.</p>
|
||||
|
||||
<h3 id="s1-2">1.2 Les 3 Principes Fondamentaux</h3>
|
||||
|
||||
@@ -1608,7 +1608,7 @@ Cosine : 0.82 → AU-DESSUS 0.75 → REJETÉ (CORRECT)
|
||||
<!-- SECTION 7 -->
|
||||
<h2 id="s7">7. Les Prompts IA — Boîte Noire Ouverte</h2>
|
||||
|
||||
<p>GeoOptions utilise 6 appels GPT distincts, chacun avec un rôle précis et une structure optimisée.</p>
|
||||
<p>OpenFin utilise 6 appels GPT distincts, chacun avec un rôle précis et une structure optimisée.</p>
|
||||
|
||||
<h3>Prompt 1 — Enrichissement News & Scoring Directionnel</h3>
|
||||
<span class="badge badge-mini">GPT-4o-mini</span> <span class="badge badge-db">~800 tokens · 1 appel batch jusqu'à 20 news</span>
|
||||
@@ -1933,7 +1933,7 @@ Seuil : confidence > 0.75 → ALERTE INVALIDATION générée dans l'UI
|
||||
<p>Ne jamais modifier le modèle GPT en production sans tester sur un cycle complet. Ne jamais baisser le Kelly fraction en dessous de 1/4 (tailles trop petites pour couvrir les frais IBKR).</p>
|
||||
|
||||
<h4>Pattern Lab (Options Lab) — Flux de Travail Détaillé</h4>
|
||||
<p>La page <strong>Patterns</strong> est le cœur opérationnel de GeoOptions. Voici le flux de travail typique d'un trader après chaque cycle :</p>
|
||||
<p>La page <strong>Patterns</strong> est le cœur opérationnel de OpenFin. Voici le flux de travail typique d'un trader après chaque cycle :</p>
|
||||
<div class="flow-mini">
|
||||
<div class="flow-step">1. Lire le<br>Cycle Report</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
@@ -2010,11 +2010,11 @@ Seuil : confidence > 0.75 → ALERTE INVALIDATION générée dans l'UI
|
||||
<tr><td><strong>Gamma</strong></td><td>Vitesse de changement du delta. Dealer gamma négatif = les market makers amplifient les mouvements. Dealer gamma positif = ils amortissent les mouvements.</td></tr>
|
||||
<tr><td><strong>Thêta (Time Decay)</strong></td><td>Érosion de la valeur d'une option avec le temps. Accélère dans les 30 derniers jours. Vendeurs bénéficient, acheteurs luttent contre lui.</td></tr>
|
||||
<tr><td><strong>Kelly Criterion</strong></td><td>Formule de sizing optimal : f* = (p×G - (1-p)) / G. Maximise la croissance géométrique du capital à long terme.</td></tr>
|
||||
<tr><td><strong>Kelly Fractionnel</strong></td><td>Kelly × 1/k (k=2, 3 ou 4). GeoOptions utilise k=3. Réduit le risque au détriment d'une partie de la croissance optimale, mais psychologiquement tenable.</td></tr>
|
||||
<tr><td><strong>Kelly Fractionnel</strong></td><td>Kelly × 1/k (k=2, 3 ou 4). OpenFin utilise k=3. Réduit le risque au détriment d'une partie de la croissance optimale, mais psychologiquement tenable.</td></tr>
|
||||
<tr><td><strong>HHI (Herfindahl-Hirschman Index)</strong></td><td>Mesure de concentration : Σ(part_i)². De 0 (parfaitement diversifié) à 1.0 (monopole total). <0.25 = bien diversifié, >0.35 = concentré.</td></tr>
|
||||
<tr><td><strong>N Effectif</strong></td><td>1/HHI. Nombre de positions "complètement indépendantes" équivalentes dans le portefeuille. 6 positions corrélées peuvent avoir N effectif = 2.5.</td></tr>
|
||||
<tr><td><strong>Brier Score</strong></td><td>Métrique de calibration probabiliste : Σ(prédiction - résultat)²/N. 0 = parfait, 0.25 = équivalent aléatoire. Mesure si les probabilités annoncées correspondent aux fréquences réelles.</td></tr>
|
||||
<tr><td><strong>Prior (Bayésien)</strong></td><td>Probabilité initiale avant d'observer des données. GeoOptions utilise un prior neutre (α=β=1) qui est progressivement écrasé par les données réelles.</td></tr>
|
||||
<tr><td><strong>Prior (Bayésien)</strong></td><td>Probabilité initiale avant d'observer des données. OpenFin utilise un prior neutre (α=β=1) qui est progressivement écrasé par les données réelles.</td></tr>
|
||||
<tr><td><strong>Posterior (Bayésien)</strong></td><td>Probabilité mise à jour après observation : α/(α+β). Converge vers le vrai win rate avec suffisamment de trades.</td></tr>
|
||||
<tr><td><strong>Distribution Bêta</strong></td><td>Distribution sur [0,1] paramétrée par (α, β). Utilisée pour modéliser l'incertitude sur un taux de succès. Conjuguée de la vraisemblance binomiale → mise à jour analytique simple.</td></tr>
|
||||
<tr><td><strong>Intervalle de Confiance IC95</strong></td><td>Plage contenant le vrai paramètre avec 95% de probabilité. IC large = incertitude élevée (peu de données). IC étroit = estimation fiable.</td></tr>
|
||||
@@ -2027,9 +2027,9 @@ Seuil : confidence > 0.75 → ALERTE INVALIDATION générée dans l'UI
|
||||
<tr><td><strong>Exit Defaults</strong></td><td>Seuils globaux de sortie configurés dans Config : objectif de gain (défaut +30%) et stop-loss (défaut -50%) exprimés en % du sous-jacent. Chaque trade peut surcharger ces valeurs. Appliqués à la volée dans l'onglet Ouverts du Journal.</td></tr>
|
||||
<tr><td><strong>P&L Réalisé vs Théorique</strong></td><td>P&L théorique = (prix_actuel - prix_entrée) / prix_entrée, calculé en temps réel. P&L réalisé = valeur enregistrée au moment de la fermeture, stockée en base. Seul le P&L réalisé entre dans les statistiques win rate / average PnL.</td></tr>
|
||||
<tr><td><strong>Alert Type</strong></td><td>Statut calculé à la volée sur chaque trade ouvert : <code>target_reached</code> si P&L ≥ seuil objectif, <code>stop_loss</code> si P&L ≤ seuil stop, <code>null</code> sinon. Déclenche le badge 🎯 ou 🛑 dans le Journal.</td></tr>
|
||||
<tr><td><strong>Cosine Similarity</strong></td><td>cos(θ) = (A·B)/(|A|×|B|). De -1 (opposés) à +1 (identiques). Mesure la direction (le sens) indépendamment de la norme. Seuil GeoOptions : >0.75 = doublon.</td></tr>
|
||||
<tr><td><strong>Jaccard Similarity</strong></td><td>|A∩B|/|A∪B|. Mesure les mots communs entre deux textes. Simple mais aveugle aux synonymes et au sens sémantique. Seuil GeoOptions : >0.30 = doublon.</td></tr>
|
||||
<tr><td><strong>DBSCAN</strong></td><td>Algorithme de clustering alternatif qui découvre des clusters de forme arbitraire et identifie les outliers. Non utilisé dans GeoOptions (K-Means suffisant pour 4 régimes fixes).</td></tr>
|
||||
<tr><td><strong>Cosine Similarity</strong></td><td>cos(θ) = (A·B)/(|A|×|B|). De -1 (opposés) à +1 (identiques). Mesure la direction (le sens) indépendamment de la norme. Seuil OpenFin : >0.75 = doublon.</td></tr>
|
||||
<tr><td><strong>Jaccard Similarity</strong></td><td>|A∩B|/|A∪B|. Mesure les mots communs entre deux textes. Simple mais aveugle aux synonymes et au sens sémantique. Seuil OpenFin : >0.30 = doublon.</td></tr>
|
||||
<tr><td><strong>DBSCAN</strong></td><td>Algorithme de clustering alternatif qui découvre des clusters de forme arbitraire et identifie les outliers. Non utilisé dans OpenFin (K-Means suffisant pour 4 régimes fixes).</td></tr>
|
||||
<tr><td><strong>Cycle Context Engine</strong></td><td>Ensemble des mécanismes (Phases 1–5) qui enrichissent le contexte IA : delta temporel (cycle_meta), decay des news par ancienneté, snapshot contexte, releases FRED, indicateurs techniques par horizon, price discovery. Objectif : passer d'un prompt "aveugle" à un prompt temporellement et techniquement conscient.</td></tr>
|
||||
<tr><td><strong>cycle_meta</strong></td><td>Dictionnaire injecté dans chaque prompt IA contenant : hours_since_last_cycle, completed_cycles_today, cycle_number_today, market_session. Permet à GPT-4o de savoir si c'est le premier cycle de la journée ou le troisième, si le marché est ouvert, et depuis combien de temps le dernier cycle a tourné.</td></tr>
|
||||
<tr><td><strong>News Decay</strong></td><td>Mécanisme de pondération temporelle des news : les articles >6h voient leur impact_score multiplié par un facteur decay(age) décroissant (plancher 0.30). Évite qu'un article de la veille ne domine les suggestions du cycle actuel.</td></tr>
|
||||
@@ -2049,7 +2049,7 @@ Seuil : confidence > 0.75 → ALERTE INVALIDATION générée dans l'UI
|
||||
<tr><td><strong>OTM / ATM</strong></td><td>Out-of-The-Money : strike moins favorable que le prix actuel. At-The-Money : strike proche du prix actuel (maximum de gamma et valeur temps).</td></tr>
|
||||
<tr><td><strong>Calendar Spread</strong></td><td>Vente d'option court terme + achat même strike long terme. Profite de la décroissance plus rapide du time value court. Adapté en backwardation.</td></tr>
|
||||
<tr><td><strong>Contango / Backwardation</strong></td><td>Contango : IV croissante avec la maturité (marché calme). Backwardation : IV décroissante (stress aigu court terme). Signaux opposés pour les stratégies de vol.</td></tr>
|
||||
<tr><td><strong>Demi-vie (Half-life)</strong></td><td>Temps nécessaire pour que le poids d'un insight KB diminue de moitié. w(t) = e^(-λt), λ = ln(2)/demi_vie. GeoOptions : 180 jours par défaut.</td></tr>
|
||||
<tr><td><strong>Demi-vie (Half-life)</strong></td><td>Temps nécessaire pour que le poids d'un insight KB diminue de moitié. w(t) = e^(-λt), λ = ln(2)/demi_vie. OpenFin : 180 jours par défaut.</td></tr>
|
||||
<tr><td><strong>Pattern Reliability Score</strong></td><td>win_rate × log(n+1). Pénalise les petits échantillons et valorise la cohérence sur de nombreux trades. >1.5 = fiable, <0.5 = insuffisant.</td></tr>
|
||||
<tr><td><strong>Régime Macro</strong></td><td>État macroéconomique parmi 8 scénarios : Goldilocks, Désinflation, Soft Landing, Reflation, Stagflation, Choc Inflationniste, Récession, Crise de Liquidité. Déterminé par scoring règle-basé sur ~50 indicateurs (signaux blendés 1j/5j/10j) + lissage Bayésien sur 25 jours d'historique. Score 0–100 par régime, non normalisé (pas des probabilités). Badge "stable Xj" = nombre de jours consécutifs du dominant actuel.</td></tr>
|
||||
<tr><td><strong>Géo Score</strong></td><td>Score agrégé de tension géopolitique mondiale, 0–100. Calculé par moyenne pondérée des scores régionaux. >60 = élevé, >75 = critique. Impacte directement les suggestions IA.</td></tr>
|
||||
@@ -2060,8 +2060,8 @@ Seuil : confidence > 0.75 → ALERTE INVALIDATION générée dans l'UI
|
||||
<tr><td><strong>Moniteur de Portefeuille IA</strong></td><td>Agent GPT-4o-mini (Step 7b du cycle) qui analyse l'état du portefeuille simulé après chaque log de trades. Produit un assessment global + liste d'actions (close / rebalance / monitor) + suggestion de rééquilibrage. Non-bloquant et persisté dans system_logs pour affichage dans le Journal.</td></tr>
|
||||
<tr><td><strong>Concentration de Classe</strong></td><td>Pourcentage d'une classe d'actif (energy, metals, agriculture, equities, indices, forex, rates) dans le portefeuille simulé total. Seuil d'alerte : > 35%. Affiché sous forme de barres horizontales colorées dans l'onglet Risque Sim.</td></tr>
|
||||
<tr><td><strong>Price Warning</strong></td><td>Badge ambre sur un trade dans l'onglet Ouverts : <code>NO DATA</code> (ni prix d'entrée ni prix actuel disponible), <code>NO ENTRY</code> (prix d'entrée absent), <code>NO LIVE</code> (prix actuel non récupéré). Signale que le monitoring MtM est impossible pour ce trade.</td></tr>
|
||||
<tr><td><strong>Specialist Desk</strong></td><td>Configuration par asset class dans GeoOptions : fondamentaux clés, sensibilité macro par régime, seuils de prix significatifs/extrêmes, et catalogue de rapports critiques (y compris non-calendrier). Injecté dans les deux prompts IA (scoring et suggestion) pour ancrer les recommandations dans la culture réelle de chaque desk.</td></tr>
|
||||
<tr><td><strong>Crédibilité Actuarielle</strong></td><td>Méthode de pondération w = n/(n+k) (k=5 dans GeoOptions) qui mesure la confiance accordée aux données observées vs l'estimation a priori. À n=5 trades : 50% observé. Inspiré de Bühlmann-Straub (actuariat). Utilisée pour blender l'estimated_move IA avec le rendement réellement observé.</td></tr>
|
||||
<tr><td><strong>Specialist Desk</strong></td><td>Configuration par asset class dans OpenFin : fondamentaux clés, sensibilité macro par régime, seuils de prix significatifs/extrêmes, et catalogue de rapports critiques (y compris non-calendrier). Injecté dans les deux prompts IA (scoring et suggestion) pour ancrer les recommandations dans la culture réelle de chaque desk.</td></tr>
|
||||
<tr><td><strong>Crédibilité Actuarielle</strong></td><td>Méthode de pondération w = n/(n+k) (k=5 dans OpenFin) qui mesure la confiance accordée aux données observées vs l'estimation a priori. À n=5 trades : 50% observé. Inspiré de Bühlmann-Straub (actuariat). Utilisée pour blender l'estimated_move IA avec le rendement réellement observé.</td></tr>
|
||||
<tr><td><strong>Calibrated Expected Move</strong></td><td>Valeur blendée de l'expected_move_pct : (1−w)×ai_estimate + w×observed_avg_win_pct. Utilisée comme G dans l'EV dès que la crédibilité w > 10%. Permet de corriger progressivement le biais d'optimisme de l'IA avec les données réelles.</td></tr>
|
||||
<tr><td><strong>Find Similar</strong></td><td>Endpoint GPT-4o-mini qui compare un pattern de la bibliothèque contre tous les autres et retourne : merge_as_instance (doublon → fusionner), counter_scenario (direction opposée, même régime → conserver les deux), new_pattern (pas de match structurel).</td></tr>
|
||||
<tr><td><strong>Pattern Merge</strong></td><td>Fusion transactionnelle de deux patterns : union des historical_instances (dédup), somme des compteurs backtest, remapping de toutes les tables FK (scores, trades, traces IA, logs), suppression du pattern discardé. Irréversible — confirmation requise.</td></tr>
|
||||
@@ -2777,7 +2777,7 @@ calibrated_expected_move = (1 − w) × ai_estimate + w × observed_avg_win_pct
|
||||
<hr>
|
||||
|
||||
<div class="footer-doc">
|
||||
<p>Document généré le 23 juin 2026 · GeoOptions Intelligence v6.1</p>
|
||||
<p>Document généré le 23 juin 2026 · OpenFin Intelligence v6.1</p>
|
||||
<p>FastAPI + React + SQLite + GPT-4o · CFTC Socrata · yfinance · 25 pages · 6 phases · Usage interne trader</p>
|
||||
<p>Sections : Architecture · Cycle complet simulé · Phase 5 Cycle Context Engine · Phase 1 IV/Structure + IV Gate + Watchlist · Phase 2 Fiabilité · Phase 3 Risk Engine · Pattern Convergence + IBKR Ticket · Phase 4 IA Probabiliste · 7 Prompts IA · 20+ Tables DB (incl. cot_data, forward_curve_data) · Guide 19 pages · 19 Décisions conception · Glossaire 50+ termes · Specialist Desks v2 (COT 19 marchés · Forward Curves · Surprise Index · Hawk/Dove Scorer) · Calibration Progressive · Find Similar+Merge · Workflow Calculs</p>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GeoOptions Intelligence — Documentation Système</title>
|
||||
<title>OpenFin Intelligence — Documentation Système</title>
|
||||
<style>
|
||||
/* ── Base ── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
@@ -178,7 +178,7 @@
|
||||
══════════════════════════════════════════════════════════════════════════ -->
|
||||
<div class="cover">
|
||||
<div class="logo">⚡</div>
|
||||
<h1>GeoOptions Intelligence</h1>
|
||||
<h1>OpenFin Intelligence</h1>
|
||||
<div class="subtitle">Architecture du Système & Logique des Prompts IA</div>
|
||||
<div class="meta">
|
||||
Version 2.0 · Juin 2026 · FastAPI + React + GPT-4o
|
||||
@@ -192,7 +192,7 @@
|
||||
<h2>1. Concept Général</h2>
|
||||
|
||||
<p class="section-intro">
|
||||
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.
|
||||
OpenFin 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.
|
||||
</p>
|
||||
|
||||
<h3>1.1 Philosophie fondamentale</h3>
|
||||
@@ -881,7 +881,7 @@
|
||||
</div>
|
||||
|
||||
<p style="margin-top:40px; font-size:9pt; color:#94a3b8; text-align:center;">
|
||||
GeoOptions Intelligence v2.0 · Document généré le 17 juin 2026 · FastAPI + React + SQLite + GPT-4o
|
||||
OpenFin Intelligence v2.0 · Document généré le 17 juin 2026 · FastAPI + React + SQLite + GPT-4o
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GeoOptions Intelligence — Roadmap de Développement</title>
|
||||
<title>OpenFin Intelligence — Roadmap de Développement</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
<!-- COVER -->
|
||||
<div class="cover">
|
||||
<div class="logo">🗺️</div>
|
||||
<h1>GeoOptions Intelligence</h1>
|
||||
<h1>OpenFin Intelligence</h1>
|
||||
<div class="subtitle">Roadmap de Développement — Vers le Niveau Hedge Fund</div>
|
||||
<div class="meta">
|
||||
Basé sur l'audit expert · Juin 2026 · 4 Phases · ~6 mois
|
||||
@@ -893,7 +893,7 @@
|
||||
</div>
|
||||
|
||||
<p style="margin-top:40px; font-size:8.5pt; color:#94a3b8; text-align:center;">
|
||||
GeoOptions Intelligence v2.0 — Roadmap de développement · Basé sur audit expert · Juin 2026
|
||||
OpenFin Intelligence v2.0 — Roadmap de développement · Basé sur audit expert · Juin 2026
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
title GeoOptions Backend
|
||||
title OpenFin Backend
|
||||
cd /d "C:\DataS\OpenFin\backend"
|
||||
if not exist venv python -m venv venv
|
||||
call venv\Scripts\activate.bat
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
title GeoOptions Frontend
|
||||
title OpenFin Frontend
|
||||
cd /d "C:\DataS\OpenFin\frontend"
|
||||
if not exist node_modules npm install
|
||||
npm run dev
|
||||
|
||||
@@ -17,6 +17,7 @@ from routers import eco as eco_router
|
||||
from routers import simulator as simulator_router
|
||||
from routers import causal_lab as causal_lab_router
|
||||
from routers import instrument_models as instrument_models_router
|
||||
from routers import instruments_watchlist as instruments_watchlist_router
|
||||
from services.database import init_db, get_config, cleanup_stale_running_cycles
|
||||
import os
|
||||
import logging
|
||||
@@ -41,7 +42,7 @@ class _DBLogHandler(logging.Handler):
|
||||
pass # Never raise from inside a log handler
|
||||
|
||||
app = FastAPI(
|
||||
title="GeoOptions Intelligence",
|
||||
title="OpenFin Intelligence",
|
||||
description="Geopolitical Options Trading Cockpit API",
|
||||
version="2.0.0",
|
||||
)
|
||||
@@ -233,11 +234,12 @@ app.include_router(eco_router.router)
|
||||
app.include_router(simulator_router.router)
|
||||
app.include_router(causal_lab_router.router)
|
||||
app.include_router(instrument_models_router.router)
|
||||
app.include_router(instruments_watchlist_router.router)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"app": "GeoOptions Intelligence Cockpit", "version": "2.0.0", "docs": "/docs"}
|
||||
return {"app": "OpenFin Intelligence Cockpit", "version": "2.0.0", "docs": "/docs"}
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
|
||||
@@ -268,7 +268,7 @@ def _gpt4o_recommend(event: dict, templates: list) -> dict:
|
||||
]
|
||||
|
||||
system_prompt = (
|
||||
"You are a macro market analyst and causal graph specialist for the GeoOptions platform. "
|
||||
"You are a macro market analyst and causal graph specialist for the OpenFin platform. "
|
||||
"Given a market event and a library of causal graph templates, recommend the most appropriate template "
|
||||
"and suggest any coefficient adjustments. Respond with a JSON object only.\n"
|
||||
"IMPORTANT: Only recommend a template if it is structurally compatible with the event type "
|
||||
@@ -1973,7 +1973,7 @@ def ai_to_grammar(template_id: int, body: AiToGrammarRequest):
|
||||
)
|
||||
|
||||
system_prompt = (
|
||||
"You are a causal graph patch generator for the GeoOptions macro-finance platform.\n"
|
||||
"You are a causal graph patch generator for the OpenFin macro-finance platform.\n"
|
||||
"Convert the user's natural language modification request into graph patch operations.\n"
|
||||
"Return ONLY the grammar lines — no explanation, no markdown, no fences.\n\n"
|
||||
+ GRAMMAR_SPEC
|
||||
@@ -2036,7 +2036,7 @@ def ai_modify_template(template_id: int, body: AiModifyRequest):
|
||||
gj = body.current_graph if body.current_graph.get("nodes") else t["graph_json"]
|
||||
|
||||
system_prompt = (
|
||||
"You are a causal graph editor for the GeoOptions macro-finance platform.\n"
|
||||
"You are a causal graph editor for the OpenFin macro-finance platform.\n"
|
||||
"You receive a causal graph in JSON and a modification request in natural language.\n"
|
||||
"Return ONLY the complete modified graph_json as a valid JSON object — no explanation, no markdown.\n\n"
|
||||
"Graph format:\n"
|
||||
|
||||
@@ -439,6 +439,7 @@ def get_calendar_events(
|
||||
"""
|
||||
from services.database import get_conn
|
||||
from datetime import datetime, date as date_type, timedelta
|
||||
import json as _json
|
||||
conn = get_conn()
|
||||
try:
|
||||
inst_upper = instrument.upper()
|
||||
@@ -453,6 +454,23 @@ def get_calendar_events(
|
||||
currencies = _INSTRUMENT_CURRENCIES.get(inst_upper, ["USD"])
|
||||
impact_filter = [i.strip() for i in impacts.split(",")] if impacts else ["high", "medium"]
|
||||
|
||||
# Build event_category → node_label map from instrument graph
|
||||
event_node_map: dict[str, str] = {}
|
||||
try:
|
||||
graph_row = conn.execute(
|
||||
"SELECT graph_json FROM instrument_models WHERE instrument=?", [inst_upper]
|
||||
).fetchone()
|
||||
if graph_row:
|
||||
gdef = _json.loads(graph_row["graph_json"])
|
||||
event_node_map = {
|
||||
n.get("event_category", ""): n.get("label", n["id"])
|
||||
for n in gdef.get("nodes", [])
|
||||
if n.get("node_type") == "input_event" and n.get("event_category")
|
||||
}
|
||||
event_node_map.pop("", None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Auto-sync live si ff_calendar vide pour cette periode
|
||||
ph = ",".join("?" * len(currencies))
|
||||
n_existing = conn.execute(
|
||||
@@ -500,6 +518,7 @@ def get_calendar_events(
|
||||
"forecast_value": r.get("forecast_value"),
|
||||
"previous_value": r.get("previous_value"),
|
||||
"category": category,
|
||||
"routed_node": event_node_map.get(category, "direct"),
|
||||
"is_future": is_future,
|
||||
"estimated_pips": est_pips,
|
||||
"has_surprise": has_surprise,
|
||||
@@ -738,12 +757,24 @@ def get_macro_guidance(instrument: str) -> List[Dict[str, Any]]:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Unit-aware conversion (same logic as get_macro_sync_items)
|
||||
unit = node.get("unit", "")
|
||||
def _uc(v):
|
||||
if v is None: return None
|
||||
if unit == "K" and abs(v) >= 1_000: return round(v / 1_000, 1)
|
||||
if unit == "pts" and v > 10: return round(v - 50.0, 1)
|
||||
return round(v, 4)
|
||||
|
||||
next_fv = _uc(next_event.get("forecast") if next_event else None)
|
||||
if next_event:
|
||||
next_event = {**next_event, "forecast": next_fv}
|
||||
|
||||
result.append({
|
||||
"node_id": node["id"],
|
||||
"node_label": node.get("label", node["id"]),
|
||||
"macro_key": macro_key,
|
||||
"unit": node.get("unit", ""),
|
||||
"current_value": round(current_v, 4) if current_v is not None else None,
|
||||
"unit": unit,
|
||||
"current_value": _uc(current_v),
|
||||
"next_event": next_event,
|
||||
})
|
||||
|
||||
|
||||
80
backend/routers/instruments_watchlist.py
Normal file
80
backend/routers/instruments_watchlist.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
router = APIRouter(prefix="/api/watchlist", tags=["watchlist"])
|
||||
|
||||
# Intentionally duplicated from market_data.py to keep this system fully decoupled
|
||||
# from the other instrument-list mechanisms (market_watchlist, INSTRUMENT_MODELS,
|
||||
# instruments.json, options_vol watchlist-tickers).
|
||||
_QUOTE_TYPE_TO_ASSET_CLASS = {
|
||||
"CURRENCY": "forex",
|
||||
"FUTURE": "energy",
|
||||
"INDEX": "indices",
|
||||
"ETF": "etfs",
|
||||
"EQUITY": "equities",
|
||||
"MUTUALFUND": "etfs",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/")
|
||||
def list_watchlist():
|
||||
from services.database import get_instruments_watchlist
|
||||
return get_instruments_watchlist()
|
||||
|
||||
|
||||
@router.get("/quotes")
|
||||
def watchlist_quotes():
|
||||
from services.database import get_instruments_watchlist
|
||||
from services.data_fetcher import get_quote
|
||||
items = []
|
||||
for row in get_instruments_watchlist():
|
||||
q = get_quote(row["ticker"]) or {}
|
||||
items.append({
|
||||
**row,
|
||||
"price": q.get("price"),
|
||||
"change_pct": q.get("change_pct"),
|
||||
})
|
||||
return {"items": items}
|
||||
|
||||
|
||||
@router.post("/{ticker}")
|
||||
def add_ticker(ticker: str):
|
||||
from services.data_fetcher import get_quote
|
||||
from services.database import get_instruments_watchlist, add_instrument_watchlist
|
||||
import yfinance as yf
|
||||
ticker = ticker.strip().upper()
|
||||
if any(row["ticker"] == ticker for row in get_instruments_watchlist()):
|
||||
raise HTTPException(409, f"'{ticker}' is already in the watchlist")
|
||||
q = get_quote(ticker)
|
||||
if not q or not q.get("price"):
|
||||
raise HTTPException(400, f"Ticker '{ticker}' not found on yfinance")
|
||||
name = ticker
|
||||
asset_class = "unknown"
|
||||
try:
|
||||
info = yf.Ticker(ticker).fast_info
|
||||
name = getattr(info, "long_name", None) or getattr(info, "short_name", None) or ticker
|
||||
quote_type = getattr(info, "quote_type", "") or ""
|
||||
asset_class = _QUOTE_TYPE_TO_ASSET_CLASS.get(quote_type.upper(), "unknown")
|
||||
except Exception:
|
||||
pass
|
||||
add_instrument_watchlist(ticker, name, asset_class)
|
||||
return {"ticker": ticker, "name": name, "asset_class": asset_class, "price": q["price"]}
|
||||
|
||||
|
||||
@router.delete("/{ticker}")
|
||||
def remove_ticker(ticker: str):
|
||||
from services.database import remove_instrument_watchlist
|
||||
remove_instrument_watchlist(ticker.strip().upper())
|
||||
return {"removed": ticker.strip().upper()}
|
||||
|
||||
|
||||
class ReorderBody(BaseModel):
|
||||
tickers: List[str]
|
||||
|
||||
|
||||
@router.put("/reorder")
|
||||
def reorder(body: ReorderBody):
|
||||
from services.database import reorder_instruments_watchlist
|
||||
reorder_instruments_watchlist(body.tickers)
|
||||
return {"ok": True}
|
||||
@@ -19,7 +19,7 @@ def _fetch_text(url: str, timeout: int = 15) -> str:
|
||||
import urllib.request
|
||||
import html
|
||||
req = urllib.request.Request(url, headers={
|
||||
"User-Agent": "Mozilla/5.0 (compatible; GeoOptions-Research/1.0)"
|
||||
"User-Agent": "Mozilla/5.0 (compatible; OpenFin-Research/1.0)"
|
||||
})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
raw = resp.read().decode("utf-8", errors="replace")
|
||||
|
||||
@@ -34,7 +34,7 @@ MONETARY_KEYWORDS = [
|
||||
HAWKISH_WORDS = ["hike", "hawkish", "raised", "tightening", "tighter"]
|
||||
DOVISH_WORDS = ["cut", "dovish", "easing", "lowered", "accommodative"]
|
||||
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; GeoOptions/1.0)"}
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; OpenFin/1.0)"}
|
||||
_LOOKBACK_DAYS = 14
|
||||
_MAX_KEY_POINTS = 8
|
||||
|
||||
|
||||
@@ -102,6 +102,14 @@ def init_db():
|
||||
added_at TEXT DEFAULT (datetime('now'))
|
||||
)""",
|
||||
"ALTER TABLE market_watchlist ADD COLUMN asset_class TEXT DEFAULT 'custom'",
|
||||
# Dashboard — instruments watchlist ("radar"), independent from market_watchlist
|
||||
"""CREATE TABLE IF NOT EXISTS instruments_watchlist (
|
||||
ticker TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
asset_class TEXT DEFAULT 'unknown',
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
added_at TEXT DEFAULT (datetime('now'))
|
||||
)""",
|
||||
]:
|
||||
try:
|
||||
c.execute(_sql)
|
||||
@@ -3002,6 +3010,52 @@ def remove_market_custom_ticker(ticker: str) -> bool:
|
||||
return changed
|
||||
|
||||
|
||||
# ── Instruments Watchlist (Dashboard "radar" card — independent from market_watchlist) ──
|
||||
|
||||
def get_instruments_watchlist() -> List[Dict]:
|
||||
conn = get_conn()
|
||||
rows = conn.execute(
|
||||
"SELECT ticker, name, asset_class, sort_order, added_at "
|
||||
"FROM instruments_watchlist ORDER BY sort_order ASC, added_at ASC"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def add_instrument_watchlist(ticker: str, name: str = "", asset_class: str = "unknown") -> bool:
|
||||
conn = get_conn()
|
||||
try:
|
||||
max_order = conn.execute("SELECT COALESCE(MAX(sort_order), -1) FROM instruments_watchlist").fetchone()[0]
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO instruments_watchlist (ticker, name, asset_class, sort_order) VALUES (?, ?, ?, ?)",
|
||||
(ticker.upper(), name, asset_class, max_order + 1),
|
||||
)
|
||||
inserted = conn.total_changes > 0
|
||||
conn.commit()
|
||||
except Exception:
|
||||
inserted = False
|
||||
finally:
|
||||
conn.close()
|
||||
return inserted
|
||||
|
||||
|
||||
def remove_instrument_watchlist(ticker: str) -> bool:
|
||||
conn = get_conn()
|
||||
conn.execute("DELETE FROM instruments_watchlist WHERE ticker=?", (ticker.upper(),))
|
||||
changed = conn.total_changes > 0
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return changed
|
||||
|
||||
|
||||
def reorder_instruments_watchlist(tickers: List[str]) -> None:
|
||||
conn = get_conn()
|
||||
for i, t in enumerate(tickers):
|
||||
conn.execute("UPDATE instruments_watchlist SET sort_order=? WHERE ticker=?", (i, t.upper()))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── System Logs ───────────────────────────────────────────────────────────────
|
||||
|
||||
def log_system_event(
|
||||
|
||||
@@ -22,7 +22,7 @@ NAAIM_URLS = [
|
||||
|
||||
AAII_URL = "https://www.aaii.com/sentimentsurvey/sent_results.js"
|
||||
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; GeoOptions/1.0)"}
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; OpenFin/1.0)"}
|
||||
_TIMEOUT = 15
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>GeoOptions Intelligence</title>
|
||||
<title>OpenFin Intelligence</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "geooptions-cockpit",
|
||||
"name": "openfin-cockpit",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "geooptions-cockpit",
|
||||
"name": "openfin-cockpit",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.59.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "geooptions-cockpit",
|
||||
"name": "openfin-cockpit",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
|
||||
52
frontend/src/components/TradeRankList.tsx
Normal file
52
frontend/src/components/TradeRankList.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ArrowUpRight } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default function TradeRankList({ trades, mode, title, linkTo }: {
|
||||
trades: any[]
|
||||
mode: 'top' | 'worst'
|
||||
title: string
|
||||
linkTo: string
|
||||
}) {
|
||||
const sorted = [...trades]
|
||||
.sort((a, b) => mode === 'top'
|
||||
? (b.pnl_pct ?? -999) - (a.pnl_pct ?? -999)
|
||||
: (a.pnl_pct ?? 999) - (b.pnl_pct ?? 999))
|
||||
.slice(0, 5)
|
||||
|
||||
return (
|
||||
<Link to={linkTo} className="card block hover:border-slate-600/60 transition-all cursor-pointer">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="section-title mb-0 text-[10px]">{title}</span>
|
||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||
</div>
|
||||
{sorted.length > 0 ? (
|
||||
<div className="space-y-1.5 mt-1">
|
||||
{sorted.map((t: any, i: number) => {
|
||||
const pnl: number | null = t.pnl_pct ?? null
|
||||
const isBear = t.strategy?.toLowerCase().includes('put') || t.strategy?.toLowerCase().includes('bear')
|
||||
const entryDate = t.entry_date ? t.entry_date.slice(0, 10) : null
|
||||
return (
|
||||
<div key={i} className="flex items-center gap-1.5 text-[10px]">
|
||||
<span className="text-[9px] text-slate-700 font-mono w-3 shrink-0">{i + 1}</span>
|
||||
<span className="shrink-0">{isBear ? '🐻' : '🐂'}</span>
|
||||
<span className="font-mono text-slate-200 font-bold shrink-0">{t.underlying ?? '—'}</span>
|
||||
<span className="text-slate-500 truncate flex-1 text-[9px]">{t.strategy ?? ''}</span>
|
||||
{entryDate && <span className="text-[8px] text-slate-700 shrink-0 font-mono">{entryDate}</span>}
|
||||
{pnl !== null ? (
|
||||
<span className={clsx('font-mono font-bold shrink-0', pnl >= 0 ? 'text-emerald-400' : 'text-red-400')}>
|
||||
{pnl >= 0 ? '+' : ''}{pnl.toFixed(1)}%
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-700 shrink-0 text-[9px]">—</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[10px] text-slate-600 mt-1">No trades logged</div>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export default function Sidebar() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="w-5 h-5 text-blue-400" />
|
||||
<div>
|
||||
<div className="text-sm font-bold text-white tracking-wide">GeoOptions</div>
|
||||
<div className="text-sm font-bold text-white tracking-wide">OpenFin</div>
|
||||
<div className="text-xs text-slate-500">Intelligence v2.0</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,7 +115,7 @@ export default function Sidebar() {
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-3 border-t border-slate-700/40 text-xs text-slate-600">
|
||||
<div>© 2026 GeoOptions</div>
|
||||
<div>© 2026 OpenFin</div>
|
||||
<div className="text-slate-700 mt-0.5">Local · IBKR ready</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
5
frontend/src/constants/assetColors.ts
Normal file
5
frontend/src/constants/assetColors.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const ASSET_CLASS_COLORS: Record<string, string> = {
|
||||
energy: '#f97316', metals: '#eab308', agriculture: '#84cc16',
|
||||
equities: '#22c55e', indices: '#3b82f6', forex: '#8b5cf6', rates: '#06b6d4',
|
||||
unknown: '#64748b',
|
||||
}
|
||||
@@ -96,6 +96,68 @@ export const useCalendar = () =>
|
||||
queryFn: () => api.get('/geo/calendar').then(r => r.data),
|
||||
})
|
||||
|
||||
// Real Forex Factory–backed calendar (used by CalendarPage.tsx and InstrumentModels)
|
||||
export interface FFEvent {
|
||||
event_date: string; event_time: string | null; currency: string
|
||||
impact: 'high' | 'medium' | 'low'; event_name: string
|
||||
actual_value: number | null; forecast_value: number | null; previous_value: number | null
|
||||
}
|
||||
export const useEcoCalendar = (params: { period?: string; limit?: number } = {}) =>
|
||||
useQuery<{ events: FFEvent[] }>({
|
||||
queryKey: ['eco-calendar', params],
|
||||
queryFn: () => api.get('/eco/calendar', { params: { period: 'recent', limit: 30, ...params } }).then(r => r.data),
|
||||
staleTime: 5 * 60_000,
|
||||
})
|
||||
|
||||
// ── Instruments Watchlist (Dashboard "radar" card) ────────────────────────────
|
||||
export interface WatchlistItem { ticker: string; name: string; asset_class: string; sort_order: number; added_at: string }
|
||||
export interface WatchlistQuoteItem extends WatchlistItem { price: number | null; change_pct: number | null }
|
||||
|
||||
export const useInstrumentsWatchlist = () =>
|
||||
useQuery<WatchlistItem[]>({
|
||||
queryKey: ['instruments-watchlist'],
|
||||
queryFn: () => api.get('/watchlist/').then(r => r.data),
|
||||
staleTime: 60_000,
|
||||
})
|
||||
|
||||
export const useInstrumentsWatchlistQuotes = () =>
|
||||
useQuery<{ items: WatchlistQuoteItem[] }>({
|
||||
queryKey: ['instruments-watchlist-quotes'],
|
||||
queryFn: () => api.get('/watchlist/quotes').then(r => r.data),
|
||||
refetchInterval: 60_000,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
|
||||
export const useAddWatchlistInstrument = () => {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (ticker: string) => api.post(`/watchlist/${encodeURIComponent(ticker)}`).then(r => r.data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['instruments-watchlist'] })
|
||||
qc.invalidateQueries({ queryKey: ['instruments-watchlist-quotes'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useRemoveWatchlistInstrument = () => {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (ticker: string) => api.delete(`/watchlist/${encodeURIComponent(ticker)}`).then(r => r.data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['instruments-watchlist'] })
|
||||
qc.invalidateQueries({ queryKey: ['instruments-watchlist-quotes'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Latest cycle report — shares the 'cycle-report-latest' queryKey with RapportIA.tsx
|
||||
export const useLatestCycleReport = () =>
|
||||
useQuery({
|
||||
queryKey: ['cycle-report-latest'],
|
||||
queryFn: () => api.get('/reports/cycle/latest').then(r => r.data),
|
||||
staleTime: 60_000,
|
||||
})
|
||||
|
||||
// ── Options ───────────────────────────────────────────────────────────────────
|
||||
export const useIvSurface = (symbol: string) =>
|
||||
useQuery({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useSources, useUpdateSources, useUpdateApiKeys, useConfig, useAiStatus, useAnalysisConfig, useSaveAnalysisConfig, useCycleStatus, useUpdateCycleConfig, useTriggerCycle, useRiskProfiles, useUpsertProfile, useDeleteProfile, useExitDefaults, useSaveExitDefaults, useOptionsGate, useSaveOptionsGate, useTechIndicatorsConfig, useSaveTechIndicatorsConfig } from '../hooks/useApi'
|
||||
import { Settings, Key, Globe, CheckCircle, XCircle, AlertCircle, Save, Eye, EyeOff, Brain, SlidersHorizontal, RefreshCw, Zap, Plus, Trash2, Pencil, X, Lock, Gauge, DollarSign, TrendingUp, ShieldAlert, DatabaseBackup } from 'lucide-react'
|
||||
import { useSources, useUpdateSources, useUpdateApiKeys, useConfig, useAiStatus, useAnalysisConfig, useSaveAnalysisConfig, useCycleStatus, useUpdateCycleConfig, useTriggerCycle, useRiskProfiles, useUpsertProfile, useDeleteProfile, useExitDefaults, useSaveExitDefaults, useOptionsGate, useSaveOptionsGate, useTechIndicatorsConfig, useSaveTechIndicatorsConfig, useInstrumentsWatchlist, useAddWatchlistInstrument, useRemoveWatchlistInstrument, validateTicker } from '../hooks/useApi'
|
||||
import { Settings, Key, Globe, CheckCircle, XCircle, AlertCircle, Save, Eye, EyeOff, Brain, SlidersHorizontal, RefreshCw, Zap, Plus, Trash2, Pencil, X, Lock, Gauge, DollarSign, TrendingUp, ShieldAlert, DatabaseBackup, Radar } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const API = ''
|
||||
@@ -330,6 +330,85 @@ function RiskProfilesCard() {
|
||||
)
|
||||
}
|
||||
|
||||
function WatchlistCard() {
|
||||
const { data: items, isLoading } = useInstrumentsWatchlist()
|
||||
const { mutateAsync: addTicker, isPending: adding } = useAddWatchlistInstrument()
|
||||
const { mutate: removeTicker } = useRemoveWatchlistInstrument()
|
||||
const [input, setInput] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const handleAdd = async () => {
|
||||
const sym = input.trim().toUpperCase()
|
||||
if (!sym) return
|
||||
setError('')
|
||||
const check = await validateTicker(sym)
|
||||
if (!check.valid) {
|
||||
setError(check.reason ?? 'Ticker not found')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await addTicker(sym)
|
||||
setInput('')
|
||||
} catch (e: unknown) {
|
||||
const msg = (e as any)?.response?.data?.detail ?? (e as { message?: string })?.message ?? 'Could not add ticker'
|
||||
setError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
const list: any[] = items ?? []
|
||||
|
||||
return (
|
||||
<div className="card bg-dark-700/20 mb-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-slate-300 flex items-center gap-1.5">
|
||||
<Radar className="w-4 h-4 text-blue-400" /> Instruments Watchlist
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-600 mt-0.5">
|
||||
Instruments surveillés pour le radar du Dashboard et les highlights Options Lab — liste indépendante des autres watchlists (Markets, Options Lab, InstrumentModels).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<input
|
||||
value={input}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && handleAdd()}
|
||||
placeholder="Ticker (ex. AAPL, EURUSD=X, ^GSPC)"
|
||||
className="bg-dark-800 border border-slate-700/40 rounded px-3 py-1.5 text-xs text-white w-64 focus:outline-none focus:border-blue-500/50"
|
||||
/>
|
||||
<button onClick={handleAdd} disabled={adding || !input.trim()}
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded text-xs bg-blue-600 text-white hover:bg-blue-500 disabled:opacity-40 transition-colors">
|
||||
<Plus className="w-3.5 h-3.5" /> Add
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className="text-[10px] text-red-400 mb-3">{error}</div>}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="text-xs text-slate-600">Loading…</div>
|
||||
) : list.length === 0 ? (
|
||||
<div className="text-xs text-slate-600">No instrument watched yet.</div>
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
{list.map(item => (
|
||||
<div key={item.ticker} className="flex items-center justify-between bg-dark-800/60 rounded px-3 py-1.5">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className="text-xs font-mono font-bold text-white">{item.ticker}</span>
|
||||
<span className="text-[10px] text-slate-500 truncate">{item.name}</span>
|
||||
<span className="text-[9px] text-slate-700 bg-dark-700 px-1.5 py-0.5 rounded capitalize">{item.asset_class}</span>
|
||||
</div>
|
||||
<button onClick={() => removeTicker(item.ticker)} className="text-slate-600 hover:text-red-400 transition-colors">
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Config() {
|
||||
const { data: sources, isLoading } = useSources()
|
||||
const { data: config } = useConfig()
|
||||
@@ -354,7 +433,7 @@ export default function Config() {
|
||||
const [calendarRefreshH, setCalendarRefreshH] = useState(6)
|
||||
const [showKeys, setShowKeys] = useState(false)
|
||||
const [savedMsg, setSavedMsg] = useState('')
|
||||
const [configTab, setConfigTab] = useState<'ia' | 'cycle' | 'sources' | 'options' | 'profiles' | 'data'>('ia')
|
||||
const [configTab, setConfigTab] = useState<'ia' | 'cycle' | 'sources' | 'options' | 'profiles' | 'watchlist' | 'data'>('ia')
|
||||
|
||||
// IV gate local state
|
||||
const [ivGateEnabled, setIvGateEnabled] = useState(true)
|
||||
@@ -525,6 +604,7 @@ export default function Config() {
|
||||
{ key: 'sources', label: 'Sources', icon: Globe },
|
||||
{ key: 'options', label: 'Options — Settings', icon: TrendingUp },
|
||||
{ key: 'profiles', label: 'Risk Profiles', icon: SlidersHorizontal },
|
||||
{ key: 'watchlist', label: 'Watchlist', icon: Radar },
|
||||
{ key: 'data', label: 'Data Management', icon: DatabaseBackup },
|
||||
] as const
|
||||
|
||||
@@ -1542,6 +1622,9 @@ export default function Config() {
|
||||
{/* ── Profils de risque ── */}
|
||||
{configTab === 'profiles' && <RiskProfilesCard />}
|
||||
|
||||
{/* ── Watchlist ── */}
|
||||
{configTab === 'watchlist' && <WatchlistCard />}
|
||||
|
||||
{/* ── Data Management ── */}
|
||||
{configTab === 'data' && <DataManagementCard />}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
|
||||
import {
|
||||
RefreshCw, Edit3, X, Trash2, ChevronDown, ChevronUp,
|
||||
TrendingUp, TrendingDown, Minus, LineChart, Table2, Network, Activity,
|
||||
Plus, Zap, Settings2, Check,
|
||||
Plus, Zap, Settings2, Check, Eye, EyeOff,
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import axios from 'axios'
|
||||
@@ -119,6 +119,8 @@ interface VirtualEventForm {
|
||||
actual_value?: string | null
|
||||
forecast_value?: string | null
|
||||
event_name?: string
|
||||
routed_node?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface EventDetail {
|
||||
@@ -149,6 +151,7 @@ interface CalendarEvent {
|
||||
forecast_value: string | null
|
||||
previous_value: string | null
|
||||
category: string
|
||||
routed_node: string
|
||||
is_future: boolean
|
||||
estimated_pips: number
|
||||
has_surprise: boolean
|
||||
@@ -966,6 +969,7 @@ function newVirtualEvent(): VirtualEventForm {
|
||||
rise_days: 1,
|
||||
plateau_days: 0,
|
||||
decay_type: 'exp',
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1004,6 +1008,7 @@ function TimelineView({ instrument }: { instrument: string }) {
|
||||
[virtuals])
|
||||
|
||||
const activeVirtuals = useMemo(() => virtuals.filter(ve => {
|
||||
if (ve.disabled) return false
|
||||
if (filterImpact.length > 0 && ve.impact && !filterImpact.includes(ve.impact)) return false
|
||||
if (filterCurrency.length > 0 && ve.currency && !filterCurrency.includes(ve.currency)) return false
|
||||
if (filterSurprise && !ve.has_surprise) return false
|
||||
@@ -1494,6 +1499,8 @@ function TimelineView({ instrument }: { instrument: string }) {
|
||||
actual_value: ev.actual_value,
|
||||
forecast_value: ev.forecast_value,
|
||||
event_name: ev.event_name,
|
||||
routed_node: ev.routed_node,
|
||||
disabled: false,
|
||||
}))
|
||||
if (imported.length === 0) {
|
||||
setCalMsg('Aucun event FF calendrier (high/medium) pour cette période')
|
||||
@@ -1643,6 +1650,14 @@ function TimelineView({ instrument }: { instrument: string }) {
|
||||
<div key={ve.id}
|
||||
className={clsx('flex items-center gap-1.5 rounded px-2 py-1 text-xs transition-all',
|
||||
isActive ? 'bg-dark-800/60' : 'bg-dark-800/20 opacity-40')}>
|
||||
{/* Disable toggle */}
|
||||
<button
|
||||
onClick={() => setVirtuals(v => v.map((x, i) => i === idx ? {...x, disabled: !x.disabled} : x))}
|
||||
className={clsx('flex-shrink-0 transition-colors',
|
||||
ve.disabled ? 'text-slate-600 hover:text-slate-400' : 'text-violet-400 hover:text-violet-300')}
|
||||
title={ve.disabled ? 'Réactiver' : 'Désactiver'}>
|
||||
{ve.disabled ? <EyeOff className="w-3 h-3"/> : <Eye className="w-3 h-3"/>}
|
||||
</button>
|
||||
{/* Impact dot */}
|
||||
<span className={clsx('w-1.5 h-1.5 rounded-full flex-shrink-0', impDot)}/>
|
||||
{/* Currency */}
|
||||
@@ -1651,6 +1666,16 @@ function TimelineView({ instrument }: { instrument: string }) {
|
||||
)}
|
||||
{/* Date */}
|
||||
<span className="text-slate-500 font-mono w-10 flex-shrink-0">{dispDate}</span>
|
||||
{/* Node routing badge */}
|
||||
{ve.routed_node && (
|
||||
<span className={clsx('flex-shrink-0 px-1 rounded border font-mono',
|
||||
ve.routed_node === 'direct'
|
||||
? 'text-slate-500 border-slate-700/40 text-[9px]'
|
||||
: 'text-violet-400 border-violet-700/40 bg-violet-900/20 text-[9px]'
|
||||
)} title={`Route → nœud : ${ve.routed_node}`}>
|
||||
{ve.routed_node === 'direct' ? '⇢' : ve.routed_node.slice(0, 10)}
|
||||
</span>
|
||||
)}
|
||||
{/* Event name */}
|
||||
<span className="flex-1 text-slate-300 truncate" title={ve.event_name || ve.label}>
|
||||
{(ve.event_name || ve.label).replace(/^\[.*?\]\s*/, '')}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BookOpen, TrendingUp, TrendingDown, Activity, AlertTriangle, RefreshCw,
|
||||
import { TradeIdeasTab, CATEGORIES } from '../components/TradeIdeas'
|
||||
import clsx from 'clsx'
|
||||
import { useJournalSummary, useMacroHistory, useGeoHistory, useTradeMtm, useClosedTrades, useCloseTrade, useUpdateExitParams, useExitDefaults, useCycleHistory, useCycleStatus, useTriggerCycle, useTradePostmortem, useAnalyzePostmortem, useIvForTrade, useKellySizing, useSimPortfolioRisk, useSkippedTrades, useDeleteTrade, api } from '../hooks/useApi'
|
||||
import { ASSET_CLASS_COLORS } from '../constants/assetColors'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { format } from 'date-fns'
|
||||
import { fr } from 'date-fns/locale'
|
||||
@@ -1453,12 +1454,6 @@ function CyclesSection() {
|
||||
|
||||
// ── Page principale ────────────────────────────────────────────────────────────
|
||||
|
||||
const ASSET_CLASS_COLORS: Record<string, string> = {
|
||||
energy: '#f97316', metals: '#eab308', agriculture: '#84cc16',
|
||||
equities: '#22c55e', indices: '#3b82f6', forex: '#8b5cf6', rates: '#06b6d4',
|
||||
unknown: '#64748b',
|
||||
}
|
||||
|
||||
function SimPortfolioRiskPanel() {
|
||||
const { data, isLoading, refetch } = useSimPortfolioRisk()
|
||||
const risk = data as any
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function MacroRegime() {
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const raw = localStorage.getItem('geo-options:macro-narration')
|
||||
const raw = localStorage.getItem('openfin:macro-narration')
|
||||
if (raw) setSavedNarration(JSON.parse(raw))
|
||||
} catch {}
|
||||
}, [])
|
||||
@@ -151,7 +151,7 @@ export default function MacroRegime() {
|
||||
const text = res.data.narration ?? res.data.text ?? JSON.stringify(res.data)
|
||||
const entry = { text, at: new Date().toISOString() }
|
||||
setSavedNarration(entry)
|
||||
localStorage.setItem('geo-options:macro-narration', JSON.stringify(entry))
|
||||
localStorage.setItem('openfin:macro-narration', JSON.stringify(entry))
|
||||
} catch {
|
||||
const entry = { text: 'Error during generation — please check your OpenAI key.', at: new Date().toISOString() }
|
||||
setSavedNarration(entry)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { useRiskDashboard, usePatternCorrelations, usePnlTimeline, useRiskExposure, useSimPortfolioRisk } from '../hooks/useApi'
|
||||
import { ASSET_CLASS_COLORS } from '../constants/assetColors'
|
||||
import clsx from 'clsx'
|
||||
import { ShieldAlert, TrendingUp, GitBranch, AlertTriangle, CheckCircle, Activity, Brain, RefreshCw, PieChart } from 'lucide-react'
|
||||
|
||||
@@ -131,11 +132,6 @@ function RecommendationCard({ rec }: { rec: any }) {
|
||||
}
|
||||
|
||||
// ── Main page ────────────────────────────────────────────────────────────────
|
||||
const ASSET_CLASS_COLORS: Record<string, string> = {
|
||||
energy: '#f97316', metals: '#eab308', agriculture: '#84cc16',
|
||||
equities: '#22c55e', indices: '#3b82f6', forex: '#8b5cf6', rates: '#06b6d4',
|
||||
unknown: '#64748b',
|
||||
}
|
||||
|
||||
function SimRiskPanel() {
|
||||
const { data, isLoading, refetch } = useSimPortfolioRisk()
|
||||
|
||||
10
start.ps1
10
start.ps1
@@ -1,11 +1,11 @@
|
||||
# GeoOptions - Start script
|
||||
# OpenFin - Start script
|
||||
# Usage: powershell -ExecutionPolicy Bypass -File c:\DataS\OpenFin\start.ps1
|
||||
|
||||
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " ============================================" -ForegroundColor Cyan
|
||||
Write-Host " GeoOptions Intelligence Cockpit v2.0" -ForegroundColor Cyan
|
||||
Write-Host " OpenFin Intelligence Cockpit v2.0" -ForegroundColor Cyan
|
||||
Write-Host " ============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
@@ -31,7 +31,7 @@ $frontendBat = "$Root\_start_frontend.bat"
|
||||
|
||||
$backendLines = @(
|
||||
"@echo off",
|
||||
"title GeoOptions Backend",
|
||||
"title OpenFin Backend",
|
||||
"cd /d `"$Root\backend`"",
|
||||
"if not exist venv python -m venv venv",
|
||||
"call venv\Scripts\activate.bat",
|
||||
@@ -42,7 +42,7 @@ $backendLines | Set-Content -Path $backendBat -Encoding ASCII
|
||||
|
||||
$frontendLines = @(
|
||||
"@echo off",
|
||||
"title GeoOptions Frontend",
|
||||
"title OpenFin Frontend",
|
||||
"cd /d `"$Root\frontend`"",
|
||||
"if not exist node_modules npm install",
|
||||
"npm run dev"
|
||||
@@ -74,7 +74,7 @@ if ($ready) {
|
||||
Write-Host " Backend OK" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ATTENTION : backend non repond apres $timeout`s" -ForegroundColor Red
|
||||
Write-Host " Verifie la fenetre GeoOptions Backend pour voir l'erreur." -ForegroundColor Red
|
||||
Write-Host " Verifie la fenetre OpenFin Backend pour voir l'erreur." -ForegroundColor Red
|
||||
}
|
||||
|
||||
# 5. Start frontend
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@echo off
|
||||
title GeoOptions Intelligence - Launcher
|
||||
title OpenFin Intelligence - Launcher
|
||||
echo.
|
||||
echo ============================================
|
||||
echo GeoOptions Intelligence Cockpit v2.0
|
||||
echo OpenFin Intelligence Cockpit v2.0
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
@@ -17,12 +17,12 @@ echo Ports 8000 et 5173 liberes.
|
||||
echo.
|
||||
|
||||
echo Demarrage du backend (port 8000)...
|
||||
start "GeoOptions Backend" cmd /k "cd /d %~dp0backend && (if not exist venv python -m venv venv) && call venv\Scripts\activate.bat && pip install -r requirements.txt -q && python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload"
|
||||
start "OpenFin Backend" cmd /k "cd /d %~dp0backend && (if not exist venv python -m venv venv) && call venv\Scripts\activate.bat && pip install -r requirements.txt -q && python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload"
|
||||
|
||||
timeout /t 5 /nobreak >nul
|
||||
|
||||
echo Demarrage du frontend (port 5173)...
|
||||
start "GeoOptions Frontend" cmd /k "cd /d %~dp0frontend && (if not exist node_modules npm install) && npm run dev"
|
||||
start "OpenFin Frontend" cmd /k "cd /d %~dp0frontend && (if not exist node_modules npm install) && npm run dev"
|
||||
|
||||
timeout /t 5 /nobreak >nul
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@echo off
|
||||
title GeoOptions - Backend API
|
||||
title OpenFin - Backend API
|
||||
cd /d "%~dp0backend"
|
||||
|
||||
echo ============================================
|
||||
echo GeoOptions Intelligence - Backend FastAPI
|
||||
echo OpenFin Intelligence - Backend FastAPI
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@echo off
|
||||
title GeoOptions - Frontend React
|
||||
title OpenFin - Frontend React
|
||||
cd /d "%~dp0frontend"
|
||||
|
||||
echo ============================================
|
||||
echo GeoOptions Intelligence - Frontend React
|
||||
echo OpenFin Intelligence - Frontend React
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user