DEV Community

Gunnar Thorderson
Gunnar Thorderson

Posted on • Originally published at getregime.com

How to Build a Crypto Trading Bot in 2026 (Complete Guide)

How to Build a Crypto Trading Bot in 2026 (Complete Guide)

The crypto trading bot landscape in 2026 looks nothing like it did three years ago. Exchange APIs have matured, data providers have commoditized basic market data, and the edge has shifted from having a bot to having a bot that adapts to market conditions.

This guide walks you through building a crypto trading bot from scratch — from choosing your exchange API to adding regime-aware logic that keeps your bot from trading a bull-market strategy in a bear market. We'll use both Python and TypeScript examples so you can follow along in whichever language you prefer.

Step 1: Choose Your Exchange and Data Stack

Your bot needs two things: market data (prices, candles, order book) and execution (placing orders). These can come from the same exchange or different providers.

Exchange APIs for Execution

Exchange Best For Maker Fee API Limits
Binance.US US-based spot trading 0.1% 1200 req/min
Coinbase Advanced Institutional, fiat on-ramp 0.05% 10 req/sec
Drift (Solana) Perpetual futures, DeFi 0.02% No hard limit
Hyperliquid Perpetual futures (non-US) 0.02% 1200 req/min

For this guide, we'll use Binance.US for data and keep execution modular so you can swap in any venue.

Market Data Providers

Basic price data is free from any exchange. The differentiator in 2026 is derived data — regime classification, funding rates, open interest, liquidation flows, and macro correlations. This is where specialized APIs like Regime come in.

Step 2: Set Up Your Project

Python Setup

# requirements.txt
ccxt==4.2.0          # Exchange abstraction
pandas==2.2.0        # Data manipulation
requests==2.31.0     # HTTP client
ta==0.11.0           # Technical indicators
Enter fullscreen mode Exit fullscreen mode
import ccxt
import pandas as pd

# Initialize exchange connection
exchange = ccxt.binanceus({
    'apiKey': 'YOUR_API_KEY',
    'secret': 'YOUR_SECRET',
    'sandbox': True,  # Start with testnet
})

# Fetch 4-hour candles
ohlcv = exchange.fetch_ohlcv('BTC/USDT', '4h', limit=200)
df = pd.DataFrame(ohlcv, columns=['ts', 'open', 'high', 'low', 'close', 'volume'])
df['ts'] = pd.to_datetime(df['ts'], unit='ms')
print(df.tail())
Enter fullscreen mode Exit fullscreen mode

TypeScript Setup

mkdir crypto-bot && cd crypto-bot
npm init -y
npm install ccxt node-fetch
npm install -D typescript @types/node tsx
Enter fullscreen mode Exit fullscreen mode
import ccxt from 'ccxt';

const exchange = new ccxt.binanceus({
  apiKey: process.env.BINANCE_API_KEY,
  secret: process.env.BINANCE_SECRET,
  sandbox: true,
});

const candles = await exchange.fetchOHLCV('BTC/USDT', '4h', undefined, 200);
console.log(`Fetched ${candles.length} candles`);
console.log('Latest close:', candles[candles.length - 1][4]);
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement a Basic Strategy

The SMA (Simple Moving Average) crossover is the starting point for most systematic traders. It's not exotic, but when combined with regime awareness and proper sizing, it's remarkably effective.

Strategy: Buy when the 50-period SMA crosses above the 200-period SMA. Sell when it crosses below.

Python Implementation

import pandas as pd
import ta

def sma_crossover_signals(df: pd.DataFrame) -> pd.DataFrame:
    """Generate SMA 50/200 crossover signals."""
    df['sma_50'] = ta.trend.sma_indicator(df['close'], window=50)
    df['sma_200'] = ta.trend.sma_indicator(df['close'], window=200)

    df['signal'] = 0
    df.loc[df['sma_50'] > df['sma_200'], 'signal'] = 1   # Long
    df.loc[df['sma_50'] < df['sma_200'], 'signal'] = -1   # Short

    # Detect crossover points (signal changes)
    df['crossover'] = df['signal'].diff().abs() > 0
    return df

signals = sma_crossover_signals(df)
latest = signals.iloc[-1]
print(f"Current signal: {'LONG' if latest['signal'] == 1 else 'SHORT'}")
print(f"SMA 50: {latest['sma_50']:.2f}, SMA 200: {latest['sma_200']:.2f}")
Enter fullscreen mode Exit fullscreen mode

TypeScript Implementation

