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.
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.