Files
2026-06-25 14:00:20 +03:00

271 lines
18 KiB
Python

"""
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"