Skip to content

Commit 2c79c66

Browse files
walk down option change until put and call IV both found
1 parent 0066a48 commit 2c79c66

File tree

3 files changed

+59
-39
lines changed

3 files changed

+59
-39
lines changed

automation.py

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ def compute_recommendation(ticker):
125125
try:
126126
exp_dates = sorted(option_chain.keys())
127127
today = datetime.today().date()
128-
exp_dates_filtered = [d for d in exp_dates if (datetime.strptime(d, "%Y-%m-%d").date() - today).days >= 0]
128+
# only consider expiries strictly after today (no 0DTE)
129+
exp_dates_filtered = [d for d in exp_dates if (datetime.strptime(d, "%Y-%m-%d").date() - today).days > 0]
129130
if len(exp_dates_filtered) < 2:
130131
raise Exception("Not enough option data from Alpaca.")
131132
underlying_price = None
@@ -151,41 +152,45 @@ def compute_recommendation(ticker):
151152
strikes = option_chain[exp_date].keys()
152153
if not strikes:
153154
continue
154-
strike = min(strikes, key=lambda x: abs(x - underlying_price))
155-
call_contract = option_chain[exp_date][strike].get('call')
156-
put_contract = option_chain[exp_date][strike].get('put')
157-
if not call_contract or not put_contract:
158-
continue
159-
call_symbol = call_contract.symbol
160-
put_symbol = put_contract.symbol
161-
# Use snapshot endpoint to get IV and latest quote
162-
req = OptionSnapshotRequest(symbol_or_symbols=[call_symbol, put_symbol])
163-
snap_resp = options_client.get_option_snapshot(req)
164-
call_snap = snap_resp.get(call_symbol)
165-
put_snap = snap_resp.get(put_symbol)
166-
if not call_snap or not put_snap:
167-
continue
168-
call_quote = call_snap.latest_quote
169-
put_quote = put_snap.latest_quote
170-
if not call_quote or not put_quote:
171-
continue
172-
call_bid = call_quote.bid_price
173-
call_ask = call_quote.ask_price
174-
put_bid = put_quote.bid_price
175-
put_ask = put_quote.ask_price
176-
# retrieve IV from snapshot
177-
call_iv = call_snap.implied_volatility
178-
put_iv = put_snap.implied_volatility
179-
if call_iv is None or put_iv is None:
155+
sorted_strikes = sorted(strikes, key=lambda s: abs(s - underlying_price))
156+
for strike in sorted_strikes:
157+
call_contract = option_chain[exp_date][strike].get('call')
158+
put_contract = option_chain[exp_date][strike].get('put')
159+
if not call_contract or not put_contract:
160+
continue
161+
call_symbol = call_contract.symbol
162+
put_symbol = put_contract.symbol
163+
req = OptionSnapshotRequest(symbol_or_symbols=[call_symbol, put_symbol])
164+
snap_resp = options_client.get_option_snapshot(req)
165+
call_snap = snap_resp.get(call_symbol)
166+
put_snap = snap_resp.get(put_symbol)
167+
if not call_snap or not put_snap:
168+
continue
169+
call_quote = call_snap.latest_quote
170+
put_quote = put_snap.latest_quote
171+
if not call_quote or not put_quote:
172+
continue
173+
call_bid = call_quote.bid_price
174+
call_ask = call_quote.ask_price
175+
put_bid = put_quote.bid_price
176+
put_ask = put_quote.ask_price
177+
call_iv = call_snap.implied_volatility
178+
put_iv = put_snap.implied_volatility
179+
if call_iv is None or put_iv is None:
180+
continue
181+
atm_iv_value = (call_iv + put_iv) / 2.0
182+
atm_iv[exp_date] = atm_iv_value
183+
if straddle is None:
184+
if None not in (call_bid, call_ask, put_bid, put_ask):
185+
call_mid = (call_bid + call_ask) / 2.0
186+
put_mid = (put_bid + put_ask) / 2.0
187+
straddle = (call_mid + put_mid)
188+
break
189+
else:
190+
# no valid IV on nearby strikes, skip this expiry
180191
continue
181-
atm_iv_value = (call_iv + put_iv) / 2.0
182-
atm_iv[exp_date] = atm_iv_value
183-
if straddle is None:
184-
if None not in (call_bid, call_ask, put_bid, put_ask):
185-
call_mid = (call_bid + call_ask) / 2.0
186-
put_mid = (put_bid + put_ask) / 2.0
187-
straddle = (call_mid + put_mid)
188-
if atm_iv:
192+
# Only accept Alpaca data if there are at least two expiries worth of IVs
193+
if len(atm_iv) >= 2:
189194
alpaca_success = True
190195
except Exception as e:
191196
print(f"Alpaca option chain error: {e}")

