100% FreeNo Signup Required

Alpaca API Trading Bot Tutorial

Build an Alpaca trading bot in Python: connect to the paper-trading API, place orders, add stop-losses and position sizing, backtest, and log every trade.

Q

QuantEngines

March 16, 2026

|8 min read

Alpaca API Trading Bot Tutorial: Complete Guide to Building Your First Algorithmic Trader

Category: Algo Trading Date: 2026-03-16

Introduction

The Alpaca API has democratized algorithmic trading by providing commission-free trading with simple REST and WebSocket APIs. This comprehensive guide will walk you through building a production-ready trading bot that leverages Alpaca's powerful infrastructure. Whether you're a complete beginner or an experienced trader looking to automate your strategies, this tutorial covers everything from account setup to live trading deployment.

Setting Up Your Alpaca Trading Environment

Before writing any code, you need to establish a secure connection to Alpaca's trading infrastructure. The process begins with creating an account and generating API credentials. ``python import os from alpaca.trading.client import TradingClient from alpaca.trading.requests import MarketOrderRequest from alpaca.trading.enums import OrderSide, TimeInForce from alpaca.data.historical import StockHistoricalDataClient from alpaca.data.requests import StockBarsRequest from alpaca.data.timeframe import TimeFrame import pandas as pd from datetime import datetime, timedelta

Initialize API credentials securely

API_KEY = os.getenv('ALPACA_API_KEY') SECRET_KEY = os.getenv('ALPACA_SECRET_KEY') BASE_URL = "https://paper-api.alpaca.markets" # Use paper trading first

Create trading client

trading_client = TradingClient(API_KEY, SECRET_KEY, base_url=BASE_URL)

Get account information

account = trading_client.get_account() print(f"Account Status: {account.status}") print(f"Buying Power: ${account.buying_power}") print(f"Portfolio Value: ${account.portfolio_value}")
`

Core Trading Bot Architecture

A robust trading bot requires several key components: data fetching, signal generation, order management, and risk control. Let's build a modular architecture that separates concerns.
`python from abc import ABC, abstractmethod from typing import List, Dict, Optional import logging

Configure logging

logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class TradingStrategy(ABC): """Abstract base class for trading strategies""" def __init__(self, symbol: str, trading_client: TradingClient): self.symbol = symbol self.trading_client = trading_client self.data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) @abstractmethod def generate_signal(self, bars: pd.DataFrame) -> Optional[str]: """ Generate trading signal: 'BUY', 'SELL', or None Args: bars: DataFrame with OHLCV data Returns: Trading signal or None """ pass def fetch_historical_data(self, days_back: int = 100) -> pd.DataFrame: """Fetch historical price data for analysis""" end_date = datetime.now() start_date = end_date - timedelta(days=days_back) request = StockBarsRequest( symbol_or_symbols=self.symbol, timeframe=TimeFrame.DAY, start=start_date, end=end_date ) bars_data = self.data_client.get_stock_bars(request) df = bars_data.df df['returns'] = df['close'].pct_change() return df def get_current_position(self) -> Optional[Dict]: """Get current position for the symbol""" try: positions = trading_client.get_all_positions() for position in positions: if position.symbol == self.symbol: return { 'qty': position.qty, 'avg_fill_price': position.avg_fill_price, 'unrealized_pl': position.unrealized_pl } except Exception as e: logger.error(f"Error fetching position: {e}") return None class MovingAverageCrossoverStrategy(TradingStrategy): """Simple Moving Average Crossover Strategy""" def __init__(self, symbol: str, trading_client: TradingClient, short_window: int = 20, long_window: int = 50): super().__init__(symbol, trading_client) self.short_window = short_window self.long_window = long_window def generate_signal(self, bars: pd.DataFrame) -> Optional[str]: """Generate signal based on MA crossover""" if len(bars) < self.long_window: return None bars['sma_short'] = bars['close'].rolling(window=self.short_window).mean() bars['sma_long'] = bars['close'].rolling(window=self.long_window).mean() current_price = bars['close'].iloc[-1] sma_short = bars['sma_short'].iloc[-1] sma_long = bars['sma_long'].iloc[-1] prev_sma_short = bars['sma_short'].iloc[-2] prev_sma_long = bars['sma_long'].iloc[-2] # Golden cross: short MA crosses above long MA if (prev_sma_short <= prev_sma_long and sma_short > sma_long and current_price > sma_short): return 'BUY' # Death cross: short MA crosses below long MA if (prev_sma_short >= prev_sma_long and sma_short < sma_long and current_price < sma_short): return 'SELL' return None
`

Order Management and Execution

Proper order management is crucial for reliable trading. We need to handle market orders, limit orders, and position sizing carefully.
`python class OrderManager: """Manages order placement and monitoring""" def __init__(self, trading_client: TradingClient, max_position_size: float = 0.05): self.trading_client = trading_client self.max_position_size = max_position_size self.open_orders = {} def calculate_position_size(self, symbol: str, risk_amount: float) -> int: """ Calculate position size based on account risk Args: symbol: Stock symbol risk_amount: Maximum amount willing to risk Returns: Number of shares to trade """ account = self.trading_client.get_account() max_size = int(account.buying_power * self.max_position_size) # Get current price bars_request = StockBarsRequest( symbol_or_symbols=symbol, timeframe=TimeFrame.MINUTE, limit=1 ) bars_data = StockHistoricalDataClient(API_KEY, SECRET_KEY).get_stock_bars(bars_request) current_price = float(bars_data.df['close'].iloc[-1]) # Risk-based sizing shares = int(risk_amount / current_price) return min(shares, max_size) def place_market_order(self, symbol: str, qty: int, side: str) -> Optional[str]: """ Place a market order Args: symbol: Stock symbol qty: Number of shares side: 'buy' or 'sell' Returns: Order ID or None if failed """ try: market_order_data = MarketOrderRequest( symbol=symbol, qty=qty, side=OrderSide.BUY if side.lower() == 'buy' else OrderSide.SELL, time_in_force=TimeInForce.DAY ) order = self.trading_client.submit_order(market_order_data) self.open_orders[order.id] = order logger.info(f"Order placed: {side.upper()} {qty} shares of {symbol}") return order.id except Exception as e: logger.error(f"Failed to place order: {e}") return None def place_stop_loss_order(self, symbol: str, qty: int, stop_price: float) -> Optional[str]: """Place a stop loss order""" from alpaca.trading.requests import StopOrderRequest try: stop_order_data = StopOrderRequest( symbol=symbol, qty=qty, side=OrderSide.SELL, stop_price=stop_price, time_in_force=TimeInForce.GTC # Good till cancelled ) order = self.trading_client.submit_order(stop_order_data) logger.info(f"Stop loss set at ${stop_price} for {qty} shares of {symbol}") return order.id except Exception as e: logger.error(f"Failed to place stop loss: {e}") return None def cancel_order(self, order_id: str) -> bool: """Cancel an open order""" try: self.trading_client.cancel_order_by_id(order_id) logger.info(f"Order {order_id} cancelled") return True except Exception as e: logger.error(f"Failed to cancel order: {e}") return False `

Risk Management Framework

