CodeForFinance

Build a Simple Trading Bot in Python

A trading bot in Python is less about genius strategy and more about plumbing: fetching data, generating signals, sizing positions, sending orders, handling errors. Get the plumbing right and you can swap strategies in and out. Get it wrong and even a perfect strategy will destroy your account.

Before You Start

Run this on a testnet or paper trading account first. A bug in a live trading loop can empty an account in seconds. We will use Binance testnet in this tutorial.

Install Dependencies

pip install ccxt pandas numpy python-dotenv

We use ccxt because it abstracts over 100 crypto exchanges with one API. If you want to use a specific exchange API directly, the logic stays the same - only the client changes.

Exchange Setup

import ccxt
import pandas as pd
import numpy as np
import time
from dotenv import load_dotenv
import os

load_dotenv()

exchange = ccxt.binance({
    'apiKey': os.getenv('BINANCE_API_KEY'),
    'secret': os.getenv('BINANCE_SECRET'),
    'enableRateLimit': True,
    'options': {'defaultType': 'spot'}
})

# CRITICAL: use testnet until confident
exchange.set_sandbox_mode(True)

SYMBOL = 'BTC/USDT'
TIMEFRAME = '1h'

API keys live in a .env file, never in code. The sandbox mode routes everything to a test environment with fake money. Always start there.

Fetch Historical Candles

def fetch_ohlcv(symbol=SYMBOL, timeframe=TIMEFRAME, limit=200):
    bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit)
    df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'volume'])
    df['ts'] = pd.to_datetime(df['ts'], unit='ms')
    df.set_index('ts', inplace=True)
    return df

The RSI Strategy

RSI (Relative Strength Index) measures the speed and magnitude of recent price changes. Below 30 is typically called oversold, above 70 overbought. The strategy here waits for RSI to cross back through 30 from below (a bounce signal) and exits on a cross down through 70.

This is not a secret alpha. It is a simple, well-known pattern - the point of this tutorial is the infrastructure, not the strategy. You would backtest and refine the signal before trusting it.

def rsi(series, period=14):
    delta = series.diff()
    gain = delta.clip(lower=0)
    loss = -delta.clip(upper=0)
    avg_gain = gain.ewm(alpha=1/period, adjust=False).mean()
    avg_loss = loss.ewm(alpha=1/period, adjust=False).mean()
    rs = avg_gain / avg_loss
    return 100 - (100 / (1 + rs))

def signal(df):
    df['rsi'] = rsi(df['close'])
    df['rsi_prev'] = df['rsi'].shift(1)

    # Buy when RSI crosses up through 30 (oversold bounce)
    buy = (df['rsi_prev'] < 30) & (df['rsi'] >= 30)
    # Sell when RSI crosses down through 70 (overbought exhaustion)
    sell = (df['rsi_prev'] > 70) & (df['rsi'] <= 70)

    df['signal'] = 0
    df.loc[buy, 'signal'] = 1
    df.loc[sell, 'signal'] = -1
    return df

Risk Management

This is the most important part of the bot. We risk a fixed percentage of equity per trade, calculated from the stop distance. If price is $50,000, equity is $10,000, risk is 1% ($100), and stop is 2% ($1,000), position size is 0.1 BTC. If we get stopped out, we lose exactly $100 regardless of price.

RISK_PER_TRADE = 0.01  # 1% of equity per trade
STOP_PCT = 0.02        # 2% stop loss

def position_size(equity_usdt, price, stop_pct=STOP_PCT):
    risk_amount = equity_usdt * RISK_PER_TRADE
    stop_distance = price * stop_pct
    qty = risk_amount / stop_distance
    return qty

def get_equity():
    balance = exchange.fetch_balance()
    return balance['total'].get('USDT', 0)

The Main Loop

The loop ties it together: fetch data, check signal, manage position, sleep, repeat. The try/except catches exchange errors (timeouts, rate limits, rejected orders) and keeps the bot running. A bot that crashes on the first network blip is useless.

def run_bot():
    position = None  # None, 'long'
    entry_price = None

    while True:
        try:
            df = fetch_ohlcv()
            df = signal(df)
            latest = df.iloc[-1]
            price = latest['close']

            if position is None and latest['signal'] == 1:
                equity = get_equity()
                qty = position_size(equity, price)
                order = exchange.create_market_buy_order(SYMBOL, qty)
                position = 'long'
                entry_price = price
                print(f"BUY {qty} at {price}")

            elif position == 'long':
                stop_hit = price < entry_price * (1 - STOP_PCT)
                signal_exit = latest['signal'] == -1
                if stop_hit or signal_exit:
                    bal = exchange.fetch_balance()
                    qty = bal['BTC']['free']
                    if qty > 0:
                        exchange.create_market_sell_order(SYMBOL, qty)
                    print(f"SELL at {price}, stop_hit={stop_hit}")
                    position = None
                    entry_price = None

            time.sleep(60 * 60)  # wait an hour for next candle

        except Exception as e:
            print(f"Error: {e}")
            time.sleep(60)

if __name__ == '__main__':
    run_bot()

Things This Bot Does Not Do Yet

  • Backtest the strategy on historical data before running.
  • Log trades to a database for analysis.
  • Send alerts on fills or errors (use Telegram or Discord webhooks).
  • Handle partial fills or slippage.
  • Use a real stop-loss order instead of a soft exit.
  • Run as a systemd service or in Docker for reliability.

Each of these is a substantial piece of work. Build them one at a time. The naive bot above will teach you more in one week of paper trading than any theoretical tutorial.

Paper Trading First

Run this on Binance testnet for at least two weeks. Watch how it handles weekends, news spikes, low liquidity periods. Count every bug, every missed fill, every weird edge case. Only when you cannot think of anything else that can go wrong should you consider live capital - and even then, start with an amount you are prepared to lose entirely.

Risk Warning

Automated trading can result in rapid and total loss of capital. Crypto markets are unregulated and highly volatile. Code is illustrative only - always test thoroughly and never trade money you cannot afford to lose.

Developer Essentials

As an Amazon Associate we may earn from qualifying purchases.