test_alpaca_option_chain.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from alpaca.data.historical.option import OptionHistoricalDataClient
66
from alpaca.data.requests import OptionSnapshotRequest
77
from alpaca_integration import get_alpaca_option_chain, select_expiries_and_strike_alpaca
8+
import pprint
89

910
if __name__ == "__main__":
1011
symbol = os.environ.get("TEST_SYMBOL", "AAPL")
@@ -32,7 +33,10 @@
3233
atm_strike = min(strikes, key=lambda s: abs(s - underlying_price))
3334
call_contract = option_chain[expiry][atm_strike].get(ContractType.CALL)
3435
put_contract = option_chain[expiry][atm_strike].get(ContractType.PUT)
35-
# Fetch implied volatility via snapshot endpoint
36+
# Skip if either leg is missing (to avoid AttributeError)
37+
if call_contract is None or put_contract is None:
38+
print(f"Skipping expiry {expiry} for strike {atm_strike} due to missing leg: call={call_contract}, put={put_contract}")
39+
continue
3640
call_symbol = call_contract.symbol
3741
put_symbol = put_contract.symbol
3842
req = OptionSnapshotRequest(symbol_or_symbols=[call_symbol, put_symbol])
@@ -66,5 +70,14 @@
6670
atm_resp = options_client.get_option_snapshot(atm_req)
6771
for sym in atm_symbols:
6872
snap = atm_resp.get(sym)
73+
# Show full snapshot object attributes to inspect keys
74+
print(f"Full ATM snapshot for {sym}:")
75+
pprint.pprint(snap.__dict__ if snap else None)
76+
# Access implied_volatility directly
6977
iv = snap.implied_volatility if snap else None
70-
print(f"ATM Snapshot: {sym}, IV={iv}")
78+
print(f"Extracted IV: {iv}\n")
79+
# Test compute_recommendation from automation
80+
from automation import compute_recommendation
81+
print("\nTesting compute_recommendation()...")
82+
rec = compute_recommendation(symbol)
83+
print(f"compute_recommendation({symbol}) => {rec}")

trade_workflow.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,13 @@ def run_trade_workflow():
180180
if is_time_to_open(earnings_date, when_norm):
181181
print(f"Preparing BMO trade for {ticker} ({when_norm})...")
182182
stock = yf.Ticker(ticker)
183-
expiry_short, expiry_long, strike = select_expiries_and_strike_alpaca(ticker, earnings_date)
183+
# allow same-day expiry for BMO by filtering from one day earlier
184+
filter_date = earnings_date - timedelta(days=1) if when_norm == 'BMO' else earnings_date
185+
expiry_short, expiry_long, strike = select_expiries_and_strike_alpaca(ticker, filter_date)
184186
if not expiry_short or not expiry_long or not strike:
185187
print(f"Could not determine expiries/strike for {ticker} using Alpaca. Trying Yahoo...")
186188
stock = yf.Ticker(ticker)
187-
expiry_short, expiry_long, strike = select_expiries_and_strike_yahoo(stock, earnings_date)
189+
expiry_short, expiry_long, strike = select_expiries_and_strike_yahoo(stock, filter_date)
188190
if not expiry_short or not expiry_long or not strike:
189191
print(f"Could not determine expiries/strike for {ticker}. Skipping.")
190192
continue

0 commit comments

Comments
 (0)