Professional trading requires strict risk management. We implement position sizing, stop losses, and portfolio-level risk checks.
`python class RiskManager: """Manages portfolio-level risk""" def __init__(self, trading_client: TradingClient, max_daily_loss: float = 0.02, max_position_loss: float = 0.03): self.trading_client = trading_client self.max_daily_loss = max_daily_loss self.max_position_loss = max_position_loss self.daily_start_equity = None def initialize_daily_equity(self): """Record starting equity for the day""" account = self.trading_client.get_account() self.daily_start_equity = float(account.portfolio_value) def check_daily_loss_limit(self) -> bool: """Check if daily loss limit exceeded""" account = self.trading_client.get_account() current_equity = float(account.portfolio_value) daily_loss = (self.daily_start_equity - current_equity) / self.daily_start_equity if daily_loss > self.max_daily_loss: logger.warning(f"Daily loss limit exceeded: {daily_loss:.2%}") return False return True def check_position_risk(self, symbol: str) -> bool: """Check if position has lost too much""" try: position = self.trading_client.get_position(symbol) if position.unrealized_pl_pct < -self.max_position_loss: logger.warning(f"Position {symbol} loss exceeds limit") return False except: pass return True def can_trade(self) -> bool: """Determine if trading should continue""" return self.check_daily_loss_limit() `

Building the Complete Trading Bot

Now let's tie everything together into a complete, production-ready trading bot.
`python import time from datetime import datetime, time as dt_time class AlpacaTradingBot: """Main trading bot orchestrator""" def __init__(self, symbols: List[str], strategy_class): self.symbols = symbols self.strategy_class = strategy_class self.trading_client = trading_client self.order_manager = OrderManager(trading_client) self.risk_manager = RiskManager(trading_client) self.strategies = {} self.running = False # Initialize strategies for each symbol for symbol in symbols: self.strategies[symbol] = strategy_class(symbol, trading_client) def is_market_open(self) -> bool: """Check if market is currently open""" try: clock = self.trading_client.get_clock() return clock.is_open except Exception as e: logger.error(f"Error checking market status: {e}") return False def process_symbol(self, symbol: str): """Process trading signal for a single symbol""" try: # Fetch historical data bars = self.strategies[symbol].fetch_historical_data() # Generate signal signal = self.strategies[symbol].generate_signal(bars) if signal is None: return # Check risk limits if not self.risk_manager.can_trade(): logger.warning("Trading halted due to risk limits") return # Get current position position = self.strategies[symbol].get_current_position() current_qty = float(position['qty']) if position else 0 if signal == 'BUY' and current_qty <= 0: # Calculate position size qty = self.order_manager.calculate_position_size(symbol, risk_amount=1000) # Place order order_id = self.order_manager.place_market_order(symbol, qty, 'buy') if order_id: # Set stop loss last_price = bars['close'].iloc[-1] stop_price = last_price * 0.97 # 3% stop loss self.order_manager.place_stop_loss_order(symbol, qty, stop_price) elif signal == 'SELL' and current_qty > 0: # Close position self.order_manager.place_market_order(symbol, int(current_qty), 'sell') except Exception as e: logger.error(f"Error processing {symbol}: {e}") def run(self): """Main trading loop""" self.running = True self.risk_manager.initialize_daily_equity() logger.info("Trading bot started") while self.running: try: if self.is_market_open(): for symbol in self.symbols: self.process_symbol(symbol) # Check every 60 seconds time.sleep(60) except KeyboardInterrupt: logger.info("Bot interrupted by user") self.stop() except Exception as e: logger.error(f"Unexpected error: {e}") time.sleep(5) def stop(self): """Stop the trading bot gracefully""" self.running = False logger.info("Trading bot stopped")

Main execution

if __name__ == "__main__": # Initialize bot with your symbols and strategy symbols = ['AAPL', 'MSFT', 'GOOGL', 'TSLA', 'AMZN'] bot = AlpacaTradingBot(symbols, MovingAverageCrossoverStrategy) # Run in paper trading mode first bot.run()
`

Backtesting Your Strategy

