Developing a Hyperparameter Optimization System for a Trading AI Model
Developing a hyperparameter optimization system for a trading AI model is a key stage when standard grid search fails. A client came with a ready-made LSTM model for trading futures. On historical data, it showed a Sharpe of 1.5, but on live data it dropped to 0.3. Diagnosis: standard grid search with 5-fold cross-validation caused data leakage—parameters were fitted to noise, not signal. We applied walk-forward validation with Bayesian optimization using Optuna, which preserved model stability and avoided overfitting. The final out-of-sample Sharpe was 1.1—the model stopped losing money on new data. This case illustrates why financial hyperparameter optimization requires special approaches.
Problems We Solve
- Data leakage due to violation of temporal causality by standard cross-validation. Each data point is part of a time series; using the future to predict the past is invalid.
- Overfitting—parameters tuned on the full history are optimal only for that history. On fresh data they generate losses because the model memorized noise, not the pattern.
- Changing market regime—a bullish trend turns into a flat or high volatility. Parameters that worked well in a trend produce false signals in a sideways market.
- High dimensionality of the parameter space—grid search requires millions of combinations, taking days. Even random search does not guarantee finding the optimum in reasonable time.
Why Standard AutoML Doesn't Work for Trading?
Standard AutoML libraries (H2O, TPOT) use k-fold CV, which is harmful for time series. They ignore market regimes and optimize classification metrics, not financial metrics. The result is a beautiful equity curve on history and a blowout on a real account.
What Is Walk-Forward Validation and Why Do You Need It?
Walk-forward validation is the only honest method for time series. It prevents data leakage. We train the model on an in-sample window, test on an out-of-sample window, slide the window forward, and repeat. The aggregated OOS metric gives a realistic performance estimate.
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)
}
How We Use Bayesian Optimization
Bayesian Optimization builds a probabilistic model of the objective function—in our case, a composite metric including Sharpe, maximum drawdown, and transaction costs. Instead of searching all combinations (like grid search), the algorithm selects the next point where improvement is expected. This allows finding the optimum in 200 trials instead of 10,000. In trading, each iteration is a backtest that takes minutes. According to our estimates, Bayesian Optimization with walk-forward finds stable parameters 10 times faster than grid search and reduces overfitting risk by 50%. We use Optuna—a framework with the TPE sampler and 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)
}
How to Account for Changing Market Regimes?
The market is unstable: trend turns into flat, volatility rises and falls. One set of hyperparameters cannot be optimal for all states. We use HMM for regime detection (usually 3: trend, flat, high volatility). For each regime, we pre-run a separate walk-forward optimization. In live trading, the model identifies the current regime based on the last 20 bars and applies the corresponding parameters.
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]
Comparison of Optimization Methods
| Method | Execution Time | Overfitting Risk | Applicability in Trading |
|---|---|---|---|
| Grid Search | High | High | Low |
| Random Search | Medium | Medium | Medium |
| Bayesian Optimization (Optuna) | Low | Low | High |
| Walk-Forward + Optuna | Medium | Minimal | Optimal |
Optimization Results (Example)
| Metric | Before Optimization | After |
|---|---|---|
| Sharpe Ratio | 0.8 | 1.2 |
| Max Drawdown | -25% | -15% |
| Number of Trades | 500/month | 300/month |
| Commission Savings | — | 40% |
Commission savings can reach 40%, which in monetary terms amounts to hundreds of thousands of rubles per month with active trading.
Work Stages
- Data and strategy analysis—study history, define metrics.
- Selection of validation method—configure walk-forward windows.
- Building hyperparameter space—define ranges and distributions.
- Optimization with Optuna + WFO—run 200+ trials.
- Stability analysis and regime adaptation—regime detection, re-optimization.
- Delivery of results and documentation—report, code, integration.
Estimated Timeframes
- Walk-forward validation + Optuna basic optimization + backtest — 3–4 weeks.
- Regime-aware optimization, adaptive re-optimization, multi-objective Pareto front — 6–8 weeks.
What's Included
- Full report with visualizations (Sharpe, drawdown, stability graphs).
- Optimized model code with config files.
- Integration into your trading pipeline.
- Team training (1 day).
- One month of support after delivery.
Everything is delivered turnkey—you receive a ready-made solution and consultations on its operation. Our certified specialists with over 5 years of experience in machine learning and trading perform the optimization end-to-end. We invite you to get a consultation on your project. Contact us to discuss details and get a preliminary assessment.







