271 lines
18 KiB
Python
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"
|