""" AHAD QUANT — Configuration (Forex Edition) Loads all settings from environment variables with sensible defaults. """ import os from dotenv import load_dotenv load_dotenv(override=True) # ─── Exchange / Broker selection ───────────────────────────────────────────── # Valeurs acceptées : oanda | mt5 | alpaca | ccxt | ib | paper EXCHANGE: str = os.getenv("EXCHANGE", "oanda") # ─── Source de données historiques (INDÉPENDANTE du broker d'exécution) ─────── # auto → OANDA si clé disponible, sinon yfinance # oanda → OANDA v20 REST API (clé requise) # yfinance → Yahoo Finance (gratuit, sans clé, 2 ans max) # twelvedata → Twelve Data (800 req/j gratuit, clé recommandée) # alphavantage→ Alpha Vantage (25 req/j gratuit) # mt5 → MT5 Python SDK (Windows uniquement) # ccxt → via broker CCXT_BROKER DATA_SOURCE: str = os.getenv("DATA_SOURCE", "auto") # ─── Paper Mode (simulate trades, no real money) ───────────────────────────── PAPER_MODE: bool = os.getenv("PAPER_MODE", "false").lower() == "true" PAPER_INITIAL_BALANCE: float = float(os.getenv("PAPER_INITIAL_BALANCE", "100.0")) # ─── OANDA credentials (primary Forex broker) ──────────────────────────────── OANDA_API_KEY: str = os.getenv("OANDA_API_KEY", "") OANDA_ACCOUNT_ID: str = os.getenv("OANDA_ACCOUNT_ID", "") OANDA_PRACTICE: bool = os.getenv("OANDA_PRACTICE", "true").lower() == "true" # ─── MetaTrader 5 credentials ───────────────────────────────────────────────── MT5_LOGIN: int = int(os.getenv("MT5_LOGIN", "0")) MT5_PASSWORD: str = os.getenv("MT5_PASSWORD", "") MT5_SERVER: str = os.getenv("MT5_SERVER", "") # ─── MT5 CSV Bridge ──────────────────────────────────────────────────────────── # Activer le bridge CSV pour connecter AHAD QUANT à un EA MetaTrader 5 # Mettre EXCHANGE=mt5 dans .env pour utiliser ce mode MT5_BRIDGE_ENABLED: bool = os.getenv("MT5_BRIDGE_ENABLED", "false").lower() == "true" MT5_FILES_PATH: str = os.getenv("MT5_FILES_PATH", "") # Chemin vers MQL5/Files/ MT5_POLL_INTERVAL: float = float(os.getenv("MT5_POLL_INTERVAL", "0.2")) # secondes MT5_SIGNAL_TIMEOUT: int = int(os.getenv("MT5_SIGNAL_TIMEOUT", "300")) # secondes (5 min) # Suffixe symbole MT5 — certains brokers ajoutent .m, .r, .pro, etc. # Ex: ICMarkets, Pepperstone → "USDJPY.m" | La plupart → "" MT5_SYMBOL_SUFFIX: str = os.getenv("MT5_SYMBOL_SUFFIX", "") # Ex: ".m" # ─── Alpaca Markets (paper + live, API REST, très accessible) ──────────────── # Paper trading : https://paper-api.alpaca.markets # Live trading : https://api.alpaca.markets ALPACA_API_KEY: str = os.getenv("ALPACA_API_KEY", "") ALPACA_SECRET: str = os.getenv("ALPACA_SECRET", "") ALPACA_PAPER: bool = os.getenv("ALPACA_PAPER", "true").lower() == "true" # ─── CCXT — broker générique (IG, GAIN Capital, Binance, Bybit, etc.) ──────── # Définir EXCHANGE=ccxt puis CCXT_BROKER=nom_du_broker (ex: "ig", "okcoin") CCXT_BROKER: str = os.getenv("CCXT_BROKER", "") CCXT_API_KEY: str = os.getenv("CCXT_API_KEY", "") CCXT_API_SECRET: str = os.getenv("CCXT_API_SECRET", "") CCXT_PASSPHRASE: str = os.getenv("CCXT_PASSPHRASE", "") CCXT_SANDBOX: bool = os.getenv("CCXT_SANDBOX", "false").lower() == "true" # ─── Twelve Data (données historiques — 800 req/j gratuit) ─────────────────── # Inscription : https://twelvedata.com/ (plan Free suffisant pour backtests) TWELVE_DATA_API_KEY: str = os.getenv("TWELVE_DATA_API_KEY", "") # ─── Alpha Vantage (données historiques — 25 req/j gratuit) ────────────────── # Inscription : https://www.alphavantage.co/support/#api-key ALPHA_VANTAGE_API_KEY: str = os.getenv("ALPHA_VANTAGE_API_KEY", "") # ─── Interactive Brokers (TWS / IB Gateway) ─────────────────────────────────── IB_HOST: str = os.getenv("IB_HOST", "127.0.0.1") IB_PORT: int = int(os.getenv("IB_PORT", "7497")) IB_CLIENT_ID: int = int(os.getenv("IB_CLIENT_ID", "1")) # ─── Fee rates (Forex spread equivalent) ───────────────────────────────────── # ~0.2 pip for majors on ECN accounts ≈ 0.00002 (2 pips round-trip = 0.00004) FEE_RATE: float = float(os.getenv("FEE_RATE", "0.00004")) MAKER_FEE_RATE: float = float(os.getenv("MAKER_FEE_RATE", "0.00002")) # ─── Trading parameters ─────────────────────────────────────────────────────── # ESMA: 30:1 majors, 20:1 minors/gold, 10:1 commodities # Offshore brokers: up to 500:1 — use responsibly LEVERAGE: int = int(os.getenv("LEVERAGE", "30")) MAX_POSITIONS: int = int(os.getenv("MAX_POSITIONS", "5")) RISK_PER_TRADE: float = float(os.getenv("RISK_PER_TRADE", "0.01")) # 1% of equity (Forex standard) MAX_DAILY_LOSS_PCT: float = float(os.getenv("MAX_DAILY_LOSS_PCT", "0.03")) # ─── Forex lot sizing ───────────────────────────────────────────────────────── # 1 standard lot = 100,000 units of base currency # 1 mini lot = 10,000 units # 1 micro lot = 1,000 units UNITS_PER_LOT: int = 100_000 MIN_LOT_SIZE: float = float(os.getenv("MIN_LOT_SIZE", "0.01")) # micro lot MAX_LOT_SIZE: float = float(os.getenv("MAX_LOT_SIZE", "10.0")) # 10 standard lots # ─── Risk management ────────────────────────────────────────────────────────── STOP_LOSS_PCT: float = float(os.getenv("STOP_LOSS_PCT", "0.0050")) # 50 pips on EUR/USD ≈ 0.5% TAKE_PROFIT_PCT: float = float(os.getenv("TAKE_PROFIT_PCT", "0.0075")) # 75 pips — R:R 1:1.5 CIRCUIT_BREAKER_LOSSES: int = int(os.getenv("CIRCUIT_BREAKER_LOSSES", "3")) CIRCUIT_BREAKER_COOLDOWN: int = int(os.getenv("CIRCUIT_BREAKER_COOLDOWN", "3600")) # ─── Multi-target TP (ATR-based) ────────────────────────────────────────────── MULTI_TP_ENABLED: bool = os.getenv("MULTI_TP_ENABLED", "true").lower() == "true" TP1_ATR_MULT: float = float(os.getenv("TP1_ATR_MULT", "0.8")) TP2_ATR_MULT: float = float(os.getenv("TP2_ATR_MULT", "1.3")) # ─── Auto-Unstuck ───────────────────────────────────────────────────────────── AUTO_UNSTUCK_ENABLED: bool = os.getenv("AUTO_UNSTUCK_ENABLED", "true").lower() == "true" _UNSTUCK_DEFAULT = "[[-0.02, 0.25], [-0.03, 0.25], [-0.04, 0.25], [-0.05, 1.0]]" try: import json as _json UNSTUCK_LEVELS: list = [tuple(x) for x in _json.loads(os.getenv("UNSTUCK_LEVELS", _UNSTUCK_DEFAULT))] except Exception: UNSTUCK_LEVELS: list = [(-0.02, 0.25), (-0.03, 0.25), (-0.04, 0.25), (-0.05, 1.0)] # ─── Model / AI ─────────────────────────────────────────────────────────────── MODEL_PATH: str = os.getenv("MODEL_PATH", "model.pkl") ENSEMBLE_MODEL_PATH: str = os.getenv("ENSEMBLE_MODEL_PATH", "model_ensemble.pkl") MIN_CONFIDENCE: float = float(os.getenv("MIN_CONFIDENCE", "0.72")) USE_ENSEMBLE: bool = os.getenv("USE_ENSEMBLE", "true").lower() == "true" USE_REGIME_FILTER: bool = os.getenv("USE_REGIME_FILTER", "false").lower() == "true" # ─── Sizing & capital controls ──────────────────────────────────────────────── COMPOUND_ENABLED: bool = os.getenv("COMPOUND_ENABLED", "true").lower() == "true" MAX_MARGIN_USAGE: float = float(os.getenv("MAX_MARGIN_USAGE", "0.20")) CAPITAL_CAP_PER_TRADE: float = float(os.getenv("CAPITAL_CAP_PER_TRADE", "1000000")) # ─── Timeout / hold duration ────────────────────────────────────────────────── TIMEOUT_ENABLED: bool = os.getenv("TIMEOUT_ENABLED", "true").lower() == "true" MAX_HOLD_CANDLES: int = int(os.getenv("MAX_HOLD_CANDLES", "3")) # ─── Auto-Retraining ────────────────────────────────────────────────────────── # AUTO_RETRAIN_ENABLED/AUTO_RETRAIN_INTERVAL_HOURS gouvernent UNIQUEMENT le # ré-entraînement complet (download_data.py + train.py) — lourd, dépendant du # réseau (téléchargement de nouvelles bougies), donc volontairement PLUS # automatique par défaut (reste disponible en appel manuel). AUTO_RETRAIN_ENABLED: bool = os.getenv("AUTO_RETRAIN_ENABLED", "false").lower() == "true" AUTO_RETRAIN_INTERVAL_HOURS: int = int(os.getenv("AUTO_RETRAIN_INTERVAL_HOURS", "24")) AUTO_RETRAIN_MIN_ACCURACY: float = float(os.getenv("AUTO_RETRAIN_MIN_ACCURACY", "0.55")) # DAILY_LOCAL_RETRAIN_ENABLED gouverne le cycle UNIFIÉ quotidien et 100% LOCAL # (aucun téléchargement réseau) : warm-start ML + fine-tune RL, tous deux sur # les données déjà sur disque + le buffer de trades réels, avec acceptation # uniquement si le nouveau modèle est meilleur (sinon l'ancien est conservé). # C'est ce cycle qui tourne automatiquement en arrière-plan par défaut. DAILY_LOCAL_RETRAIN_ENABLED: bool = os.getenv("DAILY_LOCAL_RETRAIN_ENABLED", "true").lower() == "true" DAILY_LOCAL_RETRAIN_INTERVAL_HOURS: int = int(os.getenv("DAILY_LOCAL_RETRAIN_INTERVAL_HOURS", "24")) # ─── Grid Bot ───────────────────────────────────────────────────────────────── GRID_BOT_ENABLED: bool = os.getenv("GRID_BOT_ENABLED", "false").lower() == "true" GRID_PAIR: str = os.getenv("GRID_PAIR", "EURUSD") GRID_COIN: str = GRID_PAIR # backward-compat alias GRID_STRATEGY: str = os.getenv("GRID_STRATEGY", "neutral") GRID_LOWER: float = float(os.getenv("GRID_LOWER", "0")) GRID_UPPER: float = float(os.getenv("GRID_UPPER", "0")) GRID_LEVELS: int = int(os.getenv("GRID_LEVELS", "10")) GRID_TOTAL_USDT: float = float(os.getenv("GRID_TOTAL_USDT", "1000")) GRID_LEVERAGE: int = int(os.getenv("GRID_LEVERAGE", "10")) # ─── DCA Bot ────────────────────────────────────────────────────────────────── DCA_BOT_ENABLED: bool = os.getenv("DCA_BOT_ENABLED", "false").lower() == "true" DCA_PAIR: str = os.getenv("DCA_PAIR", "EURUSD") DCA_COIN: str = DCA_PAIR # backward-compat alias DCA_STRATEGY: str = os.getenv("DCA_STRATEGY", "classic") DCA_BASE_ORDER_USDT: float = float(os.getenv("DCA_BASE_ORDER_USDT", "100")) DCA_SAFETY_ORDER_USDT: float = float(os.getenv("DCA_SAFETY_ORDER_USDT", "50")) DCA_MAX_SAFETY_ORDERS: int = int(os.getenv("DCA_MAX_SAFETY_ORDERS", "5")) DCA_PRICE_DEVIATION: float = float(os.getenv("DCA_PRICE_DEVIATION", "0.0020")) # 20 pips DCA_TAKE_PROFIT_PCT: float = float(os.getenv("DCA_TAKE_PROFIT_PCT", "0.0030")) # 30 pips # ─── Session Scanner (replaces Pump Scanner in Forex) ───────────────────────── PUMP_SCANNER_ENABLED: bool = os.getenv("SESSION_SCANNER_ENABLED", "false").lower() == "true" PUMP_VOLUME_MULT: float = float(os.getenv("BREAKOUT_VOLUME_MULT", "2.0")) PUMP_PRICE_PCT: float = float(os.getenv("BREAKOUT_PRICE_PCT", "0.003")) # 30 pips PUMP_LEVERAGE: int = int(os.getenv("BREAKOUT_LEVERAGE", "10")) PUMP_RISK_PCT: float = float(os.getenv("BREAKOUT_RISK_PCT", "0.01")) # ─── Session filter ─────────────────────────────────────────────────────────── # Trade only during high-liquidity Forex sessions (UTC hours) SESSION_FILTER_ENABLED: bool = os.getenv("SESSION_FILTER_ENABLED", "false").lower() == "true" # London: 07:00-16:00 UTC | New York: 12:00-21:00 UTC | Overlap: 12:00-16:00 SESSION_LONDON_START: int = int(os.getenv("SESSION_LONDON_START", "7")) SESSION_LONDON_END: int = int(os.getenv("SESSION_LONDON_END", "16")) SESSION_NY_START: int = int(os.getenv("SESSION_NY_START", "12")) SESSION_NY_END: int = int(os.getenv("SESSION_NY_END", "21")) # ─── Data ───────────────────────────────────────────────────────────────────── CANDLE_INTERVAL: str = os.getenv("CANDLE_INTERVAL", "1h") DATA_DIR: str = os.getenv("DATA_DIR", "data") # ─── Forex pairs to trade ────────────────────────────────────────────────────── # Majors (USD pairs) # Crosses (non-USD) _PAIRS_DEFAULT = ( "EURUSD,GBPUSD,USDJPY,USDCHF,AUDUSD,NZDUSD,USDCAD," "EURGBP,EURJPY,EURCAD,EURCHF,EURAUD,EURNZD," "GBPJPY,GBPCAD,GBPCHF,GBPAUD," "AUDCAD,AUDNZD,AUDJPY,AUDCHF," "CADJPY,CHFJPY,NZDJPY,NZDCAD" ) PAIRS: list[str] = [p.strip() for p in os.getenv("PAIRS", _PAIRS_DEFAULT).split(",") if p.strip()] # Backward-compat alias (backtest.py, train.py, pump_scanner.py use config.COINS) COINS: list[str] = PAIRS # ─── Loop timing ────────────────────────────────────────────────────────────── MAIN_LOOP_SECONDS: int = int(os.getenv("MAIN_LOOP_SECONDS", "60")) # ─── Reinforcement Learning (RL Agent) ─────────────────────────────────────── # Activer le filtre RL (nécessite rl_agent.zip + rl_scaler.pkl entraînés) USE_RL_AGENT: bool = os.getenv("USE_RL_AGENT", "false").lower() == "true" # Chemin vers le modèle PPO sauvegardé par rl_train.py RL_MODEL_PATH: str = os.getenv("RL_MODEL_PATH", "rl_agent") UNIFIED_MODEL_PATH: str = os.getenv("UNIFIED_MODEL_PATH", "ahad_quant_unified.zip") # Chemin vers le scaler (mean/std features) sauvegardé par rl_train.py RL_SCALER_PATH: str = os.getenv("RL_SCALER_PATH", "rl_scaler.pkl") # Mode du filtre RL : # "filter" → RL valide/rejette les signaux ML (recommandé en production) # "override" → RL génère ses propres signaux, ML ignoré (expérimental) RL_MODE: str = os.getenv("RL_MODE", "filter") # Confidence boost quand RL et ML sont en accord (+5% par défaut) RL_CONFIDENCE_BOOST: float = float(os.getenv("RL_CONFIDENCE_BOOST", "0.05")) # Seuil ML pour override le NEUTRAL RL (signal passe même si RL dit HOLD) RL_OVERRIDE_THRESHOLD: float = float(os.getenv("RL_OVERRIDE_THRESHOLD", "0.82")) # Re-entraînement RL hebdomadaire (fine-tuning sur nouvelles données) RL_AUTO_RETRAIN_ENABLED: bool = os.getenv("RL_AUTO_RETRAIN_ENABLED", "true").lower() == "true" RL_RETRAIN_INTERVAL_HOURS: int = int(os.getenv("RL_RETRAIN_INTERVAL_HOURS", "24")) # 1 jour (était 7 jours) RL_FINETUNE_STEPS: int = int(os.getenv("RL_FINETUNE_STEPS", "200000")) # 200k steps # ─── Apprentissage Continu des Erreurs (V7) ─────────────────────────────────── # Système d'apprentissage permanent à partir de chaque trade fermé. # Comprend : replay buffer, warm-start LGB quotidien, calibration seuil, # injection trades réels dans PPO, monitoring drift. # Activer/désactiver tout le système d'apprentissage continu CONTINUOUS_LEARNING_ENABLED: bool = os.getenv("CONTINUOUS_LEARNING_ENABLED", "true").lower() == "true" # Taille max du replay buffer (trades, FIFO) EXPERIENCE_BUFFER_MAX_SIZE: int = int(os.getenv("EXPERIENCE_BUFFER_MAX_SIZE", "10000")) # LightGBM warm-start quotidien sur les trades LOSS + TIMEOUT WARMSTART_ENABLED: bool = os.getenv("WARMSTART_ENABLED", "true").lower() == "true" # Nombre minimum de trades échoués pour déclencher le warm-start WARMSTART_MIN_TRADES: int = int(os.getenv("WARMSTART_MIN_TRADES", "5")) # [FIX] 20→5 : ne plus bloquer en phase de bonne performance # Injection des vrais trades dans le fine-tune PPO quotidien # [21/06/2026] Devenu le SEUL mode d'entraînement RL du cycle quotidien : # fine_tune_real_only() s'entraîne à 100% sur ces trades réels, zéro # simulation. RL_REPLAY_MIN_TRADES = volume minimum requis avant de # lancer ce fine-tune ; sous ce seuil, le cycle RL est skip (pas de repli # sur AhadQuantForexEnv). RL_REAL_REPLAY_ENABLED: bool = os.getenv("RL_REAL_REPLAY_ENABLED", "true").lower() == "true" RL_REPLAY_MIN_TRADES: int = int(os.getenv("RL_REPLAY_MIN_TRADES", "10")) # [FIX] 50→10 : ne plus bloquer au démarrage du bot # Surveillance des métriques (drift, alertes, pause auto) MONITOR_ENABLED: bool = os.getenv("MONITOR_ENABLED", "true").lower() == "true" # Pause auto du trading si win_rate < 35% (basculement PAPER_MODE=true) EMERGENCY_PAUSE_ENABLED: bool = os.getenv("EMERGENCY_PAUSE_ENABLED", "true").lower() == "true"