Home › Guides › Trading bots & code
Paper trading a bot: test it live, with no real money at stake
A backtest tells you how a rule behaved on the past. Paper trading shows how your bot behaves right now — real prices, real delays, fake money. It's the step that catches the bugs a backtest can't.
What paper trading a bot means
Paper trading means trading with pretend money. For a bot, it means the bot runs exactly as it would live — fetching prices, deciding, sending orders — but the orders land in a practice account.
It's sometimes called forward testing: testing on prices that hadn't happened yet when you wrote the rule. That alone makes it more honest than any backtest.
Example code for learning — test it on paper first; it is not a ready-made trading system.
What paper trading catches that backtests miss
A backtest lives in a perfect world. Paper trading shows you the real one:
- Fills. In a backtest you "get" the price you wanted. Live, you often get a slightly worse one. That gap is Slippage.
- Timing. The bot wakes a few seconds late, or the data arrives late.
- Errors. Timeouts, rejected orders, rate limits, a dropped connection.
- Restarts. What happens when the bot crashes mid-trade and starts again?
- You. Do you leave it alone, or do you keep "fixing" it?
3 ways to paper trade a bot
| Way | What it is | Good for |
|---|---|---|
| A simulator in your code | Your bot "fills" its own orders at live prices, with costs added | Learning, any market, no sign-up |
| A broker paper account | A practice account at a real broker, with its own API keys | Testing the real order code end to end |
| An exchange testnet | A practice copy of a crypto exchange, with test coins | Crypto bots |
Some examples, as of October 2026 — check each provider's docs, as details change:
- Alpaca has a paper trading account with its own keys and its own web address (paper-api.alpaca.markets), separate from live. See Alpaca API stock bot basics.
- Interactive Brokers offers a paper account you can connect to through TWS or IB Gateway. See Interactive Brokers API basics.
- Crypto exchanges often have testnets. In the ccxt library, set_sandbox_mode(True) switches to one if the exchange has it. See crypto trading bot with ccxt.
Whichever you use: paper keys only. Live keys should not exist yet. See API key security for trading bots.
Build a tiny paper broker
The simplest start is a simulator. This one fills orders at the price you give it, then makes it a little worse (slippage) and adds a fee. Every fill goes into a CSV log.
import csv
class PaperBroker:
"""Fills orders at a given price plus slippage and fees. Pretend money only."""
def __init__(self, cash, fee_per_unit=0.01, slippage=0.02, log_path="paper_log.csv"):
self.cash, self.fee, self.slip = cash, fee_per_unit, slippage
self.units, self.log_path = 0, log_path
with open(log_path, "w", newline="") as f:
csv.writer(f).writerow(["time", "side", "qty", "wanted", "filled", "fee"])
def market_order(self, time, side, qty, price):
filled = price + self.slip if side == "buy" else price - self.slip
fee = self.fee * qty
if side == "buy":
self.cash -= filled * qty + fee
self.units += qty
else:
self.cash += filled * qty - fee
self.units -= qty
with open(self.log_path, "a", newline="") as f:
csv.writer(f).writerow([time, side, qty, round(price, 4), round(filled, 4), fee])
return filled
def value(self, price):
return self.cash + self.units * price
In plain words:
- slippage: a buy fills a bit higher, a sell a bit lower. That's what usually happens live.
- fee: a cost per unit, taken off every fill.
- the log: one row per fill, with the price you wanted next to the price you got.
Feed it "live" prices
To test without any account, we feed made-up prices one candle at a time, as if they were arriving live.
import numpy as np
import pandas as pd
# Made-up prices standing in for a live feed. Not a real market.
rng = np.random.default_rng(seed=3)
prices = pd.Series(100 + rng.normal(0, 0.5, 500).cumsum(),
index=pd.date_range("2026-10-01", periods=500, freq="h"))
broker = PaperBroker(cash=10_000)
fast, slow = prices.rolling(10).mean(), prices.rolling(30).mean()
for t in range(31, len(prices)):
up_now, up_before = fast.iloc[t - 1] > slow.iloc[t - 1], fast.iloc[t - 2] > slow.iloc[t - 2]
price = prices.iloc[t] # the next price after the signal candle closed
if up_now and not up_before and broker.units == 0:
broker.market_order(prices.index[t], "buy", 50, price)
elif not up_now and up_before and broker.units > 0:
broker.market_order(prices.index[t], "sell", broker.units, price)
print("Account value:", round(broker.value(prices.iloc[-1]), 2))
The rule is a simple average crossover, just to make orders happen. The signal uses candles that have already closed (t − 1 and t − 2), and the order fills at the next price. That's how it works live: you see the close, then you act.
The account value at the end is meaningless — the prices are random, so any gain or loss is luck. Results on synthetic data mean nothing about real markets. What we want is the log.
Read the log
log = pd.read_csv("paper_log.csv")
log["slip_cost"] = (log["filled"] - log["wanted"]).abs() * log["qty"]
print(log.head(4))
print("Fills:", len(log), " Slippage paid:", round(log["slip_cost"].sum(), 2),
" Fees paid:", round(log["fee"].sum(), 2))
This is the most useful table in bot testing. It shows, fill by fill, the gap between what the backtest assumed and what the bot got. On a broker paper account, the "filled" column comes from the broker instead of your guess.
Keep this log next to your normal trading journal. Each order is a row.
The 6 things to check before going live
Run the bot on paper until it has made a good number of trades — at least several weeks, and ideally 50 to 100 trades. Then check:
- Every order is explained. For each one in the log, you can point to the candle and the rule that caused it. One mystery order = not ready.
- Behaviour matches the backtest. Same kinds of trades, at the same kinds of places. Results will differ; behaviour shouldn't.
- Costs are counted. Slippage plus fees, in units of risk (R — the amount lost if a stop is hit). If costs eat most of the average trade, the rule may not survive live.
- The guards fired. The daily loss limit, the trade cap and the kill switch each worked at least once — even if you had to trigger them on purpose. See trading bot risk controls.
- It survived a restart. Stop it mid-trade, start it again. Does it know it has a position? Does it place a duplicate order?
- It handled an error. Pull the network cable or block the connection for a minute. It should wait, retry safely, or stop and tell you.
Pass all 6, then go live small — the smallest size your broker allows. Paper and live still feel different, and fills can differ too. See paper trading vs live trading.
Don't edit during the test. Every change to the rule restarts the clock. Write ideas down, finish the test, then change one thing.
Common mistakes
- Skipping paper because the backtest looked great. Backtests can't see slippage, delays or crashes.
- Paper without costs. A simulator with no slippage and no fees is just a slower backtest.
- Too short a test. A week of a few trades says almost nothing.
- Live keys "just in case". Keep them out until the test is done.
- Judging by the balance. On paper, the balance is the least useful number. The log is the point.
Questions
How long should I paper trade a bot?
Long enough for a good number of trades — often several weeks and ideally 50 to 100 trades — and until every order in the log is explained.
Is paper trading the same as backtesting?
No. A backtest replays past prices. Paper trading runs the bot in real time on new prices, so it shows delays, fills and errors a backtest can't.
Does Alpaca have paper trading for bots?
Yes. Alpaca offers a paper trading account with separate API keys and a separate paper address, so bot code can be tested without real money.
Why are my paper trading results different from my backtest?
Usually fills, costs and timing. Live prices move between the signal and the order. Check the log for slippage, late signals and skipped trades.
Want the next step? We'll walk you through it.
Four free lessons by email, one every few days — each with a 10-minute exercise on a real chart.
Send Me the Free Lessons 4 short emails · No spam · Unsubscribe anytimeOr see the whole method, step by step:
See What’s Inside the Course Try everything free first. Decide when you're ready. · Open the free chart tool (works in the TradingView phone app)One chart idea a day, in plain English. Follow along: