@@ -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 } " )
0 commit comments