Розробка системи оптимізації гіперпараметрів торгової AI-моделі — ключовий етап, коли стандартний grid search підводить. Клієнт прийшов з готовою LSTM-моделлю для торгівлі ф'ючерсами. На історії вона показувала Sharpe 1.5, але на live-даних провалилася до 0.3. Діагноз: стандартний grid search з 5-fold крос-валідацією дав data leakage — параметри підігналися під шум, а не під сигнал. Ми застосували walk-forward validation з Bayesian optimization на Optuna, що дозволило зберегти стабільність моделі та уникнути підгонки. Підсумковий out-of-sample Sharpe склав 1.1 — модель перестала втрачати гроші на нових даних. Цей кейс — ілюстрація того, чому фінансова оптимізація гіперпараметрів потребує особливих підходів.
Проблеми, які вирішуємо
- Data leakage через порушення часової причинності при стандартній крос-валідації. Кожна точка даних — частина часового ряду, і використовувати майбутнє для прогнозу минулого не можна.
- Overfitting — параметри, підібрані на повній історії, оптимальні лише для неї. На свіжих даних вони дають збиток, тому що модель запам'ятала шум, а не патерн.
- Зміна ринкового режиму — бичачий тренд змінюється флетом або високою волатильністю. Параметри, які чудово працювали в тренді, в боковику генерують хибні сигнали.
- Висока розмірність простору параметрів — grid search потребує перебору мільйонів комбінацій, що займає дні. Навіть random search не гарантує знаходження оптимуму за прийнятний час.
Чому стандартний AutoML не працює для трейдингу?
Стандартні AutoML-бібліотеки (H2O, TPOT) використовують k-fold CV, яка для часових рядів — зло. Вони не враховують режим ринку та оптимізують метрики класифікації, а не фінансові показники. Результат — красива крива equity на історії та злив на реальному рахунку.
Що таке walk-forward validation і навіщо він потрібен?
Walk-forward validation — єдиний чесний метод для часових рядів. Він запобігає data leakage. Ми навчаємо модель на in-sample вікні, тестуємо на out-of-sample, зсуваємо вікно вперед і повторюємо. Агрегована OOS метрика — реальна оцінка якості.
import numpy as np
import pandas as pd
from typing import Callable
def walk_forward_optimization(
price_data: pd.DataFrame,
strategy_func: Callable,
param_space: dict,
in_sample_months: int = 12,
out_sample_months: int = 3
) -> dict:
"""
WFO: навчаємо на IS періоді, тестуємо на OOS — і так ковзаємо вперед.
Метрика = агрегований OOS Sharpe ratio по всіх періодах.
"""
results = []
total_months = len(price_data) // 21 # ~21 торговий день у місяці
for start_month in range(0, total_months - in_sample_months, out_sample_months):
is_end = start_month + in_sample_months
oos_end = is_end + out_sample_months
if oos_end > total_months:
break
is_data = price_data.iloc[start_month * 21: is_end * 21]
oos_data = price_data.iloc[is_end * 21: oos_end * 21]
# Оптимізація на IS періоді
best_params = optimize_on_period(strategy_func, is_data, param_space)
# Тест на OOS
oos_returns = strategy_func(oos_data, **best_params)
oos_sharpe = calculate_sharpe(oos_returns, annualization=252)
results.append({
'period_start': is_end,
'best_params': best_params,
'oos_sharpe': oos_sharpe,
'oos_returns': oos_returns
})
return {
'wfo_results': results,
'avg_oos_sharpe': np.mean([r['oos_sharpe'] for r in results]),
'sharpe_stability': np.std([r['oos_sharpe'] for r in results]),
'profitable_periods': sum(1 for r in results if r['oos_sharpe'] > 0) / len(results)
}
Як ми використовуємо Bayesian Optimization?
Bayesian Optimization будує ймовірнісну модель цільової функції — у нашому випадку складеної метрики, що включає Sharpe, максимальну просадку та транзакційні витрати. Замість перебору всіх комбінацій (як у grid search) алгоритм вибирає наступну точку, де очікується покращення. Це дозволяє знайти оптимум за 200 trials замість 10000. У трейдингу це критично: кожна ітерація — backtest на історії, який займає хвилини. За нашими оцінками, Bayesian Optimization з walk-forward знаходить стабільні параметри в 10 разів швидше grid search і знижує ризик overfitting на 50%. Для реалізації ми використовуємо Optuna — фреймворк з TPE-семплером та Hyperband pruning.
import optuna
def optimize_trading_model(train_data: pd.DataFrame,
val_data: pd.DataFrame) -> dict:
def objective(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 500),
'max_depth': trial.suggest_int('max_depth', 3, 8),
'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.1, log=True),
'min_samples_leaf': trial.suggest_int('min_samples_leaf', 50, 500),
'feature_fraction': trial.suggest_float('feature_fraction', 0.5, 1.0),
# Специфічні для фінансів параметри
'lookback_window': trial.suggest_int('lookback_window', 5, 60),
'prediction_horizon': trial.suggest_categorical('prediction_horizon', [1, 5, 10, 20]),
'threshold_long': trial.suggest_float('threshold_long', 0.001, 0.01),
'threshold_short': trial.suggest_float('threshold_short', -0.01, -0.001)
}
# Навчаємо та тестуємо
model = train_model(train_data, params)
signals = generate_signals(val_data, model, params)
returns = backtest_signals(val_data, signals)
# Складена метрика: Sharpe зі штрафом за drawdown
sharpe = calculate_sharpe(returns)
max_dd = calculate_max_drawdown(returns)
# Штраф за надмірну торгівлю (транзакційні витрати)
trade_count = signals.abs().sum()
cost_penalty = trade_count * 0.0001 # 1 bp за угоду
# Optuna максимізує: sharpe - drawdown_penalty - cost_penalty
return sharpe - abs(max_dd) * 0.5 - cost_penalty
study = optuna.create_study(
direction='maximize',
sampler=optuna.samplers.TPESampler(seed=42),
pruner=optuna.pruners.HyperbandPruner()
)
study.optimize(objective, n_trials=200, timeout=3600)
return {
'best_params': study.best_params,
'best_value': study.best_value,
'n_trials': len(study.trials)
}
Як враховувати зміну режиму ринку?
Ринок нестабільний: тренд змінюється флетом, волатильність зростає та падає. Один набір гіперпараметрів не може бути оптимальним для всіх станів. Ми використовуємо HMM для детекції режимів (зазвичай 3: тренд, флет, висока волатильність). Для кожного режиму попередньо запускаємо окрему walk-forward оптимізацію. У live-торгівлі модель визначає поточний режим за останніми 20 барами і застосовує відповідні параметри.
from hmmlearn import hmm
class RegimeAwareOptimizer:
"""
Різні режими ринку (тренд/флет/волатильність) потребують різних гіперпараметрів.
HMM визначає режим → вибираємо попередньо оптимізований набір параметрів.
"""
def __init__(self, n_regimes=3):
self.regime_model = hmm.GaussianHMM(n_components=n_regimes, covariance_type='full')
self.regime_params = {} # {режим: best_params}
def fit_regimes(self, returns: np.ndarray):
features = np.column_stack([
returns,
np.abs(returns), # волатильність
pd.Series(returns).rolling(20).std().values # rolling vol
])
self.regime_model.fit(features[~np.isnan(features).any(axis=1)])
def optimize_per_regime(self, price_data, strategy_func, param_space):
"""Для кожного режиму — окрема WFO оптимізація"""
regimes = self.get_current_regime(price_data)
for regime_id in range(self.regime_model.n_components):
regime_data = price_data[regimes == regime_id]
if len(regime_data) > 500:
self.regime_params[regime_id] = optimize_trading_model(
regime_data[:len(regime_data)//2],
regime_data[len(regime_data)//2:]
)['best_params']
def get_current_regime(self, recent_data: pd.DataFrame) -> int:
features = extract_regime_features(recent_data.tail(20))
return self.regime_model.predict(features)[-1]
Порівняння методів оптимізації
| Метод | Час виконання | Ризик перенавчання | Застосовність у трейдингу |
|---|---|---|---|
| Grid Search | Високий | Високий | Низька |
| Random Search | Середній | Середній | Середня |
| Bayesian Optimization (Optuna) | Низький | Низький | Висока |
| Walk-Forward + Optuna | Середній | Мінімальний | Оптимальна |
Результати оптимізації (приклад)
| Показник | До оптимізації | Після |
|---|---|---|
| Sharpe Ratio | 0.8 | 1.2 |
| Max Drawdown | -25% | -15% |
| Кількість угод | 500/міс | 300/міс |
| Економія на комісіях | — | 40% |
Економія на транзакційних витратах може сягати 40%, що в грошовому вираженні становить сотні тисяч рублів на місяць при активній торгівлі.
Етапи роботи
- Аналіз даних і стратегії — вивчаємо історію, визначаємо метрики.
- Вибір методу валідації — налаштовуємо walk-forward вікна.
- Побудова простору гіперпараметрів — задаємо діапазони та розподіли.
- Оптимізація за допомогою Optuna + WFO — запускаємо 200+ trials.
- Аналіз стабільності та адаптація під режими — детекція режимів, переоптимізація.
- Передача результатів і документація — звіт, код, інтеграція.
Терміни орієнтовно
- Walk-forward validation + Optuna базова оптимізація + backtest — 3–4 тижні.
- Regime-aware optimization, адаптивна переоптимізація, multi-objective Pareto front — 6–8 тижнів.
Що входить в роботу
- Повний звіт з візуалізаціями (графіки Sharpe, drawdown, стабільності).
- Оптимізований код моделі з конфігами.
- Інтеграція у ваш торговий пайплайн.
- Навчання команди (1 день).
- Підтримка 1 місяць після здачі.
Все виконується під ключ — ви отримуєте готове рішення та консультації щодо його експлуатації. Наші сертифіковані спеціалісти з досвідом більше 5 років у галузі машинного навчання та трейдингу виконають оптимізацію під ключ. Запрошуємо вас отримати консультацію щодо вашого проекту. Зв'яжіться з нами, щоб обговорити деталі та отримати попередню оцінку.







