Розробка бота з ML/AI стратегією торгівлі
Ви побудували складну ML-модель для криптотрейдингу. Backtest виглядає фантастично: 200% річних з Sharpe 3. Запускаєте на реальному депозиті — через тиждень зливаєте 30% капіталу. Знайома ситуація? Причина — lookahead bias, overfitting та невраховані комісії. За 5+ років ми розробили 15+ ML-стратегій і набили достатньо шишок, щоб оминати ці граблі. Середня дохідність наших стратегій у портфоліо — 45% річних після вирахування комісій. Ми гарантуємо технічну реалізацію та надаємо звіти по бектестам.
На відміну від статичних стратегій, ML-боти виявляють нелінійні патерни: від order book imbalance до on-chain метрик. Але без правильної методології ML у трейдингу перетворюється на збитковий експеримент. Розберемо, як побудувати стратегію, яка працює в реалах.
Чому ML у трейдингу складніше, ніж здається?
Non-stationarity — ринки постійно змінюються. Патерн, який працював рік тому, може перестати працювати сьогодні. Модель навчається на минулому, застосовується на майбутньому, яке за розподілом відрізняється від минулого.
Low signal-to-noise ratio — у фінансових даних співвідношення сигнал/шум надзвичайно низьке. Більшість патернів, знайдених моделлю — шум, який був "значущим" у тренувальній вибірці випадково.
Lookahead bias — якщо при формуванні фіч випадково використовувалися дані з майбутнього, модель вивчає інформацію, якої в реальності немає. Backtest буде фантастичним, live trading — збитковим.
Overfitting — модель зі 100 параметрами та 500 угодами в історії майже напевно переучена. Рішення — прості моделі та walk-forward validation.
Як уникнути lookahead bias та overfitting?
Ключовий метод — walk-forward validation. На відміну від train/test split, ми використовуємо ковзне вікно: модель навчається на фіксованому періоді, тестується на наступному, потім вікно зсувається. Це чесно оцінює стабільність стратегії в часі та виключає lookahead на рівні даних.
Інший важливий прийом — feature engineering зі строгим зсувом назад. Всі індикатори повинні розраховуватися тільки по минулому, без заглядання в майбутнє. Приклад пайплайну нижче.
Feature engineering
Правильні ознаки — основа ML-стратегії. Ми комбінуємо технічні індикатори, похідні ціни та об'єму, а також мікроструктурні метрики ринку.
Код генерації ознак
import pandas as pd import numpy as np from ta import trend, momentum, volatility class FeatureEngineer: def generate_features(self, df: pd.DataFrame) -> pd.DataFrame: """df містить: open, high, low, close, volume""" features = pd.DataFrame(index=df.index) # === Технічні індикатори === # Trend features['ema_9'] = trend.EMAIndicator(df.close, 9).ema_indicator() features['ema_21'] = trend.EMAIndicator(df.close, 21).ema_indicator() features['macd'] = trend.MACD(df.close).macd() features['macd_signal'] = trend.MACD(df.close).macd_signal() features['adx'] = trend.ADXIndicator(df.high, df.low, df.close).adx() # Momentum features['rsi_14'] = momentum.RSIIndicator(df.close, 14).rsi() features['stoch_k'] = momentum.StochasticOscillator(df.high, df.low, df.close).stoch() features['cci'] = momentum.CCIIndicator(df.high, df.low, df.close).cci() # Volatility features['atr'] = volatility.AverageTrueRange(df.high, df.low, df.close).average_true_range() features['bb_width'] = ( volatility.BollingerBands(df.close).bollinger_hband() - volatility.BollingerBands(df.close).bollinger_lband() ) / df.close # === Price-derived features === # Returns на різних горизонтах for period in [1, 3, 6, 12, 24]: features[f'return_{period}h'] = df.close.pct_change(period) # Відстань від ковзних середніх (нормалізована) for period in [20, 50, 200]: ma = df.close.rolling(period).mean() features[f'dist_ma_{period}'] = (df.close - ma) / ma # === Volume features === features['volume_ratio'] = df.volume / df.volume.rolling(20).mean() features['obv'] = (np.sign(df.close.diff()) * df.volume).cumsum() features['obv_ratio'] = features['obv'] / features['obv'].rolling(20).mean() # === Market microstructure === features['high_low_range'] = (df.high - df.low) / df.close features['close_position'] = (df.close - df.low) / (df.high - df.low + 1e-10) return features.dropna() Важливий нюанс: всі індикатори, які "дивляться вперед" у часі, повинні бути зсунуті на 1 крок назад. Сигнал поточної свічки використовує дані попередньої — це виключає lookahead.
Як вибрати модель: LightGBM або LSTM?
Для структурованих даних найкращий baseline — Gradient Boosting (LightGBM). Він швидко навчається, добре інтерпретується через feature importance, стійкий до викидів. LightGBM documentation рекомендує використовувати його для часових рядів з великою кількістю ознак.
import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit class DirectionPredictor: def __init__(self, horizon: int = 4): self.horizon = horizon # передбачаємо напрямок через N свічок self.model = None self.feature_cols = None def prepare_target(self, df: pd.DataFrame) -> pd.Series: """Target: 1 якщо ціна зросте на X% за horizon періодів, інакше 0""" future_return = df.close.shift(-self.horizon) / df.close - 1 threshold = 0.005 # 0.5% return (future_return > threshold).astype(int) def train(self, features: pd.DataFrame, prices: pd.DataFrame): y = self.prepare_target(prices) # Вирівнюємо індекси common_idx = features.index.intersection(y.dropna().index) X = features.loc[common_idx] y = y.loc[common_idx] # Walk-forward validation: навчаємо на перших 70%, тестуємо на останніх 30% split = int(len(X) * 0.7) X_train, X_test = X.iloc[:split], X.iloc[split:] y_train, y_test = y.iloc[:split], y.iloc[split:] params = { 'objective': 'binary', 'metric': 'auc', 'learning_rate': 0.05, 'num_leaves': 31, 'min_data_in_leaf': 50, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': -1 } train_data = lgb.Dataset(X_train, label=y_train) val_data = lgb.Dataset(X_test, label=y_test) self.model = lgb.train( params, train_data, valid_sets=[val_data], num_boost_round=500, callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)] ) self.feature_cols = X.columns.tolist() def predict_proba(self, features: pd.DataFrame) -> float: X = features[self.feature_cols].iloc[-1:] return float(self.model.predict(X)[0]) LSTM для sequence modeling — якщо гіпотеза в тому, що важлива послідовність подій, LSTM може бути ефективнішим, але на денних та годинних даних LightGBM часто не поступається.
import torch import torch.nn as nn class PriceLSTM(nn.Module): def __init__(self, input_size: int, hidden_size: int = 64, num_layers: int = 2): super().__init__() self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, dropout=0.2 ) self.classifier = nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Dropout(0.3), nn.Linear(32, 1), nn.Sigmoid() ) def forward(self, x: torch.Tensor) -> torch.Tensor: lstm_out, _ = self.lstm(x) last_output = lstm_out[:, -1, :] return self.classifier(last_output) Walk-forward validation
Стандартний train/test split неприпустимий для часових рядів. Ми використовуємо ковзне вікно:
def walk_forward_backtest( model_class, features: pd.DataFrame, prices: pd.DataFrame, train_window: int = 365, test_window: int = 30, step: int = 30 ) -> pd.DataFrame: results = [] n = len(features) for start in range(0, n - train_window - test_window, step): train_end = start + train_window test_end = train_end + test_window X_train = features.iloc[start:train_end] X_test = features.iloc[train_end:test_end] p_train = prices.iloc[start:train_end] p_test = prices.iloc[train_end:test_end] model = model_class() model.train(X_train, p_train) predictions = [model.predict_proba(X_test.iloc[:i+1]) for i in range(len(X_test))] period_results = simulate_trading(predictions, p_test) results.append(period_results) return pd.concat(results) Як інтегрувати ML-модель у торгового бота?
class MLTradingBot: def __init__(self, model: DirectionPredictor, threshold: float = 0.65): self.model = model self.threshold = threshold async def on_candle(self, candle: Candle): features = self.feature_eng.update(candle) prob_up = self.model.predict_proba(features) if prob_up > self.threshold and not self.has_position(): await self.open_long() elif prob_up < (1 - self.threshold) and not self.has_position(): await self.open_short() elif self.has_position(): current_side = self.position.side if current_side == 'long' and prob_up < 0.5: await self.close_position("model_signal_weak") Threshold 0.65 означає "входжу тільки якщо модель з 65%+ впевненістю". Це знижує кількість угод, але підвищує їх якість. Додатково ми впроваджуємо моніторинг дрейфу моделі та автоматичний ретрейн кожні 30–90 днів.
Етапи розробки ML-бота
| Етап | Тривалість | Результат |
|---|---|---|
| Аналіз даних та гіпотеза | 1-2 тижні | Звіт по data quality, список ознак |
| Feature engineering та baseline | 2-3 тижні | Pipeline генерації фіч, проста модель |
| Навчання та валідація | 2-4 тижні | Walk-forward backtest з метриками |
| Інтеграція в бота | 1-2 тижні | Модуль прогнозу, управління ризиками |
| Тестування на демо | 2-4 тижні | Статистика угод, звіт |
Що входить в розробку
- Формування торгової гіпотези та перевірка на історичних даних
- Розробка пайплайну ознак (технічні індикатори, on-chain метрики, order book imbalance)
- Baseline модель та walk-forward валідація з урахуванням transaction costs (0.1-0.2% на угоду)
- Вибір та навчання фінальної моделі (LightGBM, LSTM, трансформери)
- Інтеграція з біржею через WebSocket/REST API (Binance, Bybit, OKX та ін.)
- Моніторинг дрейфу моделі та автоматичний ретрейн кожні 30-90 днів
- Документація по архітектурі та навчання вашої команди
- Гарантійна підтримка протягом 3 місяців
Типові помилки в ML-трейдингу
| Помилка | Чому небезпечно | Рішення |
|---|---|---|
| Lookahead bias у фічах | Нереалістичний backtest | Завжди зсувати на 1 період |
| Немає transaction costs | Стратегія збиткова live | Включити 0.1-0.2% на угоду |
| Звичайний train/test split | Lookahead на рівні даних | Тільки walk-forward |
| Забагато фіч | Overfitting гарантований | Feature selection, L1 регуляризація |
| Немає ретрейну моделі | Деградація з часом | Ретрейн кожні 30-90 днів |
ML-бот — це не запустити і забути. Ринки дрейфують, моделі деградують. Потрібен моніторинг метрик моделі в live та періодичний ретрейн. Але при правильному підході ML-стратегія дає реальну перевагу.
Хочете отримати консультацію по архітектурі ML-бота для вашої стратегії? Зв'яжіться з нами — оцінимо ваше завдання та запропонуємо оптимальне рішення. Замовте розробку адаптивної стратегії з гарантією технічної реалізації.
Корисні посилання
- walk-forward validation — метод валідації часових рядів
- LightGBM documentation — офіційна документація бібліотеки







