# 🔌 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 !**