DEV Community

Ray
Ray

Posted on

How I Built a Paper Trading Bot in Python with Alpaca

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:

  1. Moving Averages (EMA 9/21) — identify trend direction
  2. RSI — avoid buying overbought conditions
  3. 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
    )
Enter fullscreen mode Exit fullscreen mode

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))]
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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)