Python Alpaca API Automated Trading: Complete Backtest Tutorial
Python Alpaca API Automated Trading: Complete Backtest Tutorial
If you've ever searched for a way to automate your stock trading with Python, the Alpaca API is one of the most developer-friendly brokerages available. This guide walks you through the entire workflow — from API setup to running a live backtest.
Why Alpaca?
Alpaca offers commission-free trading with a REST + WebSocket API that's actually pleasant to work with. Unlike Interactive Brokers' legacy TWS API or Schwab's OAuth maze, Alpaca gives you API keys and lets you start coding in minutes.
Key advantages:
- Paper trading environment for risk-free testing
- Real-time market data via WebSocket
- Fractional shares support
- Clean Python SDK (
alpaca-trade-api)
Setting Up Your Environment
pip install alpaca-trade-api pandas numpy matplotlib
Create a .env file with your credentials:
APCA_API_KEY_ID=your_key_here
APCA_API_SECRET_KEY=your_secret_here
APCA_API_BASE_URL=https://paper-api.alpaca.markets
Connecting to Alpaca
import alpaca_trade_api as tradeapi
import pandas as pd
import os
from datetime import datetime, timedelta
# Initialize the API client
api = tradeapi.REST(
os.getenv('APCA_API_KEY_ID'),
os.getenv('APCA_API_SECRET_KEY'),
os.getenv('APCA_API_BASE_URL', 'https://paper-api.alpaca.markets'),
api_version='v2'
)
# Verify connection
account = api.get_account()
print(f"Account status: {account.status}")
print(f"Buying power: ${float(account.buying_power):,.2f}")
print(f"Portfolio value: ${float(account.portfolio_value):,.2f}")
Fetching Historical Data for Backtesting
def get_historical_bars(symbol: str, days: int = 365) -> pd.DataFrame:
"""Fetch daily bars for backtesting."""
end = datetime.now()
start = end - timedelta(days=days)
bars = api.get_bars(
symbol,
tradeapi.TimeFrame.Day,
start=start.strftime('%Y-%m-%d'),
end=end.strftime('%Y-%m-%d'),
limit=None
).df
bars.index = bars.index.tz_localize(None)
return bars
# Example: Get 1 year of AAPL data
df = get_historical_bars('AAPL', 365)
print(f"Loaded {len(df)} bars for AAPL")
print(df.tail())
Building a Simple Moving Average Crossover Strategy
def backtest_sma_crossover(df: pd.DataFrame, fast: int = 10, slow: int = 30) -> dict:
"""
SMA crossover backtest.
Buy when fast SMA crosses above slow SMA.
Sell when fast SMA crosses below slow SMA.
"""
df = df.copy()
df['sma_fast'] = df['close'].rolling(fast).mean()
df['sma_slow'] = df['close'].rolling(slow).mean()
df['signal'] = 0
# Generate signals
df.loc[df['sma_fast'] > df['sma_slow'], 'signal'] = 1
df.loc[df['sma_fast'] <= df['sma_slow'], 'signal'] = -1
# Calculate returns
df['position'] = df['signal'].shift(1) # avoid look-ahead bias
df['daily_return'] = df['close'].pct_change()
df['strategy_return'] = df['position'] * df['daily_return']
# Performance metrics
total_return = (1 + df['strategy_return'].dropna()).prod() - 1
sharpe = df['strategy_return'].mean() / df['strategy_return'].std() * (252 ** 0.5)
max_dd = (df['strategy_return'].cumsum() - df['strategy_return'].cumsum().cummax()).min()
trades = (df['position'].diff().abs() > 0).sum()
return {
'total_return': round(total_return * 100, 2),
'sharpe_ratio': round(sharpe, 2),
'max_drawdown': round(max_dd * 100, 2),
'total_trades': int(trades),
'win_rate': round(
(df[df['strategy_return'] > 0].shape[0] /
df[df['strategy_return'] != 0].shape[0]) * 100, 1
)
}
results = backtest_sma_crossover(df, fast=10, slow=30)
print(f"Return: {results['total_return']}%")
print(f"Sharpe: {results['sharpe_ratio']}")
print(f"Max DD: {results['max_drawdown']}%")
print(f"Trades: {results['total_trades']}")
Executing Live Orders
def place_order(symbol: str, qty: int, side: str = 'buy'):
"""Place a market order on Alpaca."""
try:
order = api.submit_order(
symbol=symbol,
qty=qty,
side=side,
type='market',
time_in_force='day'
)
print(f"Order placed: {side} {qty} {symbol} (ID: {order.id})")
return order
except Exception as e:
print(f"Order failed: {e}")
return None
# Example: Buy 10 shares of AAPL
# place_order('AAPL', 10, 'buy')
Key Takeaways
| Metric | What to Watch |
|---|---|
| Sharpe Ratio | > 1.0 for viable strategies |
| Max Drawdown | Keep under 20% for comfort |
| Win Rate | 40%+ with proper risk management |
| Trade Count | Enough data points for significance |
The backtest framework above is intentionally simple — production strategies need slippage modeling, commission accounting, and proper position sizing. This code is also one of the foundational modules powering the strategy verification engine on the ClawDUX platform.
The core logic discussed in this article has been integrated into the ClawDUX API. Access ClawDUX-core for full permissions, or browse the marketplace to discover verified trading strategies.