diff --git a/notebooks/optim_supertrend_vectorbt.ipynb b/notebooks/optim_supertrend_vectorbt.ipynb new file mode 100644 index 0000000..00bfbcc --- /dev/null +++ b/notebooks/optim_supertrend_vectorbt.ipynb @@ -0,0 +1,230 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "a7e5f72c", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# filename: optim_supertrend_vectorbt.ipynb\n", + "\n", + "# --- 1. Imports and Setup ---\n", + "import os\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import pandas_ta as ta # noqa: F401\n", + "import vectorbt as vbt\n", + "\n", + "print(\"Libraries imported.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d38122d9", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 2. Configuration ---\n", + "# This is the control panel for the optimization.\n", + "# Change the parameters here to analyze different datasets or strategy settings.\n", + "\n", + "# --- Data Input ---\n", + "# Assumes the file is in the project's 'data/parquet/' directory\n", + "DATA_FILENAME = \"EURUSD_M15_2024-09-14_to_2025-09-14.parquet\"\n", + "# The frequency of the data is crucial for correct performance calculation.\n", + "# Examples: '15min', '1H', '4H', 'D'\n", + "FREQ = '15min'\n", + "\n", + "# --- Optimization Parameters for Supertrend ---\n", + "# We define the ranges for the grid search here.\n", + "supert_period_range = np.arange(7, 22, 2) # Test periods from 7 to 21\n", + "supert_multiplier_range = np.arange(2.0, 5.1, 0.5) # Test multipliers from 2.0 to 5.0\n", + "\n", + "# --- Backtest Settings ---\n", + "INIT_CASH = 10000\n", + "FEES = 0.0001 # 0.01% fee per trade\n", + "SLIPPAGE = 0.0001 # 0.01% slippage per trade\n", + "\n", + "# --- Analysis Settings ---\n", + "# The metric to sort the results by\n", + "OPTIMIZE_FOR = 'Sharpe Ratio'\n", + "# How many top results to display\n", + "N_TOP_RESULTS = 10\n", + "\n", + "# --- Path Construction ---\n", + "project_root = os.path.abspath(os.path.join(os.getcwd(), \"..\"))\n", + "file_path = os.path.join(project_root, \"data\", \"parquet\", DATA_FILENAME)\n", + "\n", + "print(\"Configuration loaded.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c17eeb09", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 3. Data Loading and Indicator Calculation ---\n", + "print(f\"Loading data from: {file_path}\")\n", + "try:\n", + " price_data = pd.read_parquet(file_path)\n", + " price_data.set_index('Time', inplace=True)\n", + " print(f\"Data loaded successfully: {len(price_data)} bars.\")\n", + "except Exception as e:\n", + " print(f\"Error loading data: {e}\")\n", + " exit()\n", + "\n", + "# --- CORRECTED APPROACH: Create a custom wrapper for pandas-ta ---\n", + "# This wrapper function will call pandas-ta and return only the main supertrend line.\n", + "# This solves the problem of dynamically named output columns.\n", + "def pta_supertrend_wrapper(high, low, close, length, multiplier):\n", + " # Calculate the supertrend using pandas-ta\n", + " st = ta.supertrend(high=high, low=low, close=close, length=length, multiplier=multiplier)\n", + "\n", + " # pandas-ta returns a DataFrame. The first column is the main supertrend line.\n", + " # We return only this column (as a Series).\n", + " return st.iloc[:, 0]\n", + "\n", + "\n", + "# Now, create an indicator factory from our OWN wrapper function\n", + "Supertrend = vbt.IndicatorFactory(\n", + " input_names=['high', 'low', 'close'],\n", + " param_names=['length', 'multiplier'],\n", + " output_names=['supertrend'] # We can name the output whatever we want here\n", + ").from_apply_func(\n", + " pta_supertrend_wrapper,\n", + " # Important settings for functions that expect pandas Series:\n", + " keep_pd=True,\n", + " to_2d=False\n", + ")\n", + "\n", + "print(\"\\nCalculating Supertrend for all parameter combinations...\")\n", + "supertrend_indicator = Supertrend.run(\n", + " price_data['High'],\n", + " price_data['Low'],\n", + " price_data['Close'],\n", + " length=supert_period_range,\n", + " multiplier=supert_multiplier_range,\n", + " param_product=True\n", + ")\n", + "print(\"Indicator calculation complete.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aafe29ef", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 4. Signal Generation and Backtest Execution ---\n", + "print(\"\\nGenerating signals and running backtest...\")\n", + "\n", + "# Now we can access the output via the name we defined: .supertrend\n", + "entries = price_data['Close'].vbt.crossed_above(supertrend_indicator.supertrend)\n", + "exits = price_data['Close'].vbt.crossed_below(supertrend_indicator.supertrend)\n", + "\n", + "# Run the portfolio simulation\n", + "portfolio = vbt.Portfolio.from_signals(\n", + " price_data['Close'],\n", + " entries,\n", + " exits,\n", + " freq=FREQ,\n", + " init_cash=INIT_CASH,\n", + " fees=FEES,\n", + " slippage=SLIPPAGE\n", + ")\n", + "print(\"Backtest complete.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74f45bc0", + "metadata": {}, + "outputs": [], + "source": [ + "# %%\n", + "# --- 5. Numerical Analysis of Results (FINAL, DEFINITIVE VERSION 4) ---\n", + "print(\"\\n--- Optimization Results ---\")\n", + "\n", + "if portfolio.trades.count().sum() == 0:\n", + " print(\"No trades were executed for any parameter combination.\")\n", + "else:\n", + " # --- Top N Results ---\n", + " print(f\"\\nTop {N_TOP_RESULTS} parameter combinations sorted by '{OPTIMIZE_FOR}':\")\n", + "\n", + " # Build the results DataFrame manually from individual metric Series.\n", + "\n", + " total_return = portfolio.total_return()\n", + " sharpe_ratio = portfolio.sharpe_ratio()\n", + " max_drawdown = portfolio.max_drawdown()\n", + " win_rate = portfolio.trades.win_rate()\n", + "\n", + " # CORRECTED: profit_factor is also an attribute of the `trades` accessor\n", + " profit_factor = portfolio.trades.profit_factor()\n", + "\n", + " total_trades = portfolio.trades.count()\n", + "\n", + " # Combine these Series into a single DataFrame.\n", + " results_df = pd.concat([\n", + " total_return,\n", + " sharpe_ratio,\n", + " max_drawdown,\n", + " win_rate,\n", + " profit_factor,\n", + " total_trades\n", + " ], axis=1, keys=[\n", + " 'Total Return [%]',\n", + " 'Sharpe Ratio',\n", + " 'Max Drawdown [%]',\n", + " 'Win Rate [%]',\n", + " 'Profit Factor',\n", + " 'Total Trades'\n", + " ])\n", + "\n", + " # Sort this new DataFrame by the desired metric.\n", + " top_stats = results_df.sort_values(by=OPTIMIZE_FOR, ascending=False).head(N_TOP_RESULTS)\n", + "\n", + " print(top_stats)\n", + "\n", + " # --- Best Result In-Depth ---\n", + " best_params_idx = top_stats.index[0]\n", + " best_portfolio_run = portfolio[best_params_idx]\n", + " best_stats = best_portfolio_run.stats()\n", + "\n", + " print(f\"\\n--- Detailed Stats for the Best Combination: {best_params_idx} ---\")\n", + " print(best_stats)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "strategy-optimizer_env", + "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 +}