How I Built a Paper Trading Bot in Python with Alpaca
Automated trading has always fascinated me — the idea that you can codify market intuition into rules that execute with machine precision. In this post I'll walk through building a Python paper trading bot using the Alpaca API, covering confluence signal generation, backtesting, and live paper execution.
Why Paper Trading First?
Before risking real capital, paper trading lets you stress-test your strategy against live market conditions without financial consequences. Alpaca's paper trading environment is a perfect sandbox — same API, real tick data, fake dollars.
Strategy: Confluence Signals
Rather than relying on a single indicator, confluence trading combines multiple signals. Our bot uses three:
- Moving Averages (EMA 9/21) — identify trend direction
- RSI — avoid buying overbought conditions
- Volume confirmation — filter out weak moves
A "buy" fires only when all three align: price above EMA21, EMA9 crossing up, RSI < 65, volume above 20-day average.
def is_buy_signal(bars):
ema9 = compute_ema(bars['close'], 9)
ema21 = compute_ema(bars['close'], 21)
rsi = compute_rsi(bars['close'], 14)
vol_ok = bars['volume'][-1] > bars['volume'][-20:].mean()
return (
ema9[-1] > ema21[-1] # trend up
and ema9[-2] <= ema21[-2] # fresh cross
and rsi[-1] < 65 # not overbought
and vol_ok
)
Backtesting Loop
Before live paper trading, I ran a 6-month backtest on historical data pulled from Alpaca's /v2/stocks/{symbol}/bars endpoint:
import alpaca_trade_api as tradeapi
from datetime import datetime, timedelta
api = tradeapi.REST(API_KEY, SECRET_KEY, base_url='https://paper-api.alpaca.markets')
bars = api.get_bars('AAPL', '1Day',
start=(datetime.now() - timedelta(days=180)).isoformat(),
end=datetime.now().isoformat()
).df
signals = [is_buy_signal(bars.iloc[:i+1]) for i in range(20, len(bars))]
The backtest showed a 58% win rate on the AAPL/GOOGL/MSFT universe — enough to justify live paper testing.
Live Paper Execution
The live bot runs on a cron schedule, scanning the watchlist every 5 minutes during market hours:
def scan_and_trade():
for symbol in WATCHLIST:
bars = get_recent_bars(symbol)
pos = get_open_position(symbol)
if pos is None and is_buy_signal(bars):
api.submit_order(symbol=symbol, qty=10, side='buy',
type='market', time_in_force='day')
elif pos and is_exit_signal(bars, pos):
api.submit_order(symbol=symbol, qty=pos.qty, side='sell',
type='market', time_in_force='day')
Tournament Evolution
The most interesting part of TradeSight is the strategy tournament — multiple strategy variants compete head-to-head on paper, and the winner gets promoted to live allocation. No manual parameter tuning; the market decides.
If you're building something similar or want to extend this, the full source is on GitHub. PRs welcome.
Top comments (0)