Розробка системи оптимізації гіперпараметрів торгової 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 років у галузі машинного навчання та трейдингу виконають оптимізацію під ключ. Запрошуємо вас отримати консультацію щодо вашого проекту. Зв'яжіться з нами, щоб обговорити деталі та отримати попередню оцінку.







