DEV Community

Cover image for How to Build a Production-Ready Crypto Trading Bot (Spot + Futures) with CCXT
Fxm Brand
Fxm Brand

Posted on

How to Build a Production-Ready Crypto Trading Bot (Spot + Futures) with CCXT

Building a crypto trading bot that actually survives real markets is harder than most tutorials admit. The difference between a weekend script and a production system comes down to reliability, risk controls, error handling, and clean architecture.

In this guide we’ll build a solid foundation using CCXT — the most widely used open-source library for connecting to cryptocurrency exchanges. You’ll learn how to:

  • Connect to multiple CEXs with a unified API
  • Trade both spot and futures
  • Manage orders and positions properly
  • Size positions safely
  • Handle the messy realities of rate limits, network failures, and exchange quirks
  • Deploy the bot on a VPS
  • Add basic bridges toward DEX trading

By the end you’ll have a maintainable, production-oriented skeleton you can extend for any strategy — including high-volatility memecoin approaches.

Quick note for memecoin traders: If you’re specifically hunting for ready-to-use memecoin trading frameworks and strategies (sniping logic, risk filters, entry/exit rules), check out the practical system available here: Memecoin Trading Strategy. This article focuses on the robust bot infrastructure that any serious strategy needs underneath.


Why CCXT?

CCXT gives you a single consistent interface across 100+ exchanges (Binance, Bybit, OKX, Gate, KuCoin, and many others). You write the same code for fetching balances, placing orders, or reading order books whether you’re on Binance Spot or Bybit Futures.

Key advantages for production work:

  • Unified method names and response shapes
  • Built-in rate-limit handling (with enableRateLimit)
  • Support for both REST and WebSocket (via ccxt.pro for the paid/pro version, or community wrappers)
  • Active maintenance and good documentation

For pure on-chain DEX work (Uniswap, Raydium, etc.) you’ll eventually need web3.py or similar. CCXT can still help with CEX legs of hybrid strategies and some limited DEX support through certain connectors.


Project Setup

Create a clean project structure:

mkdir crypto-trading-bot
cd crypto-trading-bot
python -m venv venv
source venv/bin/activate   # or venv\Scripts\activate on Windows
pip install ccxt python-dotenv pandas numpy
Enter fullscreen mode Exit fullscreen mode

Recommended layout:

crypto-trading-bot/
├── .env
├── config.py
├── bot.py
├── exchange.py
├── risk.py
├── utils/
│   ├── logger.py
│   └── helpers.py
├── strategies/
│   └── base.py
└── requirements.txt
Enter fullscreen mode Exit fullscreen mode

Install additional useful packages:

pip install python-telegram-bot loguru tenacity
Enter fullscreen mode Exit fullscreen mode

Create a .env file (never commit this):

BINANCE_API_KEY=your_key_here
BINANCE_SECRET=your_secret_here
BYBIT_API_KEY=...
BYBIT_SECRET=...
TELEGRAM_BOT_TOKEN=...
TELEGRAM_CHAT_ID=...
Enter fullscreen mode Exit fullscreen mode

Core Exchange Connector

Here’s a clean, reusable exchange factory:

# exchange.py
import ccxt
import os
from dotenv import load_dotenv
from loguru import logger

load_dotenv()

def create_exchange(exchange_id: str, market_type: str = "spot"):
    """
    market_type: 'spot' or 'future' / 'swap'
    """
    exchange_class = getattr(ccxt, exchange_id)

    config = {
        "apiKey": os.getenv(f"{exchange_id.upper()}_API_KEY"),
        "secret": os.getenv(f"{exchange_id.upper()}_SECRET"),
        "enableRateLimit": True,
        "options": {
            "defaultType": market_type,  # critical for futures
        }
    }

    # Some exchanges need extra options
    if exchange_id == "binance":
        config["options"]["defaultType"] = market_type
        if market_type in ["future", "swap"]:
            config["options"]["defaultType"] = "future"

    exchange = exchange_class(config)

    # Load markets once
    exchange.load_markets()
    logger.info(f"Connected to {exchange_id} ({market_type})")
    return exchange
