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
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())
TypeScript Setup
mkdir crypto-bot && cd crypto-bot
npm init -y
npm install ccxt node-fetch
npm install -D typescript @types/node tsx
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]);
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}")
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';
}
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']}")
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)")
TypeScript with the Regime SDK
npm install getregime
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 };
}
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
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()
Step 7: Go Live (Safely)
Before switching from paper to live trading:
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.
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.
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.
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.
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/briefendpoint 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.
Top comments (0)