Revue méthodologique ML — Insiders Trades Sigma
03 · Feature Engineering & Pistes Machine Learning
Date : 2026-05-14 · Auteur : analyse data science
TL;DR
- L'approche actuelle est déjà semi-ML : le score v3 intègre du Bayesian shrinkage (track record), un signal contrarian et une détection de cluster directionnelle — les gains faciles ont été pris.
- 15 171 trades backtestés sur 4 ans, ~380 signaux "winning strategy" : le dataset est petit. Sur cette taille, une régression logistique calibrée battra presque toujours un gradient boosting non régularisé hors de la période d'entraînement.
- Piste A (classifieur supervisé) est tractable maintenant et promet +3 à +6 pts de win rate au seuil de 40 %. La piste B (meta-learner stacking) est complémentaire et peu risquée. La piste C (NLP PDF) est le levier le plus sous-exploité mais demande un effort d'ingestion non négligeable.
- Anti-leakage est le vrai défi :
signalScoreest déjà calculé en look-ahead-free (cf.priorAlphaForInsider) mais plusieurs features "company" (marketCap, analyst consensus) sont des snapshots actuels, pas historiques — à corriger avant tout entraînement. - Ne pas tenter le ML sans 6 mois de paper-trade en parallèle : sur 4 ans de données avec saisonnalité et rotation sectorielle, un sur-apprentissage silencieux est probable.
1. Inventaire des features disponibles
1.1 Depuis Declaration
| Feature | Disponibilité | Qualité | Déjà dans le score |
|---|---|---|---|
totalAmount (€) |
~100 % | Bonne | Oui (pctMcap) |
pctOfMarketCap |
~85 % | Bonne après sanitize | Oui |
pctOfInsiderFlow |
~80 % | Moyenne (flux total agrégé) | Oui |
transactionNature (Acquisition / Cession) |
~95 % | Bonne | Oui (filtre dur) |
insiderFunction (rôle parsé) |
~90 % | Moyenne (texte libre) | Oui |
pubDate - transactionDate (délai AMF) |
~75 % | Bonne | Oui (filtre ≤7j) |
isCluster (directionnel) |
100 % (calculé) | Très bonne | Oui |
insiderCumNet (conviction nette) |
~80 % | Moyenne | Oui |
dcaPriorBuys12m |
100 % (calculé) | Bonne | Oui (dcaScore) |
trackRecordAlpha (Bayes shrunk) |
~60 % | Bonne si n≥2 | Oui |
| Volume (nb d'actions) | ~85 % | Bonne | Non |
unitPrice (prix unitaire) |
~85 % | Bonne | Non |
| Nombre de déclarations distinctes en cluster | Calculable | Bonne | Partiel |
Features dérivables non calculées actuellement :
- Délai AMF en jours ouvrés (vs calendaires)
- Rang du trade dans la séquence DCA (1er, 2e, 3e achat…)
- Ratio
totalAmount / (insiderCumNet + totalAmount)— part de renforcement - Jours depuis le dernier trade de cet insider sur toute société (activité récente)
- Distance au 52w-low relatif au moment du trade (historique Yahoo nécessaire)
1.2 Depuis Insider
| Feature | Disponibilité | Dérivable |
|---|---|---|
gender |
~60 % | Oui (via titre de fonction) |
| Tenure estimée (première déclaration connue) | Calculable | Oui |
| Nombre total de déclarations historiques | Calculable | Oui |
| Taux de succès global (win rate propre) | Calculable | Oui |
| Diversité sectorielle (nb de sociétés différentes) | Calculable | Oui |
| Montant moyen investi par trade | Calculable | Oui |
1.3 Depuis Company
| Feature | Disponibilité | Leakage risk |
|---|---|---|
marketCap (snapshot actuel) |
~90 % | Élevé : valeur au moment du scoring, pas au moment du trade |
sectorTag |
~70 % | Faible |
trailingPE, forwardPE, priceToBook |
~60 % | Élevé : même problème snapshot |
analystScore, numAnalysts |
~55 % | Moyen |
beta |
~55 % | Faible |
returnOnEquity, profitMargin |
~50 % | Moyen |
heldByInsiders, heldByInstitutions |
~45 % | Faible |
shortRatio |
~40 % | Moyen |
debtToEquity |
~55 % | Faible |
dividendYield |
~50 % | Faible |
fiftyTwoWeekHigh/Low (snapshot) |
~70 % | Élevé |
twoHundredDayAverage (snapshot) |
~70 % | Élevé |
Attention critique : toutes les colonnes Yahoo Finance dans
Companysont des snapshots au dernierpriceAt/analystAt, pas des séries temporelles. Utiliser ces valeurs telles quelles dans un entraînement backtest introduit du look-ahead leakage. Avant tout entraînement ML, il faut reconstituer la valeur de ces features à la date du trade, soit depuis des APIs historiques (Yahoo historical fundamentals, Macrotrends, Refinitiv), soit en les excluant.
1.4 Depuis BacktestResult
| Feature | Note |
|---|---|
returnFromPub90d |
Target variable recommandée (retail-realistic, post-publication) |
pubLeakPct |
Feature intéressante : le "drift" entre transaction et publication |
priceAtPub vs priceAtTrade |
Proxy de la visibilité préalable du marché |
1.5 Features marché non disponibles (à enrichir)
- Rendement CAC 40 / secteur sur 1M, 3M, 6M avant le trade (momentum overlay)
- Niveau de volatilité implicite (VIX-équivalent France)
- Taux sans risque OAT 10 ans au moment du trade
- Phase du cycle sectoriel (sectorTag + ETF sectoriel momentum)
- Calendrier de publication des résultats (earnings surprises)
2. Trois pistes ML détaillées
Piste A — Classifieur supervisé binaire (P positif à T+90)
Principe : transformer le problème en classification binaire. Cible : returnFromPub90d > 0 (ou mieux : returnFromPub90d > seuil_marche pour filtrer l'alpha net). Entraîner sur 2020-2023, valider sur 2024, tester sur 2025. Sortie : probabilité calibrée → ranking → top-N portfolio.
Features recommandées (anti-leakage strict) :
Transactions :
- pctOfMarketCap (log-transformé)
- log(totalAmount)
- pubDelayDays (jours calendaires)
- roleEncoded (ordinal : PDG=4, CFO=3, Directeur=2, Board=1, Autre=0)
- sameDirectionInsiderCount (cluster depth, 1..5+)
- dcaPriorBuys12m (0, 1, 2, 3+)
- insiderCumNet (signe : +1/0/-1, ou valeur normalisée)
- trackRecordAlpha_shrunk (avec n≥2, sinon global mean)
- rankInDCASequence (1er achat insider = 1, 2e = 2, etc.)
- pctOfInsiderFlow
Insider (calculés à date du trade) :
- insiderTenureYears (estimation depuis 1ère déclaration)
- insiderTradeCount_prior (nb de trades passés)
- insiderWinRate_prior_shrunk (Bayes, k=10)
- insiderAvgAmount_prior (montant moyen)
Société (uniquement features peu time-sensitive) :
- sizeLabel_encoded (Micro=0 .. Mega=5)
- sectorTag_encoded (one-hot ou embedding moyen)
- beta (relativement stable)
- debtToEquity_bucket (faible/moyen/élevé)
- heldByInsiders_bucket
- analystContrarianFlag (analystScore ≥ 3.5 = 1)
Timing :
- monthOfYear (sin/cos encoding pour cyclicité)
- quarterOfYear
- yearsSince2020 (tendance temporelle)
Algorithme recommandé : Logistic Regression avec régularisation L2 en premier (baseline robuste), puis LightGBM si la LR sous-performe de façon consistante. Sur ~15k samples dont ~6k buys et ~1500 signaux "winning", le gradient boosting avec >100 features sur un split 4 ans risque fortement l'overfit.
Split train/val/test :
- Train : trades avec
pubDateentre 2020-01-01 et 2023-12-31 - Validation : 2024 (tune hyperparams, seuil de classification)
- Test (locked) : 2025 — ne pas toucher avant validation complète
Métrique principale : Precision@K (K = taille souhaitée du portefeuille, par ex K=15). AUC-PR secondaire (plus robuste que AUC-ROC sur données déséquilibrées). Calibration curve (Brier score) pour la confiance probabiliste.
Lift estimé : +3 à +6 pts de win rate au seuil ≥ 40 % de probabilité. Sur 380 signaux/an, cela représente environ 10-20 trades additionnels bien classés. Attention : cet estimé est fragile sur 4 ans de données — le vrai test est la généralisation sur 2025.
Effort d'implémentation : 30-50h (dont ~15h de nettoyage du leakage sur les features Yahoo).
Risque principal : look-ahead leakage sur les features snapshot Yahoo Finance. Mitigation : construire un FeatureSnapshot historique ou exclure toutes les features Yahoo non-statiques.
Piste B — Meta-learner stacking (score règles + ML)
Principe : utiliser le signalScore existant (0-100) comme feature dans un meta-learner léger, en ajoutant quelques features orthogonales non capturées par le score. Objectif : corriger les zones où le score actuel est mal calibré (ex : un score 60 en cluster 3-insiders PDG petit-cap n'a pas la même valeur prédictive qu'un score 60 CFO seul large-cap).
Architecture :
Layer 1 (règles) : signalScore v3 → 0-100
Layer 2 (meta-features non redondantes) :
- interaction_cluster_x_role (cluster ≥3 × PDG/CFO)
- interaction_pctMcap_x_sectorVol (conviction × volatilité secteur)
- insiderContrarian_x_clusterFlag
- sizeLabel × trackRecordAlpha
- pubLeakPct (drift prix avant publication)
Layer 3 (meta-learner) : Logistic Regression (10 features max)
→ P(return > 0) → ranking final
Avantages :
- Préserve l'interprétabilité du score v3 (expliquable aux régulateurs)
- Faible nombre de paramètres → résiste bien à l'overfit
- Peut être déployé comme un ajustement multiplicatif du score existant
Effort : 15-20h. Risque faible.
Lift estimé : +2 à +4 pts de win rate. La valeur principale est la calibration : le meta-learner peut corriger les queues de distribution où le score v3 est optimiste (petits nombres de track records) ou pessimiste (cluster fort mais score bas car pas de fundamentals).
Piste C — NLP sur le texte des déclarations PDF
Principe : les PDFs AMF contiennent du contexte non structuré — mentions d'un programme de rachat en cours, d'une offre publique potentielle, d'une augmentation de capital, d'un plan d'actionnariat salarié (PACS/stock-options). Ces contextes ont des implications très différentes sur le signal. Un insider qui achète dans le cadre d'un programme d'actionnariat salarié déclaré n'a pas la même valeur informationnelle qu'un achat discrétionnaire.
Implémentation possible :
Étape 1 — Classification du contexte déclaratif (4 classes) :
- DISCRETIONNAIRE : achat libre, au marché, sans programme déclaré
- PROGRAMME_RACHAT : dans le cadre d'un programme de rachat d'actions
- ACTIONNARIAT_SALARIE : PACS, stock-options, attribution gratuite
- CORPORATE_ACTION : apport, fusion, conversion, succession
Méthode : fine-tuning d'un modèle multilingual (CamemBERT ou mDeBERTa) sur le champ description + texte PDF parsé. Alternative moins coûteuse : règles regex enrichies sur le champ transactionNature + description.
Étape 2 — Filtrage dur : exclure ACTIONNARIAT_SALARIE et CORPORATE_ACTION du scoring (déjà partiellement fait via NON_MARKET_NATURES, mais incomplet).
Étape 3 — Feature binaire isDiscretionnaire dans le classifieur de la piste A.
Effort : 40-60h (dont 20h d'annotation manuelle d'un jeu de 500 PDFs pour fine-tuning). Alternative rapide (8h) : extraction regex + heuristiques sur description.
Lift estimé : difficilement quantifiable sans données, mais la suppression des faux positifs (achats dans cadre de PACS, exercices d'options mal parsés) peut améliorer le win rate de +2 à +8 pts en nettoyant des ~10-15 % de "noise trades" qui passent les filtres actuels.
Risque : parsing PDF variable selon les émetteurs. La qualité du champ description actuel (qui est le texte AMF brut) est suffisante pour la classification grossière.
3. Grille de lecture comparative
| Critère | Piste A (Classifieur) | Piste B (Stacking) | Piste C (NLP) |
|---|---|---|---|
| Feature richesse | Haute (30+ features) | Moyenne (5-10 meta-features) | Très ciblée (1-3 features texte) |
| Alpha uplift estimé | +3 à +6 pts win rate | +2 à +4 pts win rate | +2 à +8 pts (faux positifs) |
| Coût implémentation | 30-50h | 15-20h | 40-60h |
| Risque overfit | Élevé si GBM, faible si LR | Très faible | Faible (règles) / Moyen (DL) |
| Risque leakage | Élevé (features snapshot) | Faible (réutilise score) | Très faible |
| Interprétabilité | Moyenne (SHAP disponible) | Haute (poids LR lisibles) | Haute (classes nommées) |
| Maintenabilité | Moyenne (retrain mensuel) | Haute (stable) | Haute si règles, Moyenne si DL |
| Ordre de priorité | 2 | 1 | 3 |
Recommandation : commencer par la Piste B (impact/effort optimal, risque minimal), puis Piste A avec LR en baseline, puis NLP par règles regex (étape 1 de la Piste C) comme nettoyage du corpus.
4. Protocole expérimental (anti-leakage, anti-overfit)
4.1 Règles de discipline temporelle strictes
Règle 1 : Jamais de feature calculée avec des données postérieures à pubDate du trade.
Règle 2 : Les features "company" snapshot (marketCap, PE, analystScore) ne peuvent
être utilisées en entraînement QUE si on dispose de leur valeur historique
à la date du trade ± 30j. Sinon : exclure ou bucketiser (sizeLabel, sectorTag).
Règle 3 : Le track record insider doit être calculé en mode "look-forward-free" —
uniquement les trades avec pubDate strictement antérieure (déjà fait dans v3).
Règle 4 : Le split temporel est impératif. Pas de K-Fold cross-validation classique
sur séries temporelles : utiliser TimeSeriesSplit ou un split forward unique.
Règle 5 : Le jeu de test 2025 est verrouillé jusqu'à la fin de la validation sur 2024.
Une seule évaluation finale sur ce jeu — pas de tuning a posteriori.
4.2 Correction du leakage Yahoo Finance
Les features critiques à reconstruire historiquement ou à exclure :
# Features à exclure ou reconstruire :
LEAKY_FEATURES = [
"marketCap", # valeur actuelle, pas historique
"currentPrice", # même problème
"fiftyTwoWeekHigh", # dépend de la date de lecture
"fiftyTwoWeekLow",
"twoHundredDayAverage",
"fiftyDayAverage",
"trailingPE", # EPS trailing = fenêtre glissante actuelle
"analystScore", # consensus change fréquemment
"targetMean",
"shortRatio",
]
# Features relativement stables (à utiliser avec précaution) :
STABLE_FEATURES = [
"sectorTag", # change rarement
"beta", # relativement stable
"debtToEquity", # annuel
"heldByInsiders", # annuel
"profitMargin", # annuel
]
Alternative pragmatique : utiliser uniquement les features dérivées de Declaration + Insider (entièrement historiques) pour une première version, et introduire les features Yahoo historiques en v2 une fois les données reconstituées.
4.3 Protocole de paper-trade (A/B test 6 mois)
Phase 0 (Semaines 1-4) :
- Entraîner le modèle sur 2020-2023
- Valider calibration sur 2024
- Figer les hyperparamètres
Phase 1 (Mois 1-6 à partir de T0) :
- Stratégie actuelle (règles v3) : continuer en production
- Modèle ML : génère en parallèle un ranking de probabilité
→ stocker dans une table `ml_signal_score` (ne pas exposer aux utilisateurs)
- Logger chaque signal : score_v3, prob_ml, features utilisées, date
Phase 2 (Fin de mois 6) :
- Comparer les métriques sur les trades dont le résultat est connu (T+90 ≥ mesurable)
- Test statistique : t-test de Welsh sur les moyennes, bootstrap CI sur le win rate
- Seuil de décision : le modèle ML doit montrer p < 0.10 ET lift > +3 pts win rate
pour justifier un remplacement ou un override du score v3
Phase 3 (optionnel, mois 7-12) :
- Activer le score ML comme signal secondaire pour les utilisateurs premium
- Mesurer la rétention / engagement différentiel (A/B utilisateurs)
4.4 Métriques d'évaluation
# Métriques primaires (portfolio-level)
- Win rate @ T+90 du top-N ML vs top-N v3 (N = taille portefeuille)
- Alpha net = rendement moyen - rendement CAC40 correspondant
- Sharpe ratio (annualisé sur la période test)
# Métriques secondaires (model-level)
- Precision@15 (les 15 meilleures probabilités : combien sont > 0 à T+90 ?)
- Calibration curve : E[y] vs mean(predicted_prob) par décile
- Brier score (log-loss calibration)
# Métriques anti-overfit
- Performance différentielle train vs test (si gap > 15 pts AUC : overfit)
- Feature importance stability (si top features changent beaucoup par fold : instable)
5. Cinq hypothèses prioritaires à tester
H1 : Le track record insider prédit-il réellement le rendement futur ?
Question : un insider avec un alpha_shrunk > 5 % sur ≥ 3 trades passés génère-t-il un T+90 significativement supérieur à un insider sans historique ?
Test : comparer les deux groupes avec un t-test de Welch sur returnFromPub90d. Contrôler pour rôle et taille de société.
Seuil : p < 0.05 ET différence moyenne > 2 pts.
Données disponibles : oui, directement depuis buildInsiderTrackRecordIndex().
H2 : Le signal analyst-contrarian ajoute-t-il de l'alpha au-delà du cluster ?
Question : parmi les trades en cluster (≥2 insiders), est-ce que analystScore ≥ 3.5 (consensus neutre/baissier) prédit un meilleur T+90 qu'un achat cluster avec consensus haussier ?
Test : régression OLS sur returnFromPub90d ~ cluster + analystContrarian + cluster*analystContrarian, en contrôlant pour taille et rôle.
Hypothèse nulle : le coefficient d'interaction est nul (β = 0).
Intérêt : valide ou invalide la décision de mettre 6 pts sur ce signal dans le score v3.
H3 : Existe-t-il un effet momentum sur le timing des trades ?
Question : les achats d'insiders réalisés quand le CAC 40 est en tendance positive (>0 sur 3M) génèrent-ils un T+90 différent de ceux réalisés en tendance négative ?
Test : enrichir les trades historiques avec le rendement CAC 40 des 90j précédents. Régression returnFromPub90d ~ cac_momentum_3m. Puis interaction avec sizeLabel.
Données manquantes : CAC 40 historique — à récupérer via Yahoo Finance (^FCHI).
H4 : Le délai AMF (pubDelayDays) est-il un proxy de l'urgence/importance du trade ?
Question : les déclarations publiées très rapidement (délai ≤ 2 jours) génèrent-elles un meilleur signal que celles publiées à 5-7 jours ? Test : segmenter en 3 buckets (0-2j, 3-5j, 5-7j) et comparer les distributions de T+90. Test de Kruskal-Wallis (non-paramétrique, distributions probablement non-normales). Note : le filtre actuel est ≤ 7j — ce test pourrait justifier de le durcir à ≤ 3j.
H5 : L'effet taille est-il stable ou artefact de la période 2022-2025 ?
Question : la concentration d'alpha sur le bucket "Sweet" (300M-1B€) est-elle robuste cross-temporellement, ou reflète-t-elle uniquement la dynamique du marché 2022-2025 (rotation small/mid-cap) ?
Test : régresser returnFromPub90d ~ sizeLabel × year avec interaction. Si l'effet Sweet est significatif en 2022 et 2023 mais disparaît en 2024-2025, c'est probablement une artefact de régime.
Méthode recommandée : régression à effets fixes année, ou modèle Bayésien avec prior hiérarchique sur la taille.
6. Bibliothèques recommandées et intégration
6.1 Stack Python (analyse offline)
# Core ML
scikit-learn >= 1.4 # LogisticRegression, TimeSeriesSplit, calibration
lightgbm >= 4.3 # si LR baseline insuffisante
xgboost >= 2.0 # alternative GBM
# Feature engineering
pandas >= 2.2
numpy >= 1.26
category_encoders # Target encoding pour sectorTag
# NLP (Piste C)
transformers >= 4.40 # CamemBERT / mDeBERTa
sentence-transformers # embeddings rapides pour similarity
# Calibration & évaluation
sklearn.calibration.CalibratedClassifierCV
sklearn.metrics.precision_recall_curve
# Visualisation
matplotlib, seaborn # courbes de calibration, feature importance
shap >= 0.45 # explicabilité du modèle ML
6.2 Export vers la stack TS/Node
Le modèle entraîné en Python peut être intégré dans l'app Next.js de deux façons :
Option 1 (recommandée pour commencer) : score tabulaire pré-calculé
// Script Python exporte un JSON : { declarationId -> mlProb }
// Script TS/mjs lit le JSON et stocke dans une nouvelle colonne
// prisma/schema.prisma :
// mlScore Float? // probabilité calibrée issue du modèle ML (0-1)
Option 2 (production) : API sidecar Python
Python FastAPI micro-service sur /api/ml-score
→ reçoit les features d'un trade, retourne mlProb
→ appelé depuis le scoring cron TS (scripts/score-declarations.ts)
→ timeout 200ms max, fallback sur score v3 seul
Option 3 : ONNX export
# Convertir le modèle scikit-learn en ONNX
from skl2onnx import convert_sklearn
# → runtime ONNX en Node.js via onnxruntime-node
# Fonctionne bien pour LogisticRegression, moins bien pour GBM complexe
L'option 1 est idéale pour la phase de paper-trade (6 mois). L'option 3 est la meilleure pour la production si le modèle reste une LR ou un GBM léger.
7. Honnêteté statistique : quand le ML paie (et quand il ne paie pas)
Sur 15k observations, 4 ans, avec les contraintes de leakage actuelles :
| Technique | Verdict |
|---|---|
| Régression logistique L2 | Recommandée. Robuste, interprétable, calibrable. Baseline très difficile à battre sur ce volume. |
| LightGBM/XGBoost | Risqué sans extensive regularisation. Peut montrer AUC=0.64 en train et 0.51 en test. À utiliser uniquement si LR AUC < 0.56 sur validation. |
| Random Forest | Déconseillé. Surfit facilement sur les features catégorielles (rôle, secteur). |
| Réseaux de neurones (MLP, Transformer) | Overkill. Pas assez de données. Variance élevée, training instable. |
| ARIMA / modèles de séries temporelles | Non applicable directement (panel hétérogène de trades non réguliers). |
| Survival analysis (Cox PH) | Intéressant pour modéliser le "time to profit" plutôt que le return fixe à T+90. Exploratory uniquement. |
La règle empirique de Geman (2014) sur les signaux financiers s'applique ici : avec moins de 100 observations par classe, un classifieur qui performe en validation de façon robuste a presque certainement mémorisé un pattern temporel spécifique à la période. La stratégie actuelle (règles + score v3 Bayésien) est une solution bien calibrée pour cette taille de dataset. Le ML est un complément marginal, pas un remplacement.
Résumé 100 mots
La stratégie règles + score Bayésien v3 est déjà bien optimisée pour la taille du dataset (15k trades, 4 ans). Le ML apporte une valeur marginale mais réelle via trois pistes : (A) classifieur supervisé LR sur features anti-leakage (30-50h, +3-6 pts win rate), (B) meta-learner léger qui staque le score v3 avec 5 interactions non capturées (15-20h, faible risque), (C) NLP regex sur PDFs pour filtrer les faux positifs corporate (8-15h rapide). Priorité absolue : corriger le leakage des features Yahoo Finance snapshot avant tout entraînement. Déployer en paper-trade 6 mois avant toute décision de capital réel.