Enter fullscreen mode Exit fullscreen mode

Usage:

spot = create_exchange("binance", "spot")
futures = create_exchange("binance", "future")
Enter fullscreen mode Exit fullscreen mode

Always call load_markets() early. It caches symbol information and helps avoid later surprises.


Fetching Market Data Safely

Never assume the network or exchange is healthy. Wrap every call:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=30))
def safe_fetch_ticker(exchange, symbol):
    return exchange.fetch_ticker(symbol)

@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=30))
def safe_fetch_ohlcv(exchange, symbol, timeframe="1m", limit=100):
    return exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit)
Enter fullscreen mode Exit fullscreen mode

Common data needs:

ticker = safe_fetch_ticker(spot, "BTC/USDT")
print(ticker["last"], ticker["bid"], ticker["ask"])

ohlcv = safe_fetch_ohlcv(spot, "ETH/USDT", "5m", 200)
# Convert to DataFrame if you want
import pandas as pd
df = pd.DataFrame(ohlcv, columns=["timestamp", "open", "high", "low", "close", "volume"])
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
Enter fullscreen mode Exit fullscreen mode

For futures you’ll often want funding rates, open interest, and mark price as well.


Order Management

A production bot needs clear, idempotent order helpers.

def place_market_order(exchange, symbol, side, amount, params=None):
    """
    side: 'buy' or 'sell'
    amount: in base currency (e.g. BTC amount)
    """
    try:
        order = exchange.create_order(
            symbol=symbol,
            type="market",
            side=side,
            amount=amount,
            params=params or {}
        )
        logger.success(f"Market {side} order placed: {order['id']}")
        return order
    except Exception as e:
        logger.error(f"Order failed: {e}")
        raise

def place_limit_order(exchange, symbol, side, amount, price, params=None):
    order = exchange.create_order(
        symbol=symbol,
        type="limit",
        side=side,
        amount=amount,
        price=price,
        params=params or {}
    )
    return order

def cancel_order(exchange, order_id, symbol):
    return exchange.cancel_order(order_id, symbol)

def get_open_orders(exchange, symbol=None):
    return exchange.fetch_open_orders(symbol)
Enter fullscreen mode Exit fullscreen mode

For futures, add leverage and margin mode:

def set_leverage(exchange, symbol, leverage=5):
    try:
        exchange.set_leverage(leverage, symbol)
        logger.info(f"Leverage set to {leverage}x on {symbol}")
    except Exception as e:
        logger.warning(f"Could not set leverage: {e}")

def set_margin_mode(exchange, symbol, mode="isolated"):  # or 'cross'
    try:
        exchange.set_margin_mode(mode, symbol)
    except Exception as e:
        logger.warning(f"Margin mode: {e}")
Enter fullscreen mode Exit fullscreen mode

Always check the exchange-specific quirks. Binance Futures, Bybit, and OKX each have slightly different parameter names.


Position Sizing — The Most Important Part

Most bot failures come from poor position sizing, not from bad signals.

Here are three practical methods:

1. Fixed Fractional (Recommended starting point)

def fixed_fractional_size(balance, risk_percent, entry_price, stop_loss_price):
    """
    Risk a fixed % of equity per trade.
    """
    risk_amount = balance * (risk_percent / 100)
    price_diff = abs(entry_price - stop_loss_price)
    if price_diff == 0:
        return 0
    size = risk_amount / price_diff
    return size
Enter fullscreen mode Exit fullscreen mode

2. Volatility-based (ATR)

def atr_position_size(balance, risk_percent, atr, atr_multiplier=2.0, entry_price=None):
    risk_amount = balance * (risk_percent / 100)
    stop_distance = atr * atr_multiplier
    size = risk_amount / stop_distance
    return size
Enter fullscreen mode Exit fullscreen mode

3. Simple percentage of balance

def percent_of_balance(balance, percent, price):
    notional = balance * (percent / 100)
    return notional / price
Enter fullscreen mode Exit fullscreen mode

Always enforce hard limits:

MAX_POSITION_PCT = 0.10          # never more than 10% of equity in one position
MAX_LEVERAGE = 5
MAX_OPEN_POSITIONS = 3
Enter fullscreen mode Exit fullscreen mode

