feat: new cockpit

This commit is contained in:
OpenSquared
2026-07-14 11:21:43 +02:00
parent 9778c74ae3
commit ad07c8d886
32 changed files with 871 additions and 551 deletions

View File

@@ -1,4 +1,4 @@
# GeoOptions Intelligence — Guide d'utilisation
# OpenFin Intelligence — Guide d'utilisation
## Démarrage

View File

@@ -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 &amp; Logique des Prompts IA</div>
<div class="meta">
Version 2.0 &nbsp;·&nbsp; Juin 2026 &nbsp;·&nbsp; 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 &nbsp;·&nbsp; Document généré le 17 juin 2026 &nbsp;·&nbsp; FastAPI + React + SQLite + GPT-4o
OpenFin Intelligence v2.0 &nbsp;·&nbsp; Document généré le 17 juin 2026 &nbsp;·&nbsp; FastAPI + React + SQLite + GPT-4o
</p>

View File

@@ -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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &gt; 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 &gt; 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). &lt;0.25 = bien diversifié, &gt;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 &gt; 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 : &gt;0.75 = doublon.</td></tr>
<tr><td><strong>Jaccard Similarity</strong></td><td>|A∩B|/|AB|. Mesure les mots communs entre deux textes. Simple mais aveugle aux synonymes et au sens sémantique. Seuil GeoOptions : &gt;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 : &gt;0.75 = doublon.</td></tr>
<tr><td><strong>Jaccard Similarity</strong></td><td>|A∩B|/|AB|. Mesure les mots communs entre deux textes. Simple mais aveugle aux synonymes et au sens sémantique. Seuil OpenFin : &gt;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 15) 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 &gt;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 &gt; 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. &gt;1.5 = fiable, &lt;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 0100 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, 0100. Calculé par moyenne pondérée des scores régionaux. &gt;60 = élevé, &gt;75 = critique. Impacte directement les suggestions IA.</td></tr>
@@ -2060,8 +2060,8 @@ Seuil : confidence &gt; 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 : &gt; 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 : (1w)×ai_estimate + w×observed_avg_win_pct. Utilisée comme G dans l'EV dès que la crédibilité w &gt; 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>

View File

@@ -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 &amp; Logique des Prompts IA</div>
<div class="meta">
Version 2.0 &nbsp;·&nbsp; Juin 2026 &nbsp;·&nbsp; 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 &nbsp;·&nbsp; Document généré le 17 juin 2026 &nbsp;·&nbsp; FastAPI + React + SQLite + GPT-4o
OpenFin Intelligence v2.0 &nbsp;·&nbsp; Document généré le 17 juin 2026 &nbsp;·&nbsp; FastAPI + React + SQLite + GPT-4o
</p>
</body>

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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"

View File

@@ -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,
})

View 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}

View File

@@ -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")

View File

@@ -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

View File

@@ -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(

View File

@@ -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

View File

@@ -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>

View File

@@ -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",

View File

@@ -1,5 +1,5 @@
{
"name": "geooptions-cockpit",
"name": "openfin-cockpit",
"private": true,
"version": "1.0.0",
"type": "module",

View 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>
)
}

View File

@@ -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>

View 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',
}

View File

@@ -96,6 +96,68 @@ export const useCalendar = () =>
queryFn: () => api.get('/geo/calendar').then(r => r.data),
})
// Real Forex Factorybacked 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({

View File

@@ -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

View File

@@ -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*/, '')}

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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.