ML/AI Trading Bot Development
You built a complex ML model for crypto trading. The backtest looks fantastic: 200% annual returns with a Sharpe ratio of 3. You deploy it on a live account—a week later, you lose 30% of your capital. Sound familiar? The cause is lookahead bias, overfitting, and unaccounted fees. Over the years, we have developed 15+ ML strategies and learned to avoid these pitfalls. The average annual return of our strategies in the portfolio is 45% after fees. We guarantee technical implementation and provide backtest reports.
Unlike static strategies, ML bots detect non-linear patterns: from order book imbalance to on-chain metrics. But without proper methodology, ML in trading becomes a losing experiment. Let's look at how to build a strategy that works in live markets.
Why ML in trading is harder than it looks
Non-stationarity—markets constantly change. A pattern that worked a year ago may stop working today. The model trains on the past, applies to a future whose distribution differs from the past.
Low signal-to-noise ratio—financial data has an extremely low signal-to-noise ratio. Most patterns found by the model are noise that happened to be "significant" in the training sample by chance.
Lookahead bias—if features accidentally use future data, the model learns information unavailable in reality. The backtest looks fantastic, live trading loses money.
Overfitting—a model with 100 parameters and 500 historical trades is almost certainly overfit. The solution is simple models and walk-forward validation.
How to avoid lookahead bias and overfitting
The key method is walk-forward validation. Unlike train/test split, we use a rolling window: the model is trained on a fixed period, tested on the next, then the window slides. This honestly evaluates the strategy's stability over time and eliminates lookahead at the data level.
Another important technique is feature engineering with strict backward shift. All indicators must be calculated using only past data, without peeking into the future. An example pipeline is below.
Feature engineering
Proper features are the foundation of an ML strategy. We combine technical indicators, price and volume derivatives, and market microstructure metrics.
Feature generation code
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 contains: open, high, low, close, volume"""
features = pd.DataFrame(index=df.index)
# === Technical indicators ===
# 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 at different horizons
for period in [1, 3, 6, 12, 24]:
features[f'return_{period}h'] = df.close.pct_change(period)
# Distance from moving averages (normalized)
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()
An important nuance: all indicators that "look forward" in time must be shifted by 1 step back. The current candle's signal uses the previous candle's data—this eliminates lookahead.
Which model to choose: LightGBM or LSTM?
For structured data, the best baseline is Gradient Boosting (LightGBM). It trains quickly, is well interpretable via feature importance, and is robust to outliers. The LightGBM documentation recommends it for time series with many features.
import lightgbm as lgb
from sklearn.model_selection import TimeSeriesSplit
class DirectionPredictor:
def __init__(self, horizon: int = 4):
self.horizon = horizon # predict direction after N candles
self.model = None
self.feature_cols = None
def prepare_target(self, df: pd.DataFrame) -> pd.Series:
"""Target: 1 if price rises by X% over horizon, else 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)
# Align indices
common_idx = features.index.intersection(y.dropna().index)
X = features.loc[common_idx]
y = y.loc[common_idx]
# Walk-forward validation: train on first 70%, test on last 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 for sequence modeling—if the hypothesis is that the sequence of events matters, LSTM can be more effective, but on daily and hourly data LightGBM often performs similarly.
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
Standard train/test split is unacceptable for time series. We use a rolling window:
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)
How to integrate an ML model into a trading bot
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")
A threshold of 0.65 means "only enter when the model is 65%+ confident." This reduces the number of trades but improves their quality. Additionally, we implement model drift monitoring and automatic retraining every 30–90 days.
ML bot development stages
| Stage | Duration | Result |
|---|---|---|
| Data analysis and hypothesis | 1-2 weeks | Data quality report, feature list |
| Feature engineering and baseline | 2-3 weeks | Feature pipeline, simple model |
| Training and validation | 2-4 weeks | Walk-forward backtest with metrics |
| Bot integration | 1-2 weeks | Prediction module, risk management |
| Demo testing | 2-4 weeks | Trade statistics, report |
What's included in development
- Formation of trading hypothesis and validation on historical data
- Development of feature pipeline (technical indicators, on-chain metrics, order book imbalance)
- Baseline model and walk-forward validation including transaction costs (0.1-0.2% per trade)
- Selection and training of final model (LightGBM, LSTM, transformers)
- Exchange integration via WebSocket/REST API (Binance, Bybit, OKX, etc.)
- Model drift monitoring and automatic retraining every 30-90 days
- Architecture documentation and team training
- 3-month warranty support
Common mistakes in ML trading
| Mistake | Why it's dangerous | Solution |
|---|---|---|
| Lookahead bias in features | Unrealistic backtest | Always shift by 1 period |
| No transaction costs | Strategy loses live | Include 0.1-0.2% per trade |
| Ordinary train/test split | Lookahead at data level | Walk-forward only |
| Too many features | Overfitting guaranteed | Feature selection, L1 regularization |
| No model retraining | Degradation over time | Retrain every 30-90 days |
An ML bot is not a set-and-forget solution. Markets drift, models degrade. It requires live model metric monitoring and periodic retraining. But with the right approach, an ML strategy provides a real edge.
Want a consultation on ML bot architecture for your strategy? Contact us—we'll evaluate your task and propose the optimal solution. Order the development of an adaptive strategy with a technical implementation guarantee.
Useful links
- walk-forward validation — time series validation method
- LightGBM documentation — official library documentation