In a real bot you should also track current exposure across all positions.


Robust Error Handling & Resilience

Exchanges will fail. Networks will drop. Rate limits will hit you. Your bot must survive.

Key patterns:

  1. Retries with exponential backoff (tenacity is excellent)
  2. Graceful degradation — if one data source fails, try another or pause
  3. Circuit breakers — stop trading after N consecutive errors
  4. Idempotency — never place the same order twice by accident
  5. State persistence — save open orders and positions so a restart doesn’t lose context

Example circuit breaker sketch:

class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=300):
        self.failures = 0
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.last_failure_time = None
        self.state = "closed"  # closed, open, half-open

    def record_failure(self):
        self.failures += 1
        self.last_failure_time = time.time()
        if self.failures >= self.failure_threshold:
            self.state = "open"
            logger.critical("Circuit breaker OPEN — trading paused")

    def record_success(self):
        self.failures = 0
        self.state = "closed"

    def can_execute(self):
        if self.state == "closed":
            return True
        if self.state == "open":
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = "half-open"
                return True
            return False
        return True  # half-open
Enter fullscreen mode Exit fullscreen mode

Also handle specific CCXT exceptions:

import ccxt

try:
    order = exchange.create_order(...)
except ccxt.InsufficientFunds as e:
    logger.error("Not enough balance")
except ccxt.RateLimitExceeded as e:
    logger.warning("Rate limit hit — backing off")
    time.sleep(10)
except ccxt.NetworkError as e:
    logger.error("Network issue")
except ccxt.ExchangeError as e:
    logger.error(f"Exchange error: {e}")
Enter fullscreen mode Exit fullscreen mode

Building the Main Bot Loop

A simple but solid structure:

# bot.py
import time
from exchange import create_exchange
from risk import calculate_position_size
from utils.logger import setup_logger

logger = setup_logger()

class TradingBot:
    def __init__(self, exchange_id="binance", market_type="spot"):
        self.exchange = create_exchange(exchange_id, market_type)
        self.symbol = "BTC/USDT"
        self.is_running = True
        self.circuit = CircuitBreaker()

    def run(self):
        logger.info("Bot started")
        while self.is_running:
            try:
                if not self.circuit.can_execute():
                    time.sleep(30)
                    continue

                # 1. Fetch data
                ticker = safe_fetch_ticker(self.exchange, self.symbol)
                balance = self.exchange.fetch_balance()

                # 2. Generate signal (your strategy here)
                signal = self.generate_signal(ticker)

                # 3. Risk & size
                if signal in ["buy", "sell"]:
                    size = calculate_position_size(...)
                    if size > 0:
                        self.execute(signal, size)

                self.circuit.record_success()
                time.sleep(5)  # adjust based on strategy

            except Exception as e:
                logger.exception(e)
                self.circuit.record_failure()
                time.sleep(10)

    def generate_signal(self, ticker):
        # Replace with your real logic
        # For memecoin strategies this is where momentum, volume spikes,
        # social signals, or on-chain filters would live.
        return None

    def execute(self, side, amount):
        place_market_order(self.exchange, self.symbol, side, amount)
Enter fullscreen mode Exit fullscreen mode

For higher frequency or multi-symbol bots, move to asyncio + ccxt.async_support.


Basic DEX Bridges

CCXT is primarily CEX-focused. For real DEX work (especially memecoins on Solana or Ethereum):

  • Use web3.py + Uniswap V2/V3 SDK or Raydium SDKs
  • Or hybrid: use CCXT for the CEX leg and a separate module for on-chain execution
  • Popular pattern: detect opportunity on-chain → route through a CEX if liquidity is better, or execute directly on DEX

A minimal bridge sketch (Ethereum example):

from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/v3/YOUR_KEY"))

# You would then load the router ABI and call swapExactTokensForTokens
# This is significantly more complex than CCXT and requires careful gas,
# slippage, and nonce management.
Enter fullscreen mode Exit fullscreen mode

