How to build an AI trading bot (with real code)

A practical, copy-pasteable walkthrough. We'll build the same SMA-crossover strategy you can test on our backtester — first in Python with ccxt, then in JavaScript — and wire in the risk management that actually keeps accounts alive.

Build steps
  1. Prerequisites
  2. 1. Fetch market data (Python + ccxt)
  3. 2. Compute the signal
  4. 3. Backtest before risking money
  5. 4. Add risk management
  6. 5. Paper trade, then go live
  7. JavaScript version
  8. FAQ

Prerequisites

Golden rule

Build the backtester and risk manager before you connect live keys. The order you build in determines whether you learn cheaply or expensively.

1. Fetch market data

Every bot starts by pulling OHLCV (open, high, low, close, volume) candles. ccxt gives one API across 100+ crypto exchanges:

python · fetch_data.pyimport ccxt, pandas as pd

exchange = ccxt.binance()                  # or bybit(), kraken(), ...
ohlcv = exchange.fetch_ohlcv('BTC/USDT', timeframe='1d', limit=365)

df = pd.DataFrame(ohlcv, columns=['ts','open','high','low','close','volume'])
df['ts'] = pd.to_datetime(df['ts'], unit='ms')
print(df.tail())

2. Compute the signal

Here is the exact SMA-crossover logic our backtester uses: go long when the 20-period average rises above the 50-period average, flat otherwise.

python · signal.pydf['sma_fast'] = df['close'].rolling(20).mean()
df['sma_slow'] = df['close'].rolling(50).mean()

# position: 1 = long, 0 = flat. Shift(1) avoids look-ahead bias.
df['position'] = (df['sma_fast'] > df['sma_slow']).astype(int).shift(1).fillna(0)
The look-ahead trap

That .shift(1) is critical. Without it your bot "decides" using a candle's close to trade during the same candle — impossible in reality, and it inflates backtest returns wildly. This single bug fools more beginners than any other.

3. Backtest before risking money

Replay history and compute equity, return and drawdown — fees included:

python · backtest.pyfee = 0.001                                   # 0.10% per side
df['ret']      = df['close'].pct_change().fillna(0)
df['turnover'] = df['position'].diff().abs().fillna(0)
df['pnl']      = df['position'] * df['ret'] - df['turnover'] * fee
df['equity']   = (1 + df['pnl']).cumprod()

total_return = (df['equity'].iloc[-1] - 1) * 100
max_dd = ((df['equity'] / df['equity'].cummax()) - 1).min() * 100
print(f"Return: {total_return:.1f}%  Max DD: {max_dd:.1f}%")

This is exactly the math powering our in-browser backtester — try the same strategy there first to sanity-check your numbers.

4. Add risk management (the part that matters most)

Never size a trade by gut. Risk a fixed small fraction of the account per trade and let the stop define the size:

python · risk.pydef position_size(account, risk_pct, entry, stop):
    """Units to trade so hitting the stop loses exactly risk_pct."""
    risk_amount = account * (risk_pct / 100)
    per_unit    = abs(entry - stop)
    return 0 if per_unit == 0 else risk_amount / per_unit

# risk 1% of a $10k account, entry 42000, stop 40000 -> 0.05 BTC
units = position_size(10000, 1, 42000, 40000)

That's the same formula behind our position sizing calculator — open it side-by-side to verify your bot's sizing.

5. Paper trade, then go live (carefully)

  1. Run on the exchange testnet for weeks. Confirm orders, fills and edge cases behave.
  2. Add logging + alerts so you know the instant it breaks.
  3. Go live with the smallest possible size. Scale only after weeks of live results match the backtest.
python · live_order.pyexchange = ccxt.binance({'apiKey': KEY, 'secret': SECRET})
exchange.set_sandbox_mode(True)              # TESTNET — always start here

if signal == 'buy':
    exchange.create_market_buy_order('BTC/USDT', units)

JavaScript version (browser-style signal)

If you prefer JS, the signal logic is identical. This is the core of the engine running on this very site:

javascript · signal.jsfunction sma(closes, p, i) {
  if (i < p - 1) return null;
  let s = 0;
  for (let k = i - p + 1; k <= i; k++) s += closes[k];
  return s / p;
}
// position[i] = 1 when fast SMA > slow SMA, else 0
const pos = closes.map((_, i) => {
  const f = sma(closes, 20, i), s = sma(closes, 50, i);
  return (f && s && f > s) ? 1 : 0;
});
Next step

You now have all six layers: data, signal, backtest, risk, execution, monitoring. Swap the SMA rule for RSI, momentum or grid (see strategies explained) and re-run the backtest before each change.

Not financial advice. This content is educational. Automated and algorithmic trading carries a real risk of financial loss. Never trade money you cannot afford to lose. Review the SEC investor.gov and CFTC resources before trading.

Frequently asked questions

What language should I use to build a trading bot?

Python is the most popular for trading bots thanks to libraries like ccxt, pandas and backtesting frameworks. JavaScript/Node works well too, especially for browser tools and exchanges with good JS SDKs. Start with whichever you already know.

How long does it take to build a basic trading bot?

A working rule-based bot that fetches data, computes a signal and paper-trades can be built in a weekend. A robust, monitored, live-capital bot with proper risk controls takes weeks of testing.

Do I need machine learning to build an AI trading bot?

No. Most profitable retail bots use simple rule-based signals. Add machine learning only after you have a solid backtesting pipeline and understand overfitting — otherwise ML usually hurts.

Can I build a trading bot for free?

Yes. The libraries (ccxt, pandas), most exchange testnets and paper-trading sandboxes are free. You only need capital when you go live, and you should paper-trade for months first.

MB

Mustafa Bilgic

Algorithmic trading practitioner · Founder, AITradingBot.us

Mustafa builds and backtests automated trading systems and writes about them without the hype. Every tool on this site is free and runs entirely in your browser.