Last active
August 2, 2026 22:49
-
-
Save jweinst1/de2345f1cee30e45e1a6c68147f6298f to your computer and use it in GitHub Desktop.
stock trading OTO cli for alpaca
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from alpaca.data.historical import StockHistoricalDataClient | |
| from alpaca.data.requests import StockSnapshotRequest | |
| from alpaca.data.enums import DataFeed | |
| from alpaca.data.historical import OptionHistoricalDataClient | |
| from alpaca.data.requests import OptionChainRequest, StockBarsRequest, OptionBarsRequest, OptionSnapshotRequest | |
| from alpaca.data.enums import OptionsFeed | |
| from alpaca.trading.enums import ContractType, AssetClass | |
| from alpaca.trading.enums import QueryOrderStatus, OrderSide, OrderClass, TimeInForce, OrderStatus, OrderType | |
| from alpaca.trading.client import TradingClient | |
| from alpaca.trading.requests import GetOrdersRequest, LimitOrderRequest, TakeProfitRequest, StopLimitOrderRequest, GetOptionContractsRequest | |
| from alpaca.data.timeframe import TimeFrame | |
| import argparse | |
| import os | |
| import statistics | |
| from datetime import datetime, timezone, timedelta | |
| import time | |
| import re | |
| import pandas as pd | |
| import numpy as np | |
| API_KEY = "******************" | |
| SECRET_KEY = "****************************" | |
| PAPER = False | |
| def is_occ_symbol(symbol): | |
| # Ticker (1-6 letters) + YYMMDD + C/P + 8-digit Strike | |
| pattern = r"^[A-Z]{1,6}\d{6}[CP]\d{8}$" | |
| return bool(re.fullmatch(pattern, symbol)) | |
| def parse_arguments(): | |
| parser = argparse.ArgumentParser(description="Alpaca Stock CLI") | |
| subparsers = parser.add_subparsers( | |
| dest='command', | |
| required=True, # Makes a subcommand mandatory | |
| title='Commands', | |
| metavar='COMMAND' | |
| ) | |
| price = subparsers.add_parser('price', help='Get Option Quotes', description='Get quotes and symbols') | |
| price.add_argument('--tick', type=str, help='Underlying stock ticker', required=True) | |
| price.add_argument('--feed', type=str, help='Pick the right feed', default='sip') | |
| bars = subparsers.add_parser('bars', help='Get Stock Bars', description='Get Bars and Movement') | |
| bars.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| bars.add_argument('--beg-date', type=str, help='ISO date of begin bars', required=True) | |
| bars.add_argument('--end-date', type=str, help='ISO date of end bars', required=True) | |
| oto = subparsers.add_parser('oto', help='Issue OTO orders', description='Issue OTO') | |
| oto.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| oto.add_argument('--price', type=float, help='Buy Price', required=True) | |
| oto.add_argument('--qty', type=int, help='Qty to trade', default=1) | |
| oto.add_argument('--prof', type=float, help='Profit Amount', default=0.10) | |
| oto.add_argument('--end-price', type=float, help='End range of prices', default=None) | |
| oto.add_argument('--span', type=float, help='Space between range order', default=0.10) | |
| oto.add_argument('--dry', action='store_true', help='Dry run for orders', default=False) | |
| oto.add_argument('--stop', type=float, help='Stop Modifier', default=None) | |
| sellcm = subparsers.add_parser('sell', help='Issue sell orders', description='Issue sell') | |
| sellcm.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| sellcm.add_argument('--price', type=float, help='Sell Price', required=True) | |
| sellcm.add_argument('--qty', type=int, help='Qty to trade', default=1) | |
| sellcm.add_argument('--dry', action='store_true', help='Dry run for orders', default=False) | |
| sellcm.add_argument('--force', type=str, help='time in force', default='gtc') | |
| buycm = subparsers.add_parser('buy', help='Issue buy orders', description='Issue buy') | |
| buycm.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| buycm.add_argument('--price', type=float, help='Buy Price', required=True) | |
| buycm.add_argument('--qty', type=int, help='Qty to trade', default=1) | |
| buycm.add_argument('--dry', action='store_true', help='Dry run for orders', default=False) | |
| buycm.add_argument('--time', type=str, help='Time in Force', default='gtc') | |
| buycmstop = subparsers.add_parser('buystop', help='Issue buy orders', description='Issue buy') | |
| buycmstop.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| buycmstop.add_argument('--price', type=float, help='Buy Price', required=True) | |
| buycmstop.add_argument('--stop', type=float, help='Stop Price', required=True) | |
| buycmstop.add_argument('--qty', type=int, help='Qty to trade', default=1) | |
| buycmstop.add_argument('--dry', action='store_true', help='Dry run for orders', default=False) | |
| buycmstop.add_argument('--time', type=str, help='Time in Force', default='gtc') | |
| power = subparsers.add_parser('power', help='Check buying power', description='Check buying power') | |
| orders = subparsers.add_parser('orders', help='Check current orders', description='Check buying power') | |
| orders.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| orders.add_argument('--after', type=str, help='ISO date of begin orders', required=True) | |
| orders.add_argument('--state', type=str, help='state of order', default='open') | |
| cancel = subparsers.add_parser('cancel', help='cancel current orders', description='cancel orders') | |
| cancel.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| cancel.add_argument('--gte', type=float, help='gte price', required=True) | |
| cancel.add_argument('--lte', type=float, help='gte price', required=True) | |
| cancel.add_argument('--after', type=str, help='ISO date of begin orders', required=True) | |
| cancel.add_argument('--sells', action='store_true', help='include sell orders', default=False) | |
| calls = subparsers.add_parser('calls', help='list potential calls to sell', description='list calls') | |
| calls.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| calls.add_argument('--feed', type=str, help='Pick the right feed', default='sip') | |
| calls.add_argument('--dte', type=int, help='day range', default=8) | |
| calls.add_argument('--high', type=float, help='percent of cur price call', default=1.1) | |
| puts = subparsers.add_parser('puts', help='list potential puts to sell', description='list puts') | |
| puts.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| puts.add_argument('--feed', type=str, help='Pick the right feed', default='sip') | |
| puts.add_argument('--dte', type=int, help='day range', default=8) | |
| puts.add_argument('--dte-min', type=int, help='day begin', default=1) | |
| puts.add_argument('--low', type=float, help='percent of cur price put', default=0.9) | |
| dips = subparsers.add_parser('dips', help='list call opt dips', description='list dips') | |
| dips.add_argument('--dte-min', type=int, help='day begin', default=4) | |
| dips.add_argument('--dte', type=int, help='day range', default=8) | |
| dips.add_argument('--tick', type=str, help='Stock Symbol', required=True) | |
| dips.add_argument('--perc', type=float, help='perc below to check', default=-0.01) | |
| dips.add_argument('--feed', type=str, help='Pick the right feed', default='sip') | |
| dips.add_argument('--min', type=float, help='minimum premium', default=1.50) | |
| backtest = subparsers.add_parser('btleap', help='Backtest LEAP options performance', description='Backtest 200+ DTE Call LEAPs') | |
| backtest.add_argument('--tick', type=str, help='Underlying ticker (e.g., KO, WMT)', required=True) | |
| backtest.add_argument('--date', type=str, help='Historical entry date (YYYY-MM-DD)', required=True) | |
| backtest.add_argument('--dte-min', type=int, help='Minimum DTE at entry', default=200) | |
| backtest.add_argument('--dte-max', type=int, help='Maximum DTE at entry', default=365) | |
| backtest.add_argument('--hold-days', type=int, help='Holding period in days', default=90) | |
| backtest.add_argument('--feed', type=str, help='Data feed', default='sip') | |
| backtest.add_argument( | |
| "--strike-offset", | |
| type=str, | |
| default="0", | |
| help="Offset for strike price selection relative to stock price. E.g. '-5' ($5 ITM call), '+5' ($5 OTM call), '-10%%' (10%% ITM call)." | |
| ) | |
| # Add subcommand for SMA deviation analysis | |
| smadev_parser = subparsers.add_parser("smadev", help="Analyze max/min stock price deviation from an SMA") | |
| smadev_parser.add_argument("--tick", type=str, required=True, help="Stock ticker symbol (e.g. KO, AAPL)") | |
| smadev_parser.add_argument("--start", type=str, required=True, help="Start date (YYYY-MM-DD)") | |
| smadev_parser.add_argument("--end", type=str, required=True, help="End date (YYYY-MM-DD)") | |
| smadev_parser.add_argument("--sma", type=int, default=50, help="SMA period window (default: 50)") | |
| smadev_parser.set_defaults(func=handle_smadev) | |
| # Subcommand for option yield ranking across tickers | |
| optrank_parser = subparsers.add_parser("optrank", help="Rank covered call / put options across multiple tickers by efficiency") | |
| optrank_parser.add_argument("--ticks", nargs="+", required=True, help="List of stock tickers (e.g. --ticks KO WMT PG)") | |
| optrank_parser.add_argument("--exp-date", type=str, required=True, help="Target expiration date (YYYY-MM-DD)") | |
| optrank_parser.add_argument("--type", type=str, default="CALL", choices=["CALL", "PUT"], help="Option type: CALL or PUT (default: CALL)") | |
| optrank_parser.add_argument("--strikes", type=int, default=3, help="Number of strikes around current price to test per ticker (default: 3)") | |
| optrank_parser.add_argument("--sort-by", type=str, default="prem_dte", choices=["prem_dte", "prem_theta", "daily_pct"], help="Metric to rank options by (default: prem_dte)") | |
| optrank_parser.set_defaults(func=handle_optrank) | |
| rsic = subparsers.add_parser('rsi', help='list out rsis', description='rsi') | |
| rsic.add_argument('--tick', type=str, nargs='+', help='Stock Symbols', required=True) | |
| rsic.add_argument('--days', type=int, help='rsi days', default=14) | |
| bbopt = subparsers.add_parser('bbopt', help='Buy back short option positions', description='Buy back sold options') | |
| bbopt.add_argument('--dry', action='store_true', help='Dry run for orders', default=False) | |
| bbopt.add_argument('--min-perc', type=float, help='Minimum return fraction required to buy back (e.g., 0.7 for 70%)', default=0.5) | |
| owned = subparsers.add_parser('owned', help='current positions', description='current positions') | |
| args = parser.parse_args() | |
| # check command via arg.command | |
| return args | |
| def retrieve_orders(client, symbols, after, state): | |
| current_until = datetime.now(timezone.utc).isoformat() | |
| stat_to_use = QueryOrderStatus(state) | |
| all_orders = [] | |
| while True: | |
| orders_request = GetOrdersRequest( | |
| status=stat_to_use, | |
| nested=True, | |
| limit=500, | |
| symbols=symbols, | |
| until=current_until, | |
| direction="desc", | |
| ) | |
| chunk = client.get_orders(filter=orders_request) | |
| if not chunk: | |
| break | |
| all_orders.extend(chunk) | |
| current_until = chunk[-1].submitted_at.isoformat() | |
| return all_orders | |
| def handle_price(argobj): | |
| data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| request_params = StockSnapshotRequest( | |
| symbol_or_symbols=argobj.tick, | |
| feed=DataFeed(argobj.feed) | |
| ) | |
| snapshot = data_client.get_stock_snapshot(request_params) | |
| stock_data = snapshot[argobj.tick] | |
| latest_close_price = stock_data.minute_bar.close | |
| latest_ask_price = stock_data.latest_quote.ask_price | |
| latest_bid_price = stock_data.latest_quote.bid_price | |
| latest_day_close = stock_data.daily_bar.close | |
| prev_day_close = stock_data.previous_daily_bar.close | |
| print(f"------{argobj.tick}-----------") | |
| print(f"Prev Day Close: ${prev_day_close:.2f}") | |
| print(f"Latest Day Close: ${latest_day_close:.2f}") | |
| print(f"Latest Minute Close: ${latest_close_price:.2f}") | |
| print(f"Current Ask Price: ${latest_ask_price:.2f}") | |
| print(f"Current Bid Price: ${latest_bid_price:.2f}") | |
| print(f"------{argobj.tick}-----------") | |
| def issue_oto_order(client, tick, buy, sell, qty, force, dry, stop = None): | |
| buy_price = round(buy, 2) | |
| sell_price = round(sell, 2) | |
| if stop is None: | |
| limit_order_data = LimitOrderRequest( | |
| symbol=tick, | |
| qty=qty, | |
| side=OrderSide.BUY, | |
| type=OrderType.LIMIT, | |
| time_in_force=force, | |
| limit_price=buy_price, | |
| order_class=OrderClass.OTO, | |
| take_profit=TakeProfitRequest(limit_price=sell_price) | |
| ) | |
| print(f"[{tick}] OTO buy={buy_price} sell={sell_price} qty={qty}") | |
| else: | |
| stop_price = round(stop, 2) | |
| limit_order_data = StopLimitOrderRequest( | |
| symbol=tick, | |
| qty=qty, | |
| side=OrderSide.BUY, | |
| type=OrderType.STOP_LIMIT, | |
| time_in_force=force, | |
| limit_price=buy_price, | |
| order_class=OrderClass.OTO, | |
| stop_price=stop_price, | |
| take_profit=TakeProfitRequest(limit_price=sell_price) | |
| ) | |
| print(f"[{tick}] OTO buy={buy_price} sell={sell_price} stop={stop_price} qty={qty}") | |
| if dry: | |
| return | |
| time.sleep(0.1) | |
| try: | |
| submitted_order = client.submit_order(order_data=limit_order_data) | |
| print(f"Order successfully submitted! ID: {submitted_order.id}") | |
| print(f"Status: {submitted_order.status}") | |
| return True | |
| except Exception as exc: | |
| print(f"Cannot place order due to {exc}") | |
| return False | |
| def handle_oto(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| stop_price_1 = (argobj.price + argobj.stop) if argobj.stop is not None else None | |
| issue_oto_order(trade_client, argobj.tick, argobj.price, argobj.price + argobj.prof, argobj.qty, TimeInForce.DAY, argobj.dry, stop_price_1) | |
| endprice = argobj.end_price if argobj.end_price is not None else argobj.price | |
| cur_price = argobj.price + argobj.span | |
| while cur_price < endprice: | |
| issue_oto_order(trade_client, argobj.tick, cur_price, cur_price + argobj.prof, argobj.qty, TimeInForce.DAY, argobj.dry, (cur_price + argobj.stop) if argobj.stop is not None else None) | |
| cur_price += argobj.span | |
| def handle_sell(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| price_to_use = round(argobj.price, 2) | |
| is_extended_hrs = not is_occ_symbol(argobj.tick) | |
| limit_order_data = LimitOrderRequest( | |
| symbol=argobj.tick, | |
| qty=argobj.qty, | |
| side=OrderSide.SELL, | |
| type=OrderType.LIMIT, | |
| extended_hours=is_extended_hrs, | |
| time_in_force=TimeInForce(argobj.force), | |
| limit_price=price_to_use, | |
| order_class=OrderClass.SIMPLE | |
| ) | |
| print(f"[{argobj.tick}] SELL price={price_to_use} qty={argobj.qty}") | |
| if argobj.dry: | |
| return | |
| try: | |
| submitted_order = trade_client.submit_order(order_data=limit_order_data) | |
| print(f"Order successfully submitted! ID: {submitted_order.id}") | |
| print(f"Status: {submitted_order.status}") | |
| return True | |
| except Exception as exc: | |
| print(f"Cannot place order due to {exc}") | |
| return False | |
| def handle_buy_stop(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| price_to_use = round(argobj.price, 2) | |
| stop_to_use = round(argobj.stop, 2) | |
| is_extended_hrs = not is_occ_symbol(argobj.tick) | |
| limit_order_data = StopLimitOrderRequest( | |
| symbol=argobj.tick, | |
| qty=argobj.qty, | |
| side=OrderSide.BUY, | |
| type=OrderType.LIMIT, | |
| extended_hours=is_extended_hrs, | |
| time_in_force=TimeInForce(argobj.time), | |
| limit_price=price_to_use, | |
| stop_price=stop_to_use, | |
| order_class=OrderClass.SIMPLE | |
| ) | |
| print(f"[{argobj.tick}] BUY stop={stop_to_use} price={price_to_use} qty={argobj.qty}") | |
| if argobj.dry: | |
| return | |
| try: | |
| submitted_order = trade_client.submit_order(order_data=limit_order_data) | |
| print(f"Order successfully submitted! ID: {submitted_order.id}") | |
| print(f"Status: {submitted_order.status}") | |
| return True | |
| except Exception as exc: | |
| print(f"Cannot place order due to {exc}") | |
| return False | |
| def handle_buy(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| price_to_use = round(argobj.price, 2) | |
| is_extended_hrs = not is_occ_symbol(argobj.tick) | |
| limit_order_data = LimitOrderRequest( | |
| symbol=argobj.tick, | |
| qty=argobj.qty, | |
| side=OrderSide.BUY, | |
| type=OrderType.LIMIT, | |
| extended_hours=is_extended_hrs, | |
| time_in_force=TimeInForce(argobj.time), | |
| limit_price=price_to_use, | |
| order_class=OrderClass.SIMPLE | |
| ) | |
| print(f"[{argobj.tick}] BUY price={price_to_use} qty={argobj.qty}") | |
| if argobj.dry: | |
| return | |
| try: | |
| submitted_order = trade_client.submit_order(order_data=limit_order_data) | |
| print(f"Order successfully submitted! ID: {submitted_order.id}") | |
| print(f"Status: {submitted_order.status}") | |
| return True | |
| except Exception as exc: | |
| print(f"Cannot place order due to {exc}") | |
| return False | |
| def handle_bars(argobj): | |
| data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| begin = datetime.fromisoformat(argobj.beg_date) | |
| enddate = datetime.fromisoformat(argobj.end_date) | |
| req = StockBarsRequest(symbol_or_symbols=argobj.tick, timeframe=TimeFrame.Day, start=begin, end=enddate) | |
| barresp = data_client.get_stock_bars(req) | |
| if argobj.tick not in barresp.data: | |
| print(f"Data not found for {argobj.sym}") | |
| return | |
| bar_objs = barresp.data[argobj.tick] | |
| for bar in bar_objs: | |
| print(f"[{argobj.tick}] vol={bar.volume} spread={round(bar.high - bar.low, 2)} dip={round(bar.low - bar.open, 2)} close={bar.close} change={round(bar.open - bar.close, 2)}") | |
| def handle_power(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| account = trade_client.get_account() | |
| print(f"POWER with_margin={account.buying_power} non_margin={account.non_marginable_buying_power} overnight={account.regt_buying_power} fees={account.accrued_fees} maint={account.maintenance_margin} equity={account.equity}") | |
| def handle_rsi(argobj): | |
| # Initialize the client (assumes API_KEY and SECRET_KEY are globally accessible in your script) | |
| data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| # 1. Map command line arguments | |
| symbols = argobj.tick # This will be a list of strings thanks to nargs='+' | |
| rsi_period = argobj.days # Dynamic period from your --days argument | |
| # 2. Set dates (RSI needs roughly 2.5x to 3x its period in historical data to properly stabilize) | |
| # We buffer by adding 30 extra calendar days to the required trading window | |
| lookback_days = rsi_period + 30 | |
| begin = datetime.now() - timedelta(days=lookback_days) | |
| enddate = datetime.now() | |
| # 3. Request data from Alpaca | |
| req = StockBarsRequest( | |
| symbol_or_symbols=symbols, | |
| timeframe=TimeFrame.Day, | |
| start=begin, | |
| end=enddate | |
| ) | |
| barresp = data_client.get_stock_bars(req) | |
| # Safety check: Ensure the response actually contains data | |
| if not barresp.data: | |
| print("No historical bar data returned for the requested symbols.") | |
| return | |
| # 4. Process the data using pandas | |
| df = barresp.df.reset_index() | |
| # Group by symbol to keep technical indicators isolated per ticker | |
| df['change'] = df.groupby('symbol')['close'].diff() | |
| df['gain'] = df['change'].clip(lower=0) | |
| df['loss'] = -df['change'].clip(upper=0) | |
| # Wilder's Exponential Moving Average smoothing | |
| df['avg_gain'] = df.groupby('symbol')['gain'].transform( | |
| lambda x: x.ewm(com=rsi_period - 1, min_periods=rsi_period).mean() | |
| ) | |
| df['avg_loss'] = df.groupby('symbol')['loss'].transform( | |
| lambda x: x.ewm(com=rsi_period - 1, min_periods=rsi_period).mean() | |
| ) | |
| # Calculate final RSI | |
| df['rs'] = df['avg_gain'] / df['avg_loss'] | |
| df['rsi'] = 100 - (100 / (1 + df['rs'])) | |
| # Drop rows without a valid RSI (the initial warmup rows) | |
| result_df = df[['symbol', 'timestamp', 'close', 'rsi']].dropna() | |
| # 5. Output results to terminal | |
| # Loops through the specified tickers and prints the single most recent row for each | |
| for ticker in symbols: | |
| ticker_data = result_df[result_df['symbol'] == ticker] | |
| if ticker_data.empty: | |
| print(f"Data or RSI calculation missing for {ticker} (Insufficient history).") | |
| continue | |
| # Get the absolute last row of data for this specific ticker | |
| latest_row = ticker_data.iloc[-1] | |
| formatted_date = latest_row['timestamp'].strftime('%Y-%m-%d') | |
| current_rsi = round(latest_row['rsi'], 2) | |
| close_price = round(latest_row['close'], 2) | |
| # Format terminal flag text if market is extreme | |
| status = "" | |
| if current_rsi >= 70: | |
| status = " [OVERBOUGHT]" | |
| elif current_rsi <= 30: | |
| status = " [OVERSOLD]" | |
| print(f"[{ticker}] Date: {formatted_date} | Close: ${close_price} | {rsi_period}-Day RSI: {current_rsi}{status}") | |
| def handle_orders(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| syms = [argobj.tick] | |
| after = datetime.fromisoformat(argobj.after) | |
| got_orders = retrieve_orders(trade_client, syms, after, argobj.state) | |
| for order in got_orders: | |
| if order.order_class != OrderClass.OTO: | |
| continue | |
| if order.legs and len(order.legs) > 0: | |
| print(f"[{argobj.tick}] bstate={str(order.status)} bprice={order.limit_price} sstate={str(order.legs[0].status)} sprice={order.legs[0].limit_price}") | |
| else: | |
| print(f"[{argobj.tick}] bstate={str(order.status)} bprice={order.limit_price}") | |
| def handle_cancel(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| syms = [argobj.tick] | |
| after = datetime.fromisoformat(argobj.after) | |
| got_orders = retrieve_orders(trade_client, syms, after, QueryOrderStatus.OPEN) | |
| for order in got_orders: | |
| if order.side != OrderSide.BUY and not argobj.sells: | |
| print(f"[{argobj.tick}] Skipping sell order") | |
| continue | |
| oprice = float(order.limit_price) | |
| if oprice <= argobj.lte and oprice >= argobj.gte: | |
| print(f"[{argobj.tick}] CANCEL price={oprice}") | |
| trade_client.cancel_order_by_id(order_id=order.id) | |
| def handle_owned(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| all_positions = trade_client.get_all_positions() | |
| for pos in all_positions: | |
| print(f"[{pos.symbol}] qty={pos.qty} avl={pos.qty_available} base={pos.cost_basis} cur={pos.current_price} avg_cost={pos.avg_entry_price} pl={pos.unrealized_pl}") | |
| def handle_calls(argobj): | |
| client = OptionHistoricalDataClient(API_KEY, SECRET_KEY) | |
| current_date = datetime.now() | |
| end_date = current_date + timedelta(days=argobj.dte) | |
| data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| request_params = StockSnapshotRequest( | |
| symbol_or_symbols=argobj.tick, | |
| feed=DataFeed(argobj.feed) | |
| ) | |
| snapshot = data_client.get_stock_snapshot(request_params) | |
| stock_data = snapshot[argobj.tick] | |
| # latest_close_price = stock_data.minute_bar.close | |
| latest_ask_price = stock_data.latest_quote.ask_price | |
| latest_bid_price = stock_data.latest_quote.bid_price | |
| mid_price = round((latest_ask_price + latest_bid_price) / 2, 2) | |
| print(f"----[{argobj.tick}]----") | |
| print(f"mid_price={mid_price} tick={argobj.tick}") | |
| req = OptionChainRequest(underlying_symbol=argobj.tick, | |
| expiration_date_gte=current_date.date().isoformat(), expiration_date_lte=end_date.date().isoformat(), | |
| type=ContractType.CALL, strike_price_lte=mid_price * argobj.high, strike_price_gte=mid_price * 0.8) | |
| resp = client.get_option_chain(req) | |
| for k, value in resp.items(): | |
| bprice = value.latest_quote.bid_price if value.latest_quote else 'NA' | |
| aprice = value.latest_quote.ask_price if value.latest_quote else 'NA' | |
| bsize = value.latest_quote.bid_size if value.latest_quote else 'NA' | |
| asize = value.latest_quote.ask_size if value.latest_quote else 'NA' | |
| delta = round(value.greeks.delta, 4) if (value.greeks and value.greeks.delta is not None) else 'NA' | |
| theta = round(value.greeks.theta, 4) if (value.greeks and value.greeks.theta is not None) else 'NA' | |
| print(f"{k} -> bprice={bprice} bsize={bsize} aprice={aprice} asize={asize} delta={delta} theta={theta}") | |
| print(f"----[{argobj.tick}]----") | |
| def handle_puts(argobj): | |
| client = OptionHistoricalDataClient(API_KEY, SECRET_KEY) | |
| current_date = datetime.now() + timedelta(days=argobj.dte_min) | |
| end_date = datetime.now() + timedelta(days=argobj.dte) | |
| data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| request_params = StockSnapshotRequest( | |
| symbol_or_symbols=argobj.tick, | |
| feed=DataFeed(argobj.feed) | |
| ) | |
| snapshot = data_client.get_stock_snapshot(request_params) | |
| stock_data = snapshot[argobj.tick] | |
| # latest_close_price = stock_data.minute_bar.close | |
| latest_ask_price = stock_data.latest_quote.ask_price | |
| latest_bid_price = stock_data.latest_quote.bid_price | |
| mid_price = round((latest_ask_price + latest_bid_price) / 2, 2) | |
| print(f"----[{argobj.tick}]----") | |
| print(f"mid_price={mid_price} tick={argobj.tick}") | |
| req = OptionChainRequest(underlying_symbol=argobj.tick, | |
| expiration_date_gte=current_date.date().isoformat(), expiration_date_lte=end_date.date().isoformat(), | |
| type=ContractType.PUT, strike_price_lte=mid_price * 1.1, strike_price_gte=mid_price * argobj.low) | |
| resp = client.get_option_chain(req) | |
| for k, value in resp.items(): | |
| bprice = value.latest_quote.bid_price if value.latest_quote else 'NA' | |
| aprice = value.latest_quote.ask_price if value.latest_quote else 'NA' | |
| bsize = value.latest_quote.bid_size if value.latest_quote else 'NA' | |
| asize = value.latest_quote.ask_size if value.latest_quote else 'NA' | |
| # Extract Greeks if available from Alpaca-py snapshot | |
| delta = round(value.greeks.delta, 4) if (value.greeks and value.greeks.delta is not None) else 'NA' | |
| theta = round(value.greeks.theta, 4) if (value.greeks and value.greeks.theta is not None) else 'NA' | |
| print(f"{k} -> bprice={bprice} bsize={bsize} aprice={aprice} asize={asize} delta={delta} theta={theta}") | |
| print(f"----[{argobj.tick}]----") | |
| def handle_dips(argobj): | |
| client = OptionHistoricalDataClient(API_KEY, SECRET_KEY) | |
| current_date = datetime.now() + timedelta(days=argobj.dte_min) | |
| end_date = datetime.now() + timedelta(days=argobj.dte) | |
| data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| request_params = StockSnapshotRequest( | |
| symbol_or_symbols=argobj.tick, | |
| feed=DataFeed(argobj.feed) | |
| ) | |
| snapshot = data_client.get_stock_snapshot(request_params) | |
| stock_data = snapshot[argobj.tick] | |
| # latest_close_price = stock_data.minute_bar.close | |
| latest_ask_price = stock_data.latest_quote.ask_price | |
| latest_bid_price = stock_data.latest_quote.bid_price | |
| mid_price = round((latest_ask_price + latest_bid_price) / 2, 2) | |
| mod_price = mid_price * (1 + argobj.perc) | |
| print(f"----[{argobj.tick}]----") | |
| print(f"mid_price={mid_price} mod={mod_price} tick={argobj.tick} perc={argobj.perc}") | |
| req = OptionChainRequest(underlying_symbol=argobj.tick, | |
| expiration_date_gte=current_date.date().isoformat(), expiration_date_lte=end_date.date().isoformat(), | |
| type=ContractType.CALL, strike_price_lte=mid_price * 2, strike_price_gte=mid_price) | |
| resp = client.get_option_chain(req) | |
| for k, value in resp.items(): | |
| if not value.greeks or not value.latest_quote: | |
| continue | |
| delta = float(value.greeks.delta) | |
| bprice = float(value.latest_quote.bid_price) | |
| aprice = float(value.latest_quote.ask_price) | |
| omid_price = (bprice + aprice) / 2 | |
| if omid_price < argobj.min: | |
| continue | |
| stock_change = mid_price - mod_price | |
| opt_change = stock_change * delta | |
| lowered_price = omid_price - opt_change | |
| print(f"{k} -> cur={omid_price} dip_price={lowered_price} perc_decr={ 1 - (lowered_price / omid_price)}") | |
| print(f"----[{argobj.tick}]----") | |
| def handle_bbopt(argobj): | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| all_positions = trade_client.get_all_positions() | |
| for pos in all_positions: | |
| qty = float(pos.qty) | |
| # Filter for OCC option symbols with negative quantity (short positions) | |
| if not is_occ_symbol(pos.symbol) or qty >= 0: | |
| continue | |
| avg_entry = float(pos.avg_entry_price) | |
| current_price = float(pos.current_price) | |
| if avg_entry <= 0: | |
| continue | |
| # Calculate profit percentage realized: (entry - current) / entry | |
| return_pct = (avg_entry - current_price) / avg_entry | |
| if return_pct >= argobj.min_perc: | |
| buy_qty = abs(int(qty)) | |
| price_to_use = round(current_price, 2) | |
| limit_order_data = LimitOrderRequest( | |
| symbol=pos.symbol, | |
| qty=buy_qty, | |
| side=OrderSide.BUY, | |
| type=OrderType.LIMIT, | |
| extended_hours=False, | |
| time_in_force=TimeInForce.DAY, | |
| limit_price=price_to_use, | |
| order_class=OrderClass.SIMPLE | |
| ) | |
| print(f"[{pos.symbol}] BUY BACK | Qty: {buy_qty} | Entry: ${avg_entry:.2f} | Current: ${price_to_use:.2f} | Return: {return_pct * 100:.1f}%") | |
| if argobj.dry: | |
| continue | |
| try: | |
| submitted_order = trade_client.submit_order(order_data=limit_order_data) | |
| print(f"Order successfully submitted! ID: {submitted_order.id} | Status: {submitted_order.status}") | |
| except Exception as exc: | |
| print(f"Cannot place order for {pos.symbol} due to {exc}") | |
| def handle_btleap(argobj): | |
| stock_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| opt_hist_client = OptionHistoricalDataClient(API_KEY, SECRET_KEY) | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| entry_date = datetime.fromisoformat(argobj.date).replace(tzinfo=timezone.utc) | |
| end_date = entry_date + timedelta(days=argobj.hold_days) | |
| # 1. Fetch underlying stock price on entry date | |
| stock_req = StockBarsRequest( | |
| symbol_or_symbols=argobj.tick, | |
| timeframe=TimeFrame.Day, | |
| start=entry_date, | |
| end=entry_date + timedelta(days=5) # 5-day window to handle weekends/holidays | |
| ) | |
| stock_bars = stock_client.get_stock_bars(stock_req) | |
| if argobj.tick not in stock_bars.data or not stock_bars.data[argobj.tick]: | |
| print(f"No stock data found for {argobj.tick} around {argobj.date}") | |
| return | |
| entry_stock_bar = stock_bars.data[argobj.tick][0] | |
| stock_price_at_entry = entry_stock_bar.close | |
| print(f"[{argobj.tick}] Entry Date: {entry_stock_bar.timestamp.strftime('%Y-%m-%d')} | Stock Price: ${stock_price_at_entry:.2f}") | |
| # 2. Calculate target strike from --strike-offset argument | |
| raw_offset = str(argobj.strike_offset).strip() | |
| if raw_offset.endswith('%'): | |
| pct = float(raw_offset.replace('%', '')) / 100.0 | |
| target_strike = stock_price_at_entry * (1.0 + pct) | |
| else: | |
| target_strike = stock_price_at_entry + float(raw_offset) | |
| print(f"Target Strike Price: ${target_strike:.2f} (Underlying: ${stock_price_at_entry:.2f}, Offset: {raw_offset})") | |
| # 3. Get target DTE range window (-20 to +15 days from target DTE) | |
| target_dte = (argobj.dte_min + argobj.dte_max) // 2 | |
| min_dte = max(1, target_dte - 20) | |
| max_dte = target_dte + 15 | |
| min_exp = (entry_date + timedelta(days=min_dte)).date() | |
| max_exp = (entry_date + timedelta(days=max_dte)).date() | |
| print(f"Searching inactive contracts expiring between {min_exp} and {max_exp} (DTE range: {min_dte} to {max_dte})...") | |
| contracts_req = GetOptionContractsRequest( | |
| underlying_symbols=[argobj.tick], | |
| status="inactive", # Explicitly search inactive/expired contracts | |
| type=ContractType.CALL, | |
| expiration_date_gte=min_exp.isoformat(), | |
| expiration_date_lte=max_exp.isoformat(), | |
| limit=1000 | |
| ) | |
| try: | |
| contracts_resp = trade_client.get_option_contracts(contracts_req) | |
| contracts = contracts_resp.option_contracts if hasattr(contracts_resp, 'option_contracts') else contracts_resp | |
| except Exception as exc: | |
| print(f"Error querying option contracts endpoint: {exc}") | |
| return | |
| if not contracts: | |
| print("No inactive/expired contracts returned from Alpaca for this range.") | |
| return | |
| # Sort candidates by strike proximity to target_strike | |
| sorted_contracts = sorted( | |
| contracts, | |
| key=lambda c: abs(float(c.strike_price) - target_strike) | |
| ) | |
| print(f"Found {len(sorted_contracts)} potential contracts. Testing bar availability across candidate pool...\n") | |
| # 4. Iterate through candidate contracts until historical bars are returned | |
| feed_to_use = OptionsFeed.OPRA if argobj.feed.lower() in ['sip', 'opra'] else OptionsFeed.INDICATIVE | |
| selected_contract = None | |
| valid_bars = None | |
| for contract in sorted_contracts: | |
| sym = contract.symbol | |
| strike = float(contract.strike_price) | |
| exp = contract.expiration_date | |
| opt_bars_req = OptionBarsRequest( | |
| symbol_or_symbols=sym, | |
| timeframe=TimeFrame.Day, | |
| start=entry_date, | |
| end=end_date + timedelta(days=5), | |
| feed=feed_to_use | |
| ) | |
| try: | |
| opt_bars_resp = opt_hist_client.get_option_bars(opt_bars_req) | |
| if sym in opt_bars_resp.data and len(opt_bars_resp.data[sym]) > 0: | |
| selected_contract = contract | |
| valid_bars = opt_bars_resp.data[sym] | |
| print(f" [SUCCESS] {sym} (Strike: ${strike:.2f} | Exp: {exp}) returned {len(valid_bars)} bars.") | |
| break | |
| else: | |
| print(f" [NO DATA] {sym} (Strike: ${strike:.2f} | Exp: {exp}) -> 0 bars.") | |
| except Exception as err: | |
| print(f" [ERROR] {sym} -> {err}") | |
| # 5. Check if any contract passed | |
| if not selected_contract or not valid_bars: | |
| print("\nAll candidate contracts in the inactive range failed to return bars.") | |
| print("Conclusion: This indicates an Alpaca historical data coverage gap or un-traded contracts for this window.") | |
| return | |
| # 6. Execute Backtest on Found Contract | |
| buy_bar = valid_bars[0] | |
| exit_bar = valid_bars[-1] | |
| buy_price = buy_bar.close | |
| exit_price = exit_bar.close | |
| pnl = exit_price - buy_price | |
| pnl_pct = (pnl / buy_price) * 100 if buy_price > 0 else 0.0 | |
| print("\n=================== BACKTEST RESULT ===================") | |
| print(f"Selected Symbol: {selected_contract.symbol}") | |
| print(f"Contract Strike: ${float(selected_contract.strike_price):.2f}") | |
| print(f"Contract Expiration: {selected_contract.expiration_date}") | |
| print(f"Option Entry Date: {buy_bar.timestamp.strftime('%Y-%m-%d')} | Entry Price: ${buy_price:.2f}") | |
| print(f"Option Exit Date: {exit_bar.timestamp.strftime('%Y-%m-%d')} | Exit Price: ${exit_price:.2f}") | |
| print(f"P&L per Contract: ${pnl * 100:+.2f} ({pnl_pct:+.1f}%)") | |
| print("=======================================================") | |
| def handle_smadev(argobj): | |
| stock_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| start_date = datetime.fromisoformat(argobj.start).replace(tzinfo=timezone.utc) | |
| end_date = datetime.fromisoformat(argobj.end).replace(tzinfo=timezone.utc) | |
| sma_period = argobj.sma | |
| # 1. Look back extra days before start_date to allow the SMA calculation to warm up | |
| warmup_days = int(sma_period * 1.6) + 10 | |
| fetch_start = start_date - timedelta(days=warmup_days) | |
| print(f"[{argobj.tick}] Fetching daily price data from {fetch_start.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}...") | |
| stock_req = StockBarsRequest( | |
| symbol_or_symbols=argobj.tick, | |
| timeframe=TimeFrame.Day, | |
| start=fetch_start, | |
| end=end_date + timedelta(days=1) | |
| ) | |
| stock_bars = stock_client.get_stock_bars(stock_req) | |
| if argobj.tick not in stock_bars.data or not stock_bars.data[argobj.tick]: | |
| print(f"No stock data returned for {argobj.tick}.") | |
| return | |
| # 2. Build pandas DataFrame from daily bars | |
| bars_data = [ | |
| {"timestamp": bar.timestamp, "close": float(bar.close)} | |
| for bar in stock_bars.data[argobj.tick] | |
| ] | |
| df = pd.DataFrame(bars_data) | |
| df.sort_values("timestamp", inplace=True) | |
| df.reset_index(drop=True, inplace=True) | |
| # 3. Calculate SMA | |
| df["sma"] = df["close"].rolling(window=sma_period).mean() | |
| # Filter DataFrame down strictly to the user's requested date window | |
| df_analysis = df[(df["timestamp"] >= start_date) & (df["timestamp"] <= end_date)].copy() | |
| if df_analysis.empty or df_analysis["sma"].isna().all(): | |
| print(f"Insufficient data to calculate a {sma_period}-day SMA within the target date range.") | |
| return | |
| # Drop any initial rows if SMA is still NaN | |
| df_analysis.dropna(subset=["sma"], inplace=True) | |
| # 4. Calculate deviations | |
| df_analysis["diff_dollar"] = df_analysis["close"] - df_analysis["sma"] | |
| df_analysis["diff_pct"] = (df_analysis["diff_dollar"] / df_analysis["sma"]) * 100 | |
| df_analysis["abs_diff_pct"] = df_analysis["diff_pct"].abs() | |
| # Identify Extreme Deviation Rows | |
| max_pos_row = df_analysis.loc[df_analysis["diff_pct"].idxmax()] | |
| max_neg_row = df_analysis.loc[df_analysis["diff_pct"].idxmin()] | |
| abs_max_row = df_analysis.loc[df_analysis["abs_diff_pct"].idxmax()] | |
| # Summary Metrics | |
| avg_pct_dev = df_analysis["diff_pct"].mean() | |
| std_pct_dev = df_analysis["diff_pct"].std() | |
| # 5. Output Output | |
| print("\n=================== SMA DEVIATION ANALYSIS ===================") | |
| print(f"Ticker: {argobj.tick}") | |
| print(f"Analysis Window: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}") | |
| print(f"SMA Span: {sma_period}-day SMA") | |
| print(f"Total Trading Days: {len(df_analysis)}") | |
| print("--------------------------------------------------------------") | |
| print("PEAK BULLISH OVEREXTENSION (Max Above SMA):") | |
| print(f" Date: {max_pos_row['timestamp'].strftime('%Y-%m-%d')}") | |
| print(f" Stock Close: ${max_pos_row['close']:.2f}") | |
| print(f" SMA Value: ${max_pos_row['sma']:.2f}") | |
| print(f" Deviation: +${max_pos_row['diff_dollar']:.2f} (+{max_pos_row['diff_pct']:.2f}%)") | |
| print("--------------------------------------------------------------") | |
| print("PEAK BEARISH OVEREXTENSION (Max Below SMA):") | |
| print(f" Date: {max_neg_row['timestamp'].strftime('%Y-%m-%d')}") | |
| print(f" Stock Close: ${max_neg_row['close']:.2f}") | |
| print(f" SMA Value: ${max_neg_row['sma']:.2f}") | |
| print(f" Deviation: -${abs(max_neg_row['diff_dollar']):.2f} ({max_neg_row['diff_pct']:.2f}%)") | |
| print("--------------------------------------------------------------") | |
| print("ABSOLUTE MAXIMUM DEVIATION:") | |
| print(f" Date: {abs_max_row['timestamp'].strftime('%Y-%m-%d')}") | |
| print(f" Stock Close: ${abs_max_row['close']:.2f}") | |
| print(f" SMA Value: ${abs_max_row['sma']:.2f}") | |
| print(f" Max Stretch: {abs_max_row['diff_pct']:+.2f}%") | |
| print("--------------------------------------------------------------") | |
| print(f"Average Stretch: {avg_pct_dev:+.2f}%") | |
| print(f"Std Deviation Stretch: ±{std_pct_dev:.2f}%") | |
| print("==============================================================") | |
| def handle_optrank(argobj): | |
| stock_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) | |
| opt_hist_client = OptionHistoricalDataClient(API_KEY, SECRET_KEY) | |
| trade_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=PAPER) | |
| tickers = [t.upper() for t in argobj.ticks] | |
| target_exp = argobj.exp_date | |
| option_type_str = argobj.type.upper() | |
| contract_type = ContractType.CALL if option_type_str == "CALL" else ContractType.PUT | |
| num_strikes = argobj.strikes | |
| today = datetime.now(timezone.utc).date() | |
| exp_date_obj = datetime.strptime(target_exp, "%Y-%m-%d").date() | |
| dte = (exp_date_obj - today).days | |
| if dte <= 0: | |
| print(f"Error: Expiration date {target_exp} must be in the future (calculated DTE: {dte}).") | |
| return | |
| print(f"Fetching current stock prices for {', '.join(tickers)}...") | |
| # 1. Get current stock prices for all input tickers | |
| try: | |
| stock_snaps = stock_client.get_stock_snapshot(StockSnapshotRequest(symbol_or_symbols=tickers)) | |
| except Exception as exc: | |
| print(f"Error fetching stock snapshots: {exc}") | |
| return | |
| stock_prices = {} | |
| for tick in tickers: | |
| if tick in stock_snaps and stock_snaps[tick].latest_trade: | |
| stock_prices[tick] = float(stock_snaps[tick].latest_trade.price) | |
| elif tick in stock_snaps and stock_snaps[tick].daily_bar: | |
| stock_prices[tick] = float(stock_snaps[tick].daily_bar.close) | |
| else: | |
| print(f"Warning: Could not fetch current stock price for {tick}. Skipping.") | |
| if not stock_prices: | |
| print("No stock prices found for any requested tickers.") | |
| return | |
| # 2. Query option contracts for each ticker around current price | |
| candidate_option_symbols = [] | |
| contract_meta = {} | |
| print(f"\nSearching {option_type_str} contracts expiring on {target_exp} (DTE: {dte} days)...") | |
| for tick, price in stock_prices.items(): | |
| contracts_req = GetOptionContractsRequest( | |
| underlying_symbols=[tick], | |
| status="active", | |
| type=contract_type, | |
| expiration_date=target_exp, | |
| limit=1000 | |
| ) | |
| try: | |
| resp = trade_client.get_option_contracts(contracts_req) | |
| contracts = resp.option_contracts if hasattr(resp, 'option_contracts') else resp | |
| except Exception as err: | |
| print(f" [{tick}] Error fetching contracts: {err}") | |
| continue | |
| if not contracts: | |
| print(f" [{tick}] No active {option_type_str} contracts found for expiration {target_exp}.") | |
| continue | |
| # Sort contracts by proximity to current stock price and take top N strikes | |
| sorted_contracts = sorted(contracts, key=lambda c: abs(float(c.strike_price) - price)) | |
| selected_contracts = sorted_contracts[:num_strikes] | |
| for c in selected_contracts: | |
| candidate_option_symbols.append(c.symbol) | |
| contract_meta[c.symbol] = { | |
| "underlying": tick, | |
| "stock_price": price, | |
| "strike": float(c.strike_price), | |
| "expiration": c.expiration_date | |
| } | |
| if not candidate_option_symbols: | |
| print("No matching candidate option contracts found across the tickers.") | |
| return | |
| print(f"Found {len(candidate_option_symbols)} contracts across {len(stock_prices)} tickers. Fetching quotes & Greeks...") | |
| # 3. Fetch snapshots (quotes & Greeks) for all selected option contracts | |
| try: | |
| opt_snaps = opt_hist_client.get_option_snapshot( | |
| OptionSnapshotRequest(symbol_or_symbols=candidate_option_symbols) | |
| ) | |
| except Exception as exc: | |
| print(f"Error fetching option snapshots: {exc}") | |
| return | |
| # 4. Calculate metrics and build ranking table | |
| results = [] | |
| for sym in candidate_option_symbols: | |
| if sym not in opt_snaps: | |
| continue | |
| snap = opt_snaps[sym] | |
| meta = contract_meta[sym] | |
| stock_price = meta["stock_price"] | |
| strike = meta["strike"] | |
| # Get Bid/Ask and calculate Premium (using Bid price since seller receives Bid) | |
| bid = float(snap.latest_quote.bid_price) if snap.latest_quote and snap.latest_quote.bid_price else 0.0 | |
| ask = float(snap.latest_quote.ask_price) if snap.latest_quote and snap.latest_quote.ask_price else 0.0 | |
| mid = (bid + ask) / 2.0 if (bid > 0 and ask > 0) else (bid or ask) | |
| premium = bid if bid > 0 else mid # Use Bid price for realistic seller credit | |
| if premium <= 0: | |
| continue | |
| # Extract Theta from Greeks if available | |
| theta = 0.0 | |
| if snap.greeks and snap.greeks.theta is not None: | |
| theta = float(snap.greeks.theta) | |
| abs_theta = abs(theta) | |
| # Calculate Key Ratios | |
| prem_to_dte = premium / dte # $/day yield per share | |
| prem_to_theta = (premium / abs_theta) if abs_theta > 0 else 0.0 # Days of theta in premium | |
| daily_pct_yield = (premium / stock_price / dte) * 100 # % daily return on underlying capital | |
| # Moneyness description (ITM, ATM, OTM) | |
| diff = strike - stock_price | |
| if abs(diff) < (stock_price * 0.005): | |
| moneyness = "ATM" | |
| elif (option_type_str == "CALL" and strike > stock_price) or (option_type_str == "PUT" and strike < stock_price): | |
| moneyness = f"OTM ({abs(diff):.2f})" | |
| else: | |
| moneyness = f"ITM ({abs(diff):.2f})" | |
| results.append({ | |
| "ticker": meta["underlying"], | |
| "option_symbol": sym, | |
| "stock_price": stock_price, | |
| "strike": strike, | |
| "moneyness": moneyness, | |
| "bid": bid, | |
| "ask": ask, | |
| "premium": premium, | |
| "theta": theta, | |
| "prem_dte": prem_to_dte, | |
| "prem_theta": prem_to_theta, | |
| "daily_pct": daily_pct_yield | |
| }) | |
| if not results: | |
| print("No contracts with active bid quotes/premia were returned.") | |
| return | |
| df = pd.DataFrame(results) | |
| # Sort based on requested metric | |
| sort_column_map = { | |
| "prem_dte": "prem_dte", | |
| "prem_theta": "prem_theta", | |
| "daily_pct": "daily_pct" | |
| } | |
| sort_col = sort_column_map.get(argobj.sort_by, "prem_dte") | |
| df.sort_values(by=sort_col, ascending=False, inplace=True) | |
| df.reset_index(drop=True, inplace=True) | |
| # 5. Format & Display Results Output | |
| print(f"\n======================================= COVERED {option_type_str} OPTION RANKING =======================================") | |
| print(f"Target Expiration: {target_exp} | DTE: {dte} Days | Ranked By: {argobj.sort_by.upper()}") | |
| print("------------------------------------------------------------------------------------------------------------------------") | |
| print(f"{'Rank':<5} {'Ticker':<7} {'Stock $':<9} {'Option Symbol':<22} {'Strike':<8} {'Type':<11} {'Bid $':<7} {'Prem/DTE':<10} {'Prem/|Theta|':<13} {'Daily Yield %':<12}") | |
| print("------------------------------------------------------------------------------------------------------------------------") | |
| for idx, row in df.iterrows(): | |
| rank = idx + 1 | |
| theta_str = f"{row['prem_theta']:.1f}d" if row['prem_theta'] > 0 else "N/A" | |
| print( | |
| f"{rank:<5} " | |
| f"{row['ticker']:<7} " | |
| f"${row['stock_price']:<8.2f} " | |
| f"{row['option_symbol']:<22} " | |
| f"${row['strike']:<7.2f} " | |
| f"{row['moneyness']:<11} " | |
| f"${row['bid']:<6.2f} " | |
| f"${row['prem_dte']:<9.3f} " | |
| f"{theta_str:<13} " | |
| f"{row['daily_pct']:<11.4f}%" | |
| ) | |
| print("================================================================================----------------------------------------") | |
| print("Note: Prem/DTE is $/day credit per share. Prem/|Theta| is the number of days of theta decay priced into the premium.") | |
| if __name__ == '__main__': | |
| args = parse_arguments() | |
| if args.command == 'price': | |
| handle_price(args) | |
| elif args.command == 'bars': | |
| handle_bars(args) | |
| elif args.command == 'oto': | |
| handle_oto(args) | |
| elif args.command == 'power': | |
| handle_power(args) | |
| elif args.command == 'orders': | |
| handle_orders(args) | |
| elif args.command == 'cancel': | |
| handle_cancel(args) | |
| elif args.command == 'owned': | |
| handle_owned(args) | |
| elif args.command == 'btleap': | |
| handle_btleap(args) | |
| elif args.command == 'sell': | |
| handle_sell(args) | |
| elif args.command == 'buy': | |
| handle_buy(args) | |
| elif args.command == 'buystop': | |
| handle_buy_stop(args) | |
| elif args.command == 'calls': | |
| handle_calls(args) | |
| elif args.command == 'puts': | |
| handle_puts(args) | |
| elif args.command == 'optrank': | |
| handle_optrank(args) | |
| elif args.command == 'dips': | |
| handle_dips(args) | |
| elif args.command == 'rsi': | |
| handle_rsi(args) | |
| elif args.command == 'smadev': | |
| handle_smadev(args) | |
| elif args.command == 'bbopt': | |
| handle_bbopt(args) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment