Files

1659 lines
1.3 MiB
Python
Raw Permalink Normal View History

2025-10-05 07:52:39 +02:00
{
"cells": [
{
"cell_type": "markdown",
"id": "b6f1606b",
"metadata": {},
"source": [
"# 3) Backtest — Walk-forward Evaluation\n",
"\n",
"Loads saved model and evaluates on the hold-out split using the same minimal env."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4222de5d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found 3 model(s):\n",
" - ppo_EURUSD_M15.zip (algo=PPO)\n",
" - a2c_EURUSD_M15.zip (algo=A2C)\n",
" - dqn_EURUSD_M15.zip (algo=DQN)\n",
"\n",
"Default selected for single-model cells: ppo_EURUSD_M15.zip | algo=PPO\n",
"Test set: (9958, 17)\n"
]
}
],
"source": [
"# ---- Path & imports ----\n",
"import sys, os\n",
"sys.path.insert(0, os.path.abspath('..')) # Path fix\n",
"\n",
"import os, json\n",
"from pathlib import Path\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# SB3 algos (multi-algo support)\n",
"from stable_baselines3 import PPO, A2C, DQN\n",
"ALGO_MAP = {\"ppo\": PPO, \"a2c\": A2C, \"dqn\": DQN}\n",
"\n",
"import gymnasium as gym\n",
"from gymnasium import spaces\n",
"from dotenv import load_dotenv\n",
"\n",
"import features\n",
"\n",
"# ---- helpers ----\n",
"def infer_algo_from_name(p: Path) -> str:\n",
" name = p.stem.lower()\n",
" for k in ALGO_MAP:\n",
" if name.startswith(k + \"_\"):\n",
" return k\n",
" return \"ppo\"\n",
"\n",
"def collect_model_candidates(models_dir: Path, symbol: str, timeframe: str, algo: str, explicit: str | None = None):\n",
" # If user explicitly passed a filename, use only that\n",
" if explicit:\n",
" p = Path(explicit)\n",
" if not p.is_absolute():\n",
" p = models_dir / p\n",
" return [p] if p.exists() else []\n",
"\n",
" cands = []\n",
" if algo in ALGO_MAP: # specific algo requested\n",
" cands += list(models_dir.glob(f\"{algo.lower()}_{symbol}_{timeframe}.zip\"))\n",
" # fallback: any file for that algo\n",
" if not cands:\n",
" cands += list(models_dir.glob(f\"{algo.lower()}_*.zip\"))\n",
" else:\n",
" # auto/all: try perfect matches for all algos first\n",
" for a in ALGO_MAP:\n",
" cands += list(models_dir.glob(f\"{a}_{symbol}_{timeframe}.zip\"))\n",
" # then looser matches for this symbol/timeframe\n",
" if not cands:\n",
" cands += list(models_dir.glob(f\"*_{symbol}_{timeframe}.zip\"))\n",
" # finally, anything under models/\n",
" if not cands:\n",
" cands += list(models_dir.glob(\"*.zip\"))\n",
"\n",
" # de-dup while preserving order\n",
" seen = set()\n",
" out = []\n",
" for p in cands:\n",
" if p not in seen:\n",
" out.append(p)\n",
" seen.add(p)\n",
" return out\n",
"\n",
"# ---- config/env ----\n",
"load_dotenv()\n",
"SYMBOL = os.getenv(\"TRAINING_SYMBOL\", \"EURUSD\")\n",
"TIMEFRAME = os.getenv(\"TIMEFRAME\", \"M15\")\n",
"SPLIT_RATIO = float(os.getenv(\"SPLIT_RATIO\", \"0.8\"))\n",
"ALGO = os.getenv(\"ALGO\", \"auto\").lower() # \"auto\" | \"all\" | \"ppo\" | \"a2c\" | \"dqn\"\n",
"MODEL_FILE = os.getenv(\"MODEL_FILE\", \"\").strip() # optional explicit filename\n",
"\n",
"# ---- paths ----\n",
"DATA_CSV = Path(\"data\") / f\"ohlc_{SYMBOL}_{TIMEFRAME}.csv\"\n",
"FEAT_JSON = Path(\"models\") / \"selected_features.json\"\n",
"MODELS_DIR = Path(\"models\")\n",
"\n",
"# ---- resolve models ----\n",
"MODEL_PATHS = collect_model_candidates(MODELS_DIR, SYMBOL, TIMEFRAME, ALGO, explicit=MODEL_FILE)\n",
"assert DATA_CSV.exists(), \"Data CSV missing. Run 1_Data.ipynb\"\n",
"assert FEAT_JSON.exists(), \"selected_features.json missing. Run 2_Train.ipynb\"\n",
"assert MODEL_PATHS, (\n",
" f\"No model zips found in {MODELS_DIR}/ for {SYMBOL} {TIMEFRAME}. \"\n",
" f\"Train first, or set MODEL_FILE env to a specific zip.\"\n",
")\n",
"\n",
"# Build (path, algo) bundle and pick a default for legacy cells\n",
"MODEL_BUNDLE = [(p, infer_algo_from_name(p)) for p in MODEL_PATHS]\n",
"MODEL_PATH, ALGO_SELECTED = MODEL_BUNDLE[0]\n",
"\n",
"# ---- report ----\n",
"print(f\"Found {len(MODEL_BUNDLE)} model(s):\")\n",
"for p, a in MODEL_BUNDLE:\n",
" print(f\" - {p.name} (algo={a.upper()})\")\n",
"print(f\"\\nDefault selected for single-model cells: {MODEL_PATH.name} | algo={ALGO_SELECTED.upper()}\")\n",
"\n",
"# ---- load data & features ----\n",
"df = pd.read_csv(DATA_CSV, parse_dates=[\"time\"], index_col=\"time\")\n",
"df_feat = features.add_indicators(df.copy())\n",
"with open(FEAT_JSON, \"r\", encoding=\"utf-8\") as f:\n",
" feature_cols = json.load(f)\n",
"\n",
"# test split\n",
"n_split = int(len(df_feat) * SPLIT_RATIO)\n",
"df_test = df_feat.iloc[n_split:].copy()\n",
"print(\"Test set:\", df_test.shape)\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "8cc0ed72",
"metadata": {},
"outputs": [],
"source": [
"# same env as training (mapping: 0=SELL, 1=HOLD, 2=BUY)\n",
"class TradingEnv(gym.Env):\n",
" \"\"\"\n",
" Discrete(3) trading environment:\n",
" action 0 -> position -1 (SELL)\n",
" action 1 -> position 0 (HOLD/FLAT)\n",
" action 2 -> position +1 (BUY)\n",
" Reward = position * return - trade_cost_on_switch\n",
" \"\"\"\n",
" metadata = {\"render_modes\": []}\n",
"\n",
" def __init__(self, df_feat, feature_cols, trade_cost=1e-4):\n",
" super().__init__()\n",
" import numpy as np\n",
" from gymnasium import spaces\n",
"\n",
" self.df = df_feat\n",
" self.cols = feature_cols\n",
" self.trade_cost = float(trade_cost)\n",
"\n",
" self.n = len(self.df)\n",
" self.idx = 0\n",
" self.position = 0\n",
"\n",
" self.observation_space = spaces.Box(\n",
" low=-np.inf, high=np.inf, shape=(len(self.cols),), dtype=np.float32\n",
" )\n",
" self.action_space = spaces.Discrete(3)\n",
"\n",
" # explicit mapping to avoid ambiguity\n",
" self.ACTION_TO_POS = {0: -1, 1: 0, 2: 1}\n",
"\n",
" def _obs(self):\n",
" row = self.df.iloc[self.idx][self.cols].astype(float).values\n",
" return row.astype(np.float32)\n",
"\n",
" def reset(self, seed=None, options=None):\n",
" super().reset(seed=seed)\n",
" # start at 1 so we can compute ret using idx-1\n",
" self.idx = 1\n",
" self.position = 0\n",
" return self._obs(), {}\n",
"\n",
" def step(self, action):\n",
" # map action to target position\n",
" pos_new = self.ACTION_TO_POS.get(int(action), 0)\n",
"\n",
" prev = float(self.df[\"close\"].iloc[self.idx - 1])\n",
" curr = float(self.df[\"close\"].iloc[self.idx])\n",
" ret = (curr - prev) / (prev + 1e-12)\n",
"\n",
" # apply cost only when we change position\n",
" cost = self.trade_cost if pos_new != self.position else 0.0\n",
" reward = pos_new * ret - cost\n",
"\n",
" # advance state\n",
" self.position = pos_new\n",
" self.idx += 1\n",
" truncated = self.idx >= (self.n - 1)\n",
"\n",
" return self._obs(), float(reward), False, truncated, {\n",
" \"ret\": ret,\n",
" \"position\": self.position\n",
" }\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4019140f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Backtesting ppo_EURUSD_M15.zip (algo=PPO) ===\n",
"Sharpe (rough): 0.897 | MaxDD: -3.73%\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1000x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Backtesting a2c_EURUSD_M15.zip (algo=A2C) ===\n",
"Sharpe (rough): -1.843 | MaxDD: -7.80%\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1000x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Backtesting dqn_EURUSD_M15.zip (algo=DQN) ===\n",
"Sharpe (rough): -11.667 | MaxDD: -29.24%\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA00AAAGACAYAAABx6olvAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAb/tJREFUeJzt3QeYU8XawPF3+7LA0ntbepcqvffyKWBDUUEUUK9YwAaIFBGwInoviA0LXhUL6lW6SJFeRXpHOix1YWF7vuedNSHZzdZkS7L/n88xJycnJ5NkNpz3zMw7PhaLxSIAAAAAAKd8nW8GAAAAACiCJgAAAABIBUETAAAAAKSCoAkAAAAAUkHQBAAAAACpIGgCAAAAgFQQNAEAAABAKgiaAAAAACAVBE0AAAAAkAqCJgC5ko+Pj0yYMCGni4F06tChg1kAb/zd6NWrlwwdOlRyu9jYWKlQoYLMnDkzp4sCeB2CJgBOffbZZ+YEJKVl/fr12VqetWvXmpOhy5cvZ8nxf/zxR+nZs6cUL15cAgMDpWzZsnLPPffI77//niWvh6yhgVtKdbZWrVq2/bQu6bbz5887PU69evUcgsCjR486HMvX11eKFi1q6sy6deuSPf+hhx6SAgUKpFhOfUz3saevMXjwYKlataoEBwdL6dKlpV27djJ+/PgU36OWIzQ0VGrWrCkPPvigLF26VDLD+nno8Y4fP57s8YiICMmXL5/ZZ/jw4Q6Pvf/++3L33XdLxYoVzeNJ31d6flPOnDkjudWaNWtkyZIl8uKLL9q2rVixwqH8QUFBUqpUKfPdTJkyRcLDw1M83q5du+SBBx6QcuXKmefpb43e3717d4qfmdaHkydPJntcX0/rqlVAQICMHDlSJk+eLFFRUW55/wAS+f9zCwBOvfLKK1K5cuVk26tVq5alr3vjxg3x9/d3CJomTpxoTsgKFy7sttexWCzy8MMPm5OTRo0amRMOPVk9ffq0CaQ6d+5sTppatWrlttdE1ipfvrxMnTo12fZChQq5fOz77rvPtDrEx8fL/v37zRX9jh07yqZNm6R+/fqZPu7Bgwfl1ltvNYGJ1sewsDBTB7du3Sqvv/66qfspvcfIyEjz/Hnz5smXX35pgn291RPojNKT+K+//lpeeOEFh+167JRo+a5evSrNmjUzZc7Mb4q7/qaT/m64w5tvvml+B5z95j311FPme9P6oIGS/k5pkDtt2jT59ttvpVOnTsk+R61DGnA/8sgj5nPQYPmTTz6R77//XubOnSt9+vRJ9jrR0dHy2muvyb///e80y6uB96hRo+Srr74ydQmAexA0AUiVXklv2rRptr+uXlnNDm+//bYJmJ555hlzoqNXda1eeuklmTNnjltOwjQ40yu/elKMrKXBkV65zwqNGzd2OHbbtm3N34i2trjSJeqdd96Ra9euyZ9//imVKlVyeOzcuXPpeo96Uq0n8VoODbo0mMkoDQidBU16At67d2/54Ycfkj1n5cqVtlam1FrXsuM3xd2/G/rZz58/X2bNmuX0cf3+77rrLodt27dvl27dusmdd95pWo/KlCljth86dMi0BlapUkVWrVolJUqUsD3n6aefNsfS7/Svv/5KFlQ2bNhQPvroIxk9erRpmUqNBqD6+vq7RtAEuA/d8wC4TLvMaQuQnsjpP9iDBg0yJ396EqX/cKc17kWfqyd5KY1N0Nvnn3/erOvJhLVLjF6hbd++vTRo0MBpubTLUvfu3VO9Kq1X67Xb1ltvveUQMFnpSY5eQbeWw9k+1i40Wh4rfT//93//J4sXLzYniBosffDBB6YrjbZMJJWQkGC669ifgOm26dOnS926dc3JoHb/efTRR+XSpUuSkz788EPThUzfk342f/zxh9P9Tpw4IX379pX8+fNLyZIlZcSIEebz0M9Kuzcl7WKkJ5j62YSEhJjP4o033pDcTk90rSfErtDna+tR0oBJ6WeXHn5+fvLee+9JnTp15D//+Y9cuXIlw+UYMGCA+dvdu3evbZt2ndNuqvqYM1pmZ38XqdGWKW2dSS/r356zxb47YNIxTdbn6fvRFjjtylisWDETpKSn+5oGTHFxcdKlS5d0l1V/j/TvVn8X9Xuwb7G6fv26+fuxD5iUdgvW3wcNnHW/pMaMGWM+Lw2M06Nr166yevVquXjxYrrLDSB1BE0AUqUnXjruw365cOGCQwuKdifRFhm9Svrqq6+ak2UNnNzljjvuMF1arFfk9bV00RMPDWr0yuzOnTsdnqPdpbT7VGotDtaTCj0Z1BNOd9u3b58pt57AvPvuu+Zqcf/+/c1V5qRjOLQsp06dknvvvde2TQMkDRZbt25tnq/dbv773/+aQFAHfOcE7Uak5dIujBrUaNluv/32ZONgNCDVLk0aJOkYGG210+AqaQuGlQaCPXr0MCec2vqngayOIVm4cGGGy6gnl0nrrC7ajc3drIFykSJFXDqOBh76Gbo6hk7rsdY5PTnXOpVROoZKgzdtWbLSLmPagqQtTe6ggbEGLxoca905cOBAun4DrH/31kVbh9MbVGrApEGSXiTR1jQNLocNG5bm87S7nQZZzoLZ1OjFD72ooGOhrH755RdzMcUaaDv77PVx3S8pvVg0cOBA09qkvxNpadKkiflt1vIDcA+65wFIlbMrrDruwXqV9n//+58JAvQE2toa9PjjjzttTcmsW265xXSL0m5D2nJh3yqlA9CffPJJM4bD/iqs3tcWDj3ZSsmePXvMrStjUVKj40wWLVrk0NqlXWvGjRtnxi/YD6hPemKqJ7wff/yxCZLsr/Dr56rBxXfffZfilf+sooGaXvHW4G/58uUmYYbSlg09AdWsXVZ6NV2DVh3Xod+R0uxjKbUK6ongF198YYJgpeM99ERVgzTtzpUR2qqQ9Eq+0mAvpW5W6aXBiAZgGpjpyb6OgVNJu2hllHar00BAA039fLUFVb9rDbg1uMgIa2KAzLR+aauMBu76t6Zjj5TWQf070r97V+j70FYha9C0ZcsW0yVWxwvq2C37+uPsN0AXK/0ONBDXv92kiTKc0aDj559/NutPPPGEeX3txvjcc885HNdZXUraCp4eOp6sRo0atu9ALz5pHXc2XsmelkV/U7UlrmDBgg6P6fvVvxHtdqkXUVKjXQCVtt5qizcA19HSBCBVM2bMMBm57Bf7q/8LFiwwY340ULK/2q2BTHbQLoF6IqIneXplVekJrQYh1q5hKdGMYCrpyYm76Ila0u6BeiKlJ8VaPistrwZRt912m23MkwZF+t70pNm+tUSvIGtwpUFLdtu8ebMZ4/HYY4/ZAiZl7ZppT+uFjuWwDyb0pDmlq/v6nuxbBfX42vXv8OHDGS6nnuQmrbO6WFsmXKEn6BqQaUubthho4K0tY64GTdoFU7vF6WegrVd6Uqz1V7tkautCRljHFemJd2ZoMK4Bv7bWWm/dEaBra8+nn35qWkz0vU2aNMm0RGrLtWZ7Sy/9e9HWNH1/mqwltb9xKw2U7Fl/n7SepkbLltlWRP0erN+B9Tat3xrr486+Ow2E9KKCXpBIK+GGtcwpZYcEkHG0NAFIlZ64pjZo+++//zYnx0kHgOt4ouyiJ2EahGj3L+3i8ttvv8nZs2dtrRYp0avNrpxcpsVZ1kGlXfS0xUZTCOvYHR3fo8GIbrfSVgy9Op1S1yNnyQHsu8ZlZjyL0qAtpSxz+l2r6tWrJ7uqbr2ybb+vZhtLOtYlpXqhXcKS7qsnftr1MqP0JDojY1BS4mycjgZ92nKmLa3alU67eWVkbE5qx9eAWlub9HjaQvDrr7+aFlx9Ta1L6X1POi7GlYsBmkVSu0dqFz0do6gBYtIscO7Spk0bad68ufmbTa+xY8eaz17HG+nYuvRIWmf1eZpe3X4cYkqsF2MySr8H63eQWjBkTx/XeqFjnFJ671pHtFU9tdYma5kzOtYMQMoImgBkG/0H3NkJSGZPOq20NUevyGuXPA2a9FZP9NI6ybTO27Njxw5z5TstKZ2ApFT+lDLlaXCkWbC0NUlbP7QLmwYq2u3OPgmEBkzaNcoZZ93PrDSA1PFPmaFj0eyTd2SXlMa
"text/plain": [
"<Figure size 1000x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Leaderboard (by Sharpe):\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>model</th>\n",
" <th>algo</th>\n",
" <th>sharpe</th>\n",
" <th>max_drawdown</th>\n",
" <th>steps</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>ppo_EURUSD_M15.zip</td>\n",
" <td>ppo</td>\n",
" <td>0.896809</td>\n",
" <td>-0.037317</td>\n",
" <td>9956</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>a2c_EURUSD_M15.zip</td>\n",
" <td>a2c</td>\n",
" <td>-1.843478</td>\n",
" <td>-0.077969</td>\n",
" <td>9956</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>dqn_EURUSD_M15.zip</td>\n",
" <td>dqn</td>\n",
" <td>-11.667301</td>\n",
" <td>-0.292419</td>\n",
" <td>9956</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" model algo sharpe max_drawdown steps\n",
"0 ppo_EURUSD_M15.zip ppo 0.896809 -0.037317 9956\n",
"1 a2c_EURUSD_M15.zip a2c -1.843478 -0.077969 9956\n",
"2 dqn_EURUSD_M15.zip dqn -11.667301 -0.292419 9956"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# ---- Multi-model backtest (loops over all discovered models) ----\n",
"from pathlib import Path\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from stable_baselines3 import PPO, A2C, DQN\n",
"\n",
"ALGO_MAP = {\"ppo\": PPO, \"a2c\": A2C, \"dqn\": DQN}\n",
"\n",
"# Fallbacks if previous cell didn't define helpers/vars\n",
"def _infer_algo_from_name(p: Path) -> str:\n",
" name = p.stem.lower()\n",
" for k in ALGO_MAP:\n",
" if name.startswith(k + \"_\"):\n",
" return k\n",
" return \"ppo\"\n",
"\n",
"if 'MODEL_BUNDLE' not in globals():\n",
" # try single-model fallback\n",
" if 'MODEL_PATH' in globals():\n",
" MODEL_BUNDLE = [(Path(MODEL_PATH), _infer_algo_from_name(Path(MODEL_PATH)))]\n",
" else:\n",
" raise RuntimeError(\"No models found: define MODEL_BUNDLE or MODEL_PATH first.\")\n",
"\n",
"def _bars_per_year(tf: str) -> float:\n",
" tf = str(tf).upper()\n",
" if tf.startswith(\"M\"):\n",
" m = int(tf[1:])\n",
" bpd = 24*60/m\n",
" elif tf.startswith(\"H\"):\n",
" m = int(tf[1:]) * 60\n",
" bpd = 24*60/m\n",
" elif tf in (\"D1\", \"1D\"):\n",
" bpd = 1\n",
" else:\n",
" bpd = 24*4 # ~M15 default\n",
" return bpd * 252.0\n",
"\n",
"tf_str = TIMEFRAME if isinstance(TIMEFRAME, str) else \"M15\"\n",
"ann_factor = _bars_per_year(tf_str)\n",
"\n",
"results = [] # collect per-model metrics\n",
"\n",
"for model_path, algo in MODEL_BUNDLE:\n",
" algo = algo.lower()\n",
" if algo not in ALGO_MAP:\n",
" print(f\"Skipping {model_path.name}: unsupported algo '{algo}'\")\n",
" continue\n",
"\n",
" print(f\"\\n=== Backtesting {model_path.name} (algo={algo.upper()}) ===\")\n",
" model = ALGO_MAP[algo].load(model_path.as_posix())\n",
"\n",
" env = TradingEnv(df_test, feature_cols)\n",
" obs, _ = env.reset()\n",
" equity = [1.0]\n",
" rets = []\n",
"\n",
" while True:\n",
" action, _ = model.predict(obs, deterministic=True)\n",
" obs, reward, terminated, truncated, info = env.step(int(action))\n",
" equity.append(equity[-1] * (1.0 + reward))\n",
" rets.append(info.get(\"ret\", reward))\n",
" if terminated or truncated:\n",
" break\n",
"\n",
" equity = pd.Series(equity, index=df_test.index[:len(equity)])\n",
" returns = equity.pct_change().dropna()\n",
" sharpe = float((returns.mean() / (returns.std() + 1e-12)) * np.sqrt(ann_factor))\n",
" dd = (equity / equity.cummax() - 1.0)\n",
" mdd = float(dd.min())\n",
"\n",
" print(f\"Sharpe (rough): {sharpe:.3f} | MaxDD: {mdd:.2%}\")\n",
"\n",
" # Save metrics row\n",
" results.append({\n",
" \"model\": model_path.name,\n",
" \"algo\": algo,\n",
" \"sharpe\": sharpe,\n",
" \"max_drawdown\": mdd,\n",
" \"steps\": len(returns)\n",
" })\n",
"\n",
" # Plot equity per model (separate figure each to keep it clear)\n",
" plt.figure(figsize=(10,4))\n",
" equity.plot(title=f\"Equity Curve — {model_path.name} ({algo.upper()})\")\n",
" plt.grid(True)\n",
" plt.show()\n",
"\n",
"# Optional: show a small leaderboard\n",
"if results:\n",
" leaderboard = pd.DataFrame(results).sort_values(\"sharpe\", ascending=False)\n",
" print(\"\\nLeaderboard (by Sharpe):\")\n",
" display(leaderboard)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Enriched Evaluation & Plots\n",
"\n",
"Adds action distribution, equity vs buy & hold, underwater (drawdown), rolling Sharpe, monthly returns, and optional QuantStats export."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"================ ppo_EURUSD_M15.zip | PPO ================\n",
"bars 9956.000000\n",
"Sharpe 0.896809\n",
"Sortino 1.245242\n",
"CAGR 0.065507\n",
"Calmar 1.755408\n",
"MaxDD -0.037317\n",
"WinRate 0.476195\n",
"ProfitFactor 1.018795\n",
"Exposure 0.995078\n",
"Trades 238.000000\n",
"AvgHoldBars 847.694255\n",
"dtype: float64\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1200x900 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABKYAAAN6CAYAAAC0RUE8AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Qd8U2X3wPHTvWgLFFooe++9l4AyFERx4kJExYlbcfwVRX3Fieirr7gQceLEAbJBRPbee5Q9Cm1pS3f+n/OkN006oEDbdPy+n09I7s3N7U1yn5ScnnMeD5vNZhMAAAAAAACgiHkW9Q8EAAAAAAAAFIEpAAAAAAAAuAWBKQAAAAAAALgFgSkAAAAAAAC4BYEpAAAAAAAAuAWBKQAAAAAAALgFgSkAAAAAAAC4BYEpAAAAAAAAuAWBKQAAAAAAALgFgSkAAIqpSZMmiYeHh+zdu9exrlevXuZi0ft0G922OKtdu7ZceeWVUhr98MMPUrFiRYmPj7+o/SxYsMC8l3pdUApjn7j4Mb1y5UopDqKjoyUoKEimT5/u7kMBAJRhBKYAACjAL5zWxdvbW6pVqyZ33HGHHDx4UEorDYwNHz5c6tWrJ/7+/lKlShW55JJL5MUXX5SyID093TzXhx56SMqVK+fuwykzVq1aZQKder7p696yZUt5//33zfuR3enTp2XUqFFSp04d8fPzM+Py+uuvl8TExDz3P2LECDOO8wqm/v7779K2bVtzztesWdOcA2lpaVLShIWFyd133y0vvPCCuw8FAFCGebv7AAAAKE1efvll8wU4KSlJli5dagJWixYtko0bN5ovsQWtVq1acubMGfHx8ZGitnPnTunQoYMEBATInXfeabKiDh8+LKtXr5Y33nhDxowZI6XdH3/8Idu2bZN77rnnovelAT19L319fQvk2EpzUKpr167SoEEDefrppyUwMFD++usveeSRR2TXrl3y3nvvObaNjY2Vnj17yoEDB8x7VL9+fTl+/Lj8888/kpycbB6bnWYz6bjNa7zqzxo8eLDJXPzvf/8rGzZskFdffVWOHTsmH330kZQ09913nwnqzZs3Ty699FJ3Hw4AoAwiMAUAQAG64oorpH379ua2ZiJUqlTJBGk0w+LGG28s8J+nWR2FEfDKj3fffdeUr61du9YEyJzpl/SipsFADep4ehZdQvgXX3wh3bp1M1k4F0uP213vZUny8ccfm+uFCxeaEkp17733mgCUBpScA1PPPvus7Nu3zwRLNWBs0YBWbmw2mzz88MNy++23y9y5c3Pd5sknnzQZWrNmzTKZkSokJERee+01Exxr3LixuJtmg+UWdMtNkyZNpHnz5ua1IzAFAHAHSvkAAChEPXr0MNeayeFMsxP0Pu3vUr58ebn66qtly5Yt573/3HpMafmgljdpCaFmdujtypUrmy/U2UudtMfM0KFDzRdrPY5hw4bJunXr8tW3Sp9T9erVcwSlVHh4eK6P0eyxjh07mgBM3bp1ZfLkyS73nzx50hxnixYtzHHrcWmwT48pt95J33//vTz//PMmMKRfxOPi4hxllRq40ICFlivpfjTYcOrUqVwzYKz3Ijg4WAYOHCibNm2S/ATCZsyYIX369HFZf+2115oyL2eDBg0yx6QBSsuyZcvMOv35efWD0qwcDRps3rxZevfubZ6jPtc333wzx/FoVpC+3/o89PV/7LHHTFZQbn788Udp166dyXbT4Oltt93mUnKqx6nHsn79ese6n3/+2azT55c9sDFkyBApKvoe6/mj56uzqlWrmudjiYmJMYFDzZTSoFRKSkqer4flq6++MtmN//nPf3K9X98Hveg+raCUeuCBB0xQ66effsp34Ohc5+Zvv/1mzsXIyEhTgqjlsq+88kqOMWydI5pJpll3eo4899xzjuyv/v37m/dYXxt9HTS7Mbu+ffua7D99DgAAFDUCUwAAFCKrcXmFChUc6+bMmWO+LGpW0UsvvSSPP/64LF682GTeODc6vxj65VV/hn7xffvtt002yTvvvCOffPKJY5uMjAwTMPnuu+9MQEq/jGspnt7ODw1I7d+/3wTZ8lv6p7199EuwHou+JhpEcw4C7d69W6ZOnWp6+4wbN06eeuopUyqlx3/o0KEc+9Qv6tOmTTPBLM1YcS6DGzlypAn26WusX/y/+eYbE7hx/vKtgQj98q9BMM1s0147Gnjo3r37Od8LDQRosCN7EEqDXBpI0wCK0p/377//mowoLSGz6G1dp+/72WjA4vLLL5dWrVqZ100zcjTjxwpoKS0BvOyyy2TmzJnmef/f//2f2b/2VspOA3eavefl5SVjx441/ZR++eUX85w1mKP0thXcy368Gly0aFnc1q1bTUAkPxISEsz21s/JLj/92DQQo6+tBnb0/dWMqAkTJpjnoBlSFj1ODR5q+Z6edxqw0eCMvt6a5ZdbLyp9XTWoo72rcrNmzRpzbWVFWjR4pEFa6/5zyc+5qe+Tnpf6+aBZYBpIHD16tDzzzDM59qcBZg3gtm7dWsaPH2+CmPr50q9fP3Me62O07PDWW281JcbZ6b71PclPQBYAgAJnAwAAF+2LL77Qb5S2OXPm2I4fP27bv3+/7aeffrJVrlzZ5ufnZ5YtrVu3toWHh9uio6Md69atW2fz9PS03X777Tn2uWfPHse6nj17motF79NtdFvLsGHDzLqXX37Z5RjbtGlja9eunWP5559/NtuNHz/esS49Pd126aWX5thnbjZu3GgLCAgw2+pzeuSRR2xTp061JSQk5Ni2Vq1aZruFCxc61h07dsy8Nk888YRjXVJSkjkGZ/ocdTvn5zN//nyzv7p169oSExNdtrdeN32uKSkpjvVvvvmmWf/bb7+Z5dOnT9vKly9vGzFihMvjjxw5YgsNDc2xPrvPPvvM7G/Dhg0u61esWGHWT58+3SyvX7/eLN9www22Tp06Oba76qqrzHuS/TnptUXfa103efJkx7rk5GRblSpVbNddd51jnb6Hut0PP/zgWKfvQ/369V32qa+HnnvNmze3nTlzxrHtn3/+abYbPXq0Y12zZs1sN954o2O5bdu25jnodlu2bDHrfvnlF7Os5+/ZnDx50nb33XfbfH19zfZ6rvfo0cP20Ucf2bZt22bbuXOn7a233jLv57mkpaXZRo4cafPx8TH70ouXl5fZl7Nx48aZ+8LCwmwdO3a0ffPNN7b//e9/toiICFuFChVshw4dctn+ySeftNWpU8ecg9Y5O3DgQJdt9Bh1n1FRUTmOq0OHDrbOnTuf9djze26q7Oe1uvfee22BgYGOY3Q+RyZMmOCy7a+//mrW6/l4LosXLzbbTpky5ZzbAgBQ0MiYAgCgAGlZl5bN1ahRw2RpaFmVlkVpNoXSjCTN1tBMIas/jtKeNZpJVJDTtmtT4+yZPJqRZNEyNG2arhkzFs2IefDBB/O1/2bNmpnnomVgmpWhWR2a9RERESGffvppju2bNm3qKG1U+jo1atTI5Zi0ZMnqEaVZX5oJolkjup32CcpOs7ucy7ecabmVc1P4+++/35RfWa/x7NmzTZbIzTffLCdOnHBcNJOoU6dOMn/+/LM+fz227Nlwqk2bNuaYrWwjzTTS918zY/Q5aBmXZsZoRo/z65EX3Ze+xhbNCtNySOfXTZ+TlrLpOWfRDKHsTdm1tEszabT0zLmflWaNaSaWZp9Z9NisDC/NJtIsMN2floVZ6/VaS+q0lOxs/ve//5nMty+//FKWLFlizg8dG5o5pO+tZjVpZl9+Gubr+6NlbZoRqPubMmWKyfzTmRE1286i/c+UZn5pv6hbbrnFnAO6jWahffjhh45tt2/fbs7ft956y5yDedHMNJXbNvp6Wvefy7nOTeV8Xuvrr+emvid6/mjWmTM9Hp0d05lV6vjnn39KamrqWY/HOof1ZwAAUNRofg4AQAHSL7sNGzY0s4FNnDjRBCecv8Rq2ZHSL+PZaa8eLcXScif90n4x9EuyBn6yf/l07mOjx6LBjOxNkjVIkF/6XLUcToNIWgKnX4K1/5HV18e5/1LNmjVzPD77MWl5oQYINJCxZ88el346Wpa
"text/plain": [
"<Figure size 1200x900 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"================ a2c_EURUSD_M15.zip | A2C ================\n",
"bars 9956.000000\n",
"Sharpe -1.843478\n",
"Sortino -2.168042\n",
"CAGR -0.128641\n",
"Calmar -1.649887\n",
"MaxDD -0.077969\n",
"WinRate 0.498393\n",
"ProfitFactor 0.962365\n",
"Exposure 0.999196\n",
"Trades 14.000000\n",
"AvgHoldBars 2746.865810\n",
"dtype: float64\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1200x900 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1200x900 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"================ dqn_EURUSD_M15.zip | DQN ================\n",
"bars 9956.000000\n",
"Sharpe -11.667301\n",
"Sortino -12.971816\n",
"CAGR -0.578343\n",
"Calmar -1.977823\n",
"MaxDD -0.292414\n",
"WinRate 0.465448\n",
"ProfitFactor 0.783297\n",
"Exposure 0.983126\n",
"Trades 1050.000000\n",
"AvgHoldBars 666.864403\n",
"dtype: float64\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1200x900 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1200x900 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Leaderboard (sorted by Sharpe, then Calmar):\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>model</th>\n",
" <th>algo</th>\n",
" <th>Sharpe</th>\n",
" <th>Sortino</th>\n",
" <th>CAGR</th>\n",
" <th>Calmar</th>\n",
" <th>MaxDD</th>\n",
" <th>WinRate</th>\n",
" <th>ProfitFactor</th>\n",
" <th>Exposure</th>\n",
" <th>Trades</th>\n",
" <th>AvgHoldBars</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>ppo_EURUSD_M15.zip</td>\n",
" <td>ppo</td>\n",
" <td>0.896809</td>\n",
" <td>1.245242</td>\n",
" <td>0.065507</td>\n",
" <td>1.755408</td>\n",
" <td>-0.037317</td>\n",
" <td>0.476195</td>\n",
" <td>1.018795</td>\n",
" <td>0.995078</td>\n",
" <td>238</td>\n",
" <td>847.694255</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>a2c_EURUSD_M15.zip</td>\n",
" <td>a2c</td>\n",
" <td>-1.843478</td>\n",
" <td>-2.168042</td>\n",
" <td>-0.128641</td>\n",
" <td>-1.649887</td>\n",
" <td>-0.077969</td>\n",
" <td>0.498393</td>\n",
" <td>0.962365</td>\n",
" <td>0.999196</td>\n",
" <td>14</td>\n",
" <td>2746.865810</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>dqn_EURUSD_M15.zip</td>\n",
" <td>dqn</td>\n",
" <td>-11.667301</td>\n",
" <td>-12.971816</td>\n",
" <td>-0.578343</td>\n",
" <td>-1.977823</td>\n",
" <td>-0.292414</td>\n",
" <td>0.465448</td>\n",
" <td>0.783297</td>\n",
" <td>0.983126</td>\n",
" <td>1050</td>\n",
" <td>666.864403</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" model algo Sharpe Sortino CAGR Calmar \\\n",
"0 ppo_EURUSD_M15.zip ppo 0.896809 1.245242 0.065507 1.755408 \n",
"1 a2c_EURUSD_M15.zip a2c -1.843478 -2.168042 -0.128641 -1.649887 \n",
"2 dqn_EURUSD_M15.zip dqn -11.667301 -12.971816 -0.578343 -1.977823 \n",
"\n",
" MaxDD WinRate ProfitFactor Exposure Trades AvgHoldBars \n",
"0 -0.037317 0.476195 1.018795 0.995078 238 847.694255 \n",
"1 -0.077969 0.498393 0.962365 0.999196 14 2746.865810 \n",
"2 -0.292414 0.465448 0.783297 0.983126 1050 666.864403 "
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Saved metrics to: G:\\My Drive\\Bots DRL\\DRL\\DRL-MT5-Lab\\notebooks\\models\\metrics_EURUSD_M15.csv\n"
]
}
],
"source": [
"# === Enriched evaluation & plots for ALL models (PPO/A2C/DQN) ===\n",
"from pathlib import Path\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from collections import Counter\n",
"\n",
"# SB3 loaders (in case not imported above)\n",
"from stable_baselines3 import PPO, A2C, DQN\n",
"ALGO_MAP = {\"ppo\": PPO, \"a2c\": A2C, \"dqn\": DQN}\n",
"\n",
"# Fallback to single model if bundle not provided\n",
"def _infer_algo_from_name(p: Path) -> str:\n",
" name = p.stem.lower()\n",
" for k in ALGO_MAP:\n",
" if name.startswith(k + \"_\"):\n",
" return k\n",
" return \"ppo\"\n",
"\n",
"if 'MODEL_BUNDLE' not in globals():\n",
" if 'MODEL_PATH' in globals():\n",
" MODEL_BUNDLE = [(Path(MODEL_PATH), _infer_algo_from_name(Path(MODEL_PATH)))]\n",
" else:\n",
" raise RuntimeError(\"No models found: define MODEL_BUNDLE or MODEL_PATH first.\")\n",
"\n",
"def bars_per_day(tf: str) -> int:\n",
" tf = str(tf).upper()\n",
" if tf.startswith(\"M\"):\n",
" try:\n",
" m = int(tf[1:])\n",
" return int((24*60)/m)\n",
" except Exception:\n",
" return 96\n",
" if tf.startswith(\"H\"):\n",
" try:\n",
" h = int(tf[1:])\n",
" return int(24/h)\n",
" except Exception:\n",
" return 24\n",
" if tf in (\"D1\", \"1D\"):\n",
" return 1\n",
" return 96 # default ≈ M15\n",
"\n",
"def ann_factor(tf: str) -> float:\n",
" return np.sqrt(252 * bars_per_day(tf))\n",
"\n",
"def CAGR(eq: pd.Series) -> float:\n",
" if len(eq) < 2:\n",
" return np.nan\n",
" total = float(eq.iloc[-1]) / float(eq.iloc[0])\n",
" yrs = (eq.index[-1] - eq.index[0]).days / 365.25\n",
" return (total ** (1/max(yrs, 1e-9))) - 1.0\n",
"\n",
"def compute_metrics(equity: pd.Series, strat_rets: pd.Series, raw_rets: pd.Series, timeframe: str, positions: pd.Series, actions: pd.Series) -> dict:\n",
" ann = ann_factor(timeframe)\n",
" sharpe = float((strat_rets.mean() / (strat_rets.std() + 1e-12)) * ann)\n",
" downside = strat_rets[strat_rets < 0]\n",
" sortino = float((strat_rets.mean() / (downside.std() + 1e-12)) * ann)\n",
" cummax = equity.cummax()\n",
" dd = equity / cummax - 1.0\n",
" mdd = float(dd.min())\n",
" cagr = CAGR(equity)\n",
" calmar = float(cagr / (abs(mdd) + 1e-12))\n",
" win_rate = float((strat_rets > 0).mean())\n",
" pf = float(strat_rets[strat_rets > 0].sum() / (abs(strat_rets[strat_rets < 0].sum()) + 1e-12))\n",
" exposure = float((positions != 0).mean())\n",
" # trades = number of position changes (ignore first)\n",
" trades = int((positions.diff().fillna(0) != 0).sum())\n",
" avg_hold = float((positions != 0).astype(int).groupby((positions == 0).astype(int).cumsum()).transform('size').mean()) if trades > 0 else np.nan\n",
" return {\n",
" \"Sharpe\": sharpe,\n",
" \"Sortino\": sortino,\n",
" \"CAGR\": cagr,\n",
" \"Calmar\": calmar,\n",
" \"MaxDD\": mdd,\n",
" \"WinRate\": win_rate,\n",
" \"ProfitFactor\": pf,\n",
" \"Exposure\": exposure,\n",
" \"Trades\": trades,\n",
" \"AvgHoldBars\": avg_hold\n",
" }\n",
"\n",
"results = []\n",
"tf_str = TIMEFRAME if isinstance(TIMEFRAME, str) else \"M15\"\n",
"BPD = bars_per_day(tf_str)\n",
"\n",
"for model_path, algo in MODEL_BUNDLE:\n",
" algo = algo.lower()\n",
" if algo not in ALGO_MAP:\n",
" print(f\"Skipping {model_path.name}: unsupported algo '{algo}'\")\n",
" continue\n",
"\n",
" print(f\"\\n================ {model_path.name} | {algo.upper()} ================\")\n",
" model = ALGO_MAP[algo].load(Path(model_path).as_posix())\n",
"\n",
" env = TradingEnv(df_test, feature_cols)\n",
" obs, _ = env.reset()\n",
"\n",
" equity_vals = [1.0]\n",
" rewards = []\n",
" raw_rets = []\n",
" positions = []\n",
" actions = []\n",
"\n",
" while True:\n",
" action, _ = model.predict(obs, deterministic=True)\n",
" obs, reward, terminated, truncated, info = env.step(int(action))\n",
" equity_vals.append(equity_vals[-1] * (1.0 + reward))\n",
" rewards.append(float(reward))\n",
" raw_rets.append(float(info.get(\"ret\", 0.0)))\n",
" positions.append(int(info.get(\"position\", 0)))\n",
" actions.append(int(action))\n",
" if terminated or truncated:\n",
" break\n",
"\n",
" # Align per-step series with the time index\n",
" idx = df_test.index[1:1+len(rewards)]\n",
" strat_rets = pd.Series(rewards, index=idx, name=\"strategy_ret\")\n",
" equity = pd.Series(equity_vals[1:], index=idx, name=\"equity\")\n",
" pos_series = pd.Series(positions, index=idx, name=\"position\")\n",
" action_series = pd.Series(actions, index=idx, name=\"action\")\n",
" bh = pd.Series((1.0 + pd.Series(raw_rets, index=idx)).cumprod(), name=\"buy_hold\")\n",
"\n",
" # Metrics\n",
" m = compute_metrics(equity, strat_rets, pd.Series(raw_rets, index=idx), tf_str, pos_series, action_series)\n",
" summary = pd.Series({\n",
" \"bars\": len(strat_rets),\n",
" **{k: (round(v, 6) if isinstance(v, float) else v) for k, v in m.items()}\n",
" })\n",
" print(summary)\n",
"\n",
" # ====== Plots (2 figures per model) ======\n",
" # Fig 1: Equity vs Buy&Hold + Drawdown + Position\n",
" fig, axes = plt.subplots(3, 1, figsize=(12, 9), sharex=True, gridspec_kw={\"height_ratios\": [3, 1.5, 1]})\n",
" (equity.rename(\"strategy\")).plot(ax=axes[0], lw=1.4)\n",
" bh.plot(ax=axes[0], lw=1.0, alpha=0.8)\n",
" axes[0].set_title(f\"Equity vs Buy&Hold — {model_path.name} ({algo.upper()})\")\n",
" axes[0].legend()\n",
" axes[0].grid(True)\n",
"\n",
" dd = equity / equity.cummax() - 1.0\n",
" dd.plot(ax=axes[1], color=\"tab:red\")\n",
" axes[1].set_title(\"Drawdown\")\n",
" axes[1].grid(True)\n",
"\n",
" pos_series.plot(ax=axes[2], drawstyle=\"steps-post\")\n",
" axes[2].set_title(\"Position (-1=Short, 0=Flat, +1=Long)\")\n",
" axes[2].grid(True)\n",
"\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" # Fig 2: Rolling Sharpe + Action distribution + Return histogram\n",
" fig, axes = plt.subplots(3, 1, figsize=(12, 9), gridspec_kw={\"height_ratios\": [2, 1.2, 1.2]})\n",
" roll_win = max(30, int(90 * BPD)) # ~90 days in bars (min 30)\n",
" rmean = strat_rets.rolling(roll_win).mean()\n",
" rstd = strat_rets.rolling(roll_win).std()\n",
" rsharpe = rmean / (rstd + 1e-12) * np.sqrt(252 * BPD)\n",
" rsharpe.plot(ax=axes[0])\n",
" axes[0].axhline(0, color=\"k\", lw=0.8)\n",
" axes[0].set_title(f\"Rolling Sharpe (window ≈ {roll_win} bars)\")\n",
" axes[0].grid(True)\n",
"\n",
" cnt = Counter(action_series.values) # 0=SELL, 1=HOLD, 2=BUY\n",
" axes[1].bar([\"SELL(0)\", \"HOLD(1)\", \"BUY(2)\"], [cnt.get(0,0), cnt.get(1,0), cnt.get(2,0)])\n",
" axes[1].set_title(\"Action distribution\")\n",
" axes[1].grid(True, axis=\"y\")\n",
"\n",
" axes[2].hist(strat_rets.values, bins=50, alpha=0.9)\n",
" axes[2].set_title(\"Strategy return distribution (per bar)\")\n",
" axes[2].grid(True)\n",
"\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" # Collect to leaderboard\n",
" results.append({\n",
" \"model\": model_path.name,\n",
" \"algo\": algo,\n",
" **m\n",
" })\n",
"\n",
"# ===== Leaderboard across models =====\n",
"if results:\n",
" leaderboard = pd.DataFrame(results).sort_values([\"Sharpe\", \"Calmar\"], ascending=False)\n",
" print(\"\\nLeaderboard (sorted by Sharpe, then Calmar):\")\n",
" display(leaderboard)\n",
"\n",
" # Optional: save metrics table\n",
" out_csv = Path(\"models\") / f\"metrics_{SYMBOL}_{tf_str}.csv\"\n",
" leaderboard.to_csv(out_csv, index=False)\n",
" print(\"Saved metrics to:\", out_csv.resolve())\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== ppo_EURUSD_M15.zip | PPO ===\n",
"Action counts:\n",
" action\n",
"Sell 9766\n",
"Buy 141\n",
"Hold 49\n",
"Name: count, dtype: int64\n",
"\n",
"=== a2c_EURUSD_M15.zip | A2C ===\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\moham\\AppData\\Local\\Temp\\ipykernel_31600\\2918636110.py:57: FutureWarning: 'M' is deprecated and will be removed in a future version, please use 'ME' instead.\n",
" monthly = strat_rets.resample(\"M\").apply(lambda x: (1 + x).prod() - 1.0)\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Action counts:\n",
" action\n",
"Buy 9948\n",
"Hold 8\n",
"Name: count, dtype: int64\n",
"\n",
"=== dqn_EURUSD_M15.zip | DQN ===\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\moham\\AppData\\Local\\Temp\\ipykernel_31600\\2918636110.py:57: FutureWarning: 'M' is deprecated and will be removed in a future version, please use 'ME' instead.\n",
" monthly = strat_rets.resample(\"M\").apply(lambda x: (1 + x).prod() - 1.0)\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Action counts:\n",
" action\n",
"Buy 6627\n",
"Sell 3161\n",
"Hold 168\n",
"Name: count, dtype: int64\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\moham\\AppData\\Local\\Temp\\ipykernel_31600\\2918636110.py:57: FutureWarning: 'M' is deprecated and will be removed in a future version, please use 'ME' instead.\n",
" monthly = strat_rets.resample(\"M\").apply(lambda x: (1 + x).prod() - 1.0)\n",
"C:\\Users\\moham\\AppData\\Local\\Temp\\ipykernel_31600\\2918636110.py:64: UserWarning: Converting to PeriodArray/Index representation will drop timezone information.\n",
" monthly_df.index = monthly_df.index.to_period(\"M\")\n"
]
},
{
"data": {
"text/html": [
"<style type=\"text/css\">\n",
"</style>\n",
"<table id=\"T_d3c84\">\n",
" <caption>Monthly compounded returns (last 12)</caption>\n",
" <thead>\n",
" <tr>\n",
" <th class=\"blank level0\" >&nbsp;</th>\n",
" <th id=\"T_d3c84_level0_col0\" class=\"col_heading level0 col0\" >ppo_EURUSD_M15</th>\n",
" <th id=\"T_d3c84_level0_col1\" class=\"col_heading level0 col1\" >a2c_EURUSD_M15</th>\n",
" <th id=\"T_d3c84_level0_col2\" class=\"col_heading level0 col2\" >dqn_EURUSD_M15</th>\n",
" </tr>\n",
" <tr>\n",
" <th class=\"index_name level0\" >time</th>\n",
" <th class=\"blank col0\" >&nbsp;</th>\n",
" <th class=\"blank col1\" >&nbsp;</th>\n",
" <th class=\"blank col2\" >&nbsp;</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th id=\"T_d3c84_level0_row0\" class=\"row_heading level0 row0\" >2024-08</th>\n",
" <td id=\"T_d3c84_row0_col0\" class=\"data row0 col0\" >-1.60%</td>\n",
" <td id=\"T_d3c84_row0_col1\" class=\"data row0 col1\" >1.09%</td>\n",
" <td id=\"T_d3c84_row0_col2\" class=\"data row0 col2\" >-2.82%</td>\n",
" </tr>\n",
" <tr>\n",
" <th id=\"T_d3c84_level0_row1\" class=\"row_heading level0 row1\" >2024-09</th>\n",
" <td id=\"T_d3c84_row1_col0\" class=\"data row1 col0\" >-1.20%</td>\n",
" <td id=\"T_d3c84_row1_col1\" class=\"data row1 col1\" >0.70%</td>\n",
" <td id=\"T_d3c84_row1_col2\" class=\"data row1 col2\" >-1.49%</td>\n",
" </tr>\n",
" <tr>\n",
" <th id=\"T_d3c84_level0_row2\" class=\"row_heading level0 row2\" >2024-10</th>\n",
" <td id=\"T_d3c84_row2_col0\" class=\"data row2 col0\" >1.37%</td>\n",
" <td id=\"T_d3c84_row2_col1\" class=\"data row2 col1\" >-2.30%</td>\n",
" <td id=\"T_d3c84_row2_col2\" class=\"data row2 col2\" >-6.79%</td>\n",
" </tr>\n",
" <tr>\n",
" <th id=\"T_d3c84_level0_row3\" class=\"row_heading level0 row3\" >2024-11</th>\n",
" <td id=\"T_d3c84_row3_col0\" class=\"data row3 col0\" >2.29%</td>\n",
" <td id=\"T_d3c84_row3_col1\" class=\"data row3 col1\" >-2.81%</td>\n",
" <td id=\"T_d3c84_row3_col2\" class=\"data row3 col2\" >-11.74%</td>\n",
" </tr>\n",
" <tr>\n",
" <th id=\"T_d3c84_level0_row4\" class=\"row_heading level0 row4\" >2024-12</th>\n",
" <td id=\"T_d3c84_row4_col0\" class=\"data row4 col0\" >1.74%</td>\n",
" <td id=\"T_d3c84_row4_col1\" class=\"data row4 col1\" >-2.09%</td>\n",
" <td id=\"T_d3c84_row4_col2\" class=\"data row4 col2\" >-10.10%</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n"
],
"text/plain": [
"<pandas.io.formats.style.Styler at 0x2e8742275b0>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Saved monthly returns to: G:\\My Drive\\Bots DRL\\DRL\\DRL-MT5-Lab\\notebooks\\models\\monthly_EURUSD_M15.csv\n"
]
}
],
"source": [
"# --- Action distribution & monthly returns (for ALL models) ---\n",
"from pathlib import Path\n",
"import numpy as np\n",
"import pandas as pd\n",
"from stable_baselines3 import PPO, A2C, DQN\n",
"\n",
"ALGO_MAP = {\"ppo\": PPO, \"a2c\": A2C, \"dqn\": DQN}\n",
"\n",
"# Fallback if only single model provided\n",
"def _infer_algo_from_name(p: Path) -> str:\n",
" name = p.stem.lower()\n",
" for k in ALGO_MAP:\n",
" if name.startswith(k + \"_\"):\n",
" return k\n",
" return \"ppo\"\n",
"\n",
"if 'MODEL_BUNDLE' not in globals():\n",
" if 'MODEL_PATH' in globals():\n",
" MODEL_BUNDLE = [(Path(MODEL_PATH), _infer_algo_from_name(Path(MODEL_PATH)))]\n",
" else:\n",
" raise RuntimeError(\"No models found: define MODEL_BUNDLE or MODEL_PATH first.\")\n",
"\n",
"all_monthly = []\n",
"\n",
"for model_path, algo in MODEL_BUNDLE:\n",
" algo = algo.lower()\n",
" if algo not in ALGO_MAP:\n",
" print(f\"Skipping {model_path.name}: unsupported algo '{algo}'\")\n",
" continue\n",
"\n",
" print(f\"\\n=== {model_path.name} | {algo.upper()} ===\")\n",
" model = ALGO_MAP[algo].load(Path(model_path).as_posix())\n",
"\n",
" # Run once to get actions & returns\n",
" env = TradingEnv(df_test, feature_cols)\n",
" obs, _ = env.reset()\n",
" rewards, actions = [], []\n",
"\n",
" while True:\n",
" action, _ = model.predict(obs, deterministic=True)\n",
" obs, reward, terminated, truncated, info = env.step(int(action))\n",
" rewards.append(float(reward))\n",
" actions.append(int(action))\n",
" if terminated or truncated:\n",
" break\n",
"\n",
" idx = df_test.index[1:1+len(rewards)]\n",
" strat_rets = pd.Series(rewards, index=idx, name=\"strategy_ret\")\n",
" action_series = pd.Series(actions, index=idx, name=\"action\")\n",
"\n",
" # Action distribution (0=Sell, 1=Hold, 2=Buy)\n",
" action_map = {0: \"Sell\", 1: \"Hold\", 2: \"Buy\"}\n",
" action_counts = action_series.map(action_map).value_counts()\n",
" print(\"Action counts:\\n\", action_counts)\n",
"\n",
" # Monthly compounded returns\n",
" monthly = strat_rets.resample(\"M\").apply(lambda x: (1 + x).prod() - 1.0)\n",
" monthly.name = model_path.stem # column label in merged table\n",
" all_monthly.append(monthly)\n",
"\n",
"# Merge monthly across models, show last 12 rows\n",
"if all_monthly:\n",
" monthly_df = pd.concat(all_monthly, axis=1)\n",
" monthly_df.index = monthly_df.index.to_period(\"M\")\n",
" try:\n",
" display(monthly_df.tail(12).style.format(\"{:.2%}\").set_caption(\"Monthly compounded returns (last 12)\"))\n",
" except Exception:\n",
" print(\"\\nMonthly compounded returns (last 12):\")\n",
" print(monthly_df.tail(12).applymap(lambda v: f\"{v:.2%}\"))\n",
"\n",
" # Optional: save to CSV\n",
" out_csv = Path(\"models\") / f\"monthly_{SYMBOL}_{TIMEFRAME}.csv\"\n",
" monthly_df.to_csv(out_csv, float_format=\"%.6f\")\n",
" print(\"Saved monthly returns to:\", out_csv.resolve())\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Building QuantStats report for ppo_EURUSD_M15.zip (PPO) ===\n",
"Saved QuantStats report to: G:\\My Drive\\Bots DRL\\DRL\\DRL-MT5-Lab\\notebooks\\reports\\qs_ppo_EURUSD_M15.html\n",
"\n",
"=== Building QuantStats report for a2c_EURUSD_M15.zip (A2C) ===\n",
"Saved QuantStats report to: G:\\My Drive\\Bots DRL\\DRL\\DRL-MT5-Lab\\notebooks\\reports\\qs_a2c_EURUSD_M15.html\n",
"\n",
"=== Building QuantStats report for dqn_EURUSD_M15.zip (DQN) ===\n",
"Saved QuantStats report to: G:\\My Drive\\Bots DRL\\DRL\\DRL-MT5-Lab\\notebooks\\reports\\qs_dqn_EURUSD_M15.html\n",
"Wrote report index: G:\\My Drive\\Bots DRL\\DRL\\DRL-MT5-Lab\\notebooks\\reports\\index.html\n"
]
}
],
"source": [
"# === QuantStats HTML reports (daily) for ALL models — robust tz handling ===\n",
"from pathlib import Path\n",
"import numpy as np\n",
"import pandas as pd\n",
"from stable_baselines3 import PPO, A2C, DQN\n",
"\n",
"ALGO_MAP = {\"ppo\": PPO, \"a2c\": A2C, \"dqn\": DQN}\n",
"\n",
"def _infer_algo_from_name(p: Path) -> str:\n",
" name = p.stem.lower()\n",
" for k in ALGO_MAP:\n",
" if name.startswith(k + \"_\"):\n",
" return k\n",
" return \"ppo\"\n",
"\n",
"# Fallback to single model if bundle missing\n",
"if 'MODEL_BUNDLE' not in globals():\n",
" if 'MODEL_PATH' in globals():\n",
" MODEL_BUNDLE = [(Path(MODEL_PATH), _infer_algo_from_name(Path(MODEL_PATH)))]\n",
" else:\n",
" raise RuntimeError(\"No models found: define MODEL_BUNDLE or MODEL_PATH first.\")\n",
"\n",
"def _to_naive_index(s: pd.Series) -> pd.Series:\n",
" \"\"\"Ensure DatetimeIndex is tz-naive (drop tz).\"\"\"\n",
" s = s.copy()\n",
" if isinstance(s.index, pd.DatetimeIndex) and s.index.tz is not None:\n",
" # if you want to preserve absolute UTC moments, convert to UTC first, then drop tz\n",
" s.index = s.index.tz_convert(\"UTC\").tz_localize(None)\n",
" return s\n",
"\n",
"def _daily_compounded(returns_bar: pd.Series) -> pd.Series:\n",
" \"\"\"Compound intrabar returns to daily returns and drop NaNs.\"\"\"\n",
" s = _to_naive_index(returns_bar.sort_index())\n",
" return s.resample(\"1D\").apply(lambda x: (1 + x).prod() - 1.0).dropna()\n",
"\n",
"try:\n",
" import quantstats as qs\n",
" qs.extend_pandas()\n",
"except Exception as e:\n",
" print(\"QuantStats not available or failed to import:\", e)\n",
" print(\"Tip: pip install quantstats\")\n",
"else:\n",
" INCLUDE_BENCHMARK = True # set False if you want to skip benchmark to avoid alignment issues\n",
" out_dir = Path(\"reports\"); out_dir.mkdir(exist_ok=True)\n",
" links = []\n",
"\n",
" for model_path, algo in MODEL_BUNDLE:\n",
" algo = algo.lower()\n",
" if algo not in ALGO_MAP:\n",
" print(f\"Skipping {model_path.name}: unsupported algo '{algo}'\")\n",
" continue\n",
"\n",
" print(f\"\\n=== Building QuantStats report for {model_path.name} ({algo.upper()}) ===\")\n",
" model = ALGO_MAP[algo].load(model_path.as_posix())\n",
"\n",
" # Run evaluation pass to collect per-bar strategy returns and raw returns\n",
" env = TradingEnv(df_test, feature_cols)\n",
" obs, _ = env.reset()\n",
" rewards, raw_rets = [], []\n",
"\n",
" while True:\n",
" action, _ = model.predict(obs, deterministic=True)\n",
" obs, reward, terminated, truncated, info = env.step(int(action))\n",
" rewards.append(float(reward))\n",
" raw_rets.append(float(info.get(\"ret\", 0.0)))\n",
" if terminated or truncated:\n",
" break\n",
"\n",
" # Build indexed series\n",
" idx = df_test.index[1:1+len(rewards)]\n",
" strat_rets = pd.Series(rewards, index=idx, name=\"strategy_ret\")\n",
" bh_rets = pd.Series(raw_rets, index=idx, name=\"bh_ret\")\n",
"\n",
" # Make both tz-naive BEFORE resampling\n",
" strat_rets = _to_naive_index(strat_rets)\n",
" bh_rets = _to_naive_index(bh_rets)\n",
"\n",
" # Daily compounded\n",
" daily_strat = _daily_compounded(strat_rets)\n",
" daily_bh = _daily_compounded(bh_rets)\n",
"\n",
" # Align indexes (important when passing a benchmark)\n",
" if INCLUDE_BENCHMARK and len(daily_bh):\n",
" common = daily_strat.index.intersection(daily_bh.index)\n",
" daily_strat = daily_strat.reindex(common).dropna()\n",
" daily_bh = daily_bh.reindex(common).dropna()\n",
" benchmark = daily_bh if len(daily_bh) else None\n",
" else:\n",
" benchmark = None\n",
"\n",
" # Debug (you can comment these out later)\n",
" # print(\"strat tz:\", getattr(daily_strat.index, \"tz\", None), \"| bh tz:\", getattr(daily_bh.index, \"tz\", None))\n",
" # print(\"strat idx dtype:\", daily_strat.index.dtype, \"| bh idx dtype:\", daily_bh.index.dtype)\n",
"\n",
" out_html = out_dir / f\"qs_{model_path.stem}.html\"\n",
" title = f\"DRL-MT5 — {SYMBOL} {TIMEFRAME}{model_path.stem}\"\n",
" try:\n",
" qs.reports.html(daily_strat, benchmark=benchmark, output=out_html.as_posix(), title=title)\n",
" print(\"Saved QuantStats report to:\", out_html.resolve())\n",
" links.append(out_html.name)\n",
" except Exception as e:\n",
" # As a fallback, try again without a benchmark if tz/type alignment still trips\n",
" print(f\"QuantStats with benchmark failed: {e}\")\n",
" try:\n",
" qs.reports.html(daily_strat, output=out_html.as_posix(), title=title)\n",
" print(\"Saved QuantStats report (no benchmark) to:\", out_html.resolve())\n",
" links.append(out_html.name)\n",
" except Exception as e2:\n",
" print(f\"QuantStats report failed for {model_path.name}:\", e2)\n",
"\n",
" # Index page with links\n",
" if links:\n",
" index_path = out_dir / \"index.html\"\n",
" html = \"<h2>QuantStats Reports</h2><ul>\" + \"\".join([f'<li><a href=\"{name}\">{name}</a></li>' for name in links]) + \"</ul>\"\n",
" index_path.write_text(html, encoding=\"utf-8\")\n",
" print(\"Wrote report index:\", index_path.resolve())\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "fd539a1a",
"metadata": {},
"outputs": [],
"source": [
"from plotly.subplots import make_subplots\n",
"import plotly.graph_objects as go\n",
"import numpy as np, pandas as pd\n",
"from pathlib import Path\n",
"\n",
"def _to_naive_index_any(x):\n",
" \"\"\"TZ-normalize Series/DataFrame.\"\"\"\n",
" if isinstance(x, (pd.Series, pd.DataFrame)):\n",
" idx = x.index\n",
" if isinstance(idx, pd.DatetimeIndex) and idx.tz is not None:\n",
" x = x.copy()\n",
" x.index = idx.tz_convert(\"UTC\").tz_localize(None)\n",
" return x\n",
"\n",
"def _align_on_common_index(series_list):\n",
" \"\"\"Return copies aligned on the intersection of all non-empty indices.\"\"\"\n",
" s_ok = [s for s in series_list if s is not None and len(s) > 0]\n",
" if not s_ok:\n",
" return series_list, pd.DatetimeIndex([])\n",
" common = s_ok[0].index\n",
" for s in s_ok[1:]:\n",
" common = common.intersection(s.index)\n",
" aligned = []\n",
" for s in series_list:\n",
" if s is None or len(s) == 0:\n",
" aligned.append(s)\n",
" else:\n",
" aligned.append(s.reindex(common))\n",
" return aligned, common\n",
"\n",
"def build_plotly_dashboard(df_with_close, pos_series, strat_rets, raw_rets,\n",
" model_name, algo, symbol, timeframe, out_dir=\"reports/plotly\"):\n",
" \"\"\"\n",
" Create interactive dashboard:\n",
" row1: Close + entries/exits\n",
" row2: Equity vs Buy&Hold\n",
" row3: Drawdown + Position (secondary axis)\n",
" \"\"\"\n",
" # 1) tz -> naive BEFORE any reindexing\n",
" pos_series = _to_naive_index_any(pos_series.sort_index())\n",
" strat_rets = _to_naive_index_any(strat_rets.sort_index())\n",
" raw_rets = _to_naive_index_any(raw_rets.sort_index())\n",
" df_with_close = _to_naive_index_any(df_with_close)\n",
"\n",
" # 2) align all on common index\n",
" (pos_series, strat_rets, raw_rets), idx = _align_on_common_index([pos_series, strat_rets, raw_rets])\n",
"\n",
" # Guard: if nothing overlaps, bail early with a friendly message\n",
" if len(idx) == 0:\n",
" print(f\"[plotly] No overlapping index after alignment for {model_name}. Skipping.\")\n",
" return None\n",
"\n",
" # Fill still-missing values conservatively\n",
" pos_series = pos_series.fillna(method=\"ffill\").fillna(0).astype(int)\n",
" strat_rets = strat_rets.fillna(0.0)\n",
" raw_rets = raw_rets.fillna(0.0)\n",
"\n",
" price = df_with_close[\"close\"].reindex(idx)\n",
"\n",
" # 3) equity/drawdown\n",
" equity = (1.0 + strat_rets).cumprod()\n",
" bh = (1.0 + raw_rets).cumprod()\n",
" dd = equity / equity.cummax() - 1.0\n",
"\n",
" # Entries/exits from position transitions\n",
" prev = pos_series.shift(1).fillna(0).astype(int)\n",
" cur = pos_series.astype(int)\n",
" long_entries = idx[(prev <= 0) & (cur == 1)]\n",
" short_entries = idx[(prev >= 0) & (cur == -1)]\n",
" long_exits = idx[(prev == 1) & (cur <= 0)]\n",
" short_exits = idx[(prev == -1) & (cur >= 0)]\n",
"\n",
" total_reward = float(strat_rets.sum())\n",
" total_profit = float(equity.iloc[-1] - 1.0) if len(equity) else 0.0\n",
"\n",
" fig = make_subplots(\n",
" rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.06,\n",
" specs=[[{\"secondary_y\": False}], [{\"secondary_y\": False}], [{\"secondary_y\": True}]]\n",
" )\n",
"\n",
" # Row 1: price + markers\n",
" fig.add_trace(go.Scattergl(x=idx, y=price, mode=\"lines\", name=\"Close\", line=dict(width=1.2)), row=1, col=1)\n",
" if len(long_entries): fig.add_trace(go.Scattergl(x=long_entries, y=price.loc[long_entries], mode=\"markers\",\n",
" name=\"Long Entry\", marker=dict(color=\"green\", size=6, symbol=\"circle\")), row=1, col=1)\n",
" if len(short_entries): fig.add_trace(go.Scattergl(x=short_entries, y=price.loc[short_entries], mode=\"markers\",\n",
" name=\"Short Entry\", marker=dict(color=\"red\", size=6, symbol=\"circle\")), row=1, col=1)\n",
" if len(long_exits): fig.add_trace(go.Scattergl(x=long_exits, y=price.loc[long_exits], mode=\"markers\",\n",
" name=\"Long Exit\", marker=dict(color=\"green\", size=8, symbol=\"triangle-down\")), row=1, col=1)\n",
" if len(short_exits): fig.add_trace(go.Scattergl(x=short_exits, y=price.loc[short_exits], mode=\"markers\",\n",
" name=\"Short Exit\", marker=dict(color=\"red\", size=8, symbol=\"triangle-up\")), row=1, col=1)\n",
"\n",
" # Row 2: equity vs buy&hold\n",
" fig.add_trace(go.Scattergl(x=idx, y=equity, mode=\"lines\", name=\"Strategy equity\", line=dict(width=1.4)), row=2, col=1)\n",
" if len(bh):\n",
" fig.add_trace(go.Scattergl(x=idx, y=bh, mode=\"lines\", name=\"Buy&Hold\", line=dict(width=1, dash=\"dot\")), row=2, col=1)\n",
"\n",
" # Row 3: drawdown + position\n",
" fig.add_trace(go.Scattergl(x=idx, y=dd, mode=\"lines\", name=\"Drawdown\", line=dict(color=\"red\")), row=3, col=1, secondary_y=False)\n",
" fig.add_trace(go.Scattergl(x=idx, y=pos_series, mode=\"lines\", name=\"Position (-1/0/+1)\",\n",
" line=dict(width=1), line_shape=\"hv\"), row=3, col=1, secondary_y=True)\n",
"\n",
" fig.update_yaxes(title_text=\"Price\", row=1, col=1)\n",
" fig.update_yaxes(title_text=\"Equity\", row=2, col=1)\n",
" fig.update_yaxes(title_text=\"Drawdown\", row=3, col=1, secondary_y=False, tickformat=\".1%\")\n",
" fig.update_yaxes(title_text=\"Pos\", row=3, col=1, secondary_y=True, range=[-1.2, 1.2], tickvals=[-1,0,1])\n",
"\n",
" fig.update_layout(\n",
" title=f\"{symbol} {timeframe}{model_name} ({algo.upper()})<br>\"\n",
" f\"Total Reward: {total_reward:.6f} • Total Profit: {total_profit:.6f}\",\n",
" hovermode=\"x unified\",\n",
" legend=dict(orientation=\"h\", yanchor=\"bottom\", y=1.02, xanchor=\"right\", x=1),\n",
" xaxis=dict(rangeslider=dict(visible=True))\n",
" )\n",
"\n",
" out_dir = Path(out_dir); out_dir.mkdir(parents=True, exist_ok=True)\n",
" out_html = out_dir / f\"plotly_{Path(model_name).stem}.html\"\n",
" fig.write_html(out_html.as_posix(), include_plotlyjs=\"cdn\", auto_open=False)\n",
" return out_html\n"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "d55f4970",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\moham\\AppData\\Local\\Temp\\ipykernel_15772\\1367230339.py:54: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.\n",
" pos_series = pos_series.fillna(method=\"ffill\").fillna(0).astype(int)\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Saved: G:\\My Drive\\Github\\Deep-Reinforcement-Learning-MT5-Bot\\notebooks\\reports\\plotly\\plotly_ppo_EURUSD_M15.html\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\moham\\AppData\\Local\\Temp\\ipykernel_15772\\1367230339.py:54: FutureWarning:\n",
"\n",
"Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.\n",
"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Saved: G:\\My Drive\\Github\\Deep-Reinforcement-Learning-MT5-Bot\\notebooks\\reports\\plotly\\plotly_a2c_EURUSD_M15.html\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\moham\\AppData\\Local\\Temp\\ipykernel_15772\\1367230339.py:54: FutureWarning:\n",
"\n",
"Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.\n",
"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Saved: G:\\My Drive\\Github\\Deep-Reinforcement-Learning-MT5-Bot\\notebooks\\reports\\plotly\\plotly_dqn_EURUSD_M15.html\n",
"Index: G:\\My Drive\\Github\\Deep-Reinforcement-Learning-MT5-Bot\\notebooks\\reports\\plotly\\index.html\n"
]
}
],
"source": [
"links = []\n",
"for p, algo in MODEL_BUNDLE:\n",
" pos_series, strat_rets, raw_rets, model_name_str, algo_used = run_model_collect_series_with_raw(p, algo)\n",
" out_path = build_plotly_dashboard(df_test, pos_series, strat_rets, raw_rets,\n",
" model_name_str, algo_used, SYMBOL, TIMEFRAME)\n",
" if out_path:\n",
" print(\"Saved:\", out_path.resolve())\n",
" links.append(out_path.name)\n",
"\n",
"# optional index page\n",
"if links:\n",
" idx_path = Path(\"reports/plotly/index.html\")\n",
" idx_path.parent.mkdir(parents=True, exist_ok=True)\n",
" idx_html = \"<h2>Interactive Model Dashboards</h2><ul>\" + \"\".join(\n",
" f'<li><a href=\"{name}\">{name}</a></li>' for name in links\n",
" ) + \"</ul>\"\n",
" idx_path.write_text(idx_html, encoding=\"utf-8\")\n",
" print(\"Index:\", idx_path.resolve())\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "e95d96c5",
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"import numpy as np, pandas as pd, matplotlib.pyplot as plt\n",
"\n",
"def plot_trades_on_price_from_series(df_with_close, pos_series, rewards, model_name, algo, save_png=False):\n",
" L = len(pos_series)\n",
" rewards = np.asarray(rewards, dtype=float)[:L]\n",
" idx = pos_series.index\n",
"\n",
" price = df_with_close[\"close\"].reindex(idx)\n",
" prev = pos_series.shift(1).fillna(0).astype(int)\n",
" cur = pos_series.astype(int)\n",
"\n",
" long_entries = idx[(prev <= 0) & (cur == 1)]\n",
" short_entries = idx[(prev >= 0) & (cur == -1)]\n",
" long_exits = idx[(prev == 1) & (cur <= 0)]\n",
" short_exits = idx[(prev == -1) & (cur >= 0)]\n",
"\n",
" total_reward = float(rewards.sum())\n",
" rewards_s = pd.Series(rewards, index=idx[:len(rewards)])\n",
" total_profit = float((1.0 + rewards_s).prod() - 1.0)\n",
"\n",
" plt.figure(figsize=(16, 6))\n",
" plt.plot(price.index, price.values, linewidth=1.2, label=\"Close\")\n",
" plt.scatter(long_entries, price.loc[long_entries], s=22, c=\"green\", marker=\"o\", label=\"Long Entry\")\n",
" plt.scatter(short_entries, price.loc[short_entries], s=22, c=\"red\", marker=\"o\", label=\"Short Entry\")\n",
" plt.scatter(long_exits, price.loc[long_exits], s=36, c=\"green\", marker=\"v\", label=\"Long Exit\")\n",
" plt.scatter(short_exits, price.loc[short_exits], s=36, c=\"red\", marker=\"^\", label=\"Short Exit\")\n",
" plt.title(f\"Total Reward: {total_reward:.6f} ~ Total Profit: {total_profit:.6f} \"\n",
" f\"({model_name} | {algo.upper()})\")\n",
" plt.legend(loc=\"best\"); plt.grid(True); plt.tight_layout()\n",
"\n",
" if save_png:\n",
" out = Path(\"reports\") / f\"trades_{Path(model_name).stem}.png\"\n",
" out.parent.mkdir(exist_ok=True)\n",
" plt.savefig(out, dpi=140)\n",
" print(\"Saved trades plot:\", out.resolve())\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "751e808b",
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"from stable_baselines3 import PPO, A2C, DQN\n",
"\n",
"ALGO_MAP = {\"ppo\": PPO, \"a2c\": A2C, \"dqn\": DQN}\n",
"\n",
"def _infer_algo_from_name(p: Path) -> str:\n",
" name = p.stem.lower()\n",
" for k in ALGO_MAP:\n",
" if name.startswith(k + \"_\"):\n",
" return k\n",
" return \"ppo\"\n",
"\n",
"def run_model_collect_series(model_path, algo=\"auto\"):\n",
" p = Path(model_path)\n",
" if algo == \"auto\":\n",
" algo = _infer_algo_from_name(p)\n",
" model = ALGO_MAP[algo].load(p.as_posix())\n",
"\n",
" env = TradingEnv(df_test, feature_cols)\n",
" obs, _ = env.reset()\n",
" rewards, positions = [], []\n",
"\n",
" while True:\n",
" action, _ = model.predict(obs, deterministic=True)\n",
" obs, reward, terminated, truncated, info = env.step(int(action))\n",
" rewards.append(float(reward))\n",
" positions.append(int(info.get(\"position\", 0)))\n",
" if terminated or truncated:\n",
" break\n",
"\n",
" idx = df_test.index[1:1+len(rewards)]\n",
" pos_series = pd.Series(positions, index=idx, name=\"position\")\n",
" return pos_series, rewards, p.name, algo\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "2d2c10bb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Saved trades plot: G:\\My Drive\\Github\\Deep-Reinforcement-Learning-MT5-Bot\\notebooks\\reports\\trades_ppo_EURUSD_M15.png\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1600x600 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Uses MODEL_PATH / ALGO_SELECTED from your first block; falls back to auto if not set\n",
"try:\n",
" model_name = MODEL_PATH\n",
" algo_hint = ALGO_SELECTED if 'ALGO_SELECTED' in globals() else \"auto\"\n",
"except NameError:\n",
" # If you didnt run the first block, hardcode a file here:\n",
" model_name = \"models/ppo_EURUSD_M15.zip\"\n",
" algo_hint = \"auto\"\n",
"\n",
"pos_series, rewards, model_name_str, algo_used = run_model_collect_series(model_name, algo_hint)\n",
"plot_trades_on_price_from_series(df_test, pos_series, rewards, model_name_str, algo_used, save_png=True)\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "bb64e225",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Saved trades plot: G:\\My Drive\\Github\\Deep-Reinforcement-Learning-MT5-Bot\\notebooks\\reports\\trades_ppo_EURUSD_M15.png\n",
"Saved trades plot: G:\\My Drive\\Github\\Deep-Reinforcement-Learning-MT5-Bot\\notebooks\\reports\\trades_a2c_EURUSD_M15.png\n",
"Saved trades plot: G:\\My Drive\\Github\\Deep-Reinforcement-Learning-MT5-Bot\\notebooks\\reports\\trades_dqn_EURUSD_M15.png\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1600x600 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1600x600 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABjUAAAJOCAYAAAD/KYUYAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Qd4U+X3B/BvRvegZa/Slr03KBtUtqKIIhtx/V2I4gIHUBy490B+LhQExQGIiCIyZMjeS1bZu4PuNuP/nDe9aZKmi66Ufj/PE5Lc3NzcJPfelPfcc47OarVaQURERERERERERERE5OH0pb0CRERERERERERERERE+cGgBhERERERERERERERlQkMahARERERERERERERUZnAoAYREREREREREREREZUJDGoQEREREREREREREVGZwKAGERERERERERERERGVCQxqEBERERERERERERFRmcCgBhERERERERERERERlQkMahARERERERERERERUZnAoAYREVE5t2rVKuh0OnVNUJ/FtGnTSns1qByKiIjA3XffDU+ybNkytG7dGr6+vmrfiIuLU+so60rXnpMnT6rvet26dXnO27NnT3UhupZ+s6Ojo8v830QzZ85EnTp1kJaWVtqrQkREVGwY1CAiIioF8h/m/Fzy85/qV199FQsXLiz2df7666+d1s1oNKJWrVpqgPP06dPF/vplnQwGP/DAA6hSpQoCAgLQq1cvbNu2LV/P/d///ocePXqgWrVq8PHxQWRkJMaNG6cGX3Kzdu1a+/d16dKlXOft3bu3mu/RRx/NNsgZFRWFjh07IjQ0FJUrV1YDmX/99Ve2Zcj0nLZlLy+vbPMvXrwYbdu2VYOoMgAzdepUmEwmlBTZdvOzH+Yn0LB06dISC4Y5rpter0fNmjXRp0+fIh+Eu3z5MoYOHQo/Pz98/PHH+Pbbb9W26yo5OVm99+IcBJRjjKxLSEgIgoODceutt+Lo0aN5Pk/WTdZdPp8aNWogKCgIbdq0waeffgqz2Zxt/sOHD+OOO+5Q27q/vz+6du2KlStXOs1jsVjU8XDQoEEICwtTn0nz5s3x8ssvIzU11e16fPHFF2jSpIna1hs0aIAPP/ww2zwHDx7EE088gc6dO9uDSLnt4wkJCXjmmWfU8UCOC3I8lnWX93y1pk+fjuuuuw5dunRBWZHbcadx48b2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
"text/plain": [
"<Figure size 1600x600 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Requires MODEL_BUNDLE from the earlier “collect models” block\n",
"for p, algo in MODEL_BUNDLE:\n",
" pos_series, rewards, model_name_str, algo_used = run_model_collect_series(p, algo)\n",
" plot_trades_on_price_from_series(df_test, pos_series, rewards, model_name_str, algo_used, save_png=True)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "drl",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.18"
}
},
"nbformat": 4,
"nbformat_minor": 5
}