Skip to content

Instantly share code, notes, and snippets.

@jweinst1
Last active August 2, 2026 22:49
Show Gist options
  • Select an option

  • Save jweinst1/de2345f1cee30e45e1a6c68147f6298f to your computer and use it in GitHub Desktop.

Select an option

Save jweinst1/de2345f1cee30e45e1a6c68147f6298f to your computer and use it in GitHub Desktop.
stock trading OTO cli for alpaca
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