Système d'exécution multi-marchés : OMS, routage intelligent, TWAP/VWAP, slippage control, gestion des horaires US/EU/APAC, et monitoring des fills en temps réel.
La microstructure des marchés étudie comment les prix se forment tick par tick. Pour un trader algo, la comprendre est la différence entre perdre 0.5% par trade en slippage (catastrophique sur 1000 trades/an) et gagner 0.05% en execution alpha.
| Type d'actif | ADV typique | Spread moyen | Impact pour $10K | Impact pour $50K | Fenêtre d'exécution |
|---|---|---|---|---|---|
| US Large-Cap (AAPL, MSFT) | $5B+ | 1-2 bps | ~0 bps | ~0 bps | Market order OK |
| US Mid-Cap ($5-30B) | $100-500M | 3-8 bps | 1-2 bps | 3-5 bps | Limit order |
| US Small-Cap ($1-5B) | $10-100M | 10-30 bps | 3-5 bps | 10-20 bps | TWAP 30min |
| EU Large-Cap (ASML, SAP) | €200M-1B | 5-15 bps | 2-5 bps | 5-10 bps | Limit order |
| EU Mid-Cap | €20-200M | 15-40 bps | 5-10 bps | 15-30 bps | TWAP 1h |
| APAC (TSE, HKEX) | Variable | 10-30 bps | 3-8 bps | 10-25 bps | TWAP adaptatif |
| ETF (SPY, QQQ) | $20B+ | 1 bps | ~0 bps | ~0 bps | Market order OK |
Ne jamais placer un ordre représentant plus de 1% du volume quotidien moyen (ADV) d'une action en un seul bloc. Au-delà de ce seuil, le market impact devient significatif et potentiellement détectable par les prédateurs HFT. Pour notre portefeuille de 100K€ démarrant, cette contrainte est rarement atteinte sur les large-caps mais critique sur les small-caps EU.
L'OMS est le composant qui transforme les signaux du méta-allocateur en ordres réels envoyés au broker. Il gère le cycle de vie complet d'un ordre : création, validation, envoi, suivi, fill, et reporting.
claude -p "Implémente l'OMS dans execution/oms.py :
from enum import Enum
from dataclasses import dataclass
from datetime import datetime
import asyncio
class OrderStatus(Enum):
PENDING = 'pending' # Créé, en attente de validation
VALIDATED = 'validated' # Passé les checks pre-trade
SUBMITTED = 'submitted' # Envoyé au broker
PARTIAL_FILL = 'partial' # Partiellement exécuté
FILLED = 'filled' # Entièrement exécuté
CANCELLED = 'cancelled' # Annulé (par nous ou par le broker)
REJECTED = 'rejected' # Rejeté par le broker
EXPIRED = 'expired' # TTL dépassé sans fill
class OrderType(Enum):
MARKET = 'MKT'
LIMIT = 'LMT'
LIMIT_ON_CLOSE = 'LOC' # Exécuté au prix de clôture
ADAPTIVE = 'IBALGO:Adaptive' # Algo IBKR natif (patient/urgent)
TWAP = 'TWAP' # Notre implémentation
VWAP = 'VWAP' # Notre implémentation
@dataclass
class Order:
id: str # UUID unique
symbol: str # e.g. 'AAPL' ou 'ASML-AMS'
side: str # 'BUY' ou 'SELL'
quantity: int # Nombre d'actions
order_type: OrderType
limit_price: float | None # None pour MARKET
strategy: str # Stratégie source
urgency: str # 'low', 'medium', 'high', 'emergency'
ttl_minutes: int # Time-to-live avant expiration
max_participation_rate: float # Max % du volume (ex: 0.05 = 5%)
created_at: datetime
status: OrderStatus = OrderStatus.PENDING
filled_qty: int = 0
avg_fill_price: float = 0.0
slippage_bps: float = 0.0
class OrderManagementSystem:
'''Gestionnaire du cycle de vie des ordres.
Pre-trade checks (validate()):
1. Position finale respecte les contraintes de portefeuille ?
2. Taille de l'ordre < 1% de l'ADV ?
3. Marché ouvert pour ce symbole ?
4. Pas de duplication (même symbole+side dans les 5 dernières minutes) ?
5. Capital disponible suffisant (cash + sells en cours) ?
6. Pas de trading halt sur ce symbole ?
Routing logic (route()):
- Large-cap + urgence low: LIMIT au mid-price, TTL 2h
- Large-cap + urgence high: ADAPTIVE (IBKR algo)
- Small-cap: TWAP sur 30-60min
- Clôture (dernier 30min): LOC
- Emergency (circuit-breaker): MARKET immédiat
Fill management:
- Partial fills: relancer avec le residual
- Unfilled après TTL: annuler + alerter si > 20% unfilled
- Amélioration de prix: logger le positive slippage
Post-trade reporting:
- Slippage = (fill_price - decision_price) / decision_price × 10000 bps
- Stocker dans fills.parquet pour analyse
'''
"
| Check | Condition | Action si échec | Severité |
|---|---|---|---|
| Position max | Position résultante > 10% NAV | Réduire la taille | Hard block |
| ADV limit | Ordre > 1% de l'ADV | Découper en TWAP | Auto-fix |
| Marché ouvert | Bourse fermée ou pre/post-market | Queue pour prochaine ouverture | Soft delay |
| Duplication | Même symbole+side en < 5min | Rejeter + alerte | Hard block |
| Capital | Cash insuffisant pour l'achat | Rejeter + alerte | Hard block |
| Trading halt | Symbole en halt (SEC, LULD) | Queue pour reprise | Hard block |
| Fat finger | Taille > 5× la taille moyenne historique | Rejeter + alerte Discord | Hard block |
| Prix aberrant | Limit price > ±5% du mid-price | Rejeter + alerte | Hard block |
Le TWAP découpe un gros ordre en tranches égales exécutées à intervalles réguliers. Idéal pour les actions moyennement liquides où on veut minimiser le market impact.
claude -p "Implémente un TWAPExecutor dans execution/algos/twap.py :
class TWAPExecutor:
'''Exécution TWAP avec gestion des partial fills.
Paramètres:
- total_qty: quantité totale à exécuter
- duration_minutes: durée totale (ex: 30min, 60min)
- n_slices: nombre de tranches (ex: 10)
- order_type_per_slice: LIMIT au mid-price avec offset
- max_participation: max % du volume par tranche
Algorithme:
1. Calculer interval = duration / n_slices
2. slice_qty = total_qty / n_slices (arrondi)
3. Toutes les `interval` minutes:
a. Calculer le mid-price actuel
b. Placer un LIMIT à mid-price + offset (0 pour buy, -0 pour sell)
c. TTL = interval - 10sec (cancel avant prochaine tranche)
d. Si partial fill, accumuler le residual
4. Dernière tranche: envoyer tout le residual en ADAPTIVE
Anti-gaming:
- Ajouter un jitter aléatoire de ±20% sur l'interval
- Varier la taille des tranches (±30%) pour être moins prédictible
- Ne pas exécuter si le spread > 2× le spread médian (marché stressé)
'''
"
Le VWAP adapte la taille des tranches au profil de volume intraday. Plus de volume en open et close → plus grosses tranches à ces moments. Objectif : battre le VWAP du jour.
claude -p "Implémente un VWAPExecutor dans execution/algos/vwap.py :
class VWAPExecutor:
'''Exécution VWAP basée sur le profil de volume historique.
1. Charger le profil de volume intraday moyen (30 jours)
→ volume_pct[i] = volume_bucket_i / total_daily_volume
2. Pour chaque bucket de 5 minutes:
target_qty[i] = total_qty × volume_pct[i]
3. Placer les tranches proportionnellement
4. Tracking: calculer le VWAP réalisé vs VWAP marché
Le volume profile est typiquement en U:
- 9:30-10:00 (US): 15-20% du volume
- 10:00-15:30: 50-60% (réparti uniformément)
- 15:30-16:00: 20-25% du volume
Avantage vs TWAP: meilleur prix moyen en marché normal
Inconvénient: prédictible si quelqu'un connaît notre profil cible
'''
"
Interactive Brokers fournit un algorithme Adaptive natif qui ajuste dynamiquement entre passive (limit) et aggressive (market) selon les conditions. On l'utilise pour les ordres urgents.
claude -p "Implémente l'IBKRAdaptiveRouter dans execution/algos/adaptive.py :
class IBKRAdaptiveRouter:
'''Wrapper pour l'algo Adaptive d'IBKR via ib_insync.
Modes:
- Patient: commence passif (limit au bid/ask), escalade si non-fill
- Normal: mix 50/50 passif/agressif
- Urgent: commence agressif, remplit au plus vite
Mapping urgence → mode IBKR:
- urgency='low' → Patient, TTL=4h
- urgency='medium' → Normal, TTL=1h
- urgency='high' → Urgent, TTL=15min
- urgency='emergency' → MARKET (pas d'algo)
'''
async def execute(self, order: Order) -> Fill:
from ib_insync import IB, Stock, LimitOrder, MarketOrder
# Créer le contrat IBKR
contract = Stock(order.symbol, exchange, currency)
# Qualifier le contrat
await self.ib.qualifyContractsAsync(contract)
# Créer l'ordre avec algo params
ib_order = LimitOrder(
order.side, order.quantity, order.limit_price,
algoStrategy='Adaptive',
algoParams=[TagValue('adaptivePriority', priority)]
)
trade = self.ib.placeOrder(contract, ib_order)
# Monitor fills...
"
| Algo | Cas d'usage | Slippage typique | Fill rate | Prédictibilité |
|---|---|---|---|---|
| Market Order | Emergency uniquement | 5-50 bps | 100% | N/A |
| Limit Order | Large-caps, pas d'urgence | -2 à +5 bps | 60-80% | Faible |
| TWAP | Mid/small-caps, gros ordres | 2-10 bps | 90-95% | Moyenne |
| VWAP | Large-caps, benchmark VWAP | 1-5 bps | 95% | Élevée |
| IBKR Adaptive | Urgence moyenne à haute | 3-15 bps | 95-99% | Faible |
| LOC (Limit on Close) | Rebalancement fin de journée | 1-3 bps | 99% | Aucune |
Notre univers couvre 3 régions, chacune avec ses horaires, ses conventions, et ses particularités. Le scheduler d'exécution doit orchestrer tout cela de manière fluide.
| Marché | Ouverture (UTC) | Fermeture (UTC) | Pre-market | Currency | Settlement |
|---|---|---|---|---|---|
| NYSE / NASDAQ | 14:30 | 21:00 | 10:00-14:30 | USD | T+1 |
| Euronext Paris | 08:00 | 16:30 | 07:15-08:00 | EUR | T+2 |
| Xetra (Francfort) | 08:00 | 16:30 | 07:30-08:00 | EUR | T+2 |
| LSE (Londres) | 08:00 | 16:30 | — | GBP | T+1 |
| TSE (Tokyo) | 00:00 | 06:00 | — | JPY | T+2 |
| HKEX (Hong Kong) | 01:30 | 08:00 | 01:00-01:30 | HKD | T+2 |
| ASX (Sydney) | 00:00 | 06:00 | — | AUD | T+2 |
claude -p "Implémente le MultiMarketScheduler dans execution/scheduler.py :
class MultiMarketScheduler:
'''Orchestre l'exécution sur 3 fuseaux horaires.
Timeline quotidienne (UTC):
─────────────────────────────────────────────
00:00 │ TSE/ASX ouvrent → Exécuter ordres APAC
01:30 │ HKEX ouvre → Exécuter ordres HK
06:00 │ TSE/ASX ferment → Réconcilier APAC
08:00 │ EU ouvre → Exécuter ordres Europe
14:30 │ US ouvre → Exécuter ordres US
16:30 │ EU ferme → Réconcilier Europe
20:00 │ Pipeline allocation (MetaAllocator)
21:00 │ US ferme → Réconcilier US
21:30 │ Post-trade reporting + Discord summary
─────────────────────────────────────────────
Pour chaque marché:
1. Attendre l'ouverture
2. Attendre 15min (éviter la volatilité d'ouverture)
3. Exécuter les ordres par priorité (high → low)
4. Pour les gros ordres: TWAP sur la session
5. 30min avant la fermeture: convertir les unfilled en LOC
6. Réconcilier: vérifier fills, calculer slippage, logger
Gestion du FX:
- Comptes IBKR multi-devises (USD, EUR, GBP, JPY, HKD, AUD)
- Pas de conversion FX automatique (IBKR gère le margin)
- Si cash EUR insuffisant pour achat EU: alerter (pas de FX auto)
'''
"
| Événement | Impact | Action du scheduler |
|---|---|---|
| Jour férié US | NYSE/NASDAQ fermés | Skip ordres US, exécuter EU/APAC normalement |
| Jour férié EU | Euronext/Xetra fermés | Skip ordres EU, exécuter US/APAC normalement |
| FOMC announcement | Volatilité extrême 14:00 UTC | Suspendre les ordres US 13:30-15:00 UTC |
| NFP (premier vendredi) | Spike de volume 12:30 UTC | Réduire la participation rate de 50% |
| Earnings du ticker | Gap potentiel | Ne pas exécuter avant l'annonce |
| Triple witching | Volume anormal, spreads élargis | Basculer tout en TWAP, réduire taille |
| Circuit breaker | Marché halt 15min | Pause automatique, reprise après halt |
Le slippage est l'ennemi silencieux du trader algo. Sur 1000 trades/an, même 5 bps de slippage par trade = 5% de rendement perdu. La Transaction Cost Analysis (TCA) est le processus de mesure et d'optimisation continue.
| Composante | Typique US large-cap | Typique EU mid-cap | Typique APAC small-cap |
|---|---|---|---|
| Commission broker | 0.5 bps | 1.0 bps | 2.0 bps |
| Half-spread | 1.0 bps | 8.0 bps | 15.0 bps |
| Market impact | 0.5 bps | 5.0 bps | 12.0 bps |
| Timing cost | 2.0 bps | 3.0 bps | 5.0 bps |
| Opportunity cost | 1.0 bps | 3.0 bps | 5.0 bps |
| Total one-way | 5.0 bps | 20.0 bps | 39.0 bps |
| Total round-trip | 10.0 bps | 40.0 bps | 78.0 bps |
claude -p "Implémente un TransactionCostAnalyzer dans execution/tca.py :
class TransactionCostAnalyzer:
'''Analyse post-trade de chaque fill.
Pour chaque fill, calculer:
1. Slippage vs decision_price (prix au moment du signal)
2. Slippage vs arrival_price (prix à la soumission de l'ordre)
3. Slippage vs VWAP (benchmark standard)
4. Implementation Shortfall = decision_price → fill_price (total cost)
Stockage: fills.parquet avec colonnes:
[timestamp, symbol, side, qty, fill_price, decision_price,
arrival_price, vwap_benchmark, slippage_bps, commission,
market_impact_estimate, algo_used, strategy, region]
Reporting hebdomadaire (dimanche):
- Slippage moyen par algo (TWAP vs VWAP vs Adaptive vs Market)
- Slippage moyen par région (US vs EU vs APAC)
- Slippage moyen par taille d'ordre (déciles)
- Top 10 fills les plus coûteux (investigation)
- Trend du slippage sur les 4 dernières semaines
Si slippage moyen > 2× la target:
→ Alerte Discord + ajuster les algos automatiquement
'''
"
Chaque semaine, le système analyse les fills de la semaine et ajuste automatiquement :
L'exécution doit être monitorée en temps réel. Le dashboard couvre 3 dimensions : les ordres en cours, les positions résultantes, et la santé du système.
| Alerte | Trigger | Urgence | Action requise |
|---|---|---|---|
| 🔴 Ordre rejeté | Broker rejette l'ordre | Haute | Investiguer la raison |
| 🟡 Fill lent | < 50% filled après 50% du TTL | Moyenne | Considérer escalade (ADAPTIVE) |
| 🔴 Slippage excessif | Slippage > 20 bps sur un fill | Haute | Review le ticker/algo |
| 🟡 Connection perdue | Déconnexion IBKR > 30sec | Haute | Auto-reconnect, vérifier ordres |
| 🔴 Position divergence | Position IBKR ≠ position interne | Critique | Stop trading, réconcilier |
| 🟢 Exécution complète | Tous les ordres du jour filled | Info | Aucune (confirmation) |
| 🟡 Unfilled significatif | > 20% des ordres non exécutés | Moyenne | Ajuster les algos pour demain |
claude -p "Implémente les alertes Discord d'exécution dans execution/alerts.py :
class ExecutionAlertManager:
'''Envoie des alertes Discord formatées pour chaque événement d'exécution.
Utilise discord.py webhook pour envoyer des embeds riches:
Embed fill:
┌────────────────────────────────────────┐
│ ✅ FILL: AAPL BUY 150 @ $182.34 │
│ Slippage: +1.2 bps vs VWAP │
│ Algo: TWAP (30min, 8 slices) │
│ Stratégie: CS_Momentum │
│ Region: US │ Commission: $0.75 │
└────────────────────────────────────────┘
Embed daily summary (21:30 UTC):
┌────────────────────────────────────────┐
│ 📊 EXECUTION SUMMARY - 2026-02-28 │
│ Orders: 24 submitted, 22 filled, 2 LOC│
│ Fill rate: 91.7% │
│ Avg slippage: 3.2 bps (target: 5 bps) │
│ Best: ASML -1.5 bps (saved €12) │
│ Worst: 7203.T +18.2 bps (cost €45) │
│ Total commission: $18.50 │
│ Net execution cost: $67.30 (4.1 bps) │
└────────────────────────────────────────┘
'''
"
Après chaque session de marché, le système compare les positions internes (notre base de données) avec les positions réelles chez IBKR. Toute divergence → arrêt immédiat du trading + alerte Discord critique. Les causes typiques :
La réconciliation automatique est lancée 3 fois par jour : après fermeture de chaque marché.
L'infrastructure est maintenant complète : data pipeline, alpha factors, portfolio construction, et exécution. Il est temps de construire nos premières stratégies de production. La Partie 6 couvre les 4 stratégies momentum — le moteur principal du portefeuille en régime Risk-On.