Разработка системы оптимизации параметров стратегии (Bayesian optimization)

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка системы оптимизации параметров стратегии (Bayesian optimization)
Сложный
~5 дней
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1286
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    902
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1122
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    859

Разработка системы оптимизации параметров стратегии (Bayesian optimization)

Bayesian optimization — умный поиск оптимума: вместо случайного перебора строит суррогатную модель зависимости метрики от параметров и выбирает следующую точку для оценки там, где ожидается наибольшее улучшение. Находит хорошие параметры за 50–200 итераций там, где grid search требует тысяч.

Принцип Bayesian Optimization

Суррогатная модель (Gaussian Process) — аппроксимирует функцию bэктеста. После каждой оценки обновляет своё представление о том, как параметры влияют на результат.

Acquisition Function — определяет где искать дальше. Expected Improvement (EI) — стандартный выбор: ищем точки, где ожидаемое улучшение максимально.

Exploration vs Exploitation — баланс между исследованием неизведанных областей и уточнением вокруг известных хороших точек.

Реализация с Optuna

import optuna
from optuna.samplers import TPESampler
import pandas as pd

optuna.logging.set_verbosity(optuna.logging.WARNING)

class BayesianOptimizer:
    def __init__(
        self,
        backtest_fn: callable,
        n_trials: int = 100,
        n_startup_trials: int = 10,  # случайные начальные пробы
        n_jobs: int = 1,
        metric: str = 'sharpe_ratio',
        direction: str = 'maximize',
    ):
        self.backtest_fn = backtest_fn
        self.n_trials = n_trials
        self.n_startup = n_startup_trials
        self.n_jobs = n_jobs
        self.metric = metric
        self.direction = direction
        self.results_log = []

    def create_objective(self, param_space: dict):
        def objective(trial: optuna.Trial) -> float:
            params = {}
            for name, spec in param_space.items():
                if spec['type'] == 'int':
                    params[name] = trial.suggest_int(name, spec['low'], spec['high'])
                elif spec['type'] == 'float':
                    params[name] = trial.suggest_float(name, spec['low'], spec['high'])
                elif spec['type'] == 'categorical':
                    params[name] = trial.suggest_categorical(name, spec['choices'])
                elif spec['type'] == 'log':
                    params[name] = trial.suggest_float(name, spec['low'], spec['high'], log=True)

            try:
                metrics = self.backtest_fn(params)
                value = metrics.get(self.metric, float('-inf'))

                # Штраф за слишком мало сделок
                n_trades = metrics.get('total_trades', 0)
                if n_trades < 15:
                    value = value * n_trades / 15

                # Штраф за экстремальный drawdown
                max_dd = abs(metrics.get('max_drawdown_pct', 0))
                if max_dd > 40:
                    value = value * (40 / max_dd) ** 2

                self.results_log.append({**params, self.metric: value, 'total_trades': n_trades})
                return value
            except Exception as e:
                return float('-inf')

        return objective

    def run(self, param_space: dict) -> tuple[dict, pd.DataFrame]:
        sampler = TPESampler(
            n_startup_trials=self.n_startup,
            seed=42,
        )

        study = optuna.create_study(
            direction=self.direction,
            sampler=sampler,
        )

        study.optimize(
            self.create_objective(param_space),
            n_trials=self.n_trials,
            n_jobs=self.n_jobs,
            show_progress_bar=True,
        )

        best_params = study.best_params
        results_df = pd.DataFrame(self.results_log).sort_values(self.metric, ascending=False)

        return best_params, results_df, study

Использование

optimizer = BayesianOptimizer(
    backtest_fn=lambda params: run_backtest(params, train_data),
    n_trials=150,
    n_startup_trials=15,
    n_jobs=4,
)

param_space = {
    'fast_period': {'type': 'int', 'low': 5, 'high': 30},
    'slow_period': {'type': 'int', 'low': 15, 'high': 100},
    'rsi_period': {'type': 'int', 'low': 7, 'high': 21},
    'rsi_oversold': {'type': 'int', 'low': 20, 'high': 40},
    'stop_loss_pct': {'type': 'float', 'low': 0.01, 'high': 0.10},
    'take_profit_pct': {'type': 'float', 'low': 0.02, 'high': 0.25},
    'commission': {'type': 'categorical', 'choices': ['market', 'limit']},
}

best_params, results, study = optimizer.run(param_space)
print("Best parameters:", best_params)
print(f"Best {optimizer.metric}: {study.best_value:.3f}")

Анализ результатов

import optuna.visualization as vis

# Важность параметров
fig = vis.plot_param_importances(study)
fig.show()

# Contour plot двух параметров
fig = vis.plot_contour(study, params=['fast_period', 'slow_period'])
fig.show()

# История оптимизации
fig = vis.plot_optimization_history(study)
fig.show()

Защита от overfitting: Cross-Validation

def time_series_cv_objective(params: dict, data: pd.DataFrame, n_splits: int = 5) -> dict:
    """K-fold кросс-валидация для временных рядов"""
    fold_size = len(data) // (n_splits + 1)
    sharpe_scores = []

    for fold in range(n_splits):
        train_start = 0
        train_end = (fold + 1) * fold_size
        test_start = train_end
        test_end = train_end + fold_size

        train_data = data.iloc[train_start:train_end]
        test_data = data.iloc[test_start:test_end]

        # Оптимизируем на train, оцениваем на test
        metrics = run_backtest(params, test_data)
        sharpe_scores.append(metrics.get('sharpe_ratio', 0))

    return {
        'sharpe_ratio': np.mean(sharpe_scores),
        'sharpe_std': np.std(sharpe_scores),
        'min_sharpe': min(sharpe_scores),  # худший период
    }

Сравнение методов оптимизации

Метод Итераций для хорошего результата Интерпретируемость Параллелизм
Grid Search Все комбинации Полная Хороший
Random Search 100–200 Слабая Хороший
Genetic Algorithm 500–2000 Слабая Ограниченный
Bayesian (TPE) 50–150 Средняя Ограниченный

Для большинства задач оптимизации торговых стратегий Bayesian optimization через Optuna — оптимальный выбор: быстро находит хорошие параметры, поддерживает parallelism, имеет отличную визуализацию.