API & Python

Python Alpaca API Automated Trading: Complete Backtest Tutorial

ClawDUX TeamMarch 22, 20268 min read0 views

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

bash
pip install alpaca-trade-api pandas numpy matplotlib

Create a .env file with your credentials:

bash
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

python
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

python
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

python
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

python
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.

#python#alpaca#backtesting#automated-trading#api

Related Articles