Оптимизация гиперпараметров торговой стратегии: Байесовская оптимизация с Optuna в Python

Оптимизация гиперпараметров торговой стратегии: Байесовская оптимизация с Optuna в Python Анализ данных и Бэктесты
Подробное руководство по оптимизации гиперпараметров торговых стратегий с использованием библиотеки Optuna и байесовской оптимизации в Python. Узнайте, как повысить эффективность бэктестинга.
Суть: Байесовская оптимизация с использованием библиотеки Optuna позволяет эффективно находить оптимальные гиперпараметры для торговых стратегий, минимизируя вычислительные затраты по сравнению с традиционными методами. Она строит вероятностную модель функции потерь и использует ее для выбора следующих точек для оценки, что приводит к более быстрому сходимости к лучшим решениям.

В мире количественной торговли и разработки алгоритмических стратегий, поиск оптимальных параметров является краеугольным камнем успеха. Гиперпараметры, такие как периоды скользящих средних, пороги входа/выхода или коэффициенты в моделях, оказывают огромное влияние на прибыльность и рискованность стратегии. Традиционные методы, такие как Grid Search или Random Search, часто оказываются неэффективными из-за огромного пространства поиска и высокой стоимости каждой оценки (бэктеста).

Байесовская оптимизация предлагает элегантное решение этой проблемы. Вместо слепого перебора, она строит вероятностную модель (суррогатную модель) функции потерь (например, Sharpe Ratio) и использует ее для выбора наиболее перспективных гиперпараметров для следующего бэктеста. Это значительно сокращает количество необходимых итераций для нахождения близких к оптимальным решений. В Python библиотека Optuna является мощным и гибким инструментом для реализации байесовской оптимизации.

В этой статье мы рассмотрим, как использовать Optuna для оптимизации гиперпараметров простой торговой стратегии, используя в качестве метрики производительности коэффициент Шарпа. Для более глубокого понимания метрик, вы можете ознакомиться с нашей статьей Реализация кастомной функции потерь Sharpe Ratio в PyTorch.

Исходный код

Для демонстрации мы используем простую стратегию пересечения скользящих средних (SMA Crossover). Наша цель — найти оптимальные периоды для быстрой и медленной скользящих средних, которые максимизируют коэффициент Шарпа. Мы будем использовать библиотеку backtesting.py для удобного бэктестинга.


import pandas as pd
import numpy as np
import optuna
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA, GOOG # Используем тестовые данные GOOG

# 1. Определение торговой стратегии
class SMACrossover(Strategy):
    # Гиперпараметры, которые будут оптимизироваться
    n1 = 10  # Период быстрой SMA
    n2 = 20  # Период медленной SMA

    def init(self):
        # Предварительный расчет скользящих средних
        self.sma1 = self.I(SMA, self.data.Close, self.n1)
        self.sma2 = self.I(SMA, self.data.Close, self.n2)

    def next(self):
        # Логика входа/выхода из позиции
        if crossover(self.sma1, self.sma2):
            self.buy()
        elif crossover(self.sma2, self.sma1):
            self.sell()

# 2. Определение функции цели для Optuna
def objective(trial):
    # Определение диапазонов для гиперпараметров
    n1 = trial.suggest_int('n1', 5, 50)
    n2 = trial.suggest_int('n2', 20, 200)

    # Убедимся, что n2 всегда больше n1
    if n1 >= n2:
        # Если условие нарушено, можно вернуть очень плохое значение
        # или пропустить итерацию, но Optuna лучше работает с числовыми значениями
        # Поэтому возвращаем отрицательное бесконечность или очень низкое значение
        return -np.inf 

    # Создание экземпляра стратегии с предложенными гиперпараметрами
    strategy_params = {'n1': n1, 'n2': n2}

    # Инициализация бэктестера
    bt = Backtest(GOOG, SMACrossover,
                  cash=10_000, commission=.002,
                  exclusive_orders=True)

    # Запуск бэктеста
    output = bt.run(**strategy_params)

    # Возвращаем метрику, которую хотим максимизировать (например, Sharpe Ratio)
    # Если Sharpe Ratio не определен (например, из-за отсутствия сделок), возвращаем 0 или -inf
    sharpe_ratio = output['Sharpe Ratio'] if 'Sharpe Ratio' in output and not pd.isna(output['Sharpe Ratio']) else -np.inf
    
    # Optuna по умолчанию стремится минимизировать, поэтому для максимизации Sharpe Ratio
    # мы возвращаем его с отрицательным знаком, либо указываем direction='maximize' в create_study
    return sharpe_ratio

