Files
trader-ml/docs/IG_INTEGRATION.md
Tika da30ef19ed Initial commit — Trading AI Secure project complet
Architecture Docker (8 services), FastAPI, TimescaleDB, Redis, Streamlit.
Stratégies : scalping, intraday, swing. MLEngine + RegimeDetector (HMM).
BacktestEngine + WalkForwardAnalyzer + Optuna optimizer.
Routes API complètes dont /optimize async.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 17:38:09 +00:00

720 lines
22 KiB
Markdown

# 🔌 Guide d'Intégration IG Markets - Trading AI Secure
## 📋 Table des Matières
1. [Vue d'ensemble IG Markets](#vue-densemble-ig-markets)
2. [Configuration Compte](#configuration-compte)
3. [API REST](#api-rest)
4. [Streaming Lightstreamer](#streaming-lightstreamer)
5. [Gestion des Ordres](#gestion-des-ordres)
6. [Risk Management IG](#risk-management-ig)
7. [Migration Progressive](#migration-progressive)
8. [Implémentation](#implémentation)
---
## 🎯 Vue d'ensemble IG Markets
### Pourquoi IG Markets ?
-**API complète** : REST + Streaming temps réel
-**Compte démo gratuit** : Test sans risque
-**Large gamme d'instruments** : Forex, Indices, Actions, Crypto
-**CFD et DMA** : Flexibilité trading
-**Guaranteed stops** : Protection slippage
-**Documentation** : API bien documentée
### Architecture IG
```
┌──────────────────────────────────────────────────────────┐
│ IG MARKETS API │
├──────────────────────────────────────────────────────────┤
│ │
│ REST API STREAMING API │
│ ├─ Authentication ├─ Lightstreamer │
│ ├─ Account Info ├─ Prix temps réel │
│ ├─ Market Data ├─ Positions updates │
│ ├─ Orders (CRUD) └─ Account updates │
│ ├─ Positions │
│ └─ Historical Data │
│ │
└──────────────────────────────────────────────────────────┘
```
---
## 🔐 Configuration Compte
### Étape 1 : Créer Compte Démo
1. **S'inscrire** : https://www.ig.com/uk/demo-account
2. **Activer compte démo** : Capital virtuel £10,000
3. **Accéder API** : Dashboard → API → Generate API Key
### Étape 2 : Obtenir Credentials
```yaml
# config/ig_config.yaml (EXEMPLE - NE PAS COMMITER)
ig_credentials:
# Démo
demo:
api_key: "YOUR_DEMO_API_KEY"
username: "YOUR_DEMO_USERNAME"
password: "YOUR_DEMO_PASSWORD"
account_id: "YOUR_DEMO_ACCOUNT_ID"
api_url: "https://demo-api.ig.com/gateway/deal"
# Live (À ACTIVER APRÈS VALIDATION)
live:
api_key: "YOUR_LIVE_API_KEY"
username: "YOUR_LIVE_USERNAME"
password: "YOUR_LIVE_PASSWORD"
account_id: "YOUR_LIVE_ACCOUNT_ID"
api_url: "https://api.ig.com/gateway/deal"
# Lightstreamer
lightstreamer:
demo_url: "https://demo-apd.marketdatasys.com"
live_url: "https://apd.marketdatasys.com"
```
### Étape 3 : Sécuriser Credentials
```python
# src/core/config_manager.py
import os
from cryptography.fernet import Fernet
import yaml
class SecureConfigManager:
"""
Gestion sécurisée des credentials IG
"""
def __init__(self):
# Générer clé encryption (à stocker en variable d'environnement)
self.key = os.getenv('ENCRYPTION_KEY') or Fernet.generate_key()
self.cipher = Fernet(self.key)
def load_ig_credentials(self, environment='demo'):
"""
Charge credentials IG de manière sécurisée
"""
with open('config/ig_config.yaml', 'r') as f:
config = yaml.safe_load(f)
credentials = config['ig_credentials'][environment]
# Décrypter password si encrypté
if credentials['password'].startswith('encrypted:'):
encrypted_password = credentials['password'].replace('encrypted:', '')
credentials['password'] = self.cipher.decrypt(
encrypted_password.encode()
).decode()
return credentials
def encrypt_password(self, password: str) -> str:
"""Encrypte password"""
return 'encrypted:' + self.cipher.encrypt(password.encode()).decode()
```
---
## 🌐 API REST
### Authentification
```python
# src/data/ig_connector.py
import requests
from typing import Dict, Optional
import logging
logger = logging.getLogger(__name__)
class IGRestAPI:
"""
Connecteur IG REST API
"""
def __init__(self, credentials: Dict):
self.api_key = credentials['api_key']
self.username = credentials['username']
self.password = credentials['password']
self.account_id = credentials['account_id']
self.base_url = credentials['api_url']
self.session = requests.Session()
self.access_token = None
self.cst_token = None
self.security_token = None
def authenticate(self) -> bool:
"""
Authentification IG API
Returns:
True si succès, False sinon
"""
url = f"{self.base_url}/session"
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-IG-API-KEY': self.api_key,
'Version': '2'
}
payload = {
'identifier': self.username,
'password': self.password
}
try:
response = self.session.post(url, json=payload, headers=headers)
response.raise_for_status()
# Extraire tokens
self.cst_token = response.headers.get('CST')
self.security_token = response.headers.get('X-SECURITY-TOKEN')
# Sélectionner compte
self._select_account(self.account_id)
logger.info("IG API authentication successful")
return True
except requests.exceptions.RequestException as e:
logger.error(f"IG API authentication failed: {e}")
return False
def _select_account(self, account_id: str):
"""Sélectionne compte de trading"""
url = f"{self.base_url}/session"
headers = self._get_headers()
headers['Version'] = '1'
headers['_method'] = 'PUT'
payload = {
'accountId': account_id,
'defaultAccount': True
}
response = self.session.put(url, json=payload, headers=headers)
response.raise_for_status()
def _get_headers(self) -> Dict:
"""Headers pour requêtes authentifiées"""
return {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-IG-API-KEY': self.api_key,
'CST': self.cst_token,
'X-SECURITY-TOKEN': self.security_token
}
def get_account_info(self) -> Dict:
"""Récupère informations compte"""
url = f"{self.base_url}/accounts"
response = self.session.get(url, headers=self._get_headers())
response.raise_for_status()
return response.json()
def get_market_details(self, epic: str) -> Dict:
"""
Récupère détails d'un marché
Args:
epic: Identifiant marché IG (ex: 'CS.D.EURUSD.MINI.IP')
"""
url = f"{self.base_url}/markets/{epic}"
headers = self._get_headers()
headers['Version'] = '3'
response = self.session.get(url, headers=headers)
response.raise_for_status()
return response.json()
def get_positions(self) -> Dict:
"""Récupère positions ouvertes"""
url = f"{self.base_url}/positions"
headers = self._get_headers()
headers['Version'] = '2'
response = self.session.get(url, headers=headers)
response.raise_for_status()
return response.json()
def place_order(
self,
epic: str,
direction: str,
size: float,
stop_loss: Optional[float] = None,
take_profit: Optional[float] = None,
guaranteed_stop: bool = True
) -> Dict:
"""
Place ordre de trading
Args:
epic: Identifiant marché
direction: 'BUY' ou 'SELL'
size: Taille position
stop_loss: Niveau stop-loss
take_profit: Niveau take-profit
guaranteed_stop: Utiliser guaranteed stop (recommandé)
"""
url = f"{self.base_url}/positions/otc"
headers = self._get_headers()
headers['Version'] = '2'
payload = {
'epic': epic,
'direction': direction,
'size': size,
'orderType': 'MARKET',
'timeInForce': 'FILL_OR_KILL',
'guaranteedStop': guaranteed_stop,
'currencyCode': 'GBP'
}
# Ajouter stop-loss
if stop_loss:
payload['stopLevel'] = stop_loss
payload['stopDistance'] = None # Calculé automatiquement
# Ajouter take-profit
if take_profit:
payload['limitLevel'] = take_profit
payload['limitDistance'] = None
try:
response = self.session.post(url, json=payload, headers=headers)
response.raise_for_status()
result = response.json()
deal_reference = result.get('dealReference')
# Confirmer ordre
return self._confirm_order(deal_reference)
except requests.exceptions.RequestException as e:
logger.error(f"Order placement failed: {e}")
raise
def _confirm_order(self, deal_reference: str) -> Dict:
"""Confirme exécution ordre"""
url = f"{self.base_url}/confirms/{deal_reference}"
headers = self._get_headers()
headers['Version'] = '1'
response = self.session.get(url, headers=headers)
response.raise_for_status()
return response.json()
def close_position(self, deal_id: str) -> Dict:
"""Ferme position"""
# Récupérer détails position
position = self._get_position_details(deal_id)
url = f"{self.base_url}/positions/otc"
headers = self._get_headers()
headers['Version'] = '1'
headers['_method'] = 'DELETE'
payload = {
'dealId': deal_id,
'direction': 'SELL' if position['direction'] == 'BUY' else 'BUY',
'size': position['size'],
'orderType': 'MARKET'
}
response = self.session.delete(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()
def get_historical_prices(
self,
epic: str,
resolution: str = 'MINUTE',
num_points: int = 100
) -> Dict:
"""
Récupère prix historiques
Args:
epic: Identifiant marché
resolution: 'SECOND', 'MINUTE', 'MINUTE_5', 'HOUR', 'DAY'
num_points: Nombre de points (max 1000)
"""
url = f"{self.base_url}/prices/{epic}"
headers = self._get_headers()
headers['Version'] = '3'
params = {
'resolution': resolution,
'max': min(num_points, 1000)
}
response = self.session.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
```
---
## 📡 Streaming Lightstreamer
### Configuration
```python
# src/data/ig_streaming.py
from lightstreamer.client import LightstreamerClient, Subscription
import logging
logger = logging.getLogger(__name__)
class IGStreamingAPI:
"""
Connecteur IG Streaming (Lightstreamer)
Permet de recevoir:
- Prix en temps réel
- Updates positions
- Updates compte
"""
def __init__(self, credentials: Dict, cst_token: str, security_token: str):
self.lightstreamer_url = credentials.get('lightstreamer_url')
self.cst_token = cst_token
self.security_token = security_token
self.account_id = credentials['account_id']
self.client = None
self.subscriptions = {}
def connect(self):
"""Connexion Lightstreamer"""
self.client = LightstreamerClient(self.lightstreamer_url, "DEFAULT")
# Configuration connexion
self.client.connectionDetails.setUser(self.account_id)
self.client.connectionDetails.setPassword(f"CST-{self.cst_token}|XST-{self.security_token}")
# Listeners
self.client.addListener(ConnectionListener())
# Connecter
self.client.connect()
logger.info("Lightstreamer connection established")
def subscribe_market(self, epic: str, callback):
"""
Subscribe à prix temps réel d'un marché
Args:
epic: Identifiant marché
callback: Fonction appelée à chaque update
"""
# Items à subscribe
items = [f"MARKET:{epic}"]
# Fields à recevoir
fields = [
"BID", "OFFER", "HIGH", "LOW",
"MID_OPEN", "CHANGE", "CHANGE_PCT",
"UPDATE_TIME", "MARKET_STATE"
]
# Créer subscription
subscription = Subscription(
mode="MERGE",
items=items,
fields=fields
)
# Ajouter listener
subscription.addListener(MarketDataListener(callback))
# Subscribe
self.client.subscribe(subscription)
self.subscriptions[epic] = subscription
logger.info(f"Subscribed to market: {epic}")
def subscribe_account(self, callback):
"""Subscribe à updates compte"""
items = [f"ACCOUNT:{self.account_id}"]
fields = [
"AVAILABLE_CASH", "PNL", "DEPOSIT",
"USED_MARGIN", "AVAILABLE_TO_DEAL", "EQUITY"
]
subscription = Subscription(
mode="MERGE",
items=items,
fields=fields
)
subscription.addListener(AccountListener(callback))
self.client.subscribe(subscription)
self.subscriptions['account'] = subscription
def unsubscribe(self, key: str):
"""Unsubscribe d'un stream"""
if key in self.subscriptions:
self.client.unsubscribe(self.subscriptions[key])
del self.subscriptions[key]
def disconnect(self):
"""Déconnexion Lightstreamer"""
if self.client:
self.client.disconnect()
logger.info("Lightstreamer disconnected")
class MarketDataListener:
"""Listener pour données marché"""
def __init__(self, callback):
self.callback = callback
def onItemUpdate(self, update):
"""Appelé à chaque update prix"""
data = {
'bid': update.getValue('BID'),
'offer': update.getValue('OFFER'),
'high': update.getValue('HIGH'),
'low': update.getValue('LOW'),
'change': update.getValue('CHANGE'),
'change_pct': update.getValue('CHANGE_PCT'),
'update_time': update.getValue('UPDATE_TIME'),
'market_state': update.getValue('MARKET_STATE')
}
self.callback(data)
class AccountListener:
"""Listener pour updates compte"""
def __init__(self, callback):
self.callback = callback
def onItemUpdate(self, update):
"""Appelé à chaque update compte"""
data = {
'available_cash': update.getValue('AVAILABLE_CASH'),
'pnl': update.getValue('PNL'),
'deposit': update.getValue('DEPOSIT'),
'used_margin': update.getValue('USED_MARGIN'),
'equity': update.getValue('EQUITY')
}
self.callback(data)
class ConnectionListener:
"""Listener pour état connexion"""
def onStatusChange(self, status):
logger.info(f"Lightstreamer status: {status}")
```
---
## 📊 Gestion des Ordres
### Wrapper Unifié
```python
# src/core/order_manager.py
class IGOrderManager:
"""
Gestionnaire d'ordres IG avec validation
"""
def __init__(self, ig_api: IGRestAPI, risk_manager: RiskManager):
self.ig_api = ig_api
self.risk_manager = risk_manager
def execute_signal(self, signal: Signal) -> Optional[str]:
"""
Exécute signal de trading
Returns:
Deal ID si succès, None sinon
"""
# 1. Valider avec risk manager
is_valid, error = self.risk_manager.validate_trade(
symbol=signal.symbol,
quantity=signal.quantity,
price=signal.entry_price,
stop_loss=signal.stop_loss,
take_profit=signal.take_profit,
strategy=signal.strategy
)
if not is_valid:
logger.warning(f"Trade rejected: {error}")
return None
# 2. Convertir symbol en EPIC IG
epic = self._symbol_to_epic(signal.symbol)
# 3. Placer ordre
try:
result = self.ig_api.place_order(
epic=epic,
direction='BUY' if signal.direction == 'LONG' else 'SELL',
size=signal.quantity,
stop_loss=signal.stop_loss,
take_profit=signal.take_profit,
guaranteed_stop=True # Toujours utiliser guaranteed stop
)
deal_id = result.get('dealId')
# 4. Enregistrer position dans risk manager
if deal_id:
self.risk_manager.add_position(Position(
symbol=signal.symbol,
quantity=signal.quantity,
entry_price=signal.entry_price,
current_price=signal.entry_price,
stop_loss=signal.stop_loss,
take_profit=signal.take_profit,
strategy=signal.strategy,
entry_time=datetime.now(),
unrealized_pnl=0.0,
risk_amount=abs(signal.entry_price - signal.stop_loss) * signal.quantity
))
return deal_id
except Exception as e:
logger.error(f"Order execution failed: {e}")
return None
def _symbol_to_epic(self, symbol: str) -> str:
"""
Convertit symbol standard en EPIC IG
Exemples:
- EURUSD → CS.D.EURUSD.MINI.IP
- GBPUSD → CS.D.GBPUSD.MINI.IP
- US500 → IX.D.SPTRD.IFE.IP
"""
# Mapping symbol → EPIC
EPIC_MAP = {
'EURUSD': 'CS.D.EURUSD.MINI.IP',
'GBPUSD': 'CS.D.GBPUSD.MINI.IP',
'USDJPY': 'CS.D.USDJPY.MINI.IP',
'US500': 'IX.D.SPTRD.IFE.IP',
'US30': 'IX.D.DOW.IFE.IP',
'GER40': 'IX.D.DAX.IFE.IP',
}
return EPIC_MAP.get(symbol, symbol)
```
---
## 🛡️ Risk Management IG
### Spécificités IG
```python
class IGRiskCalculator:
"""
Calculs risk spécifiques IG
"""
@staticmethod
def calculate_margin_required(
epic: str,
size: float,
market_details: Dict
) -> float:
"""
Calcule margin requis pour position
IG utilise margin factor variable selon instrument
"""
margin_factor = market_details['dealingRules']['minDealSize']['value']
current_price = market_details['snapshot']['offer']
margin_required = size * current_price * margin_factor
return margin_required
@staticmethod
def calculate_guaranteed_stop_premium(
stop_distance: float,
market_details: Dict
) -> float:
"""
Calcule coût du guaranteed stop
IG facture premium pour guaranteed stops
"""
premium_factor = market_details['dealingRules']['marketOrderPreference']
# Premium généralement 0.3-0.5% du stop distance
premium = stop_distance * 0.003 # 0.3%
return premium
```
---
## 🚀 Migration Progressive
### Plan de Migration
```
┌──────────────────────────────────────────────────────────┐
│ MIGRATION VERS IG MARKETS │
├──────────────────────────────────────────────────────────┤
│ │
│ PHASE 1: Développement (Semaines 1-8) │
│ └─ Sources gratuites (Yahoo, Alpha Vantage) │
│ │
│ PHASE 2: Tests IG Démo (Semaines 9-10) │
│ ├─ Connexion API IG démo │
│ ├─ Paper trading 30 jours │
│ └─ Validation performance │
│ │
│ PHASE 3: Live Progressif (Semaine 11+) │
│ ├─ Capital initial minimal (£500) │
│ ├─ 1 stratégie uniquement │
│ ├─ Monitoring 24/7 │
│ └─ Scale progressif si succès │
│ │
└──────────────────────────────────────────────────────────┘
```
---
**Documentation IG Markets complète !**