Before deploying to live trading, thoroughly backtest your strategy using historical data. If you want to validate the rules before writing this scaffolding yourself, our Strategy Builder lets you configure and backtest a strategy directly, no signup required.
`python class BacktestEngine: """Simple backtesting framework""" def __init__(self, strategy, initial_capital: float = 100000): self.strategy = strategy self.initial_capital = initial_capital self.cash = initial_capital self.position = 0 self.trades = [] self.equity_curve = [] def run_backtest(self, symbol: str, start_date, end_date) -> pd.DataFrame: """Run backtest on historical data""" from alpaca.data.requests import StockBarsRequest from alpaca.data.historical import StockHistoricalDataClient data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) request = StockBarsRequest( symbol_or_symbols=symbol, timeframe=TimeFrame.DAY, start=start_date, end=end_date ) bars = data_client.get_stock_bars(request) df = bars.df.reset_index() for idx in range(len(df)): current_bars = df.iloc[:idx+1] signal = self.strategy.generate_signal(current_bars) close_price = df.iloc[idx]['close'] if signal == 'BUY' and self.position == 0: shares = int(self.cash * 0.95 / close_price) self.position = shares self.cash -= shares * close_price self.trades.append({ 'date': df.iloc[idx]['timestamp'], 'type': 'BUY', 'price': close_price, 'shares': shares }) elif signal == 'SELL' and self.position > 0: proceeds = self.position * close_price self.cash += proceeds self.trades.append({ 'date': df.iloc[idx]['timestamp'], 'type': 'SELL', 'price': close_price, 'shares': self.position }) self.position = 0 # Calculate equity equity = self.cash + (self.position * close_price) self.equity_curve.append(equity) # Calculate metrics total_return = (self.equity_curve[-1] - self.initial_capital) / self.initial_capital max_drawdown = self._calculate_max_drawdown() results = { 'total_return': total_return, 'max_drawdown': max_drawdown, 'trades': len(self.trades), 'equity_curve': self.equity_curve } return results def _calculate_max_drawdown(self) -> float: """Calculate maximum drawdown percentage""" peak = self.initial_capital max_dd = 0 for equity in self.equity_curve: if equity > peak: peak = equity dd = (peak - equity) / peak if dd > max_dd: max_dd = dd return max_dd

Run backtest

backtest = BacktestEngine(MovingAverageCrossoverStrategy('AAPL', trading_client)) results = backtest.run_backtest('AAPL', datetime(2023, 1, 1), datetime(2024, 1, 1)) print(f"Total Return: {results['total_return']:.2%}") print(f"Max Drawdown: {results['max_drawdown']:.2%}") print(f"Total Trades: {results['trades']}")
`

Monitoring and Logging

Production trading requires comprehensive monitoring and alerting.
`python import json from datetime import datetime class BotLogger: """Comprehensive logging system for trading bot""" def __init__(self, log_file: str = 'trading_bot.log'): self.log_file = log_file def log_trade(self, symbol: str, side: str, qty: int, price: float): """Log executed trade""" log_entry = { 'timestamp': datetime.now().isoformat(), 'type': 'TRADE', 'symbol': symbol, 'side': side, 'qty': qty, 'price': price } self._write_log(log_entry) def log_signal(self, symbol: str, signal: str, reason: str): """Log trading signal""" log_entry = { 'timestamp': datetime.now().isoformat(), 'type': 'SIGNAL', 'symbol': symbol, 'signal': signal, 'reason': reason } self._write_log(log_entry) def _write_log(self, entry: Dict): """Write log entry to file""" with open(self.log_file, 'a') as f: f.write(json.dumps(entry) + '\n') ``

Conclusion

Building a professional trading bot requires careful attention to API integration, risk management, and execution strategy. Start with paper trading, validate your approach thoroughly, and gradually scale your capital as you gain confidence. The Alpaca API provides all the tools necessary for retail traders to compete with institutional trading systems. Key takeaways:
  • Always use paper trading to validate strategies
  • Implement strict risk management from the beginning
  • Monitor your bot's performance continuously
  • Be prepared to adapt your strategy as market conditions change
  • Keep detailed logs of all trades and decisions
With this foundation, you're ready to build more sophisticated strategies including machine learning models, multi-leg options strategies, and dynamic risk management systems.

Related Articles

Get new strategy write-ups by email

New backtesting guides and strategy breakdowns as we publish them. Free, unsubscribe anytime.