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
186 changes: 186 additions & 0 deletions notebooks/optim_walk_forward_analysis_sma.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
{
"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",
"# --- Matplotlib Setup for Pandas Plots ---\n",
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"import pandas_ta as ta\n",
"from backtesting import Backtest, Strategy\n",
"from backtesting.lib import crossover\n",
"from backtesting.test import EURUSD\n",
"\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
}