Crypto Bots

Automated Trading with Alpaca API: Free Paper Trading and Live Stock Bot Setup

Alpaca offers a free commission-free trading API for stocks and crypto. This guide shows how to build an AI trading bot using Alpaca's paper trading environment before going live.

A
AI Agents Hubยท2026-03-11ยท4 min readยท723 words

Builder of AI agents, crypto trading bots, and open-source automation tools. Sharing practical guides on how to build, deploy, and profit from AI and DeFi technology.

Why Alpaca for Trading Bots?

Most trading tutorials focus on crypto exchanges. But Alpaca offers something unique: a completely free, commission-free brokerage API for US equities AND crypto โ€” with paper trading that uses real market data.

This makes Alpaca the best place to develop and test trading bots before risking real money:

  • Paper trading with live market data (no fake prices)
  • Commission-free live trading for US stocks
  • WebSocket streaming for real-time price feeds
  • REST API for orders, account management, and history
  • Crypto support (BTC, ETH, and others)

Setting Up Alpaca

pip install alpaca-trade-api alpaca-py

Get API keys from alpaca.markets (both paper and live trading require different keys).

from alpaca.trading.client import TradingClient
from alpaca.data.historical import StockHistoricalDataClient
from alpaca.trading.requests import MarketOrderRequest
from alpaca.trading.enums import OrderSide, TimeInForce

# Paper trading (safe for testing)
ALPACA_KEY = "YOUR_PAPER_KEY"
ALPACA_SECRET = "YOUR_PAPER_SECRET"
BASE_URL = "https://paper-api.alpaca.markets"

trading_client = TradingClient(ALPACA_KEY, ALPACA_SECRET, paper=True)
data_client = StockHistoricalDataClient(ALPACA_KEY, ALPACA_SECRET)

# Check account
account = trading_client.get_account()
print(f"Portfolio value: ${float(account.portfolio_value):,.2f}")
print(f"Buying power: ${float(account.buying_power):,.2f}")

Building a Moving Average Crossover Bot

import pandas as pd
from alpaca.data.requests import StockBarsRequest
from alpaca.data.timeframe import TimeFrame
from datetime import datetime, timedelta

def get_stock_data(symbol: str, days: int = 100) -> pd.DataFrame:
    request = StockBarsRequest(
        symbol_or_symbols=symbol,
        timeframe=TimeFrame.Day,
        start=datetime.now() - timedelta(days=days),
        end=datetime.now()
    )
    bars = data_client.get_stock_bars(request)
    df = bars.df.reset_index()
    df.set_index('timestamp', inplace=True)
    return df

def calculate_signals(df: pd.DataFrame, short_window=20, long_window=50) -> pd.DataFrame:
    df['sma_short'] = df['close'].rolling(short_window).mean()
    df['sma_long'] = df['close'].rolling(long_window).mean()
    
    df['signal'] = 0
    df.loc[df['sma_short'] > df['sma_long'], 'signal'] = 1   # Bullish
    df.loc[df['sma_short'] < df['sma_long'], 'signal'] = -1  # Bearish
    
    # Crossover events (signal changed)
    df['crossover'] = df['signal'].diff()
    
    return df

def execute_signal(symbol: str, signal: int, allocation_pct: float = 0.10):
    """Execute buy/sell based on signal"""
    account = trading_client.get_account()
    portfolio_value = float(account.portfolio_value)
    current_price = get_latest_price(symbol)
    
    # Check current position
    try:
        position = trading_client.get_open_position(symbol)
        has_position = True
        position_qty = float(position.qty)
    except Exception:
        has_position = False
        position_qty = 0
    
    if signal == 1 and not has_position:
        # Buy: allocate 10% of portfolio
        dollars_to_invest = portfolio_value * allocation_pct
        qty = int(dollars_to_invest / current_price)
        
        if qty > 0:
            order = trading_client.submit_order(
                MarketOrderRequest(
                    symbol=symbol,
                    qty=qty,
                    side=OrderSide.BUY,
                    time_in_force=TimeInForce.DAY
                )
            )
            print(f"โœ… BUY {qty} shares of {symbol} @ ~${current_price:.2f}")
    
    elif signal == -1 and has_position:
        # Sell entire position
        order = trading_client.submit_order(
            MarketOrderRequest(
                symbol=symbol,
                qty=position_qty,
                side=OrderSide.SELL,
                time_in_force=TimeInForce.DAY
            )
        )
        print(f"โœ… SELL {position_qty} shares of {symbol}")

# Main strategy loop
WATCHLIST = ['AAPL', 'MSFT', 'NVDA', 'TSLA', 'GOOGL']

def run_strategy():
    for symbol in WATCHLIST:
        df = get_stock_data(symbol)
        df = calculate_signals(df)
        
        latest = df.iloc[-1]
        if latest['crossover'] == 2:   # Bullish crossover
            execute_signal(symbol, 1)
        elif latest['crossover'] == -2:  # Bearish crossover
            execute_signal(symbol, -1)

AI-Enhanced Alpaca Bot with News Sentiment

from openai import OpenAI
import requests

openai_client = OpenAI()

def get_news_sentiment(symbol: str) -> float:
    """Return sentiment score from -1 (bearish) to +1 (bullish)"""
    # Alpaca has a built-in news API
    news_url = f"https://data.alpaca.markets/v1beta1/news?symbols={symbol}&limit=10"
    headers = {'APCA-API-KEY-ID': ALPACA_KEY, 'APCA-API-SECRET-KEY': ALPACA_SECRET}
    
    news = requests.get(news_url, headers=headers).json()
    headlines = [article['headline'] for article in news.get('news', [])]
    
    if not headlines:
        return 0.0
    
    response = openai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{
            "role": "user",
            "content": f"""Rate the overall sentiment of these news headlines about {symbol} 
            from -1.0 (very bearish) to +1.0 (very bullish).
            Headlines: {headlines}
            
            Reply with just a number between -1.0 and 1.0."""
        }]
    )
    
    try:
        return float(response.choices[0].message.content.strip())
    except:
        return 0.0

def combined_signal(symbol: str) -> str:
    """Combine technical + sentiment for final signal"""
    df = get_stock_data(symbol)
    df = calculate_signals(df)
    tech_signal = df.iloc[-1]['signal']
    
    sentiment = get_news_sentiment(symbol)
    
    # Combined score: 60% technical, 40% sentiment
    combined = 0.6 * tech_signal + 0.4 * sentiment
    
    if combined > 0.3:
        return 'BUY'
    elif combined < -0.3:
        return 'SELL'
    else:
        return 'HOLD'

WebSocket for Real-Time Trading

from alpaca.data.live import StockDataStream

stream = StockDataStream(ALPACA_KEY, ALPACA_SECRET)

async def handle_trade(data):
    """Called on every trade update for subscribed symbols"""
    symbol = data.symbol
    price = data.price
    
    # Simple momentum: if price spikes 1% in 1 minute, buy
    if price_change_1min(symbol) > 0.01:
        execute_signal(symbol, 1, allocation_pct=0.05)

stream.subscribe_trades(handle_trade, 'AAPL', 'NVDA', 'MSFT')
stream.run()

Paper Trading Strategy

Step 1: Run on paper for 1 month
Step 2: Review win rate and Sharpe ratio
Step 3: If profitable with >1.0 Sharpe, go live with 25% of intended capital
Step 4: Scale up over 3 months if live results match paper results

The paper-to-live transition is where most bots fail. Slippage, partial fills, and market impact are all different in live trading. Start small and scale responsibly.

Related Articles