Initial commit - AHAD QUANT v1
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
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"
|
||||
Reference in New Issue
Block a user