Spaces:
Sleeping
Documentation Technique — Plateforme de Prédictions Boursières
Projet 4A — ENSIM (École Nationale Supérieure d'Ingénieurs du Mans)
Application web complète de prédiction boursière par intelligence artificielle, combinant trois modèles de Machine Learning, une authentification biométrique par reconnaissance faciale, un assistant IA conversationnel et un tableau de bord de suivi d'investissements en temps réel.
Table des matières
- Vue d'ensemble du projet
- Stack technologique
- Architecture générale
- Structure des fichiers
- Modèles d'Intelligence Artificielle
- Authentification biométrique — Reconnaissance faciale
- Assistant IA conversationnel (Chatbot)
- Pages et fonctionnalités détaillées
- Base de données — Schéma complet
- APIs REST
- Pipeline de données et sources
- Installation et démarrage
1. Vue d'ensemble du projet
Cette plateforme est une application web de prédiction boursière développée en Python. Elle intègre trois approches complémentaires de Machine Learning pour prédire les mouvements de prix de 8 actifs financiers (actions technologiques + Bitcoin).
Actifs financiers supportés
| Symbole | Entreprise / Actif | Classe d'actif |
|---|---|---|
| AAPL | Apple Inc. | Action |
| MSFT | Microsoft Corporation | Action |
| TSLA | Tesla Inc. | Action |
| NVDA | NVIDIA Corporation | Action |
| GOOGL | Alphabet Inc. | Action |
| AMZN | Amazon.com Inc. | Action |
| META | Meta Platforms Inc. | Action |
| BTC-USD | Bitcoin | Crypto-monnaie |
Fonctionnalités principales
- Prédictions IA en temps réel : signaux ACHETER / VENDRE / SURVEILLER sur les 8 actifs
- Trois modèles distincts : Sentiment (NLP), BiLSTM (deep learning sur prix), Transformer Hybride (prix + actualités)
- Backtesting sur 180 jours : simulation de portefeuille en suivant les conseils de chaque modèle
- Comparaison inter-modèles : courbes de performance, taux de réussite, PnL comparé
- Suivi d'investissements personnel : enregistrement, historique, statistiques
- Assistant IA conversationnel : LLaMA 3.3 70B via Groq, streaming temps réel, enregistrement d'investissements
- Authentification biométrique : connexion par reconnaissance faciale (OpenCV)
- Panneau administrateur : gestion utilisateurs, statistiques plateforme, modération
2. Stack technologique
Framework et interface
| Technologie | Version | Utilisation |
|---|---|---|
| Python | 3.11 | Langage principal |
| Dash (Plotly) | 2.x | Framework web Python (SPA réactive) |
| Flask | 2.x | Serveur HTTP sous-jacent, APIs REST |
| Plotly | 5.x | Graphiques interactifs (courbes, camemberts) |
| Lightweight-charts | 4.x | Graphiques en chandeliers japonais (TradingView) |
| CSS3 custom | — | Thème glassmorphism néon (fonds translucides, lueurs cyan) |
| Font Awesome | 6.5 | Icônes vectorielles |
Machine Learning — Prédiction boursière
| Technologie | Utilisation |
|---|---|
| TensorFlow / Keras | Modèle BiLSTM (14 features, séquence 30 jours) |
| PyTorch | Modèle Transformer hybride (44 features, séquence 60 jours) |
| scikit-learn | RobustScaler, StandardScaler, LabelEncoder |
| NumPy / Pandas | Feature engineering, séries temporelles |
| joblib | Sauvegarde/chargement des scalers |
Intelligence Artificielle — Chatbot
| Technologie | Utilisation |
|---|---|
| Groq API | Inférence LLM ultra-rapide (~200 tokens/s) |
| LLaMA 3.3 70B | Modèle de langage conversationnel (Meta AI) |
| Server-Sent Events (SSE) | Streaming des réponses mot par mot |
Biométrie — Reconnaissance faciale
| Technologie | Utilisation |
|---|---|
| OpenCV | Détection faciale (Haar Cascade frontal) |
| cv2.TM_CCOEFF_NORMED | Template matching (cross-corrélation) |
| Filtres Sobel | Comparaison des contours/structure |
| JavaScript (camera.js) | Capture webcam, encodage base64 |
Données et base de données
| Technologie | Utilisation |
|---|---|
| SQLite | Base de données locale (users.db) |
| bcrypt | Hachage sécurisé des mots de passe |
| yfinance | Cours boursiers temps réel et historiques (OHLCV) |
| Alpha Vantage API | Source de secours pour données intraday |
| GitHub CSV | Articles de presse financière (sentiment, mis à jour quotidiennement) |
Déploiement
| Technologie | Utilisation |
|---|---|
| Docker | Conteneurisation (Python 3.11, port 7860) |
| python-dotenv | Gestion des variables d'environnement |
3. Architecture générale
┌─────────────────────────────────────────────────────────────────────┐
│ NAVIGATEUR (Client) │
│ │
│ Dash React SPA · Plotly · Lightweight-charts │
│ chatbot.js (streaming, modal, drag) · camera.js (webcam) │
└──────────────────────────────┬──────────────────────────────────────┘
│ HTTP / SSE
┌──────────────────────────────▼──────────────────────────────────────┐
│ SERVEUR FLASK (app.py) │
│ │
│ ┌──────────────┐ ┌────────────────────┐ ┌────────────────────┐ │
│ │ Dash Pages │ │ APIs Flask │ │ Assets statiques │ │
│ │ (Routing) │ │ /api/chat (SSE) │ │ chatbot.js │ │
│ │ / │ │ /api/chat-invest │ │ chatbot.css │ │
│ │ /mon-suivi │ │ /api/ohlcv/<sym> │ │ camera.js │ │
│ │ /analysis │ │ /api/backtest- │ │ style.css │ │
│ │ /actions │ │ compare │ │ marche_chart.html │ │
│ │ /profil │ │ /api/face-login │ └────────────────────┘ │
│ │ /admin │ └────────────────────┘ │
│ └──────────────┘ │
└────────┬────────────────┬───────────────┬────────────────┬──────────┘
│ │ │ │
┌────────▼──────┐ ┌───────▼────────┐ ┌───▼──────────┐ ┌──▼──────────┐
│ LSTM (Keras) │ │ Transformer │ │ Groq API │ │ SQLite DB │
│ │ │ (PyTorch) │ │ LLaMA 3.3 │ │ users.db │
│ 14 features │ │ 44 features │ │ 70B │ │ 4 tables │
│ 30 jours │ │ 60 jours │ │ Streaming │ │ │
└───────────────┘ └────────────────┘ └──────────────┘ └─────────────┘
│ │
┌────────▼────────────────▼──────────────────────────────────────────┐
│ SOURCES DE DONNÉES │
│ │
│ yfinance (cours temps réel OHLCV) │
│ Alpha Vantage (backup intraday) │
│ GitHub CSV (articles presse financière + scores sentiment) │
└────────────────────────────────────────────────────────────────────┘
4. Structure des fichiers
Projet4A_PredictionsBoursieres-main/
│
├── Interface Graphique/ # Application web principale
│ ├── app.py # Point d'entrée : Dash + toutes les APIs Flask
│ ├── .env # Variables d'environnement (GROQ_API_KEY, etc.)
│ │
│ ├── pages/ # Pages Dash (routing automatique par nom de fichier)
│ │ ├── home.py # Accueil : prédictions temps réel
│ │ ├── mon_suivi.py # Suivi des investissements + backtest 180j
│ │ ├── actions_page.py # Graphiques chandeliers (Lightweight-charts)
│ │ ├── analyse_page.py # Dashboard analyse de sentiment
│ │ ├── profil.py # Profil utilisateur + KPIs trading
│ │ ├── login.py # Connexion email/mot de passe
│ │ ├── signup.py # Inscription + capture visage optionnelle
│ │ ├── face_login.py # Connexion par reconnaissance faciale
│ │ ├── temoignages.py # Témoignages publics + stats plateforme
│ │ └── admin/ # Panneau administrateur (accès restreint)
│ │ ├── dashboard.py # KPIs + navigation admin
│ │ ├── users.py # Gestion utilisateurs (CRUD)
│ │ ├── stats.py # Statistiques plateforme
│ │ ├── logs.py # Historique des connexions
│ │ └── testimonials.py # File de modération témoignages
│ │
│ ├── services/ # Couche logique métier et IA
│ │ ├── database.py # Schéma SQLite + init + chemin absolu DB
│ │ ├── auth_service.py # Authentification, création comptes (bcrypt)
│ │ ├── tracking_service.py # Enregistrement trades + statistiques
│ │ ├── admin_service.py # Requêtes admin (CRUD, stats, logs)
│ │ ├── lstm_service.py # Service prédiction BiLSTM (Keras)
│ │ ├── transformer_service.py # Service prédiction Transformer (PyTorch)
│ │ ├── chat_service.py # Service chatbot Groq LLaMA 3.3
│ │ ├── face_auth.py # Capture et stockage des visages
│ │ ├── face_comparator.py # Algorithme de comparaison biométrique
│ │ └── market_data.py # Récupération données marché (yfinance + AV)
│ │
│ └── assets/ # Fichiers statiques (CSS, JS, images)
│ ├── style.css # Styles principaux (thème glassmorphism)
│ ├── chatbot.css # Styles chatbot + modal comparaison
│ ├── chatbot.js # Logique chatbot (v6 : streaming, drag, modal)
│ ├── camera.js # Gestion webcam (reconnaissance faciale)
│ ├── tooltip.js # Tooltips personnalisés
│ ├── suivi.css, analyse.css, etc. # Styles par page
│ ├── marche_chart.html # Interface Lightweight-charts embarquée
│ └── logo.png
│
├── Modèle IA/ # Fichiers modèle BiLSTM
│ ├── global_return_lstm.keras # Modèle Keras sauvegardé (BiLSTM)
│ ├── return_scaler.save # RobustScaler (joblib)
│ └── symbol_encoder.save # LabelEncoder (8 symboles)
│
├── Model AI Transformer/ # Fichiers modèle Transformer
│ ├── transformer_model.pt # Poids PyTorch (HybridTransformerBounded)
│ ├── feature_scaler.save # StandardScaler 44 features (joblib)
│ ├── symbol_encoder.save # LabelEncoder
│ ├── return_scaler.save # Scaler sortie (log-rendement)
│ ├── config.json # Hyperparamètres (d_model, nhead, layers, etc.)
│ └── feature_cols.json # Liste des 44 noms de features
│
├── Data/ # Données d'entraînement
│ ├── ALL_CLEANED.csv # Dataset complet nettoyé
│ ├── ALL_FEATURES.csv # Matrice de features construite
│ └── data_report.csv # Rapport qualité des données
│
├── face_data/ # Images faciales encodées (persistance)
├── temp_faces/ # Images temporaires (auth en cours)
├── users.db # Base de données SQLite (racine projet)
├── Dockerfile # Conteneurisation Docker (Python 3.11)
├── requirements.txt # Dépendances Python
└── DOCUMENTATION.md # Ce fichier
5. Modèles d'Intelligence Artificielle
La plateforme propose trois approches complémentaires de prédiction. Chacune exploite des sources de données et une architecture différente, permettant à l'utilisateur de croiser les signaux.
5.1 Modèle Sentiment — Analyse des actualités
Type : Modèle à base de règles (rule-based NLP)
Service : services/lstm_service.py (fonction _compute_signal)
Source de données : Articles de presse financière (CSV GitHub, mis à jour quotidiennement)
Description
Ce modèle ne repose pas sur un réseau de neurones mais sur l'agrégation de scores de sentiment extraits automatiquement d'articles de presse financière. Le score de chaque article (entre -1 et +1) est calculé en amont via un modèle NLP de traitement de texte financier, puis stocké dans un fichier CSV centralisé.
Pipeline de calcul du signal
1. Chargement du CSV GitHub (ALL_ARTICLES_MASTER.csv)
Colonnes : symbol, date_publication, score_sentiment, score_pondere, confiance
2. Filtrage temporel
→ Articles des 48 dernières heures (ou 15 derniers si aucun récent)
3. Calcul du score agrégé
→ Si colonne score_pondere : moyenne directe
→ Sinon : mean(score_sentiment × confiance)
→ Calcul de la confiance globale : min(|score| × (articles/10) × 100, 95)%
4. Classification
score > +0.15 → HAUSSIER (signal ACHETER)
score < -0.15 → BAISSIER (signal VENDRE)
|score| ≤ 0.15 → NEUTRE (signal SURVEILLER)
5. Validité du signal : ~48h (lié à la fraîcheur des articles)
Points forts
- Capture les chocs exogènes (annonces de résultats, scandales, régulations) non visibles dans les données de prix
- Très réactif aux événements : mise à jour dès que de nouveaux articles sont publiés
- Particulièrement efficace pour les actifs très médiatisés : Tesla, Bitcoin, NVIDIA
5.2 Modèle BiLSTM (Long Short-Term Memory)
Type : Deep Learning — Réseau de neurones récurrents bidirectionnel
Framework : TensorFlow / Keras
Fichier modèle : Modèle IA/global_return_lstm.keras
Service : services/lstm_service.py
Architecture
Le modèle est un BiLSTM (Bidirectional Long Short-Term Memory) qui lit la séquence de données à la fois dans le sens chronologique (passé → présent) et dans le sens inverse (présent → passé), capturant ainsi des dépendances dans les deux directions.
Entrée : Tenseur (batch_size, 30 jours, 14 features)
↓
[Symbol Embedding] → vecteur 8D concaténé à la séquence
↓
[BiLSTM Layer(s)]
→ Lecture avant (t=0 → t=29)
→ Lecture arrière (t=29 → t=0)
→ Sortie : représentation enrichie de la séquence
↓
[Dense Layer] → [Activation Sigmoid]
↓
Sortie : probabilité ∈ [0.0, 1.0]
→ ≥ 0.52 : signal ACHETER
→ < 0.52 : signal VENDRE
14 Features engineered
Ces 14 indicateurs techniques sont calculés à partir des données OHLCV brutes récupérées de Yahoo Finance :
| Feature | Catégorie | Calcul |
|---|---|---|
ret1 |
Rendement | (Close[t] - Close[t-1]) / Close[t-1] |
ret3 |
Rendement | Rendement cumulé sur 3 jours |
ret5 |
Rendement | Rendement cumulé sur 5 jours |
trend_s |
Tendance court terme | MA5 / MA10 - 1 |
trend_l |
Tendance long terme | MA10 / MA20 - 1 |
mom3 |
Momentum | Close[t] / Close[t-3] - 1 |
mom10 |
Momentum | Close[t] / Close[t-10] - 1 |
vol10 |
Volatilité | Écart-type des rendements sur 10 jours |
vol_ratio |
Volatilité relative | vol10 / vol30 |
rsi14 |
Oscillateur | RSI (Relative Strength Index) 14 périodes |
rsi7 |
Oscillateur | RSI 7 périodes |
atr |
Risque | Average True Range normalisé par le prix |
bb |
Bandes de Bollinger | Position dans les bandes [0, 1] |
volr |
Volume | Volume courant / MA volume 10j (clipé [0, 1]) |
Pipeline de prédiction complet
# Étape 1 : Téléchargement données (120j pour fenêtres glissantes)
hist = yf.Ticker(ticker).history(period="120d", interval="1d")
# Étape 2 : Feature engineering → DataFrame (120, 14 features)
df = _build_features(hist).dropna(subset=FEATURES).reset_index(drop=True)
# Étape 3 : Encodage du symbole
sym_id = int(encoder.transform([ticker])[0])
# Étape 4 : Extraction dernière fenêtre 30 jours → shape (30, 14)
window = df[FEATURES].values[-SEQ_LEN:] # SEQ_LEN = 30
# Étape 5 : Normalisation RobustScaler + clip [-5, 5]
window_scaled = np.clip(scaler.transform(window), -5, 5).astype(np.float32)
# Étape 6 : Inférence BiLSTM
prob = model.predict(
[window_scaled.reshape(1, 30, 14), np.array([[sym_id]])],
verbose=0
).flatten()[0]
# Étape 7 : Génération du signal et du rendement estimé
signal = "ACHETER" if prob >= 0.52 else "VENDRE"
return_pct = (prob - 0.5) * 2 * vol10_last # rendement estimé proportionnel à la volatilité
direction_prob = prob # probabilité directionnelle affichée
Backtest 180 jours
La fonction _run_backtest_lstm() simule un portefeuille sur les 180 derniers jours en mode rolling window : pour chaque jour i, elle prédit le signal à partir de la fenêtre [i-30 : i] et applique le PnL réel du lendemain.
Pour chaque jour i de bt_start à N-1 :
1. Fenêtre = données [i-30 : i] → normalisation → prédiction
2. Signal = ACHETER si prob ≥ 0.52, sinon VENDRE
3. PnL réel = rendement réel (Close[i+1] - Close[i]) / Close[i]
→ position longue si ACHETER : PnL = +rendement réel
→ position courte si VENDRE : PnL = -rendement réel
4. Portefeuille *= (1 + PnL)
5. Buy & hold = start_amount × Close[i+1] / Close[bt_start]
Retour : {dates, portfolio_values, buy_hold_values, métriques}
5.3 Modèle Transformer Hybride
Type : Deep Learning — Transformer Encoder avec fusion prix + sentiment
Framework : PyTorch
Fichier modèle : Model AI Transformer/transformer_model.pt
Classe : HybridTransformerBounded
Service : services/transformer_service.py
Description
Le Transformer Hybride est l'architecture la plus sophistiquée de la plateforme. Contrairement au BiLSTM qui ne traite que les données de prix, ce modèle fusionne explicitement les données de prix et les données de sentiment (articles de presse) dans un unique vecteur de 44 features par pas de temps. Le mécanisme d'attention multi-têtes du Transformer permet au modèle d'apprendre automatiquement quels jours passés (et quelles combinaisons features/sentiment) sont les plus pertinents pour prédire le lendemain.
Architecture : HybridTransformerBounded
Entrée : (batch_size, 60 jours, 44 features)
↓
[Projection linéaire] : 44 → 64 (d_model)
↓
[Symbol Embedding] : 8 symboles → vecteurs 64D (ajoutés au signal)
↓
[Positional Encoding] : encodage de position appris (par-position)
↓
[TransformerEncoder] × 2 couches
┌─ MultiHeadAttention (nhead=4, 4 têtes d'attention)
├─ Add & LayerNorm
├─ FeedForward (64 → 256 → 64) avec GELU
└─ Add & LayerNorm
↓
[Token CLS] → extraction du vecteur de résumé
↓
[LayerNorm] → [Dense 64→32] → [GELU] → [Dense 32→1] → [Tanh × 0.05]
↓
Sortie : log-rendement ∈ [-0.05, +0.05]
→ > 0 : signal ACHETER
→ < 0 : signal VENDRE
Hyperparamètres (config.json)
{
"d_input" : 44,
"d_model" : 64,
"nhead" : 4,
"num_layers": 2,
"d_ff" : 256,
"dropout" : 0.15,
"max_logret": 0.05,
"window" : 60
}
44 Features (16 prix + 28 sentiment)
Features de prix (16) :
| Feature | Description |
|---|---|
open, high, low, close, volume |
OHLCV bruts |
close_lag1 |
Prix de clôture J-1 |
ma_5 |
Moyenne mobile 5 jours |
volatility |
Écart-type glissant |
rsi |
RSI 14 périodes |
macd |
MACD (convergence/divergence) |
bb_position |
Position dans les Bandes de Bollinger |
ret_1, ret_5 |
Rendements sur 1 et 5 jours |
logret |
Log-rendement quotidien |
sma_ratio |
Ratio close / SMA |
hl_range |
Amplitude High-Low normalisée |
Features de sentiment (28) :
| Catégorie | Features | Nombre |
|---|---|---|
| Instantanées | sentiment_score, articles_count, sentiment_weighted, has_news | 4 |
| Décalages temporels | sentiment_lag_{1,2,3,5}j, articles_lag_{1,2,3,5}j | 8 |
| Moyennes glissantes | sentiment_mean_{3,5,7,14}j | 4 |
| Sommes glissantes | sentiment_sum_{3,5,7,14}j, articles_sum_{3,5,7,14}j | 8 |
| EMA sentiment | sentiment_ema_5, sentiment_ema_10 | 2 |
| Signaux binaires | strong_positive_sentiment, strong_negative_sentiment | 2 |
Pipeline de prédiction complet
# Étape 1 : Chargement modèle + config (cache mémoire)
model, scaler, encoder, cfg = _load()
window = cfg.get('window', 60)
# Étape 2 : Données prix (150j) et articles depuis GitHub
hist = yf.Ticker(ticker).history(period="150d", interval="1d")
articles = _get_articles() # cache 5 minutes (DataFrame complet)
# Étape 3 : Construction des 44 features (fusion prix + sentiment)
df_feat = _build_features(ticker, df_price, articles)
# df_feat shape : (N, 44)
# Étape 4 : Normalisation StandardScaler
feat_norm = np.nan_to_num(scaler.transform(df_feat[FEATURE_COLS].values))
# Étape 5 : Encodage symbole
sym_id = int(encoder.transform([ticker])[0])
# Étape 6 : Inférence PyTorch (fenêtre de 60 jours)
with torch.no_grad():
seq = torch.FloatTensor(feat_norm[-window:]).unsqueeze(0) # (1, 60, 44)
sym = torch.LongTensor([[sym_id]])
logret = model(seq, sym).item() # log-rendement prédit ∈ [-0.05, 0.05]
# Étape 7 : Conversion en signal et prix prédit
direction_prob = 0.5 + logret / (2 * max_logret) # [0, 1]
signal = 'ACHETER' if logret > 0 else 'VENDRE'
predicted_price = current_price * np.exp(logret)
return_pct = (np.exp(logret) - 1) * 100 # rendement prédit en %
Source des articles (GitHub CSV)
URL : https://raw.githubusercontent.com/[repo]/main/ALL_ARTICLES_MASTER.csv
Colonnes : symbol, date_publication, score_sentiment, [score_pondere], [confiance]
Cache : 5 minutes en mémoire (variable globale _articles_cache)
Fallback : DataFrames vides si réseau indisponible (prédiction sans sentiment)
5.4 Comparaison des trois modèles
| Critère | Sentiment | BiLSTM | Transformer |
|---|---|---|---|
| Type | Rule-based | Deep Learning RNN | Deep Learning Attention |
| Framework | Python pur | TensorFlow/Keras | PyTorch |
| Features | Score NLP agrégé | 14 indicateurs techniques | 44 (prix + sentiment) |
| Fenêtre temporelle | 48h d'articles | 30 jours | 60 jours |
| Sortie | Signal discret | Probabilité [0,1] | Log-rendement continu |
| Points forts | Événements exogènes | Patterns techniques | Fusion prix + news |
| Complexité | Faible | Moyenne | Élevée |
| Temps d'inférence | < 1s | ~2s | ~3s |
| Backtest | Oui (180j, step=3j) | Oui (180j, batch) | Oui (180j, batch) |
Comparaison de modèles dans le chatbot
Quand l'utilisateur demande "quel modèle pour Tesla ?", la plateforme :
- Lance les 3 backtests en parallèle (
ThreadPoolExecutor, max_workers=3) - Affiche une modal flottante déplaçable avec pour chaque modèle :
- Courbe SVG du portefeuille IA vs buy & hold (180 jours)
- Gain/perte total en euros et en pourcentage
- Valeur finale du portefeuille, taux de réussite des décisions
- Explication textuelle : "Si vous aviez investi 500€ depuis [date]..."
- Lien direct "Voir dans Mon Suivi" avec modèle pré-sélectionné
- Envoie un message de synthèse dans le chat avec les vrais résultats
- Met à jour l'historique LLM pour que les recommandations futures reflètent les données réelles
6. Authentification biométrique — Reconnaissance faciale
Vue d'ensemble
La plateforme propose une authentification optionnelle par visage, sans mot de passe. Cette fonctionnalité peut être activée lors de l'inscription ou depuis la page Profil.
Modules impliqués
| Fichier | Rôle |
|---|---|
services/face_auth.py |
Capture webcam, détection visage, stockage |
services/face_comparator.py |
Algorithme de comparaison multi-critères |
assets/camera.js |
Interface webcam navigateur (capture base64) |
pages/face_login.py |
Page de connexion biométrique |
pages/signup.py |
Capture initiale lors de l'inscription |
pages/profil.py |
Gestion (activer/désactiver/recapturer) |
Algorithme de comparaison (face_comparator.py)
La comparaison utilise 4 métriques complémentaires pondérées, conçues pour être robustes aux changements d'éclairage, de pose et d'expression :
Score final = (Cross-corrélation × 0.50)
+ (Contours Sobel × 0.30)
+ (MSE × 0.10)
+ (Histogramme × 0.10)
| Métrique | Poids | Méthode OpenCV | Justification |
|---|---|---|---|
| Cross-corrélation | 50% | cv2.TM_CCOEFF_NORMED |
Correspondance pixel par pixel — précision maximale |
| Contours Sobel | 30% | Gradient Sobel X+Y → corrélation | Robustesse aux variations d'éclairage (structure des contours) |
| MSE | 10% | Erreur quadratique moyenne normalisée | Similitude globale de l'image |
| Histogramme | 10% | Comparaison histogrammes niveaux de gris | Distribution tonale générale |
Pénalité : si la détection Haar Cascade ne trouve pas de visage dans l'une des deux images → score × 0.5 (protection contre les tentatives avec des photos non-humaines).
Seuil de validation : score ≥ 0.50 (50%) pour valider la correspondance et ouvrir la session.
Flux d'authentification complet
[Utilisateur sur /face-login]
↓
camera.js active la webcam (navigator.mediaDevices.getUserMedia)
↓
Capture frame toutes les 2s → canvas.toDataURL("image/jpeg") → base64
↓
POST /api/face-login {image: "data:image/jpeg;base64,..."}
↓
[Serveur Flask — face_auth.py]
1. Décodage base64 → tableau NumPy (OpenCV)
2. Conversion en niveaux de gris
3. Détection visage : cv2 Haar Cascade (haarcascade_frontalface_default.xml)
4. Recadrage ROI → redimensionnement 100×100
↓
[Serveur Flask — face_comparator.py]
5. Chargement image référence depuis table users (face_image en base64)
6. Même prétraitement sur l'image de référence
7. Calcul 4 métriques → score composite
8. Application pénalité si visage non détecté
↓
score ≥ 0.50 ?
OUI → Création session, redirect vers /
NON → {"match": false} → retry automatique côté client (2s)
Stockage des visages
Les images faciales sont stockées sous forme de chaînes base64 dans la colonne face_image de la table users. Cela évite la gestion d'un système de fichiers séparé et facilite les sauvegardes. Le dossier face_data/ contient les fichiers pickle de secours.
7. Assistant IA conversationnel (Chatbot)
Technologie
| Composant | Détail |
|---|---|
| Modèle LLM | LLaMA 3.3 70B Versatile (Meta AI) |
| Fournisseur | Groq Cloud (inférence ~200 tokens/s) |
| Protocol | Server-Sent Events (SSE) — streaming |
| Contexte envoyé | System prompt + 6 derniers messages |
| Température | 0.0 (déterministe) |
| Max tokens | 1024 par réponse |
Interface utilisateur
Le chatbot est un panel flottant positionné en bas à droite de l'écran, accessible depuis toutes les pages de la plateforme. Il dispose de :
- Streaming temps réel : les tokens s'affichent lettre par lettre
- Historique multi-conversations : stocké en
localStorage, jusqu'à 30 conversations × 60 messages - Dictée vocale : bouton microphone →
SpeechRecognitionAPI - Drag & drop : le panel peut être repositionné librement
- Badge de notification : indicateur "!" si une carte de confirmation attend
Fonctionnalité 1 — Réponses conversationnelles
Le system prompt (124 lignes) encode la connaissance complète de la plateforme :
- Explication des 3 modèles IA et de leurs différences
- Guide de navigation entre les pages
- FAQ sur les fonctionnalités (backtest, suivi, témoignages)
- Règles strictes de comportement (ne jamais inventer de données)
Fonctionnalité 2 — Enregistrement d'investissements via marqueur ##INVEST##
Mécanisme central pour l'enregistrement fiable des investissements depuis le chatbot :
Côté LLM : Quand l'utilisateur mentionne un investissement (montant, actif, modèle, action), le LLM génère un marqueur structuré à la fin de sa réponse :
##INVEST##{"symbol":"TSLA","model":"transformer","action":"ACHETER","amount":500}##
Côté JavaScript (chatbot.js) :
// Détection par regex après streaming
const INVEST_RE = /##INVEST##\s*(\{[\s\S]*?\})\s*##/;
const markerData = INVEST_RE.exec(savedText);
// Le marqueur est TOUJOURS supprimé du texte affiché (invisible pour l'utilisateur)
botBubble.textContent = _stripInvestMarker(savedText);
// Affichage de la carte de confirmation si :
// 1. Marqueur trouvé ET montant > 0
// 2. Message utilisateur avait une intention d'investissement (_INTENT_RE)
if (markerData && markerData.amount > 0 && hadInvestIntent) {
_showInvestConfirm(botBubble, markerData);
}
Carte de confirmation :
- Affiche symbole, modèle, action, montant
- Bouton "Enregistrer dans Mon Suivi" →
POST /api/chat-invest - En cas de succès : badge vert + lien vers Mon Suivi
- Renforcement dans l'historique LLM pour les investissements suivants
Fonctionnalité 3 — Comparaison de modèles par backtest
Détection de l'intention :
const _COMPARE_RE = /quel\s+mod.le|meilleur\s+mod.le|compare\s+.{0,15}mod.le|.../i;
const _SYMBOL_MAP = {'tesla':'TSLA', 'apple':'AAPL', 'nvidia':'NVDA', ...};
Flux complet :
Utilisateur : "quel modèle pour Tesla ?"
↓
1. _COMPARE_RE détecte l'intention de comparaison
2. _extractCompareSymbol() extrait "TSLA" depuis "Tesla"
3. LLM répond immédiatement (réponse générique basée sur sa connaissance)
4. Modal flottante déplaçable s'ouvre avec spinner "Simulation en cours..."
5. GET /api/backtest-compare?symbol=TSLA
→ 3 backtests lancés en parallèle (ThreadPoolExecutor)
→ ~15 secondes
6. Modal mise à jour avec :
- Courbe SVG : portefeuille IA (cyan) vs buy & hold (pointillé blanc)
- Explication textuelle : "Si vous aviez investi 500€ depuis le [date]..."
- Grille stats : Valeur finale / Taux réussite / Sans IA
- Badge "Recommandé" sur le meilleur modèle
- Lien "Voir dans Mon Suivi" → /mon-suivi?mode=lstm&ticker=TSLA
7. Message de correction dans le chat avec les vrais résultats
8. Historique LLM mis à jour → recommandations futures fondées sur données réelles
Fonctionnalité 4 — Détection de l'intention d'investissement
// Regex détectant les formulations investissement en français
const _INTENT_RE = /j.?ai\s*(investi|mis\b|achet|vendu|suivi|fait\b)|enregistr|vas-y/i;
Exemples déclencheurs :
- "J'ai investi 500€ sur Tesla avec le Transformer"
- "J'ai mis 1000€ sur AAPL en suivant le LSTM"
- "Vas-y, enregistre mon investissement"
8. Pages et fonctionnalités détaillées
Page d'accueil /
Fonctionnalités :
- Barre de tickers défilante en haut de page (BTC-USD, ETH-USD, NASDAQ, AAPL, GOOGL) — prix + variation %, rafraîchissement toutes les 5 minutes via
yfinance - Sélecteur de modèle : 3 boutons (Actualités / LSTM / Transformer) — paramètre URL
?mode=lstm - Cartes de prédiction (une par actif) :
- Signal coloré (vert/rouge/gris) : HAUSSIER / BAISSIER / SURVEILLER
- Prix d'entrée et prix courant
- Rendement prédit (%)
- Validité du signal (date d'expiration ~48h)
- Tooltip au survol : explication détaillée du signal et du modèle
- Bouton "Calculer mon gain" → redirection vers
/mon-suivi?mode=[modèle]
- Rafraîchissement automatique toutes les 5 minutes (
dcc.Interval) - Protection : redirection automatique vers
/loginsi non authentifié
Page Mon Suivi /mon-suivi
Fonctionnalités :
Section 1 — Prédictions et simulation
- Vue en cartes des actifs avec signal IA courant (selon modèle sélectionné)
- Modal de simulation au clic sur un actif :
- Saisie du montant et choix BUY/SELL
- Calcul instantané du PnL simulé (prix entrée vs prix courant)
- Enregistrement en base de données si l'utilisateur confirme
Section 2 — Backtest "Et si j'avais suivi les conseils ?"
- Simulation rolling 180 jours avec le modèle sélectionné
- Graphique Plotly interactif : courbe portefeuille IA vs courbe buy & hold
- Métriques affichées : valeur finale, gain/perte €, rendement %, taux de réussite des décisions, date de début
- Texte explicatif : "Si vous aviez investi X€ depuis [date] et suivi tous les conseils, vous auriez Y€"
- Champ pour modifier le montant de référence + bouton "Recalculer"
Section 3 — Historique des trades personnels
- Table des investissements enregistrés (date, symbole, modèle, montant, PnL)
- KPIs globaux : PnL total, win rate, nombre de trades fermés
- Export rapport PDF
Navigation par URL :
?mode=lstm→ mode BiLSTM activé immédiatement?mode=transformer→ mode Transformer activé?ticker=TSLA→ ouverture automatique de la simulation backtest pour Tesla
Page Marchés /actions_page
Fonctionnalités :
- Intégration de la bibliothèque Lightweight-charts (TradingView-like) via iframe embarqué (
marche_chart.html) - 8 actifs disponibles : AAPL, AMZN, BTC-USD, GOOGL, META, MSFT, NVDA, TSLA
- 2 ans d'historique de prix en chandeliers japonais (OHLCV)
- Sélecteur d'actif dynamique
- API dédiée :
GET /api/ohlcv/<symbol>→ JSON [{time, open, high, low, close}]
Page Analyse de sentiment /analysis
Fonctionnalités :
- Cartes par actif : score sentiment courant, nombre d'articles analysés (48h), signal HAUSSIER/BAISSIER/NEUTRE
- Graphique temporel (Plotly) : évolution du sentiment sur 30 jours + barres indiquant le nombre d'articles
- Liste des derniers articles : titre, date, score sentiment, lien vers la source
- Données chargées depuis le GitHub CSV (cache 15 minutes)
Page Profil /profil
Fonctionnalités :
- Informations personnelles (modifiables) : prénom, nom, téléphone
- KPIs trading automatiques : total trades, trades fermés, win rate %, PnL total €, rendement moyen %
- Gestion reconnaissance faciale : activer / désactiver / recapturer l'image de référence
- Visibilité publique : partager ses statistiques sur la page Témoignages
- Statut en ligne : affichage sur la page Témoignages
Pages d'authentification
/login — Connexion classique
- Champ email + mot de passe (toggle visibilité)
- Vérification bcrypt côté serveur
- Session stockée dans
dcc.Store(storage_type="session")→sessionStorage - Lien vers
/face-login
/signup — Inscription
- Formulaire : prénom, nom, email, téléphone (+33)
- Mot de passe : 8 caractères min, 1 majuscule, 1 chiffre (validation temps réel)
- Capture faciale optionnelle via webcam (JavaScript)
- Hash bcrypt du mot de passe avant insertion en base
/face-login — Connexion biométrique
- Activation automatique webcam
- Détection et comparaison en temps réel (toutes les 2s)
- Connexion instantanée si score ≥ 0.50
Page Témoignages /temoignages
Fonctionnalités :
- Statistiques globales de la plateforme : utilisateurs inscrits, note moyenne, win rate global, PnL cumulé
- Témoignages approuvés : contenu, note (étoiles), montant investi, gain réalisé, période
- Formulaire de soumission : texte + note + investissement → en attente de validation admin
- Workflow de modération : pending → approved (public) / rejected
Panneau Administrateur /admin
Accès restreint : vérification session["is_admin"] == True
Dashboard principal :
- KPIs plateforme (actualisés toutes les 5s) : utilisateurs totaux, traders actifs, PnL global, win rate moyen
Gestion des utilisateurs :
- Table complète de tous les comptes
- Promotion / rétrogradation admin (toggle)
- Suppression avec confirmation modale (cascade : suppression user + tous ses trades)
Statistiques :
- Graphique des inscriptions sur 30 jours
- Répartition victoires/défaites (camembert)
- Classement des top traders par PnL
Logs de connexion :
- Historique de toutes les tentatives de connexion (date, email, succès/échec, IP)
Modération des témoignages :
- File d'attente des témoignages en attente
- Boutons Approuver / Rejeter
9. Base de données — Schéma complet
Type : SQLite
Fichier : users.db à la racine du projet
Chemin : calculé dynamiquement depuis __file__ dans database.py (indépendant du répertoire de lancement)
Table users
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
password BLOB NOT NULL, -- Hash bcrypt (bytes)
face_image TEXT, -- Image faciale en base64
is_admin INTEGER DEFAULT 0, -- 0 = utilisateur, 1 = administrateur
prenom TEXT,
nom TEXT,
telephone TEXT,
public_stats INTEGER DEFAULT 0, -- 0 = privé, 1 = affiché sur /temoignages
is_online INTEGER DEFAULT 0, -- 0 = hors ligne, 1 = en ligne
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Table user_trades
CREATE TABLE IF NOT EXISTS user_trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_email TEXT NOT NULL,
symbol TEXT NOT NULL, -- AAPL, TSLA, BTC-USD, etc.
entry_price REAL NOT NULL, -- Prix d'entrée ($)
exit_price REAL, -- Prix de sortie ($, NULL si trade ouvert)
entry_date DATETIME DEFAULT CURRENT_TIMESTAMP,
exit_date DATETIME,
quantity REAL, -- Montant investi en euros
prediction_direction TEXT, -- 'up' ou 'down' (signal du modèle)
actual_direction TEXT, -- Direction réelle constatée
pnl REAL, -- Profit/perte en euros
pnl_percentage REAL, -- Rendement en %
status TEXT DEFAULT 'open', -- 'open' ou 'closed'
model_type TEXT DEFAULT 'sentiment', -- 'sentiment', 'lstm', 'transformer'
FOREIGN KEY (user_email) REFERENCES users(email)
);
Table predictions_log
CREATE TABLE IF NOT EXISTS predictions_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL,
prediction_price REAL,
prediction_direction TEXT, -- 'ACHETER' ou 'VENDRE'
confidence REAL,
model_version TEXT,
predicted_date DATE,
actual_price REAL,
actual_direction TEXT,
accuracy BOOLEAN,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Table testimonials
CREATE TABLE IF NOT EXISTS testimonials (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_email TEXT NOT NULL,
user_name TEXT NOT NULL,
content TEXT NOT NULL,
rating INTEGER DEFAULT 5, -- Note de 1 à 5 étoiles
investment REAL, -- Montant investi (€)
gain REAL, -- Gain réalisé (€)
period TEXT, -- Ex : "6 mois"
status TEXT DEFAULT 'pending', -- 'pending', 'approved', 'rejected'
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
approved_at DATETIME,
FOREIGN KEY (user_email) REFERENCES users(email)
);
10. APIs REST
Toutes les routes API sont définies dans app.py et montées sur le serveur Flask de Dash (app.server).
| Méthode | Route | Corps / Params | Réponse | Description |
|---|---|---|---|---|
POST |
/api/chat |
{messages: [...]} |
SSE stream | Streaming LLaMA 3.3 70B |
POST |
/api/chat-invest |
{email, symbol, model, action, amount} |
{success, price} |
Enregistrement investissement |
POST |
/api/chat-detect-invest |
{messages: [...]} |
{invest: {...}} |
Extraction JSON investissement |
GET |
/api/model-compare |
?email=&symbol= |
{models, best_model} |
Comparaison sur trades personnels |
GET |
/api/backtest-compare |
?symbol=TSLA |
{models, best_model, start} |
Backtests 180j en parallèle |
GET |
/api/ohlcv/<symbol> |
— | [{time, open, high, low, close}] |
Chandeliers pour Lightweight-charts |
POST |
/api/face-login |
{image: "base64..."} |
{match, email, session} |
Authentification faciale |
11. Pipeline de données et sources
Données de marché (prix OHLCV)
Source primaire : yfinance
→ yf.Ticker(symbol).history(period, interval)
→ DataFrame OHLCV (Open, High, Low, Close, Volume)
→ Utilisé pour : prédictions temps réel, backtest, graphiques
Source secondaire : Alpha Vantage (si clé configurée)
→ Données intraday (1min, 5min, 15min, 60min)
→ Utilisé comme fallback si yfinance retourne données insuffisantes
Données de sentiment (articles de presse)
Source : GitHub CSV (ALL_ARTICLES_MASTER.csv)
→ Téléchargé à chaque requête (timeout 15s)
→ Cache mémoire : 5 minutes (variable globale dans transformer_service.py)
→ Colonnes : symbol, date_publication, score_sentiment, score_pondere, confiance
→ Mis à jour quotidiennement par un pipeline externe
Backtest rolling window (180 jours)
1. Téléchargement données brutes (180 + SEQ_LEN + 50 jours de sécurité)
2. Feature engineering sur l'ensemble de la période (pas de fuite de données)
3. Normalisation globale (scaler fitted hors ligne lors de l'entraînement)
4. Pour chaque jour i de bt_start à N-1 :
a. Fenêtre [i-SEQ_LEN : i] → inférence → signal
b. Rendement réel [i → i+1] → PnL selon signal
c. Mise à jour portefeuille et buy & hold
5. Retour : {
portfolio: [valeur jour 1, ..., valeur jour 180],
buy_hold: [valeur buy&hold jour 1, ..., jour 180],
final_value, total_return, win_rate, n_trades, wins, losses
}
12. Installation et démarrage
Prérequis
- Python 3.11+
- Clé API Groq (gratuite sur console.groq.com)
Installation locale
# 1. Cloner le dépôt
git clone <url-du-depot>
cd Projet4A_PredictionsBoursieres-main
# 2. Installer les dépendances Python
pip install -r requirements.txt
# 3. Créer le fichier d'environnement
cd "Interface Graphique"
cat > .env << EOF
GROQ_API_KEY=votre_cle_groq_ici
ALPHA_VANTAGE_KEY=optionnel
EOF
# 4. Lancer l'application
python app.py
Accès : http://localhost:8050
Compte administrateur par défaut
Créé automatiquement au premier démarrage par init_db() :
- Email :
admin@ensim.fr - Mot de passe :
Admin1234!
Variables d'environnement
| Variable | Obligatoire | Description |
|---|---|---|
GROQ_API_KEY |
Oui | Clé API Groq pour le chatbot LLaMA 3.3 70B |
ALPHA_VANTAGE_KEY |
Non | Clé Alpha Vantage (backup données intraday) |
Déploiement Docker
# Build
docker build -t predictions-boursieres .
# Run (port 7860 par défaut dans le Dockerfile)
docker run -p 7860:7860 -e GROQ_API_KEY=votre_cle predictions-boursieres
13. Architecture Dash — Comment les modèles sont connectés au frontend
13.1 Principe de Dash : callbacks réactifs
Dash est un framework Python qui permet de construire des interfaces web entièrement en Python, sans écrire de JavaScript côté applicatif. Son mécanisme central est le callback : une fonction Python décorée avec @callback qui se déclenche automatiquement quand un composant de l'interface change.
Composant A change (Input)
↓
Dash envoie une requête HTTP POST au serveur Flask
↓
La fonction Python @callback s'exécute
↓
La valeur de retour met à jour le composant B (Output)
Exemple concret : sélection d'un modèle sur la page d'accueil
# pages/home.py
@callback(
Output("home-pred-mode", "data"), # → met à jour le store du mode
Output("home-btn-lstm", "className"), # → change la classe CSS du bouton
Input("home-btn-lstm", "n_clicks"), # ← déclenché au clic
prevent_initial_call=True,
)
def toggle_mode(n_clicks):
return "lstm", "home-mode-btn home-mode-active"
Quand l'utilisateur clique sur "LSTM" :
- Dash détecte le clic → requête POST vers Flask
- La fonction
toggle_mode()s'exécute côté serveur Python - Elle retourne
"lstm"→ stocké dansdcc.Store(id="home-pred-mode") - Ce changement de store déclenche automatiquement le callback suivant
13.2 Mécanisme dcc.Store — Partage d'état entre callbacks
dcc.Store est un composant invisible dans le DOM qui stocke des données JSON dans le navigateur. Il permet de transmettre des données entre plusieurs callbacks sans les afficher.
| Type de store | Stockage navigateur | Durée de vie |
|---|---|---|
storage_type="session" |
sessionStorage |
Jusqu'à fermeture de l'onglet |
storage_type="local" |
localStorage |
Persistant entre sessions |
storage_type="memory" |
RAM JavaScript | Jusqu'au rechargement de la page |
Exemple : session utilisateur
# app.py — layout
dcc.Store(id="session-store", storage_type="session")
# Après login réussi — pages/login.py
@callback(
Output("session-store", "data"), # Stocke l'email + is_admin dans sessionStorage
Input("login-btn", "n_clicks"),
State("login-email", "value"),
State("login-password", "value"),
)
def process_login(n_clicks, email, password):
user = verify_user(email, password) # bcrypt check
if user:
return {"email": email, "is_admin": user["is_admin"]}
return no_update
Le chatbot JS lit ce store pour récupérer l'email :
// assets/chatbot.js
function _getUserEmail() {
const raw = sessionStorage.getItem("session-store"); // clé Dash = id du Store
const d = JSON.parse(raw);
return d?.email ?? null;
}
13.3 Mécanisme dcc.Interval — Rafraîchissement automatique
dcc.Interval est un composant qui génère des événements à intervalle régulier. Il permet de déclencher des callbacks périodiquement sans action utilisateur.
# app.py
dcc.Interval(id="interval-component", interval=5*60*1000, n_intervals=0)
# ↑ 5 minutes en millisecondes
# home.py
dcc.Interval(id="home-init", interval=300, max_intervals=1)
# ↑ 300ms après chargement, une seule fois
# mon_suivi.py
dcc.Interval(id="suivi-auto", interval=5 * 60 * 1000) # refresh toutes les 5 min
Ces composants génèrent des n_intervals croissants → utilisés comme Input dans les callbacks de chargement de données.
13.4 Chargement parallèle avec ThreadPoolExecutor
Puisque chaque prédiction nécessite un appel réseau (yfinance) ET une inférence modèle, les 7 actifs sont traités en parallèle pour réduire le temps de chargement :
# pages/home.py — render_home_predictions()
from concurrent.futures import ThreadPoolExecutor, as_completed
from services.lstm_service import predict as lstm_predict
def _fetch(ticker):
return ticker, lstm_predict(ticker) # appel réseau + inférence
with ThreadPoolExecutor(max_workers=7) as ex:
futures = {ex.submit(_fetch, t): t for t in _COMPANIES}
preds = {}
for f in as_completed(futures):
ticker, result = f.result()
preds[ticker] = result
# Rendu des cartes Dash avec les résultats
cards = [_lstm_card(ticker, company, preds.get(ticker))
for ticker, company in _COMPANIES.items()]
return cards # → Output("home-pred-grid", "children")
Sans parallélisme : 7 × ~2s = 14s de chargement
Avec ThreadPoolExecutor : max(2s) ≈ 2-3s de chargement
14. Flux d'intégration complets bout en bout
Flux 1 — Prédictions IA vers la page d'accueil
UTILISATEUR charge /
↓
[Dash] page d'accueil rendue côté serveur Python
→ Layout HTML envoyé au navigateur
→ dcc.Interval(id="home-init", interval=300ms, max_intervals=1) créé
↓
300ms plus tard : n_intervals passe de 0 à 1 → déclenche :
↓
@callback(
Output("home-pred-grid", "children"), ← les cartes de prédiction
Input("home-init", "n_intervals"), ← déclencheur
Input("home-pred-mode", "data"), ← mode actuel (sentiment/lstm/transformer)
State("session-store", "data"), ← email utilisateur
)
def render_home_predictions(_, mode, session):
↓
[Vérification session] — si non connecté → mur de connexion
↓
[Choix du service selon mode]
mode == "lstm" :
ThreadPoolExecutor(max_workers=7)
→ 7 threads en parallèle
→ chacun appelle lstm_service.predict(ticker)
→ yf.Ticker(ticker).history(period="120d") [réseau]
→ _build_features(hist) [calcul 14 features]
→ scaler.transform(window[-30:]) [normalisation]
→ model.predict([seq, sym_id]) [inférence Keras BiLSTM]
→ retourne {signal, probability, return_pct, entry_price, current_price}
→ preds = {ticker: result, ...}
→ [_lstm_card(ticker, ...) for ticker in COMPANIES] [rendu HTML Dash]
mode == "transformer" :
ThreadPoolExecutor(max_workers=7)
→ chacun appelle transformer_service.predict(ticker)
→ yf.Ticker(ticker).history(period="150d") [réseau]
→ _get_articles() [GitHub CSV, cache 5min]
→ _build_features(ticker, df_price, articles) [44 features]
→ scaler.transform(feat[-60:]) [normalisation]
→ torch.no_grad() → model(seq, sym_id) [inférence PyTorch]
→ retourne {signal, direction_prob, return_pct, predicted_price, ...}
mode == "sentiment" :
→ _load_articles() [GitHub CSV]
→ [_compute_signal(df, ticker) for ticker in COMPANIES]
→ filtrage 48h, calcul score_pondere
→ retourne {rec: "ACHETER"/"VENDRE"/"SURVEILLER", ...}
↓
RETOUR : liste de html.Div (cartes Dash) → injectés dans Output("home-pred-grid")
↓
[Navigateur] Dash React reçoit les nouveaux composants → re-render DOM
Flux 2 — Backtest "Et si j'avais suivi les conseils ?" (Mon Suivi)
UTILISATEUR clique sur une carte actif → modal s'ouvre → clique "Voir si j'avais suivi"
↓
@callback(
Output("suivi-backtest-bg", "className"), ← afficher la modal backtest
Output("suivi-backtest-ticker-store", "data"), ← stocker le ticker sélectionné
Input({"type": "suivi-bt-open", "index": ALL}, "n_clicks"),
)
def toggle_backtest_modal(card_clicks, close_n):
ticker = triggered["index"] # ex: "TSLA"
return "suivi-modal-visible", ticker
↓
[Changement de suivi-backtest-ticker-store déclenche :]
↓
@callback(
Output("suivi-backtest-chart", "figure"), ← graphique Plotly
Output("suivi-backtest-summary", "children"), ← texte explicatif
Output("suivi-backtest-stats", "children"), ← métriques (valeur finale, etc.)
Input("suivi-backtest-ticker-store", "data"), ← ticker sélectionné
State("suivi-mode", "data"), ← modèle actif
State("suivi-backtest-amount-input", "value"), ← montant de référence
)
def render_backtest(ticker, mode, amount):
amount = float(amount) or 500.0
if mode == "lstm":
bt = _run_backtest_lstm(ticker, start_amount=amount, days=180)
# → yf.Ticker(ticker).history(period="230d")
# → feature engineering 14 features sur tout l'historique
# → batch predict sur toutes les fenêtres [i-30:i]
# → simulation portefeuille jour par jour
elif mode == "transformer":
from services.transformer_service import predict_backtest as trans_backtest
bt = trans_backtest(ticker, start_amount=amount, days=180)
# → yf.Ticker(ticker).history(period="300d")
# → articles GitHub CSV
# → 44 features sur tout l'historique
# → batch predict PyTorch sur toutes les fenêtres [i-60:i]
else: # sentiment
bt = _run_backtest(ticker, start_amount=amount, days=180)
# → prix GitHub CSV
# → articles → signal tous les 3 jours (step=3)
# → simulation portefeuille
# Construction graphique Plotly
fig = go.Figure()
fig.add_trace(go.Scatter(x=dates, y=bt["portfolio"], name="Modèle IA", line=dict(color="#00d4ff")))
fig.add_trace(go.Scatter(x=dates, y=bt["buy_hold"], name="Sans IA", line=dict(dash="dot")))
return fig, summary_html, stats_html
↓
[Navigateur] Graphique Plotly rendu dans le DOM
Flux 3 — Chatbot : streaming SSE bout en bout
Ce flux illustre comment un message de l'utilisateur voyage du navigateur jusqu'au modèle LLaMA 3.3 70B et revient en streaming.
UTILISATEUR tape un message → clique Envoyer
↓
[chatbot.js] sendMessage()
input.value → text
_history.push({role:"user", content: text})
addMessage("user", text) → bulle utilisateur dans le DOM
showTyping() → indicateur "..." dans le DOM
↓
fetch('/api/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ messages: _history }) ← 6 derniers messages
})
↓
[Flask — app.py] @app.server.route('/api/chat', methods=['POST'])
def api_chat():
messages = request.get_json()["messages"]
from services.chat_service import stream_chat
def generate():
for chunk in stream_chat(messages): ← générateur Python
payload = json.dumps({"text": chunk})
yield f"data: {payload}\n\n" ← format SSE
yield "data: [DONE]\n\n"
return Response(
stream_with_context(generate()),
content_type='text/event-stream' ← en-tête SSE
)
↓
[services/chat_service.py] stream_chat(messages)
full_messages = [{"role":"system", "content": SYSTEM_PROMPT}] + messages
client = Groq(api_key=GROQ_API_KEY)
stream = client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=full_messages,
max_tokens=1024,
stream=True, ← streaming activé
temperature=0.0,
)
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
yield delta.content ← envoyé token par token
↓
[Réseau] Chaque token traverse :
Groq Cloud → Flask (stream_with_context) → HTTP SSE → Navigateur
↓
[chatbot.js] — lecture du ReadableStream
const reader = response.body.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
for (const line of decoder.decode(value).split('\n')) {
if (!line.startsWith('data: ')) continue
const chunk = line.slice(6).trim()
if (chunk === '[DONE]') break
const p = JSON.parse(chunk)
if (p.text) {
fullText += p.text
botBubble.textContent = fullText ← mise à jour DOM à chaque token
scrollBottom()
}
}
}
↓
[POST-STREAMING] analyse de la réponse complète
botBubble.textContent = _stripInvestMarker(fullText) ← suppression marqueur si présent
_history.push({role:"assistant", content: fullText})
_saveCurrentConversation() ← localStorage
↓
Vérification marqueur ##INVEST## → si présent → _showInvestConfirm()
Vérification comparaison modèles → si compareSymbol → _loadAndShowCompare()
Flux 4 — Enregistrement d'investissement via le chatbot
UTILISATEUR : "J'ai investi 500€ sur TSLA avec le Transformer"
↓
[chatbot.js] sendMessage()
_INTENT_RE.test(text) → true (détection intention d'investissement)
_extractInvestment(text, _history) → {symbol:"TSLA", model:"transformer", amount:500, action:"ACHETER"}
↓
[LLM génère (après streaming)] :
"Voici le récapitulatif de votre investissement :
##INVEST##{"symbol":"TSLA","model":"transformer","action":"ACHETER","amount":500}##"
↓
[chatbot.js] — post-streaming
INVEST_RE.exec(savedText) → markerData = {symbol:"TSLA", model:"transformer", ...}
botBubble.textContent = _stripInvestMarker(savedText) ← marqueur invisible
hadInvestIntent = true
_showInvestConfirm(botBubble, markerData)
↓
Carte de confirmation apparaît dans le chatbot :
[Symbole: TSLA] [Modèle: Transformer] [Action: ACHETER] [Montant: 500€]
[Bouton "Enregistrer dans Mon Suivi"]
↓
UTILISATEUR clique "Enregistrer"
↓
[chatbot.js] — listener click
email = _getUserEmail() ← depuis sessionStorage["session-store"]
fetch('/api/chat-invest', {
method: 'POST',
body: JSON.stringify({email, symbol:"TSLA", model:"transformer", action:"ACHETER", amount:500})
})
↓
[Flask — app.py] /api/chat-invest
email, symbol, model, action, amount = request.get_json()
# Validation : email non vide, symbol valide, montant > 0
↓
# Récupération prix d'entrée actuel
hist = yf.Ticker(symbol).history(period="5d", interval="1d")
entry_price = float(hist["Close"].iloc[-1])
↓
from services.tracking_service import save_followed_trade
save_followed_trade(
user_email=email,
symbol="TSLA",
signal="HAUSSIER",
entry_price=entry_price,
current_price=entry_price,
amount=500,
action="ACHETER",
model_type="transformer"
)
↓
[database.py] INSERT INTO user_trades (user_email, symbol, entry_price, quantity,
prediction_direction, model_type, status) VALUES (...)
↓
return jsonify({"success": True, "price": entry_price})
↓
[chatbot.js]
card.innerHTML = "Investissement enregistré · TSLA · 500€ à $XXX.XX"
_history.push({role:"user", content:"[Système] Investissement TSLA enregistré..."})
_saveCurrentConversation()
Flux 5 — Comparaison de modèles : du chatbot à la modal SVG
UTILISATEUR : "quel modèle tu me conseilles pour Tesla ?"
↓
[chatbot.js] sendMessage() — avant le fetch LLM
_COMPARE_RE.test(text) → true
_extractCompareSymbol(text, _history) :
lo = " quel modèle tu me conseilles pour tesla "
_findSymbol(lo) → cherche dans _SYMBOL_MAP → "tesla" → "TSLA"
compareSymbol = "TSLA"
↓
[LLM répond en streaming] (réponse générique basée sur connaissance générale)
↓
[POST-STREAMING — chatbot.js]
_loadAndShowCompare("TSLA", botBubble)
↓
_openCompareModal("TSLA") :
modal = document.createElement("div")
modal.style.cssText = "left:XXpx; top:XXpx" ← positionné à gauche du chatbot
modal.innerHTML = "... spinner 'Simulation en cours...' ..."
document.body.appendChild(modal) ← ajouté au body (pas au chatbot)
_makeDraggable(modal, header) ← drag & drop activé
↓
fetch('/api/backtest-compare?symbol=TSLA')
↓
[Flask — app.py] /api/backtest-compare
symbol = "TSLA"
def _run_lstm():
from pages.mon_suivi import _run_backtest_lstm
return 'lstm', _run_backtest_lstm("TSLA", start_amount=500, days=180)
def _run_transformer():
from services.transformer_service import predict_backtest
return 'transformer', predict_backtest("TSLA", start_amount=500, days=180)
def _run_sentiment():
from pages.mon_suivi import _run_backtest
return 'sentiment', _run_backtest("TSLA", start_amount=500, days=180)
with ThreadPoolExecutor(max_workers=3) as ex:
futures = [ex.submit(f) for f in [_run_lstm, _run_transformer, _run_sentiment]]
for f in as_completed(futures):
key, bt = f.result()
if bt:
results[key] = {
'final': bt['final_value'], # ex: 547.23
'return_pct': bt['total_return'], # ex: +9.4
'win_rate': bt['win_rate'], # ex: 55
'bh_final': bt['buy_hold'][-1], # ex: 523.10
'series': _downsample(bt['portfolio'], n=30), # 30 points
'bh_series': _downsample(bt['buy_hold'], n=30),
...
}
best = max(results.items(), key=lambda x: x[1]['return_pct'])[0]
return jsonify({'symbol':'TSLA', 'models': results, 'best_model': best})
↓
[chatbot.js] — réponse JSON reçue (~15s plus tard)
_populateCompareModal(body, "TSLA", data)
pour chaque modèle k dans ["lstm", "transformer", "sentiment"] :
gain = m.final - 500
gainCls = gain >= 0 ? 'cb-cmp-pos' : 'cb-cmp-neg'
spark = _makeSpark(m.series, m.bh_series, 400, 80)
# SVG inline généré en JavaScript :
# <svg viewBox="0 0 400 80">
# <polyline points="0,70 13,65 ..." stroke="#3ef5a0" /> ← portefeuille IA
# <polyline points="0,70 13,67 ..." stroke-dasharray="4,3" /> ← buy&hold
# </svg>
modal mis à jour avec HTML complet
↓
_addBacktestSummaryToChat("TSLA", data) :
addMessage("bot", "Voici les résultats réels sur 180 jours pour TSLA :
• Actualités : +X€ (+Y%)
• LSTM : -Z€ (-W%)
• Transformer : ...
Recommandation : [meilleur modèle]")
_history.push({role:"assistant", content: ...}) ← LLM connaît maintenant les vraies données
Flux 6 — Reconnaissance faciale : de la webcam à la session
UTILISATEUR va sur /face-login
↓
[pages/face_login.py] layout Dash
html.Div(id="camera-container")
html.Canvas(id="camera-canvas")
dcc.Interval(id="face-interval", interval=2000) ← capture toutes les 2s
↓
[assets/camera.js] — chargé par Dash
navigator.mediaDevices.getUserMedia({video: true})
→ stream webcam → affiché dans <video> HTML
↓
setInterval(() => {
ctx.drawImage(video, 0, 0, 100, 100) ← dessin frame 100×100 sur canvas
const base64 = canvas.toDataURL("image/jpeg", 0.8)
// Envoi au serveur via fetch
fetch('/api/face-login', {
method: 'POST',
body: JSON.stringify({image: base64})
})
.then(r => r.json())
.then(result => {
if (result.match) {
window.location.href = "/" ← redirection si visage reconnu
}
})
}, 2000)
↓
[Flask — app.py] /api/face-login (POST)
image_b64 = request.get_json()["image"]
↓
# Décodage base64 → numpy array (OpenCV)
img_bytes = base64.b64decode(image_b64.split(",")[1])
nparr = np.frombuffer(img_bytes, np.uint8)
frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
↓
# Détection Haar Cascade
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.3, minNeighbors=5)
if len(faces) > 0:
x, y, w, h = faces[0]
face_roi = cv2.resize(gray[y:y+h, x:x+w], (100, 100))
else:
face_roi = cv2.resize(gray, (100, 100))
↓
# Comparaison contre tous les utilisateurs ayant face_image != NULL
conn = get_connection()
users_with_face = conn.execute(
"SELECT email, face_image FROM users WHERE face_image IS NOT NULL"
).fetchall()
↓
best_score, best_email = 0, None
for email, face_b64 in users_with_face:
score = face_comparator.compare(face_roi, face_b64)
# score = corr×0.5 + sobel×0.3 + mse×0.1 + hist×0.1
if score > best_score:
best_score = score
best_email = email
↓
if best_score >= 0.50:
# Création de la session Flask
session["email"] = best_email
session["is_admin"] = user["is_admin"]
return jsonify({"match": True, "email": best_email})
else:
return jsonify({"match": False, "score": best_score})
↓
[camera.js] — si match=True
window.location.href = "/" ← accueil
↓
[Dash — app.py] mise à jour du dcc.Store session-store via callback Dash
Flux 7 — Rendu des prédictions : du modèle aux composants HTML Dash
Ce flux montre comment une sortie numérique du modèle devient une carte visuelle dans le navigateur.
# Sortie brute du modèle BiLSTM
prob = 0.63 # probabilité de hausse
return_pct = (0.63 - 0.5) * 2 * 0.018 * 100 = +0.47%
signal = "ACHETER"
# ─── services/lstm_service.py ───
predict("TSLA") → {
"signal": "ACHETER",
"probability": 0.63,
"return_pct": 0.47,
"entry_price": 247.30,
"current_price": 251.15,
"change_pct": +1.56,
"confidence": 72.0,
"signal_valid_to": "30 mai à 23h",
"name": "Tesla",
"is_lstm": True
}
# ─── pages/home.py — _lstm_card() ───
# Construit un html.Div Dash à partir du dictionnaire :
html.Div(className="home-pred-card home-pred-buy", children=[ # ← classe CSS selon signal
html.Div(className="home-pred-top", children=[
html.Div("TSLA", className="home-pred-ticker"),
html.Div("Tesla", className="home-pred-company"),
]),
html.Div(className="home-pred-rec home-pred-rec-buy", children="ACHETER"), # badge coloré
html.Div([
html.Span("Prix d'entrée : "),
html.Strong("$247.30"),
html.Span(" → "),
html.Strong("$251.15", style={"color": "#00ff87"}), # vert car en hausse
]),
html.Div(f"Rendement prédit : +0.47% · LSTM"), # pied de carte
# Tooltip au survol (html.Div caché, affiché par CSS :hover)
html.Div(className="home-pred-tooltip", children=[
html.Div("Bonne nouvelle — c'est le moment d'acheter", className="tooltip-title"),
html.Div("Notre IA a analysé 30 jours de données...", className="tooltip-body"),
]),
dcc.Link("Calculer mon gain", href="/mon-suivi?mode=lstm"),
])
# ─── Dash ───
# ce html.Div Python est sérialisé en JSON et envoyé au navigateur
# React (Dash frontend) le convertit en nœuds DOM :
# <div class="home-pred-card home-pred-buy">
# <div class="home-pred-ticker">TSLA</div>
# <div class="home-pred-rec home-pred-rec-buy">ACHETER</div>
# ...
# </div>
14.1 Résumé : tableau des flux d'intégration
| Flux | Déclencheur | Traitement Python | Retour vers le navigateur |
|---|---|---|---|
| Prédictions accueil | dcc.Interval (300ms) |
lstm/transformer/sentiment_service.predict() × 7 en parallèle |
html.Div[] composants Dash |
| Backtest Mon Suivi | Clic card → dcc.Store → @callback |
_run_backtest_lstm/transformer/sentiment() 180j |
go.Figure Plotly + html.Div stats |
| Chatbot streaming | fetch('/api/chat') JS |
stream_chat() → Groq LLaMA → yield chunk |
SSE data: {"text":"..."} |
| Enregistrement invest | fetch('/api/chat-invest') JS |
save_followed_trade() → SQLite INSERT |
JSON {"success": true, "price": X} |
| Comparaison backtest | fetch('/api/backtest-compare') JS |
3 backtests ThreadPoolExecutor + downsample |
JSON {models, best_model, series[]} |
| Reconnaissance faciale | fetch('/api/face-login') JS (2s) |
OpenCV Haar + face_comparator.compare() |
JSON {"match": bool, "email": str} |
| Chandeliers marché | fetch('/api/ohlcv/TSLA') JS |
yf.Ticker().history() → format OHLCV |
JSON [{time, open, high, low, close}] |
Tableau récapitulatif des fonctionnalités
| Domaine | Fonctionnalité | Technologie |
|---|---|---|
| Prédiction IA | 3 modèles distincts, 8 actifs, signaux ACHETER/VENDRE | TF/Keras, PyTorch, NLP |
| Backtest | Simulation portefeuille 180j glissants vs buy & hold | NumPy, Pandas, yfinance |
| Comparaison modèles | Modal flottante déplaçable, courbes SVG, recommandation | JavaScript, Flask, ThreadPool |
| Trades utilisateur | Enregistrement, historique, KPIs, export PDF | SQLite, Flask |
| Chatbot IA | LLaMA 3.3 70B, streaming SSE, enregistrement via marqueur | Groq, JavaScript |
| Biométrie | Connexion sans mot de passe, 4 métriques de comparaison | OpenCV, JavaScript |
| Analyse sentiment | Articles presse, scores agrégés, évolution temporelle | pandas, GitHub CSV |
| Graphiques marché | Chandeliers japonais, 2 ans d'historique, 8 actifs | Lightweight-charts |
| Profil | KPIs trading, visibilité publique, gestion faciale | Dash, SQLite |
| Témoignages | Soumission, workflow modération, stats plateforme | Flask, SQLite |
| Admin | CRUD utilisateurs, stats, logs connexion, modération | Flask, Plotly |
| Authentification | Email/bcrypt + reconnaissance faciale | bcrypt, OpenCV |
| Déploiement | Docker Python 3.11, port configurable | Docker |
Projet réalisé dans le cadre de la 4ème année à l'ENSIM — École Nationale Supérieure d'Ingénieurs du Mans Technologies principales : Python 3.11 · Dash/Flask · TensorFlow · PyTorch · Groq LLaMA 3.3 · OpenCV · SQLite