diff --git a/alpaca_trading_example/.env.example b/alpaca_trading_example/.env.example new file mode 100644 index 0000000000000..392743009c9d8 --- /dev/null +++ b/alpaca_trading_example/.env.example @@ -0,0 +1,15 @@ +# Alpaca API Configuration +# Copy this file to .env and fill in your actual API credentials + +# Paper Trading (recommended for testing) +ALPACA_API_KEY=your_paper_api_key_here +ALPACA_SECRET_KEY=your_paper_secret_key_here +ALPACA_BASE_URL=https://paper-api.alpaca.markets + +# Live Trading (use with caution) +# ALPACA_API_KEY=your_live_api_key_here +# ALPACA_SECRET_KEY=your_live_secret_key_here +# ALPACA_BASE_URL=https://api.alpaca.markets + +# Optional: WebSocket URL for real-time data +ALPACA_WEBSOCKET_URL=wss://paper-api.alpaca.markets/stream/v2/iex \ No newline at end of file diff --git a/alpaca_trading_example/README.md b/alpaca_trading_example/README.md new file mode 100644 index 0000000000000..acd6759c9540c --- /dev/null +++ b/alpaca_trading_example/README.md @@ -0,0 +1,233 @@ +# Alpaca Trading API - Multi-Leg Options Trading + +This project demonstrates how to use the Alpaca Trading API to create multi-leg options strategies with stop loss and take profit orders. + +## Features + +- **Multi-leg options strategies**: Bull call spreads, iron condors, straddles, strangles +- **Stop loss and take profit**: Automated risk management +- **Position monitoring**: Real-time position tracking +- **Order management**: Comprehensive order handling and history +- **Paper trading**: Safe testing environment + +## Prerequisites + +1. **Alpaca Account**: Sign up at [alpaca.markets](https://alpaca.markets) +2. **Options Trading Enabled**: Ensure your account has options trading permissions +3. **API Keys**: Generate API keys from your Alpaca dashboard +4. **Python 3.8+**: Required for the alpaca-py library + +## Installation + +1. **Clone or download this project** +2. **Install dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Set up environment variables**: + ```bash + cp .env.example .env + ``` + + Edit `.env` and add your Alpaca API credentials: + ```env + ALPACA_API_KEY=your_paper_api_key_here + ALPACA_SECRET_KEY=your_paper_secret_key_here + ALPACA_BASE_URL=https://paper-api.alpaca.markets + ``` + +## Usage Examples + +### 1. Simple Options Trading + +The `simple_options_trading.py` file provides basic options trading functionality: + +```python +from simple_options_trading import SimpleOptionsTrader + +# Initialize trader +trader = SimpleOptionsTrader() + +# Buy a call option with stop loss and take profit +call_trade = trader.buy_call_option( + symbol='AAPL', + expiration_date='2024-01-19', + strike_price=180.0, + quantity=1, + stop_loss_pct=0.25, # 25% stop loss + take_profit_pct=0.50 # 50% take profit +) + +# Sell a put option with stop loss and take profit +put_trade = trader.sell_put_option( + symbol='SPY', + expiration_date='2024-01-19', + strike_price=450.0, + quantity=1, + stop_loss_pct=0.30, + take_profit_pct=0.60 +) +``` + +### 2. Multi-Leg Options Strategies + +The `multi_leg_options_trading.py` file provides advanced multi-leg strategies: + +```python +from multi_leg_options_trading import MultiLegOptionsTrader + +# Initialize trader +trader = MultiLegOptionsTrader() + +# Create a bull call spread +bull_spread = trader.create_bull_call_spread( + symbol='AAPL', + expiration_date='2024-01-19', + buy_strike=180.0, + sell_strike=185.0, + quantity=1, + stop_loss_pct=0.25, + take_profit_pct=0.50 +) + +# Create an iron condor +iron_condor = trader.create_iron_condor( + symbol='SPY', + expiration_date='2024-01-19', + sell_put_strike=450.0, + buy_put_strike=445.0, + sell_call_strike=460.0, + buy_call_strike=465.0, + quantity=1, + stop_loss_pct=0.30, + take_profit_pct=0.60 +) +``` + +## Strategy Explanations + +### Bull Call Spread +- **Strategy**: Buy a call at a lower strike, sell a call at a higher strike +- **Max Loss**: Net premium paid +- **Max Profit**: Difference between strikes minus net premium +- **When to Use**: Moderately bullish outlook + +### Iron Condor +- **Strategy**: Sell a put spread and a call spread simultaneously +- **Max Loss**: Width of the wider spread minus net premium received +- **Max Profit**: Net premium received +- **When to Use**: Neutral outlook with low volatility expectations + +### Stop Loss and Take Profit + +Both examples include automated stop loss and take profit orders: + +- **Stop Loss**: Automatically closes position if loss exceeds specified percentage +- **Take Profit**: Automatically closes position if profit reaches specified percentage +- **Order Types**: Uses stop orders for stop loss and limit orders for take profit + +## Risk Management + +### Important Considerations + +1. **Paper Trading First**: Always test strategies in paper trading before live trading +2. **Position Sizing**: Never risk more than you can afford to lose +3. **Stop Losses**: Always use stop losses to limit potential losses +4. **Expiration Dates**: Be aware of option expiration dates and time decay +5. **Liquidity**: Ensure options have sufficient liquidity for easy entry/exit + +### Risk Parameters + +- **Stop Loss Percentage**: Typically 25-50% of the position value +- **Take Profit Percentage**: Typically 50-100% of the position value +- **Position Size**: Limit to 1-5% of total portfolio per trade + +## API Endpoints Used + +### Trading Endpoints +- `POST /v2/orders` - Submit orders +- `GET /v2/orders` - Get order history +- `DELETE /v2/orders/{order_id}` - Cancel orders +- `GET /v2/positions` - Get current positions +- `DELETE /v2/positions/{symbol}` - Close positions + +### Data Endpoints +- `GET /v2/stocks/{symbol}/bars` - Get historical data +- `GET /v2/stocks/{symbol}/trades` - Get real-time trades + +## Error Handling + +The code includes comprehensive error handling for: + +- **API Errors**: Network issues, authentication problems +- **Order Errors**: Invalid symbols, insufficient funds, market closed +- **Position Errors**: Position not found, already closed +- **Timeout Errors**: Orders taking too long to fill + +## Monitoring and Management + +### Position Monitoring +```python +# Get current positions +positions = trader.get_positions() +print(f"Current positions: {positions}") + +# Get order history +order_history = trader.get_order_history() +print(f"Order history: {order_history}") +``` + +### Account Information +```python +# Get account details +account_info = trader.get_account_info() +print(f"Account info: {account_info}") +``` + +## Common Issues and Solutions + +### 1. "Symbol not found" Error +- **Cause**: Option symbol format is incorrect +- **Solution**: Verify expiration date format and strike price format +- **Example**: `AAPL20240119C00180000` for AAPL call at $180 strike + +### 2. "Insufficient buying power" Error +- **Cause**: Account doesn't have enough funds +- **Solution**: Check account balance and margin requirements +- **Note**: Options require margin for short positions + +### 3. "Market closed" Error +- **Cause**: Trying to trade outside market hours +- **Solution**: Use GTC (Good Till Canceled) orders or wait for market open + +### 4. "Order not filled" Error +- **Cause**: Poor liquidity or unrealistic prices +- **Solution**: Use market orders or adjust limit prices + +## Best Practices + +1. **Start Small**: Begin with small position sizes +2. **Test Thoroughly**: Use paper trading extensively +3. **Monitor Closely**: Check positions regularly +4. **Document Trades**: Keep detailed records of all trades +5. **Learn Continuously**: Study options theory and market dynamics + +## Legal Disclaimer + +This code is for educational purposes only. Trading options involves substantial risk and may not be suitable for all investors. Past performance does not guarantee future results. Always consult with a financial advisor before making investment decisions. + +## Support + +For issues with the Alpaca API: +- [Alpaca Documentation](https://docs.alpaca.markets/) +- [Alpaca Support](https://alpaca.markets/support) + +For issues with this code: +- Check the error messages and logs +- Verify API credentials and permissions +- Ensure all dependencies are installed correctly + +## License + +This project is provided as-is for educational purposes. Use at your own risk. \ No newline at end of file diff --git a/alpaca_trading_example/multi_leg_options_trading.py b/alpaca_trading_example/multi_leg_options_trading.py new file mode 100644 index 0000000000000..0f95e51ee392d --- /dev/null +++ b/alpaca_trading_example/multi_leg_options_trading.py @@ -0,0 +1,616 @@ +""" +Alpaca Trading API - Multi-Leg Options Trading with Stop Loss and Take Profit + +This example demonstrates how to: +1. Create multi-leg options strategies (spreads, straddles, strangles) +2. Set stop loss and take profit orders +3. Monitor positions and manage risk +4. Handle different order types and time-in-force settings + +Requirements: +- Alpaca account with options trading enabled +- API keys configured in environment variables +- Sufficient buying power for the strategies +""" + +import os +import time +import logging +from datetime import datetime, timedelta +from typing import List, Dict, Optional, Tuple +from decimal import Decimal +import json + +from alpaca.trading.client import TradingClient +from alpaca.trading.requests import ( + MarketOrderRequest, + LimitOrderRequest, + StopOrderRequest, + StopLimitOrderRequest, + GetOrdersRequest +) +from alpaca.trading.enums import ( + OrderSide, + TimeInForce, + OrderType, + OrderStatus +) +from alpaca.data.historical import StockHistoricalDataClient +from alpaca.data.requests import StockBarsRequest +from alpaca.data.timeframe import TimeFrame +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +class MultiLegOptionsTrader: + """ + A class to handle multi-leg options trading with stop loss and take profit + """ + + def __init__(self): + """Initialize the trading client and data client""" + # Get API credentials from environment variables + self.api_key = os.getenv('ALPACA_API_KEY') + self.secret_key = os.getenv('ALPACA_SECRET_KEY') + self.base_url = os.getenv('ALPACA_BASE_URL', 'https://paper-api.alpaca.markets') + + if not self.api_key or not self.secret_key: + raise ValueError("Please set ALPACA_API_KEY and ALPACA_SECRET_KEY environment variables") + + # Initialize clients + self.trading_client = TradingClient( + api_key=self.api_key, + secret_key=self.secret_key, + paper=True # Use paper trading + ) + + self.data_client = StockHistoricalDataClient( + api_key=self.api_key, + secret_key=self.secret_key + ) + + logger.info("MultiLegOptionsTrader initialized successfully") + + def get_account_info(self) -> Dict: + """Get current account information""" + account = self.trading_client.get_account() + return { + 'cash': float(account.cash), + 'buying_power': float(account.buying_power), + 'portfolio_value': float(account.portfolio_value), + 'equity': float(account.equity), + 'daytrade_count': account.daytrade_count + } + + def create_bull_call_spread( + self, + symbol: str, + expiration_date: str, + buy_strike: float, + sell_strike: float, + quantity: int, + stop_loss_pct: float = 0.25, + take_profit_pct: float = 0.50 + ) -> Dict: + """ + Create a bull call spread strategy + + Args: + symbol: Stock symbol (e.g., 'AAPL') + expiration_date: Option expiration date (YYYY-MM-DD) + buy_strike: Strike price for the long call + sell_strike: Strike price for the short call + quantity: Number of contracts + stop_loss_pct: Stop loss percentage (0.25 = 25% loss) + take_profit_pct: Take profit percentage (0.50 = 50% profit) + """ + + # Option symbols for the spread + buy_call_symbol = f"{symbol}{expiration_date.replace('-', '')}C{buy_strike:08.0f}" + sell_call_symbol = f"{symbol}{expiration_date.replace('-', '')}C{sell_strike:08.0f}" + + logger.info(f"Creating bull call spread for {symbol}") + logger.info(f"Buy call: {buy_call_symbol} at strike {buy_strike}") + logger.info(f"Sell call: {sell_call_symbol} at strike {sell_strike}") + + # Calculate max loss and max profit + max_loss = (sell_strike - buy_strike) * 100 * quantity + max_profit = (sell_strike - buy_strike) * 100 * quantity + + # Create the spread orders + orders = [] + + try: + # Buy the lower strike call + buy_order = MarketOrderRequest( + symbol=buy_call_symbol, + qty=quantity, + side=OrderSide.BUY, + time_in_force=TimeInForce.DAY + ) + + buy_response = self.trading_client.submit_order(buy_order) + orders.append({ + 'order_id': buy_response.id, + 'symbol': buy_call_symbol, + 'side': 'BUY', + 'quantity': quantity, + 'status': buy_response.status + }) + + logger.info(f"Buy order submitted: {buy_response.id}") + + # Sell the higher strike call + sell_order = MarketOrderRequest( + symbol=sell_call_symbol, + qty=quantity, + side=OrderSide.SELL, + time_in_force=TimeInForce.DAY + ) + + sell_response = self.trading_client.submit_order(sell_order) + orders.append({ + 'order_id': sell_response.id, + 'symbol': sell_call_symbol, + 'side': 'SELL', + 'quantity': quantity, + 'status': sell_response.status + }) + + logger.info(f"Sell order submitted: {sell_response.id}") + + # Wait for orders to fill + self._wait_for_orders_to_fill([buy_response.id, sell_response.id]) + + # Set stop loss and take profit orders + stop_loss_order = self._set_stop_loss_for_spread( + symbol, buy_call_symbol, sell_call_symbol, + quantity, max_loss, stop_loss_pct + ) + + take_profit_order = self._set_take_profit_for_spread( + symbol, buy_call_symbol, sell_call_symbol, + quantity, max_profit, take_profit_pct + ) + + return { + 'strategy': 'bull_call_spread', + 'symbol': symbol, + 'orders': orders, + 'max_loss': max_loss, + 'max_profit': max_profit, + 'stop_loss_order': stop_loss_order, + 'take_profit_order': take_profit_order, + 'status': 'active' + } + + except Exception as e: + logger.error(f"Error creating bull call spread: {e}") + # Cancel any open orders + self._cancel_orders([order['order_id'] for order in orders]) + raise + + def create_iron_condor( + self, + symbol: str, + expiration_date: str, + sell_put_strike: float, + buy_put_strike: float, + sell_call_strike: float, + buy_call_strike: float, + quantity: int, + stop_loss_pct: float = 0.25, + take_profit_pct: float = 0.50 + ) -> Dict: + """ + Create an iron condor strategy + + Args: + symbol: Stock symbol + expiration_date: Option expiration date + sell_put_strike: Strike price for the short put + buy_put_strike: Strike price for the long put + sell_call_strike: Strike price for the short call + buy_call_strike: Strike price for the long call + quantity: Number of contracts + stop_loss_pct: Stop loss percentage + take_profit_pct: Take profit percentage + """ + + # Option symbols + sell_put_symbol = f"{symbol}{expiration_date.replace('-', '')}P{sell_put_strike:08.0f}" + buy_put_symbol = f"{symbol}{expiration_date.replace('-', '')}P{buy_put_strike:08.0f}" + sell_call_symbol = f"{symbol}{expiration_date.replace('-', '')}C{sell_call_strike:08.0f}" + buy_call_symbol = f"{symbol}{expiration_date.replace('-', '')}C{buy_call_strike:08.0f}" + + logger.info(f"Creating iron condor for {symbol}") + + # Calculate max loss and max profit + put_spread_width = sell_put_strike - buy_put_strike + call_spread_width = buy_call_strike - sell_call_strike + max_loss = max(put_spread_width, call_spread_width) * 100 * quantity + max_profit = (sell_call_strike - sell_put_strike) * 100 * quantity + + orders = [] + + try: + # Sell put + sell_put_order = MarketOrderRequest( + symbol=sell_put_symbol, + qty=quantity, + side=OrderSide.SELL, + time_in_force=TimeInForce.DAY + ) + sell_put_response = self.trading_client.submit_order(sell_put_order) + orders.append({ + 'order_id': sell_put_response.id, + 'symbol': sell_put_symbol, + 'side': 'SELL', + 'quantity': quantity, + 'status': sell_put_response.status + }) + + # Buy put + buy_put_order = MarketOrderRequest( + symbol=buy_put_symbol, + qty=quantity, + side=OrderSide.BUY, + time_in_force=TimeInForce.DAY + ) + buy_put_response = self.trading_client.submit_order(buy_put_order) + orders.append({ + 'order_id': buy_put_response.id, + 'symbol': buy_put_symbol, + 'side': 'BUY', + 'quantity': quantity, + 'status': buy_put_response.status + }) + + # Sell call + sell_call_order = MarketOrderRequest( + symbol=sell_call_symbol, + qty=quantity, + side=OrderSide.SELL, + time_in_force=TimeInForce.DAY + ) + sell_call_response = self.trading_client.submit_order(sell_call_order) + orders.append({ + 'order_id': sell_call_response.id, + 'symbol': sell_call_symbol, + 'side': 'SELL', + 'quantity': quantity, + 'status': sell_call_response.status + }) + + # Buy call + buy_call_order = MarketOrderRequest( + symbol=buy_call_symbol, + qty=quantity, + side=OrderSide.BUY, + time_in_force=TimeInForce.DAY + ) + buy_call_response = self.trading_client.submit_order(buy_call_order) + orders.append({ + 'order_id': buy_call_response.id, + 'symbol': buy_call_symbol, + 'side': 'BUY', + 'quantity': quantity, + 'status': buy_call_response.status + }) + + # Wait for orders to fill + order_ids = [sell_put_response.id, buy_put_response.id, + sell_call_response.id, buy_call_response.id] + self._wait_for_orders_to_fill(order_ids) + + # Set stop loss and take profit + stop_loss_order = self._set_stop_loss_for_iron_condor( + symbol, orders, max_loss, stop_loss_pct + ) + + take_profit_order = self._set_take_profit_for_iron_condor( + symbol, orders, max_profit, take_profit_pct + ) + + return { + 'strategy': 'iron_condor', + 'symbol': symbol, + 'orders': orders, + 'max_loss': max_loss, + 'max_profit': max_profit, + 'stop_loss_order': stop_loss_order, + 'take_profit_order': take_profit_order, + 'status': 'active' + } + + except Exception as e: + logger.error(f"Error creating iron condor: {e}") + self._cancel_orders([order['order_id'] for order in orders]) + raise + + def _set_stop_loss_for_spread( + self, + symbol: str, + buy_symbol: str, + sell_symbol: str, + quantity: int, + max_loss: float, + stop_loss_pct: float + ) -> Optional[Dict]: + """Set stop loss order for a spread strategy""" + + stop_loss_amount = max_loss * stop_loss_pct + + # Create a stop order to close the spread if loss exceeds threshold + stop_order = StopOrderRequest( + symbol=buy_symbol, # Close the long position + qty=quantity, + side=OrderSide.SELL, + time_in_force=TimeInForce.GTC, + stop_price=stop_loss_amount / (quantity * 100) # Approximate stop price + ) + + try: + response = self.trading_client.submit_order(stop_order) + logger.info(f"Stop loss order submitted: {response.id}") + return { + 'order_id': response.id, + 'type': 'stop_loss', + 'amount': stop_loss_amount, + 'status': response.status + } + except Exception as e: + logger.error(f"Error setting stop loss: {e}") + return None + + def _set_take_profit_for_spread( + self, + symbol: str, + buy_symbol: str, + sell_symbol: str, + quantity: int, + max_profit: float, + take_profit_pct: float + ) -> Optional[Dict]: + """Set take profit order for a spread strategy""" + + take_profit_amount = max_profit * take_profit_pct + + # Create a limit order to close the spread when profit target is reached + limit_order = LimitOrderRequest( + symbol=buy_symbol, # Close the long position + qty=quantity, + side=OrderSide.SELL, + time_in_force=TimeInForce.GTC, + limit_price=take_profit_amount / (quantity * 100) # Approximate limit price + ) + + try: + response = self.trading_client.submit_order(limit_order) + logger.info(f"Take profit order submitted: {response.id}") + return { + 'order_id': response.id, + 'type': 'take_profit', + 'amount': take_profit_amount, + 'status': response.status + } + except Exception as e: + logger.error(f"Error setting take profit: {e}") + return None + + def _set_stop_loss_for_iron_condor( + self, + symbol: str, + orders: List[Dict], + max_loss: float, + stop_loss_pct: float + ) -> Optional[Dict]: + """Set stop loss for iron condor strategy""" + + stop_loss_amount = max_loss * stop_loss_pct + + # For iron condor, we need to close all positions if stop loss is hit + # This is a simplified approach - in practice, you might want more sophisticated logic + + try: + # Create a stop order for the short put (most vulnerable leg) + short_put_symbol = orders[0]['symbol'] # First order is sell put + stop_order = StopOrderRequest( + symbol=short_put_symbol, + qty=orders[0]['quantity'], + side=OrderSide.BUY, # Buy to close the short put + time_in_force=TimeInForce.GTC, + stop_price=stop_loss_amount / (orders[0]['quantity'] * 100) + ) + + response = self.trading_client.submit_order(stop_order) + logger.info(f"Iron condor stop loss order submitted: {response.id}") + return { + 'order_id': response.id, + 'type': 'stop_loss', + 'amount': stop_loss_amount, + 'status': response.status + } + except Exception as e: + logger.error(f"Error setting iron condor stop loss: {e}") + return None + + def _set_take_profit_for_iron_condor( + self, + symbol: str, + orders: List[Dict], + max_profit: float, + take_profit_pct: float + ) -> Optional[Dict]: + """Set take profit for iron condor strategy""" + + take_profit_amount = max_profit * take_profit_pct + + try: + # Close the short put when profit target is reached + short_put_symbol = orders[0]['symbol'] + limit_order = LimitOrderRequest( + symbol=short_put_symbol, + qty=orders[0]['quantity'], + side=OrderSide.BUY, # Buy to close + time_in_force=TimeInForce.GTC, + limit_price=take_profit_amount / (orders[0]['quantity'] * 100) + ) + + response = self.trading_client.submit_order(limit_order) + logger.info(f"Iron condor take profit order submitted: {response.id}") + return { + 'order_id': response.id, + 'type': 'take_profit', + 'amount': take_profit_amount, + 'status': response.status + } + except Exception as e: + logger.error(f"Error setting iron condor take profit: {e}") + return None + + def _wait_for_orders_to_fill(self, order_ids: List[str], timeout: int = 300): + """Wait for orders to fill with timeout""" + start_time = time.time() + + while time.time() - start_time < timeout: + all_filled = True + + for order_id in order_ids: + order = self.trading_client.get_order_by_id(order_id) + if order.status not in [OrderStatus.FILLED, OrderStatus.CANCELED]: + all_filled = False + break + + if all_filled: + logger.info("All orders have been filled") + return + + time.sleep(2) + + logger.warning("Timeout waiting for orders to fill") + + def _cancel_orders(self, order_ids: List[str]): + """Cancel multiple orders""" + for order_id in order_ids: + try: + self.trading_client.cancel_order_by_id(order_id) + logger.info(f"Canceled order: {order_id}") + except Exception as e: + logger.error(f"Error canceling order {order_id}: {e}") + + def get_positions(self) -> List[Dict]: + """Get current positions""" + positions = self.trading_client.get_all_positions() + return [ + { + 'symbol': pos.symbol, + 'qty': float(pos.qty), + 'side': pos.side, + 'market_value': float(pos.market_value), + 'unrealized_pl': float(pos.unrealized_pl), + 'current_price': float(pos.current_price) + } + for pos in positions + ] + + def close_position(self, symbol: str, qty: float = None): + """Close a specific position""" + try: + if qty is None: + # Close entire position + self.trading_client.close_position(symbol) + else: + # Close partial position + order = MarketOrderRequest( + symbol=symbol, + qty=abs(qty), + side=OrderSide.SELL if qty > 0 else OrderSide.BUY, + time_in_force=TimeInForce.DAY + ) + self.trading_client.submit_order(order) + + logger.info(f"Closed position for {symbol}") + except Exception as e: + logger.error(f"Error closing position for {symbol}: {e}") + + def get_order_history(self, symbol: str = None) -> List[Dict]: + """Get order history""" + request = GetOrdersRequest() + if symbol: + request.symbols = [symbol] + + orders = self.trading_client.get_orders(request) + return [ + { + 'id': order.id, + 'symbol': order.symbol, + 'qty': float(order.qty), + 'side': order.side, + 'type': order.type, + 'status': order.status, + 'filled_at': order.filled_at, + 'filled_avg_price': float(order.filled_avg_price) if order.filled_avg_price else None + } + for order in orders + ] + + +def main(): + """Example usage of the MultiLegOptionsTrader""" + + # Initialize the trader + trader = MultiLegOptionsTrader() + + # Get account information + account_info = trader.get_account_info() + logger.info(f"Account Info: {account_info}") + + # Example 1: Create a bull call spread + try: + bull_spread = trader.create_bull_call_spread( + symbol='AAPL', + expiration_date='2024-01-19', # Adjust to available expiration + buy_strike=180.0, + sell_strike=185.0, + quantity=1, + stop_loss_pct=0.25, + take_profit_pct=0.50 + ) + logger.info(f"Bull call spread created: {json.dumps(bull_spread, indent=2)}") + except Exception as e: + logger.error(f"Failed to create bull call spread: {e}") + + # Example 2: Create an iron condor + try: + iron_condor = trader.create_iron_condor( + symbol='SPY', + expiration_date='2024-01-19', # Adjust to available expiration + sell_put_strike=450.0, + buy_put_strike=445.0, + sell_call_strike=460.0, + buy_call_strike=465.0, + quantity=1, + stop_loss_pct=0.30, + take_profit_pct=0.60 + ) + logger.info(f"Iron condor created: {json.dumps(iron_condor, indent=2)}") + except Exception as e: + logger.error(f"Failed to create iron condor: {e}") + + # Get current positions + positions = trader.get_positions() + logger.info(f"Current positions: {json.dumps(positions, indent=2)}") + + # Get order history + order_history = trader.get_order_history() + logger.info(f"Order history: {json.dumps(order_history, indent=2)}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/alpaca_trading_example/requirements.txt b/alpaca_trading_example/requirements.txt new file mode 100644 index 0000000000000..868389a715869 --- /dev/null +++ b/alpaca_trading_example/requirements.txt @@ -0,0 +1,6 @@ +alpaca-py==0.13.1 +pandas==2.0.3 +numpy==1.24.3 +python-dotenv==1.0.0 +requests==2.31.0 +websocket-client==1.6.1 \ No newline at end of file diff --git a/alpaca_trading_example/setup.py b/alpaca_trading_example/setup.py new file mode 100644 index 0000000000000..230427b0d8167 --- /dev/null +++ b/alpaca_trading_example/setup.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +""" +Setup script for Alpaca Trading API examples +""" + +import os +import sys +from pathlib import Path + +def check_python_version(): + """Check if Python version is compatible""" + if sys.version_info < (3, 8): + print("❌ Python 3.8 or higher is required") + print(f"Current version: {sys.version}") + return False + print("✅ Python version is compatible") + return True + +def check_dependencies(): + """Check if required packages are installed""" + required_packages = [ + 'alpaca-py', + 'pandas', + 'numpy', + 'python-dotenv', + 'requests', + 'websocket-client' + ] + + missing_packages = [] + + for package in required_packages: + try: + __import__(package.replace('-', '_')) + print(f"✅ {package} is installed") + except ImportError: + missing_packages.append(package) + print(f"❌ {package} is missing") + + if missing_packages: + print(f"\nTo install missing packages, run:") + print(f"pip install {' '.join(missing_packages)}") + return False + + return True + +def setup_environment(): + """Set up environment variables""" + env_file = Path('.env') + env_example = Path('.env.example') + + if not env_example.exists(): + print("❌ .env.example file not found") + return False + + if env_file.exists(): + print("✅ .env file already exists") + return True + + # Copy .env.example to .env + try: + with open(env_example, 'r') as f: + content = f.read() + + with open(env_file, 'w') as f: + f.write(content) + + print("✅ Created .env file from .env.example") + print("⚠️ Please edit .env file with your Alpaca API credentials") + return True + except Exception as e: + print(f"❌ Error creating .env file: {e}") + return False + +def check_api_credentials(): + """Check if API credentials are set""" + from dotenv import load_dotenv + load_dotenv() + + api_key = os.getenv('ALPACA_API_KEY') + secret_key = os.getenv('ALPACA_SECRET_KEY') + + if not api_key or api_key == 'your_paper_api_key_here': + print("❌ ALPACA_API_KEY not set in .env file") + return False + + if not secret_key or secret_key == 'your_paper_secret_key_here': + print("❌ ALPACA_SECRET_KEY not set in .env file") + return False + + print("✅ API credentials are configured") + return True + +def test_connection(): + """Test connection to Alpaca API""" + try: + from alpaca.trading.client import TradingClient + from dotenv import load_dotenv + + load_dotenv() + + api_key = os.getenv('ALPACA_API_KEY') + secret_key = os.getenv('ALPACA_SECRET_KEY') + + client = TradingClient( + api_key=api_key, + secret_key=secret_key, + paper=True + ) + + account = client.get_account() + print(f"✅ Successfully connected to Alpaca API") + print(f" Account ID: {account.id}") + print(f" Status: {account.status}") + print(f" Cash: ${float(account.cash):,.2f}") + return True + except Exception as e: + print(f"❌ Failed to connect to Alpaca API: {e}") + return False + +def main(): + """Main setup function""" + print("🚀 Setting up Alpaca Trading API Examples") + print("=" * 50) + + # Check Python version + if not check_python_version(): + return + + print() + + # Check dependencies + if not check_dependencies(): + print("\nPlease install missing dependencies and run setup again.") + return + + print() + + # Setup environment + if not setup_environment(): + return + + print() + + # Check API credentials + if not check_api_credentials(): + print("\nPlease configure your API credentials in the .env file and run setup again.") + return + + print() + + # Test connection + if not test_connection(): + print("\nPlease check your API credentials and internet connection.") + return + + print() + print("🎉 Setup completed successfully!") + print("\nNext steps:") + print("1. Review the examples in simple_options_trading.py") + print("2. Review the advanced examples in multi_leg_options_trading.py") + print("3. Start with paper trading to test your strategies") + print("4. Always use proper risk management") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/alpaca_trading_example/simple_options_trading.py b/alpaca_trading_example/simple_options_trading.py new file mode 100644 index 0000000000000..935c72d50c921 --- /dev/null +++ b/alpaca_trading_example/simple_options_trading.py @@ -0,0 +1,390 @@ +""" +Simple Options Trading with Alpaca API + +This example shows basic options trading with stop loss and take profit orders. +It's simpler than the multi-leg strategies and easier to understand. +""" + +import os +import time +import logging +from datetime import datetime, timedelta +from typing import Dict, Optional +import json + +from alpaca.trading.client import TradingClient +from alpaca.trading.requests import ( + MarketOrderRequest, + LimitOrderRequest, + StopOrderRequest, + GetOrdersRequest +) +from alpaca.trading.enums import ( + OrderSide, + TimeInForce, + OrderStatus +) +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +class SimpleOptionsTrader: + """ + Simple options trading with stop loss and take profit + """ + + def __init__(self): + """Initialize the trading client""" + self.api_key = os.getenv('ALPACA_API_KEY') + self.secret_key = os.getenv('ALPACA_SECRET_KEY') + self.base_url = os.getenv('ALPACA_BASE_URL', 'https://paper-api.alpaca.markets') + + if not self.api_key or not self.secret_key: + raise ValueError("Please set ALPACA_API_KEY and ALPACA_SECRET_KEY environment variables") + + self.trading_client = TradingClient( + api_key=self.api_key, + secret_key=self.secret_key, + paper=True + ) + + logger.info("SimpleOptionsTrader initialized successfully") + + def get_account_info(self) -> Dict: + """Get current account information""" + account = self.trading_client.get_account() + return { + 'cash': float(account.cash), + 'buying_power': float(account.buying_power), + 'portfolio_value': float(account.portfolio_value), + 'equity': float(account.equity) + } + + def buy_call_option( + self, + symbol: str, + expiration_date: str, + strike_price: float, + quantity: int, + stop_loss_pct: float = 0.25, + take_profit_pct: float = 0.50 + ) -> Dict: + """ + Buy a call option with stop loss and take profit + + Args: + symbol: Stock symbol (e.g., 'AAPL') + expiration_date: Option expiration date (YYYY-MM-DD) + strike_price: Strike price for the call option + quantity: Number of contracts + stop_loss_pct: Stop loss percentage (0.25 = 25% loss) + take_profit_pct: Take profit percentage (0.50 = 50% profit) + """ + + # Create option symbol + option_symbol = f"{symbol}{expiration_date.replace('-', '')}C{strike_price:08.0f}" + + logger.info(f"Buying call option: {option_symbol}") + logger.info(f"Strike: ${strike_price}, Quantity: {quantity}") + + try: + # Buy the call option + buy_order = MarketOrderRequest( + symbol=option_symbol, + qty=quantity, + side=OrderSide.BUY, + time_in_force=TimeInForce.DAY + ) + + response = self.trading_client.submit_order(buy_order) + logger.info(f"Buy order submitted: {response.id}") + + # Wait for order to fill + self._wait_for_order_to_fill(response.id) + + # Get the filled order to get the average price + filled_order = self.trading_client.get_order_by_id(response.id) + avg_price = float(filled_order.filled_avg_price) if filled_order.filled_avg_price else 0 + + # Calculate stop loss and take profit prices + stop_loss_price = avg_price * (1 - stop_loss_pct) + take_profit_price = avg_price * (1 + take_profit_pct) + + # Set stop loss order + stop_loss_order = self._set_stop_loss( + option_symbol, quantity, stop_loss_price + ) + + # Set take profit order + take_profit_order = self._set_take_profit( + option_symbol, quantity, take_profit_price + ) + + return { + 'strategy': 'buy_call', + 'symbol': option_symbol, + 'quantity': quantity, + 'strike_price': strike_price, + 'avg_price': avg_price, + 'stop_loss_price': stop_loss_price, + 'take_profit_price': take_profit_price, + 'buy_order_id': response.id, + 'stop_loss_order': stop_loss_order, + 'take_profit_order': take_profit_order, + 'status': 'active' + } + + except Exception as e: + logger.error(f"Error buying call option: {e}") + raise + + def sell_put_option( + self, + symbol: str, + expiration_date: str, + strike_price: float, + quantity: int, + stop_loss_pct: float = 0.25, + take_profit_pct: float = 0.50 + ) -> Dict: + """ + Sell a put option with stop loss and take profit + + Args: + symbol: Stock symbol (e.g., 'AAPL') + expiration_date: Option expiration date (YYYY-MM-DD) + strike_price: Strike price for the put option + quantity: Number of contracts + stop_loss_pct: Stop loss percentage (0.25 = 25% loss) + take_profit_pct: Take profit percentage (0.50 = 50% profit) + """ + + # Create option symbol + option_symbol = f"{symbol}{expiration_date.replace('-', '')}P{strike_price:08.0f}" + + logger.info(f"Selling put option: {option_symbol}") + logger.info(f"Strike: ${strike_price}, Quantity: {quantity}") + + try: + # Sell the put option + sell_order = MarketOrderRequest( + symbol=option_symbol, + qty=quantity, + side=OrderSide.SELL, + time_in_force=TimeInForce.DAY + ) + + response = self.trading_client.submit_order(sell_order) + logger.info(f"Sell order submitted: {response.id}") + + # Wait for order to fill + self._wait_for_order_to_fill(response.id) + + # Get the filled order to get the average price + filled_order = self.trading_client.get_order_by_id(response.id) + avg_price = float(filled_order.filled_avg_price) if filled_order.filled_avg_price else 0 + + # Calculate stop loss and take profit prices + stop_loss_price = avg_price * (1 + stop_loss_pct) # Higher price for short position + take_profit_price = avg_price * (1 - take_profit_pct) # Lower price for short position + + # Set stop loss order (buy to close) + stop_loss_order = self._set_stop_loss( + option_symbol, quantity, stop_loss_price, side=OrderSide.BUY + ) + + # Set take profit order (buy to close) + take_profit_order = self._set_take_profit( + option_symbol, quantity, take_profit_price, side=OrderSide.BUY + ) + + return { + 'strategy': 'sell_put', + 'symbol': option_symbol, + 'quantity': quantity, + 'strike_price': strike_price, + 'avg_price': avg_price, + 'stop_loss_price': stop_loss_price, + 'take_profit_price': take_profit_price, + 'sell_order_id': response.id, + 'stop_loss_order': stop_loss_order, + 'take_profit_order': take_profit_order, + 'status': 'active' + } + + except Exception as e: + logger.error(f"Error selling put option: {e}") + raise + + def _set_stop_loss( + self, + symbol: str, + quantity: int, + stop_price: float, + side: OrderSide = OrderSide.SELL + ) -> Optional[Dict]: + """Set stop loss order""" + + try: + stop_order = StopOrderRequest( + symbol=symbol, + qty=quantity, + side=side, + time_in_force=TimeInForce.GTC, + stop_price=stop_price + ) + + response = self.trading_client.submit_order(stop_order) + logger.info(f"Stop loss order submitted: {response.id} at ${stop_price}") + return { + 'order_id': response.id, + 'type': 'stop_loss', + 'price': stop_price, + 'status': response.status + } + except Exception as e: + logger.error(f"Error setting stop loss: {e}") + return None + + def _set_take_profit( + self, + symbol: str, + quantity: int, + limit_price: float, + side: OrderSide = OrderSide.SELL + ) -> Optional[Dict]: + """Set take profit order""" + + try: + limit_order = LimitOrderRequest( + symbol=symbol, + qty=quantity, + side=side, + time_in_force=TimeInForce.GTC, + limit_price=limit_price + ) + + response = self.trading_client.submit_order(limit_order) + logger.info(f"Take profit order submitted: {response.id} at ${limit_price}") + return { + 'order_id': response.id, + 'type': 'take_profit', + 'price': limit_price, + 'status': response.status + } + except Exception as e: + logger.error(f"Error setting take profit: {e}") + return None + + def _wait_for_order_to_fill(self, order_id: str, timeout: int = 300): + """Wait for an order to fill with timeout""" + start_time = time.time() + + while time.time() - start_time < timeout: + order = self.trading_client.get_order_by_id(order_id) + if order.status in [OrderStatus.FILLED, OrderStatus.CANCELED]: + logger.info(f"Order {order_id} status: {order.status}") + return + time.sleep(2) + + logger.warning(f"Timeout waiting for order {order_id} to fill") + + def get_positions(self) -> list: + """Get current positions""" + positions = self.trading_client.get_all_positions() + return [ + { + 'symbol': pos.symbol, + 'qty': float(pos.qty), + 'side': pos.side, + 'market_value': float(pos.market_value), + 'unrealized_pl': float(pos.unrealized_pl), + 'current_price': float(pos.current_price) + } + for pos in positions + ] + + def close_position(self, symbol: str): + """Close a specific position""" + try: + self.trading_client.close_position(symbol) + logger.info(f"Closed position for {symbol}") + except Exception as e: + logger.error(f"Error closing position for {symbol}: {e}") + + def get_order_history(self, symbol: str = None) -> list: + """Get order history""" + request = GetOrdersRequest() + if symbol: + request.symbols = [symbol] + + orders = self.trading_client.get_orders(request) + return [ + { + 'id': order.id, + 'symbol': order.symbol, + 'qty': float(order.qty), + 'side': order.side, + 'type': order.type, + 'status': order.status, + 'filled_at': order.filled_at, + 'filled_avg_price': float(order.filled_avg_price) if order.filled_avg_price else None + } + for order in orders + ] + + +def main(): + """Example usage of the SimpleOptionsTrader""" + + # Initialize the trader + trader = SimpleOptionsTrader() + + # Get account information + account_info = trader.get_account_info() + logger.info(f"Account Info: {account_info}") + + # Example 1: Buy a call option + try: + call_trade = trader.buy_call_option( + symbol='AAPL', + expiration_date='2024-01-19', # Adjust to available expiration + strike_price=180.0, + quantity=1, + stop_loss_pct=0.25, + take_profit_pct=0.50 + ) + logger.info(f"Call option trade: {json.dumps(call_trade, indent=2)}") + except Exception as e: + logger.error(f"Failed to buy call option: {e}") + + # Example 2: Sell a put option + try: + put_trade = trader.sell_put_option( + symbol='SPY', + expiration_date='2024-01-19', # Adjust to available expiration + strike_price=450.0, + quantity=1, + stop_loss_pct=0.30, + take_profit_pct=0.60 + ) + logger.info(f"Put option trade: {json.dumps(put_trade, indent=2)}") + except Exception as e: + logger.error(f"Failed to sell put option: {e}") + + # Get current positions + positions = trader.get_positions() + logger.info(f"Current positions: {json.dumps(positions, indent=2)}") + + # Get order history + order_history = trader.get_order_history() + logger.info(f"Order history: {json.dumps(order_history, indent=2)}") + + +if __name__ == "__main__": + main() \ No newline at end of file