Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions notebooks/optim_backtestingpy_sma_optimization.ipynb
Original file line number Diff line number Diff line change
@@ -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
}