function calculateSMA(closes: number[], period: number): number[] {
  const sma: number[] = [];
  for (let i = 0; i < closes.length; i++) {
    if (i < period - 1) { sma.push(NaN); continue; }
    const slice = closes.slice(i - period + 1, i + 1);
    sma.push(slice.reduce((a, b) => a + b, 0) / period);
  }
  return sma;
}

function getSignal(closes: number[]): 'LONG' | 'SHORT' | 'NEUTRAL' {
  const sma50 = calculateSMA(closes, 50);
  const sma200 = calculateSMA(closes, 200);
  const latest50 = sma50[sma50.length - 1];
  const latest200 = sma200[sma200.length - 1];

  if (isNaN(latest50) || isNaN(latest200)) return 'NEUTRAL';
  return latest50 > latest200 ? 'LONG' : 'SHORT';
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Backtest Before You Risk a Single Dollar

The most common mistake new bot builders make is going live without backtesting. A strategy that feels right can easily lose money over hundreds of trades.

def backtest_sma(df: pd.DataFrame) -> dict:
    """Simple SMA crossover backtest."""
    signals = sma_crossover_signals(df)
    signals['returns'] = signals['close'].pct_change()
    signals['strategy_returns'] = signals['signal'].shift(1) * signals['returns']

    cumulative = (1 + signals['strategy_returns']).cumprod()
    total_return = cumulative.iloc[-1] - 1

    # Calculate max drawdown
    peak = cumulative.expanding().max()
    drawdown = (cumulative - peak) / peak
    max_dd = drawdown.min()

    return {
        'total_return': f"{total_return:.1%}",
        'max_drawdown': f"{max_dd:.1%}",
        'sharpe': signals['strategy_returns'].mean() / signals['strategy_returns'].std() * (252**0.5),
    }

results = backtest_sma(df)
print(f"Return: {results['total_return']}, Max DD: {results['max_drawdown']}")
Enter fullscreen mode Exit fullscreen mode

What Our Backtests Show

We backtested SMA 50/200 across 302,000+ candles (March 2023 to March 2026) on BTC, ETH, and SOL:

Asset Timeframe Return Max Drawdown
SOL 4h +586% -34%
ETH 1d +166% -28%
BTC 1d +41% -18%

These returns are with 5x leverage and no stop loss. More on why stop losses hurt in the next section.

Step 5: Add Regime Detection (The Edge)

Here's where your bot goes from "basic tutorial project" to "production trading system." A raw SMA crossover doesn't know if the market is trending or chopping. In a choppy market, it generates false signals that bleed your account through fees and slippage.

Regime detection solves this. By checking the current market regime before acting on a signal, you can:

  • Reduce position size in chop (where SMA crossovers whipsaw)
  • Go full size in clean trends (where SMA crossovers print money)
  • Avoid counter-trend trades (don't short in a high-confidence bull regime)

Using the Regime API

import requests

def get_regime():
    """Fetch current market regime from Regime API."""
    resp = requests.get('https://getregime.com/api/v1/market/regime')
    data = resp.json()
    return {
        'regime': data['regime'],           # 'bull', 'bear', or 'chop'
        'confidence': data['confidence'],    # 0.0 to 1.0
        'signals': data['signals'],          # individual signal breakdown
    }

regime = get_regime()
print(f"Current regime: {regime['regime']} ({regime['confidence']:.0%} confidence)")
Enter fullscreen mode Exit fullscreen mode

TypeScript with the Regime SDK

npm install getregime
Enter fullscreen mode Exit fullscreen mode
import { RegimeClient } from 'getregime';

const regime = new RegimeClient(); // Free tier, no API key needed

async function getRegimeAwareSignal(closes: number[]) {
  const smaSignal = getSignal(closes);
  const market = await regime.getRegime();

  // Regime-aware position sizing
  let sizeFactor = 1.0;

  if (market.regime === 'chop') {
    sizeFactor = 0.3; // Cut size 70% in choppy markets
  } else if (market.regime === 'bear' && smaSignal === 'LONG') {
    sizeFactor = 0.5; // Reduce longs in bear markets
  } else if (market.confidence > 0.85) {
    sizeFactor = 1.0; // Full size in high-confidence trends
  }

  return { signal: smaSignal, regime: market.regime, sizeFactor };
}
Enter fullscreen mode Exit fullscreen mode

Combining Regime with Strategy Logic

The most effective pattern we've found is using regime as a sizing filter, not a trade blocker. Our backtests showed that completely blocking trades in unfavorable regimes hurts performance — you miss the early moves of regime transitions. Instead, scale down:

def regime_adjusted_size(base_size: float, regime: dict) -> float:
    """Adjust position size based on market regime."""
    confidence = regime['confidence']

    if regime['regime'] == 'chop':
        return base_size * 0.3  # Choppy = small positions

    if regime['regime'] == 'bull':
        # Scale with confidence: 50% at low confidence, 100% at high
        return base_size * (0.5 + 0.5 * confidence)

    if regime['regime'] == 'bear':
        # Bear regime: full size for shorts, reduced for longs
        return base_size * confidence  # Higher confidence = bigger short

    return base_size
Enter fullscreen mode Exit fullscreen mode

Step 6: Build the Execution Loop

Now let's wire everything together into a loop that runs on a schedule.

import time
import ccxt

def run_bot():
    exchange = ccxt.binanceus({
        'apiKey': 'YOUR_KEY',
        'secret': 'YOUR_SECRET',
    })

    position = None  # Track current position

    while True:
        try:
            # 1. Fetch latest candles
            ohlcv = exchange.fetch_ohlcv('BTC/USDT', '4h', limit=200)
            df = pd.DataFrame(ohlcv, columns=['ts','open','high','low','close','volume'])

            # 2. Generate signal
            signals = sma_crossover_signals(df)
            signal = signals.iloc[-1]['signal']

            # 3. Check regime
            regime = get_regime()

            # 4. Calculate position size
            base_size = 1000  # $1000 base
            size = regime_adjusted_size(base_size, regime)

            # 5. Execute if signal changed
            if signal == 1 and position != 'LONG':
                amount = size / df.iloc[-1]['close']
                exchange.create_market_buy_order('BTC/USDT', amount)
                position = 'LONG'
                print(f"LONG BTC | size=${size:.0f} | regime={regime['regime']}")

            elif signal == -1 and position != 'SHORT':
                if position == 'LONG':
                    # Close long first
                    exchange.create_market_sell_order('BTC/USDT', amount)
                position = 'SHORT'
                print(f"SHORT BTC | size=${size:.0f} | regime={regime['regime']}")

            # 6. Sleep until next candle
            time.sleep(4 * 60 * 60)  # 4 hours

        except Exception as e:
            print(f"Error: {e}")
            time.sleep(60)  # Retry after 1 min

if __name__ == '__main__':
    run_bot()
Enter fullscreen mode Exit fullscreen mode

Step 7: Go Live (Safely)

Before switching from paper to live trading:

  1. Paper trade for at least 2 weeks. Compare your bot's paper results to what a manual execution would have done. This catches bugs in order sizing, timing, and signal logic.

  2. Start with 10% of your intended capital. If your target allocation is $10,000, start with $1,000. Scale up after 30+ live trades confirm the backtest.

  3. Monitor regime transitions. The most dangerous periods for systematic strategies are regime changes — when the market shifts from bull to bear or vice versa. The Regime API's confidence score helps here: when confidence drops below 50%, your bot should automatically reduce exposure.

  4. Set operational alerts, not just trading alerts. You need to know when your bot crashes, when the exchange API is down, and when your account balance drops below a threshold.

  5. Never use fixed stop losses with leveraged trend-following. Our backtesting across 300K+ candles proved that a 3% stop loss with 5x leverage destroys SMA crossover returns. The strategy's own signal flip (SMA cross) is a better exit than any fixed stop loss.

Common Mistakes to Avoid

Over-fitting to historical data. If your strategy has 15 parameters, it's curve-fit to the past. SMA 50/200 has exactly 2 parameters and works across multiple assets and timeframes.

Ignoring fees. A strategy that makes 0.1% per trade sounds great until you realize you're paying 0.1% in taker fees. Use limit orders (maker fees) wherever possible.

No regime awareness. This is the single biggest differentiator between bots that survive and bots that blow up. A bot that trades the same way in a bull market and a bear market is a bot that loses money.

Trading too many pairs. Start with BTC. Add ETH once you're profitable. Add SOL once ETH is working. Complexity is the enemy of reliability.

What's Next

This guide gives you a working bot with regime-aware sizing. To take it further:

  • Add more intelligence signals — The Regime API's /intelligence/brief endpoint provides crowd positioning, macro divergences, and funding rate analysis that can further refine your entries.
  • Implement proper risk management — Kelly criterion sizing, correlation guards, and drawdown scaling.
  • Deploy to production — PM2 or systemd process manager with auto-restart and alerting.

Ready to add regime intelligence to your bot? Get started with the free tier at getregime.com/quickstart — no API key required for basic regime data, and you'll be making regime-aware trades in under 60 seconds.


Try Regime Intelligence

Regime is a real-time crypto market regime detection API. One endpoint tells you if the market is bull, bear, or chop — so your bot only trades when conditions match your strategy.

Free API access → | See pricing → | API docs →

Top comments (0)