diff --git a/notebooks/optim_backtestingpy_sma_optimization.ipynb b/notebooks/optim_backtestingpy_sma_optimization.ipynb new file mode 100644 index 0000000..1d53fb1 --- /dev/null +++ b/notebooks/optim_backtestingpy_sma_optimization.ipynb @@ -0,0 +1,202 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "ab3bfd28", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# filename: optim_backtestingpy_sma_optimization.ipynb\n", + "\n", + "# --- 1. Imports and Setup ---\n", + "import pandas as pd\n", + "import pandas_ta as ta # We'll use pandas-ta for the indicator calculation inside the strategy\n", + "from backtesting import Backtest, Strategy\n", + "from backtesting.lib import crossover\n", + "from backtesting.test import EURUSD\n", + "\n", + "# --- 2. Load Sample Data ---\n", + "print(\"Loading built-in EURUSD sample data...\")\n", + "data = EURUSD.copy()\n", + "print(\"Data loaded successfully.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55d51b03", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 3. The Parameterized Strategy Class ---\n", + "\n", + "class SmaCrossOptimize(Strategy):\n", + " # --- Parameters for Optimization ---\n", + " # These values will be replaced by the optimizer.\n", + " # The values here are just defaults for a single run.\n", + " fast_period = 10\n", + " slow_period = 40\n", + "\n", + " def init(self):\n", + " # In init(), we define the indicators. Since the periods are dynamic,\n", + " # we calculate them here. self.I() is smart and caches the results.\n", + " # We use pandas_ta for its clean syntax.\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", + " # The same trading logic as before\n", + " if crossover(self.fast_sma, self.slow_sma):\n", + " if not self.position:\n", + " self.buy()\n", + " elif crossover(self.slow_sma, self.fast_sma):\n", + " self.position.close()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ad1f5d7", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 4. Running the Optimization ---\n", + "print(\"\\n--- Starting Grid Search Optimization ---\")\n", + "\n", + "bt = Backtest(data, SmaCrossOptimize, cash=10000, commission=.002)\n", + "\n", + "# The optimize method performs a grid search.\n", + "# We store the results, which include the heatmap data, in a variable.\n", + "# return_heatmap=True is the default, so we don't need to specify it, but it's good to be aware of.\n", + "stats, heatmap = bt.optimize(\n", + " fast_period=range(10, 31, 5),\n", + " slow_period=range(40, 81, 10),\n", + " maximize='Sharpe Ratio',\n", + " constraint=lambda p: p.fast_period < p.slow_period,\n", + " return_heatmap=True # Explicitly ask for the heatmap data\n", + ")\n", + "\n", + "print(\"\\n--- Optimization Results ---\")\n", + "print(\"Best stats found:\")\n", + "print(stats)\n", + "\n", + "print(\"\\nBest parameters found:\")\n", + "print(stats._strategy)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7257ef5", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 5. Visualizing the Optimization Results ---\n", + "from backtesting.lib import plot_heatmaps\n", + "\n", + "print(\"\\n--- Plotting Optimization Heatmap ---\")\n", + "\n", + "# The plot_heatmaps function is imported directly from backtesting.lib\n", + "# and takes the heatmap data returned by the optimize() method.\n", + "plot_heatmaps(heatmap, agg='mean')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84ddd2e7", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 6. Walk-Forward Analysis (WFA) ---\n", + "# This is a more robust way to test our strategy. We optimize on a training set\n", + "# and validate on an unseen testing set, moving this window through time.\n", + "\n", + "print(\"\\n--- Starting Walk-Forward Analysis ---\")\n", + "\n", + "# --- WFA Configuration ---\n", + "n_train_bars = 24 * 30 * 3 # Approx. 3 months\n", + "n_test_bars = 24 * 30 * 1 # Approx. 1 month\n", + "step = n_test_bars\n", + "\n", + "oos_results = []\n", + "\n", + "# The main loop for walking forward\n", + "for i in range(n_train_bars, len(data) - n_test_bars, step):\n", + " train_data = data.iloc[i - n_train_bars : i]\n", + " test_data = data.iloc[i : i + n_test_bars]\n", + "\n", + " print(f\"\\nTraining on data from {train_data.index[0]} to {train_data.index[-1]}\")\n", + " print(f\"Testing on data from {test_data.index[0]} to {test_data.index[-1]}\")\n", + "\n", + " # --- In-Sample Optimization ---\n", + " # For the training part, finalizing trades is less critical, but we can add it for consistency.\n", + " bt_train = Backtest(train_data, SmaCrossOptimize, cash=10000, commission=.002, finalize_trades=True)\n", + "\n", + " stats_train, _ = bt_train.optimize(\n", + " fast_period=range(10, 31, 5),\n", + " slow_period=range(40, 81, 10),\n", + " maximize='Sharpe Ratio',\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: {best_params.fast_period}/{best_params.slow_period}\")\n", + "\n", + " # --- Out-of-Sample Validation ---\n", + " # CORRECTED: Add finalize_trades=True to get more accurate OOS stats.\n", + " bt_test = Backtest(test_data, SmaCrossOptimize, 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", + " )\n", + "\n", + " oos_results.append(stats_test)\n", + "\n", + "\n", + "# --- 7. Analyze WFA Results ---\n", + "print(\"\\n\\n--- Walk-Forward Analysis Final Results ---\")\n", + "\n", + "if oos_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(\"Performance of each Out-of-Sample period:\")\n", + " print(wfa_df[['Return [%]', 'Sharpe Ratio', 'Win Rate [%]', '# Trades']])\n", + "\n", + " print(\"\\nAverage performance across all OOS periods:\")\n", + " print(wfa_df[['Return [%]', 'Sharpe Ratio', 'Win Rate [%]']].mean())\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 +}