22import requests
33from dotenv import load_dotenv
44from datetime import datetime , timedelta , time
5- from automation import compute_recommendation , get_tomorrows_earnings
5+ from automation import compute_recommendation , get_tomorrows_earnings , get_todays_earnings
66from alpaca_integration import place_calendar_spread_order , close_calendar_spread_order , get_portfolio_value
77import yfinance as yf
88
@@ -150,25 +150,29 @@ def run_trade_workflow():
150150 except Exception as e :
151151 print (f"Error closing trade: { e } " )
152152 # 2. Screen and open new trades
153- ticker_dicts = get_tomorrows_earnings ()
153+ # Fetch both today's and tomorrow's earnings
154+ todays_earnings = get_todays_earnings ()
155+ tomorrows_earnings = get_tomorrows_earnings ()
154156 portfolio_value = get_portfolio_value ()
155157 if not portfolio_value :
156158 print ("Could not fetch portfolio value. Skipping trade opening." )
157159 return
158- for ticker_info in ticker_dicts :
160+ # Open BMO trades for tomorrow's earnings (open the day before)
161+ for ticker_info in tomorrows_earnings :
159162 ticker = ticker_info ['act_symbol' ]
160163 when = ticker_info .get ('when' )
161164 if not when :
162165 print (f"Skipping { ticker } : no 'when' info available." )
163166 continue
164- # Normalize 'when' to BMO/AMC
165167 when_norm = 'BMO' if 'before' in (when or '' ).lower () else 'AMC'
168+ if when_norm != 'BMO' :
169+ continue # Only process BMO here
166170 try :
167171 rec = compute_recommendation (ticker )
168172 if isinstance (rec , dict ) and rec .get ('avg_volume' ) and rec .get ('iv30_rv30' ) and rec .get ('ts_slope_0_45' ):
169173 earnings_date = datetime .now ().date () + timedelta (days = 1 )
170174 if is_time_to_open (earnings_date , when_norm ):
171- print (f"Preparing trade for { ticker } ({ when_norm } )..." )
175+ print (f"Preparing BMO trade for { ticker } ({ when_norm } )..." )
172176 stock = yf .Ticker (ticker )
173177 expiry_short , expiry_long , strike = select_expiries_and_strike (stock , earnings_date )
174178 if not expiry_short or not expiry_long or not strike :
@@ -185,24 +189,110 @@ def run_trade_workflow():
185189 print (f"Kelly sizing yields 0 contracts for { ticker } . Skipping." )
186190 continue
187191 implied_move = rec .get ('expected_move' , '' )
188- print (f"Opening trade for { ticker } : { quantity } x { expiry_short } /{ expiry_long } @ { strike } , cost/spread: ${ spread_cost :.2f} , Kelly allocation: ${ max_allocation :.2f} , Implied Move: { implied_move } " )
192+ print (f"Opening BMO trade for { ticker } : { quantity } x { expiry_short } /{ expiry_long } @ { strike } , cost/spread: ${ spread_cost :.2f} , Kelly allocation: ${ max_allocation :.2f} , Implied Move: { implied_move } " )
193+ order = place_calendar_spread_order (
194+ ticker ,
195+ quantity ,
196+ expiry_short ,
197+ expiry_long ,
198+ strike
199+ )
200+ if order is None :
201+ print (f"Order placement failed for { ticker } . Skipping posting to Google Sheets." )
202+ continue
203+ open_price = spread_cost
204+ open_comm = 0
205+ if hasattr (order , 'legs' ):
206+ try :
207+ open_price = sum ([float (getattr (leg , 'filled_avg_price' , 0 ) or 0 ) for leg in order .legs ])
208+ open_comm = getattr (order , 'commission' , 0 ) or 0
209+ except Exception :
210+ pass
211+ post_trade ({
212+ 'Ticker' : ticker ,
213+ 'Implied Move' : implied_move ,
214+ 'Structure' : 'Calendar Spread' ,
215+ 'Side' : 'Long' ,
216+ 'Size' : quantity ,
217+ 'Open Date' : datetime .now ().strftime ('%Y-%m-%d' ),
218+ 'Open Price' : open_price ,
219+ 'Open Comm.' : open_comm ,
220+ 'Close Date' : '' ,
221+ 'Close Price' : '' ,
222+ 'Close Comm.' : ''
223+ })
224+ else :
225+ print (f"Skipping { ticker } : not in correct time window to open BMO trade." )
226+ except Exception as e :
227+ print (f"Error screening/opening BMO trade for { ticker } : { e } " )
228+ # Open AMC trades for today's earnings (open the day of)
229+ for ticker_info in todays_earnings :
230+ ticker = ticker_info ['act_symbol' ]
231+ when = ticker_info .get ('when' )
232+ if not when :
233+ print (f"Skipping { ticker } : no 'when' info available." )
234+ continue
235+ when_norm = 'BMO' if 'before' in (when or '' ).lower () else 'AMC'
236+ if when_norm != 'AMC' :
237+ continue # Only process AMC here
238+ try :
239+ rec = compute_recommendation (ticker )
240+ if isinstance (rec , dict ) and rec .get ('avg_volume' ) and rec .get ('iv30_rv30' ) and rec .get ('ts_slope_0_45' ):
241+ earnings_date = datetime .now ().date ()
242+ if is_time_to_open (earnings_date , when_norm ):
243+ print (f"Preparing AMC trade for { ticker } ({ when_norm } )..." )
244+ stock = yf .Ticker (ticker )
245+ expiry_short , expiry_long , strike = select_expiries_and_strike (stock , earnings_date )
246+ if not expiry_short or not expiry_long or not strike :
247+ print (f"Could not determine expiries/strike for { ticker } . Skipping." )
248+ continue
249+ spread_cost = calculate_calendar_spread_cost (stock , expiry_short , expiry_long , strike )
250+ if not spread_cost or spread_cost <= 0 :
251+ print (f"Invalid spread cost for { ticker } . Skipping." )
252+ continue
253+ kelly_fraction = 0.10
254+ max_allocation = portfolio_value * kelly_fraction
255+ quantity = int (max_allocation // (spread_cost * 100 )) # 1 contract = 100 shares
256+ if quantity < 1 :
257+ print (f"Kelly sizing yields 0 contracts for { ticker } . Skipping." )
258+ continue
259+ implied_move = rec .get ('expected_move' , '' )
260+ print (f"Opening AMC trade for { ticker } : { quantity } x { expiry_short } /{ expiry_long } @ { strike } , cost/spread: ${ spread_cost :.2f} , Kelly allocation: ${ max_allocation :.2f} , Implied Move: { implied_move } " )
261+ order = place_calendar_spread_order (
262+ ticker ,
263+ quantity ,
264+ expiry_short ,
265+ expiry_long ,
266+ strike
267+ )
268+ if order is None :
269+ print (f"Order placement failed for { ticker } . Skipping posting to Google Sheets." )
270+ continue
271+ open_price = spread_cost
272+ open_comm = 0
273+ if hasattr (order , 'legs' ):
274+ try :
275+ open_price = sum ([float (getattr (leg , 'filled_avg_price' , 0 ) or 0 ) for leg in order .legs ])
276+ open_comm = getattr (order , 'commission' , 0 ) or 0
277+ except Exception :
278+ pass
189279 post_trade ({
190280 'Ticker' : ticker ,
191281 'Implied Move' : implied_move ,
192282 'Structure' : 'Calendar Spread' ,
193283 'Side' : 'Long' ,
194284 'Size' : quantity ,
195285 'Open Date' : datetime .now ().strftime ('%Y-%m-%d' ),
196- 'Open Price' : spread_cost , # or actual fill price if available
197- 'Open Comm.' : 0 ,
286+ 'Open Price' : open_price ,
287+ 'Open Comm.' : open_comm ,
198288 'Close Date' : '' ,
199289 'Close Price' : '' ,
200290 'Close Comm.' : ''
201291 })
202292 else :
203- print (f"Skipping { ticker } : not in correct time window to open trade ( { when_norm } ) ." )
293+ print (f"Skipping { ticker } : not in correct time window to open AMC trade ." )
204294 except Exception as e :
205- print (f"Error screening/opening trade for { ticker } : { e } " )
295+ print (f"Error screening/opening AMC trade for { ticker } : { e } " )
206296
207297if __name__ == "__main__" :
208298 run_trade_workflow ()
0 commit comments