# 3. Запуск исследования Optuna
if __name__ == '__main__':
    # Создание исследования Optuna
    # direction='maximize' указывает, что мы хотим максимизировать значение, возвращаемое objective
    study = optuna.create_study(direction='maximize')

    # Запуск оптимизации
    # n_trials - количество итераций (бэктестов)
    study.optimize(objective, n_trials=100)

    print("Оптимизация завершена.")
    print(f"Лучший пробный запуск (trial): {study.best_trial.number}")
    print(f"Лучшее значение (Sharpe Ratio): {study.best_value:.4f}")
    print("Лучшие гиперпараметры:")
    for key, value in study.best_params.items():
        print(f"  {key}: {value}")

    # Дополнительно: визуализация результатов (требует plotly)
    # fig = optuna.visualization.plot_optimization_history(study)
    # fig.show()
    # fig = optuna.visualization.plot_param_importances(study)
    # fig.show()

Разбор параметров

  • SMACrossover(Strategy):

    Класс, определяющий нашу торговую стратегию. Он наследуется от Strategy из библиотеки backtesting.py и содержит логику инициализации индикаторов (init) и принятия торговых решений (next).

  • n1, n2:

    Гиперпараметры стратегии — периоды быстрой и медленной скользящих средних соответственно. Эти значения будут оптимизироваться.

  • objective(trial):

    Основная функция, которую Optuna будет вызывать для каждой итерации оптимизации. Она принимает объект trial, который позволяет предлагать значения для гиперпараметров.

  • trial.suggest_int('name', low, high):

    Метод объекта trial, который предлагает целочисленное значение для гиперпараметра в заданном диапазоне [low, high]. Optuna использует свою внутреннюю модель для выбора наиболее перспективных значений.

  • trial.suggest_float('name', low, high, log=False):

    Аналогично suggest_int, но для вещественных чисел. Параметр log=True позволяет искать значения в логарифмическом масштабе, что полезно для параметров, которые могут изменяться на порядки.

  • Backtest(data, Strategy, ...):

    Класс из backtesting.py для проведения бэктестов. Принимает исторические данные, класс стратегии и параметры бэктеста (например, начальный капитал, комиссия).

  • output['Sharpe Ratio']:

    Метрика производительности, возвращаемая бэктестером. Коэффициент Шарпа измеряет доходность стратегии с учетом риска. Мы стремимся максимизировать это значение.

  • optuna.create_study(direction='maximize'):

    Создает новое исследование Optuna. Параметр direction='maximize' указывает, что функция objective должна быть максимизирована. Если не указать, по умолчанию будет минимизация.

  • study.optimize(objective, n_trials):

    Запускает процесс оптимизации. objective — это наша функция цели, а n_trials — количество раз, которое Optuna будет вызывать objective с различными наборами гиперпараметров.

  • study.best_trial, study.best_value, study.best_params:

    Атрибуты объекта study, которые содержат информацию о лучшем найденном наборе гиперпараметров, соответствующем ему значении функции цели и сам объект пробного запуска.

Как запустить

Для запуска этого примера вам потребуется установить необходимые библиотеки Python. Рекомендуется использовать виртуальное окружение.

  1. Создайте виртуальное окружение (необязательно, но рекомендуется):
    
    python -m venv venv
    source venv/bin/activate # Для Linux/macOS
    # venv\Scripts\activate # Для Windows
            

  2. Установите необходимые библиотеки:
    
    pip install pandas numpy optuna backtesting
    # Для визуализации (необязательно):
    pip install plotly
            

  3. Сохраните код:

    Скопируйте предоставленный выше Python код и сохраните его в файл с именем, например, optimize_strategy.py.

  4. Запустите скрипт:
    
    python optimize_strategy.py
            

    Скрипт выполнит 100 итераций оптимизации, каждая из которых будет включать бэктест стратегии с новым набором гиперпараметров. По завершении вы увидите лучшие найденные параметры и соответствующий им коэффициент Шарпа.

Этот подход может быть расширен для более сложных стратегий и множества гиперпараметров. Например, если ваша стратегия использует Z-Score для определения входа, как в статье Расчет Z-Score для пары ETH/BTC на Python с statsmodels, вы можете оптимизировать параметры окна Z-Score. Также, для стратегий, зависящих от рыночных режимов, как описано в Моделирование рыночных режимов с помощью марковских цепей на Python, можно оптимизировать параметры, специфичные для каждого режима.

Помните, что оптимизация на исторических данных не гарантирует будущих результатов. Важно проводить дополнительную валидацию, такую как кросс-валидация и тестирование на данных, не использовавшихся в процессе оптимизации (out-of-sample testing), чтобы избежать переобучения.

Оцените статью
FinFluct