171 lines
5.6 KiB
Python
171 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
# ============================================================
|
|
# evaluate_model.py — Génère un rapport sur le modèle entraîné
|
|
# ============================================================
|
|
|
|
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
import logging
|
|
import config
|
|
from mt5_connector import MT5Connector
|
|
from trading_env import XAUUSDTradingEnv
|
|
from ppo_agent import PPOAgent
|
|
from macro_features import MacroFeaturesModule
|
|
import numpy as np
|
|
import torch
|
|
import time
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s | %(levelname)-8s | %(message)s"
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def evaluate_agent(agent, env, n_episodes=5):
|
|
"""Évalue l'agent sur n épisodes."""
|
|
rewards = []
|
|
win_rates = []
|
|
pnls = []
|
|
|
|
for ep in range(n_episodes):
|
|
obs, info = env.reset()
|
|
ep_reward = 0
|
|
ep_pnl = 0
|
|
ep_trades = 0
|
|
ep_wins = 0
|
|
|
|
done = False
|
|
step = 0
|
|
while not done and step < 1000:
|
|
with torch.no_grad():
|
|
action, _, _ = agent.predict(obs, deterministic=True)
|
|
result = env.step(action)
|
|
# Gymnasium retourne 5 valeurs, ancienne Gym en retourne 4
|
|
if len(result) == 5:
|
|
obs, reward, terminated, truncated, info = result
|
|
done = terminated or truncated
|
|
else:
|
|
obs, reward, done, info = result
|
|
ep_reward += reward
|
|
ep_pnl += info.get("pnl", 0)
|
|
if info.get("trade_executed"):
|
|
ep_trades += 1
|
|
if info.get("pnl", 0) > 0:
|
|
ep_wins += 1
|
|
step += 1
|
|
|
|
wr = (ep_wins / max(ep_trades, 1)) * 100
|
|
rewards.append(ep_reward)
|
|
pnls.append(ep_pnl)
|
|
win_rates.append(wr)
|
|
logger.info(f"Episode {ep+1}/{n_episodes}: Reward={ep_reward:.2f}, PnL={ep_pnl:.2f}$, WR={wr:.1f}%")
|
|
|
|
return {
|
|
"mean_reward": np.mean(rewards),
|
|
"std_reward": np.std(rewards),
|
|
"mean_pnl": np.mean(pnls),
|
|
"mean_winrate": np.mean(win_rates) / 100,
|
|
}
|
|
|
|
def main():
|
|
print("\n" + "=" * 70)
|
|
print("RAPPORT D'ÉVALUATION DU MODÈLE PPO XAUUSD")
|
|
print("=" * 70 + "\n")
|
|
|
|
# 1. Connexion MT5 et données
|
|
logger.info("Connexion à MetaTrader5...")
|
|
mt5 = MT5Connector()
|
|
if not mt5.connect():
|
|
logger.error("Impossible de se connecter à MT5")
|
|
return
|
|
|
|
logger.info("Téléchargement des données historiques...")
|
|
df_bars = mt5.get_historical_data(years=config.TRAINING_YEARS)
|
|
if df_bars is None or len(df_bars) < config.LOOKBACK_BARS:
|
|
logger.error("Données insuffisantes")
|
|
return
|
|
|
|
# 2. Split train/val
|
|
n_train = int(len(df_bars) * 0.85)
|
|
df_train = df_bars.iloc[:n_train]
|
|
df_val = df_bars.iloc[n_train:]
|
|
|
|
logger.info(f"📊 Train: {len(df_train)} barres | Val: {len(df_val)} barres")
|
|
|
|
# 3. Environnements
|
|
logger.info("Création des environnements...")
|
|
macro_mod = MacroFeaturesModule()
|
|
macro_mod.start()
|
|
|
|
env_train = XAUUSDTradingEnv(df_train, lookback=config.LOOKBACK_BARS, sentiment_score=0.0)
|
|
env_val = XAUUSDTradingEnv(df_val, lookback=config.LOOKBACK_BARS, sentiment_score=0.0)
|
|
|
|
# 4. Agent
|
|
logger.info("Chargement du modèle...")
|
|
obs_size = env_train.observation_space.shape[0]
|
|
agent = PPOAgent(obs_size=obs_size, n_actions=4)
|
|
|
|
try:
|
|
agent.load(config.MODEL_PATH)
|
|
logger.info(f"✅ Modèle chargé : {config.MODEL_PATH}")
|
|
except Exception as e:
|
|
logger.error(f"Erreur chargement : {e}")
|
|
return
|
|
|
|
# 5. Évaluation
|
|
print("\n--- ÉVALUATION IN-SAMPLE (Train) ---")
|
|
eval_train = evaluate_agent(agent, env_train, n_episodes=3)
|
|
|
|
print("\n--- ÉVALUATION OUT-OF-SAMPLE (Val, données non vues) ---")
|
|
eval_val = evaluate_agent(agent, env_val, n_episodes=5)
|
|
|
|
# 6. Profit Factor
|
|
pf_ratio = "N/A"
|
|
try:
|
|
wins = eval_val["mean_pnl"] * eval_val["mean_winrate"]
|
|
loss = abs(eval_val["mean_pnl"]) * (1 - eval_val["mean_winrate"])
|
|
pf = wins / (loss + 1e-8)
|
|
pf_ratio = f"{pf:.2f}"
|
|
except:
|
|
pass
|
|
|
|
overfitting_gap = eval_train["mean_reward"] - eval_val["mean_reward"]
|
|
|
|
# 7. Rapport
|
|
print("\n" + "=" * 70)
|
|
print(" RÉSUMÉ FINAL")
|
|
print("=" * 70)
|
|
print(f" Modèle : {config.MODEL_PATH}")
|
|
print(f" Taille observation : {obs_size:,}")
|
|
print(f"")
|
|
print(f" -- IN-SAMPLE (train) --")
|
|
print(f" Reward moyen : {eval_train['mean_reward']:+.3f} (±{eval_train['std_reward']:.3f})")
|
|
print(f" Win Rate : {eval_train['mean_winrate']*100:.1f}%")
|
|
print(f"")
|
|
print(f" -- OUT-OF-SAMPLE (val, données non vues) --")
|
|
print(f" Reward moyen : {eval_val['mean_reward']:+.3f}")
|
|
print(f" PnL moyen / épisode : {eval_val['mean_pnl']:+.2f}$")
|
|
print(f" Win Rate : {eval_val['mean_winrate']*100:.1f}%")
|
|
print(f" Profit Factor : {pf_ratio}")
|
|
print(f"")
|
|
print(f" Overfitting gap : {overfitting_gap:.2f} (< 5 = bon)")
|
|
print("=" * 70)
|
|
|
|
if overfitting_gap > 10:
|
|
print(" ⚠️ ATTENTION : Overfitting détecté (gap train/val > 10)")
|
|
elif eval_val["mean_winrate"] > 0.45:
|
|
print(" ✅ Modèle PRÊT pour le live trading !")
|
|
else:
|
|
print(" ⚠️ Continuer l'entraînement ou ajuster la reward")
|
|
|
|
print("\n Prochaine étape: python live_bot.py\n")
|
|
print("=" * 70 + "\n")
|
|
|
|
macro_mod.stop()
|
|
mt5.disconnect()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|