⬡ TECHNICAL TUTORIAL · WITH CODE · 14 MIN READ

Python trading bot with the Binance API.

From the environment to your first computed signal, with real, safe code. We'll connect, pull candles, structure the strategy and — most importantly — test on the testnet before any real money.

By the RoboTraderIA Team· updated May 2026· intermediate level

Binance has one of the most complete and best-documented APIs in crypto, and Python is the ideal language to explore it. In this tutorial you'll build the foundation of a real trading bot — not a toy. We'll prioritize three things that separate hobby code from serious code: key security, separation of concerns and testing before trading.

First of all — about risk: a crypto bot trades in a highly volatile market, 24/7, with no circuit breaker. A bug in your code can drain the account while you sleep. Everything here must be tested exhaustively on the testnet. Never run it with money you can't afford to lose, and never enable withdrawal permission on your API keys.

01Setting up the environment

Python 3.11+, a virtualenv and the library. There are two main choices: python-binance (specific, more direct for Binance) and ccxt (generic, works on dozens of exchanges). To get started focused, we'll use python-binance.

# Create the project and an isolated environment
mkdir binance-bot && cd binance-bot
python -m venv venv
source venv/bin/activate  # Linux/Mac (Windows: venv\Scripts\activate)

pip install python-binance pandas python-dotenv

02Generate API keys (securely)

In your Binance account, go to API Management and create a key. Golden rules of security:

  • Minimal permissions: enable only "Read" and "Spot Trading." NEVER enable "Withdrawal."
  • Restrict by IP if you'll run it on a VPS with a fixed IP.
  • Save the Secret Key immediately — it appears only once.

Put the keys in a .env file (and add .env to .gitignore):

# .env — NEVER commit this file
BINANCE_API_KEY=your_key_here
BINANCE_API_SECRET=your_secret_here

The mistake that costs dearly: pasting the key directly into the code and pushing it to GitHub. Bots scan GitHub within seconds for exposed keys and drain accounts. Always .env + .gitignore.

03Connect and pull candles

First contact — connect to the testnet (a test environment with fake balance) and pull data:

# connect.py
import os
from binance.client import Client
from dotenv import load_dotenv
import pandas as pd

load_dotenv()
# testnet=True uses the test environment — ALWAYS start here
client = Client(os.getenv("BINANCE_API_KEY"),
                os.getenv("BINANCE_API_SECRET"),
                testnet=True)

def get_candles(symbol="BTCUSDT", interval="15m", limit=100):
    raw = client.get_klines(symbol=symbol, interval=interval, limit=limit)
    df = pd.DataFrame(raw, columns=[
        "open_time","open","high","low","close","volume",
        "close_time","qav","trades","tbav","tqav","ignore"])
    df["close"] = df["close"].astype(float)
    return df

df = get_candles()
print(df[["close"]].tail())

04Compute the signal (strategy as a pure function)

Here's the principle that separates professional code: the strategy is a pure function — it takes data, returns a signal, with no side effects. That makes it testable in isolation, without connecting to anything. Example with a moving-average crossover + RSI:

# strategy.py
import pandas as pd

def compute_rsi(series, period=14):
    delta = series.diff()
    gain = delta.clip(lower=0).rolling(period).mean()
    loss = -delta.clip(upper=0).rolling(period).mean()
    rs = gain / loss
    return 100 - (100 / (1 + rs))

def compute_signal(df: pd.DataFrame) -> str:
    df["fast_ma"] = df["close"].rolling(9).mean()
    df["slow_ma"] = df["close"].rolling(21).mean()
    df["rsi"] = compute_rsi(df["close"])

    last = df.iloc[-1]
    # Buy: uptrend + RSI not overbought
    if last["fast_ma"] > last["slow_ma"] and last["rsi"] < 70:
        return "BUY"
    if last["fast_ma"] < last["slow_ma"] and last["rsi"] > 30:
        return "SELL"
    return "WAIT"

Why a pure function matters: you can run compute_signal() over 5 years of historical data in seconds, without connecting to the API. It's the basis of backtesting. Strategy logic mixed with execution code is impossible to test properly.

05Place an order (on the testnet first!)

With the signal computed, the execution layer sends the order. Note this runs on the testnet — fake balance:

# executor.py
from binance.enums import *

def place_order(client, symbol, signal, quantity):
    if signal == "BUY":
        order = client.create_order(
            symbol=symbol, side=SIDE_BUY,
            type=ORDER_TYPE_MARKET, quantity=quantity)
        return order
    if signal == "SELL":
        order = client.create_order(
            symbol=symbol, side=SIDE_SELL,
            type=ORDER_TYPE_MARKET, quantity=quantity)
        return order
    return None  # WAIT: do nothing

Want the complete project, built and commented?

Download our open-source sample bot — clean structure, tests and risk management included.

Download the free bot →

06The architecture of a bot that won't break you

Put the pieces together with a clear separation. A serious bot has independent layers:

# recommended structure
# ├── client.py     # Binance connection + reconnection
# ├── strategy.py   # pure functions: data → signal (testable)
# ├── risk.py       # position size, stop, take, limits
# ├── executor.py   # sends orders, manages state
# ├── logger.py     # logs everything (auditing is vital)
# └── main.py       # main loop, glues it together

# main.py — basic loop
import time

while True:
    try:
        df = get_candles()
        signal = compute_signal(df)
        qty = compute_position_size(balance, risk_pct=1)  # from risk.py
        if signal != "WAIT":
            place_order(client, "BTCUSDT", signal, qty)
            log(signal, qty)
        time.sleep(60)  # check every minute
    except Exception as e:
        log_error(e)
        time.sleep(30)  # never let the loop die silently

07Risk management in code

The strategy defines when to enter; risk management defines how much. Without the second, the first doesn't matter. At a minimum, risk.py should contain:

  • Position sizing based on a % of the account (e.g. risk 1% per trade).
  • An automatic stop loss on every position — never go without one.
  • A daily loss limit that shuts the bot down when reached.
  • A maximum number of simultaneous positions so you don't concentrate risk.

08Testnet and backtest before going live

The mandatory sequence before any real money: (1) backtest the strategy function over historical data, (2) testnet on Binance for weeks with fake balance in real market conditions, (3) only then live with a minimal amount. Skipping any step is like driving with your eyes closed.

The natural next step: after mastering the Binance API, the pattern transfers almost identically to other exchanges via ccxt, and the same architecture (pure strategy + executor + risk) applies to MT5 bots. See our general bot-building guide.

09Frequently asked questions

python-binance or ccxt?

python-binance is specific and more direct to start focused on Binance. ccxt is generic and works on dozens of exchanges with the same syntax — better if you plan to trade on several. To learn, start with python-binance.

Do you need money to test?

No. Binance has a free testnet with fake balance, in real market conditions. Use testnet=True in the client. Test for weeks before any real money.

Is it safe to leave the API key in the code?

Never. Use environment variables (.env), restrict the key's permissions to the minimum (read + spot, never withdrawal), and add .env to .gitignore. Bots scan GitHub for exposed keys.

Can the bot run 24h on its own?

Yes, but it needs to run on an always-on machine — ideally a VPS. Crypto trades 24/7, so a bot on your personal PC stops when you turn the machine off.

Can you use this for futures markets?

The Binance API is for crypto. For index futures, the path is MT5 via Python's MetaTrader5 library, or your broker's API. The architecture (pure strategy + executor) is the same.