ML Bot for Crypto Trading: Strategy Development with LightGBM and LSTM

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 unac

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1450
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1308
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    1003
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1269
  • image_logo-advance_0.webp
    B2B Advance company logo design
    719
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1009

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