В мире количественной торговли и разработки алгоритмических стратегий, поиск оптимальных параметров является краеугольным камнем успеха. Гиперпараметры, такие как периоды скользящих средних, пороги входа/выхода или коэффициенты в моделях, оказывают огромное влияние на прибыльность и рискованность стратегии. Традиционные методы, такие как 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. Рекомендуется использовать виртуальное окружение.
- Создайте виртуальное окружение (необязательно, но рекомендуется):
python -m venv venv source venv/bin/activate # Для Linux/macOS # venv\Scripts\activate # Для Windows - Установите необходимые библиотеки:
pip install pandas numpy optuna backtesting # Для визуализации (необязательно): pip install plotly - Сохраните код:
Скопируйте предоставленный выше Python код и сохраните его в файл с именем, например,
optimize_strategy.py. - Запустите скрипт:
python optimize_strategy.pyСкрипт выполнит 100 итераций оптимизации, каждая из которых будет включать бэктест стратегии с новым набором гиперпараметров. По завершении вы увидите лучшие найденные параметры и соответствующий им коэффициент Шарпа.
Этот подход может быть расширен для более сложных стратегий и множества гиперпараметров. Например, если ваша стратегия использует Z-Score для определения входа, как в статье Расчет Z-Score для пары ETH/BTC на Python с statsmodels, вы можете оптимизировать параметры окна Z-Score. Также, для стратегий, зависящих от рыночных режимов, как описано в Моделирование рыночных режимов с помощью марковских цепей на Python, можно оптимизировать параметры, специфичные для каждого режима.
Помните, что оптимизация на исторических данных не гарантирует будущих результатов. Важно проводить дополнительную валидацию, такую как кросс-валидация и тестирование на данных, не использовавшихся в процессе оптимизации (out-of-sample testing), чтобы избежать переобучения.




