diff --git a/notebooks/optim_walk_forward_analysis_sma.ipynb b/notebooks/optim_walk_forward_analysis_sma.ipynb new file mode 100644 index 0000000..0cf150c --- /dev/null +++ b/notebooks/optim_walk_forward_analysis_sma.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "5ae2f039", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# filename: optim_walk_forward_analysis_sma.ipynb\n", + "\n", + "# --- 1. Imports and Setup ---\n", + "import pandas as pd\n", + "import numpy as np\n", + "from backtesting import Backtest, Strategy\n", + "from backtesting.lib import crossover\n", + "from backtesting.test import EURUSD\n", + "import pandas_ta as ta\n", + "\n", + "# --- Matplotlib Setup for Pandas Plots ---\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "# This magic command tells Jupyter to render plots directly in the output cell.\n", + "plt.style.use('dark_background') # Optional: Set a dark theme consistent with other plots\n", + "\n", + "# --- 2. Load Sample Data ---\n", + "print(\"Loading built-in EURUSD sample data...\")\n", + "data = EURUSD.copy()\n", + "print(f\"Data loaded. Contains {len(data)} bars from {data.index[0]} to {data.index[-1]}.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "367c50d3", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 3. The Parameterized Strategy Class ---\n", + "# This is the same strategy we optimized in the previous notebook.\n", + "\n", + "class SmaCrossSLTP(Strategy):\n", + " fast_period = 10\n", + " slow_period = 40\n", + " sl_percent = 1.5\n", + " tp_percent = 3.0\n", + "\n", + " def init(self):\n", + " self.fast_sma = self.I(ta.sma, pd.Series(self.data.Close), length=self.fast_period)\n", + " self.slow_sma = self.I(ta.sma, pd.Series(self.data.Close), length=self.slow_period)\n", + "\n", + " def next(self):\n", + " sl_price = self.data.Close[-1] * (1 - self.sl_percent / 100)\n", + " tp_price = self.data.Close[-1] * (1 + self.tp_percent / 100)\n", + "\n", + " if crossover(self.fast_sma, self.slow_sma):\n", + " if not self.position:\n", + " self.buy(sl=sl_price, tp=tp_price)\n", + " elif crossover(self.slow_sma, self.fast_sma):\n", + " self.position.close()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a932019", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 4. Walk-Forward Analysis Implementation ---\n", + "print(\"\\n--- Starting Walk-Forward Analysis ---\")\n", + "\n", + "# --- WFA Configuration ---\n", + "# Using integer indices for slicing the DataFrame\n", + "total_bars = len(data)\n", + "# ~6 months of training data, ~2 months of validation data (4-hour bars)\n", + "n_train_bars = int(total_bars * 0.25) # Use 25% of data for the first training\n", + "n_test_bars = int(n_train_bars / 3) # Test period is 1/3 of the training period\n", + "step = n_test_bars # Slide the window forward by the test period size\n", + "\n", + "# List to store the results from each out-of-sample (OOS) run\n", + "oos_results = []\n", + "\n", + "# The main loop for walking forward\n", + "for i in range(n_train_bars, total_bars - n_test_bars, step):\n", + " # Define the training and testing data slices\n", + " train_data = data.iloc[i - n_train_bars : i]\n", + " test_data = data.iloc[i : i + n_test_bars]\n", + "\n", + " print(\"-\" * 50)\n", + " print(f\"Training on: {train_data.index[0]} -> {train_data.index[-1]} ({len(train_data)} bars)\")\n", + " print(f\"Testing on: {test_data.index[0]} -> {test_data.index[-1]} ({len(test_data)} bars)\")\n", + "\n", + " # --- In-Sample Optimization ---\n", + " bt_train = Backtest(train_data, SmaCrossSLTP, cash=10000, commission=.002, finalize_trades=True)\n", + "\n", + " # Using a smaller parameter grid for faster WFA execution\n", + " stats_train, _ = bt_train.optimize(\n", + " fast_period=[10, 20],\n", + " slow_period=[40, 50],\n", + " sl_percent=[1.0, 1.5],\n", + " tp_percent=[2.0, 3.0],\n", + " maximize='SQN',\n", + " constraint=lambda p: p.fast_period < p.slow_period,\n", + " return_heatmap=True\n", + " )\n", + "\n", + " best_params = stats_train._strategy\n", + " print(f\"Best params found in-sample: fast={best_params.fast_period}, slow={best_params.slow_period}, sl={best_params.sl_percent}%, tp={best_params.tp_percent}%\")\n", + "\n", + " # --- Out-of-Sample Validation ---\n", + " bt_test = Backtest(test_data, SmaCrossSLTP, cash=10000, commission=.002, finalize_trades=True)\n", + "\n", + " stats_test = bt_test.run(\n", + " fast_period=best_params.fast_period,\n", + " slow_period=best_params.slow_period,\n", + " sl_percent=best_params.sl_percent,\n", + " tp_percent=best_params.tp_percent\n", + " )\n", + "\n", + " oos_results.append(stats_test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b1d250d", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 5. Analyze WFA Results ---\n", + "print(\"\\n\\n\" + \"=\"*50)\n", + "print(\"--- Walk-Forward Analysis Final Results ---\")\n", + "\n", + "if oos_results:\n", + " # Create a DataFrame from the list of Series results\n", + " wfa_df = pd.concat([res.to_frame(f\"Period_{i}\") for i, res in enumerate(oos_results)], axis=1).T\n", + "\n", + " print(\"\\nPerformance of each Out-of-Sample period:\")\n", + " print(wfa_df[['Return [%]', 'Sharpe Ratio', 'Win Rate [%]', '# Trades', 'Profit Factor']])\n", + "\n", + " print(\"\\nAverage performance across all OOS periods:\")\n", + " print(wfa_df[['Return [%]', 'Sharpe Ratio', 'Win Rate [%]']].mean())\n", + "\n", + " # Plot the equity curve of each OOS period\n", + " print(\"\\nPlotting cumulative equity of OOS periods...\")\n", + " # This plot will now render correctly thanks to the setup in the first cell\n", + " wfa_df['Equity Final [$]'].plot(\n", + " kind='bar',\n", + " title='Final Equity per Out-of-Sample Period',\n", + " xlabel='WFA Period',\n", + " ylabel='Final Equity [$]',\n", + " figsize=(10, 6), # Optional: make the plot bigger\n", + " grid=True\n", + " )\n", + " plt.show() # Explicitly showing the plot is the most robust way\n", + "else:\n", + " print(\"No out-of-sample periods were successfully tested.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "strategy-optimizer_env_bck", + "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.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}