For Solana memecoins the stack is different (solders, anchorpy, Jupiter aggregator, etc.). Many production memecoin bots combine CEX data feeds with on-chain execution.

If your focus is memecoin-specific execution and risk frameworks, the strategy resource at https://selar.com/60lw5u0623 is worth reviewing — it addresses the unique challenges of low-liquidity, high-speed environments that generic CEX bots often struggle with.


Deployment on a VPS

Recommended starting setup:

  • DigitalOcean Droplet, Hetzner, or AWS Lightsail (2–4 GB RAM is usually enough for a single bot)
  • Ubuntu 22.04 or 24.04
  • Docker (strongly recommended)

Simple Docker approach

Dockerfile:

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "bot.py"]
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml:

version: "3.8"
services:
  trading-bot:
    build: .
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./logs:/app/logs
Enter fullscreen mode Exit fullscreen mode

Run with:

docker compose up -d --build
Enter fullscreen mode Exit fullscreen mode

Alternative: systemd service

Create /etc/systemd/system/trading-bot.service:

[Unit]
Description=Crypto Trading Bot
After=network.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/crypto-trading-bot
ExecStart=/home/ubuntu/crypto-trading-bot/venv/bin/python bot.py
Restart=always
RestartSec=10
EnvironmentFile=/home/ubuntu/crypto-trading-bot/.env

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Then:

sudo systemctl daemon-reload
sudo systemctl enable trading-bot
sudo systemctl start trading-bot
Enter fullscreen mode Exit fullscreen mode

Monitoring & Alerts

Minimum viable monitoring:

  • Structured logging (loguru or structlog) → files + optional remote
  • Telegram (or Discord) notifications for:
    • Order fills
    • Errors / circuit breaker trips
    • Daily P&L summary
    • Heartbeat (bot is still alive)

Simple Telegram helper:

import requests

def send_telegram(message: str):
    token = os.getenv("TELEGRAM_BOT_TOKEN")
    chat_id = os.getenv("TELEGRAM_CHAT_ID")
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    requests.post(url, json={"chat_id": chat_id, "text": message})
Enter fullscreen mode Exit fullscreen mode

Also consider a simple health endpoint if you use Docker + a reverse proxy.


Security Checklist

  • Use API keys with trading permissions only (disable withdrawals)
  • IP whitelist the VPS if the exchange supports it
  • Never hard-code secrets
  • Run the bot under a non-root user
  • Keep dependencies updated
  • Consider a separate “read-only” key for monitoring scripts
  • Encrypt any local state files that contain sensitive data

Going Further: Memecoin & High-Volatility Considerations

Memecoins introduce extra challenges:

  • Extremely fast moves and thin order books
  • Frequent contract changes / new pairs
  • Higher chance of rugs and liquidity pulls
  • Need for tighter circuit breakers and faster reaction times

A production CEX bot (what we built above) is excellent for larger-cap pairs and as a reliable execution layer. For pure memecoin work you will usually combine:

  • Fast on-chain listeners
  • Strict pre-trade filters (liquidity, holder distribution, mint/freeze authority, etc.)
  • Very conservative position sizing
  • Hard kill switches

The infrastructure in this article gives you the reliable foundation. For battle-tested memecoin-specific strategy logic and risk frameworks, the resource at https://selar.com/60lw5u0623 is designed exactly for that environment.


Final Thoughts

You now have:

  • Clean exchange abstraction with CCXT
  • Spot + Futures support
  • Proper order helpers
  • Position sizing primitives
  • Retry & circuit-breaker patterns
  • Deployment path (Docker or systemd)
  • Monitoring hooks
  • Security baseline

Treat this as a solid skeleton, not a finished product. Real edge comes from your signal generation, risk rules, and continuous monitoring.

Start small: paper trade or use very low size. Measure everything. Only increase capital when the system has proven stable for weeks.

If you’re building specifically around memecoin opportunities, pair the infrastructure from this guide with a dedicated strategy layer — the one linked above is a strong starting point for many developers.

Happy (and careful) building.


Useful links

Questions or improvements? Drop them in the comments.



Enter fullscreen mode Exit fullscreen mode

Top comments (0)