Overall Statistics |
Total Orders 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Start Equity 100000 End Equity 100000 Net Profit 0% Sharpe Ratio 0 Sortino Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -1.693 Tracking Error 0.104 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset Portfolio Turnover 0% |
from AlgorithmImports import * class EarningsATRUniverseAlgorithm(QCAlgorithm): def Initialize(self): # sample start date self.set_start_date(2024, 1, 1) # sample end date self.set_end_date(2024, 10, 31) self.set_cash(100000) # get 2024 earnings calendar from object store, can store custom data in object store self.df_earnings = self.get_df_from_store('sylvaint/earnings_2024.csv') # List to store filtered symbols and dates for earnings and ATR checks self.filtered_symbols = [] # set resolution to Daily self.universe_settings.resolution = Resolution.DAILY # add stock universe with coarse filter function self.add_universe(self.CoarseSelectionFunction) # Dictionary to track symbols' ATR and earnings previous close self.symbol_data = {} self.prev_close = {} # check symbol has earnings for date def is_stock_earnings(self, date_string, symbol): try: stock_array = ( self.df_earnings .query('date==@date_string') .assign(symbols=lambda df_: df_['symbols'].apply(lambda x: x.split(','))) .symbols .to_list()[0] ) except: return False return symbol in stock_array # read earnings dataframe from store def get_df_from_store(self, key): file_path = self.object_store.get_file_path(key) return pd.read_csv(file_path) # convert self.time to date string def time_to_date_string(self, t): return t.strftime('%Y-%m-%d') # this is the coarse filter to our stock universe, it is run for every date in our sample range def CoarseSelectionFunction(self, coarse): # universe selection occurs at midnight so previous day data # print the current algorithm date self.Debug(f"Coarse selection date: {self.time}") selected = [] for stock in coarse: # Filter by price > 10 and volume > 500,000 and earnings today if (stock.Price > 10 and stock.Volume > 500000 and # this checks if stock reported earnings yesterday self.is_stock_earnings(self.time_to_date_string(self.time - timedelta(days=1)), stock.symbol.value)): # store previous close self.prev_close[stock.Symbol.value] = stock.adjusted_price selected.append(stock.Symbol) # self.debug(f"{len(selected)} stocks from coarse") return selected # this is called every day at midnight and registers added and removed securities according to the coarse filter def OnSecuritiesChanged(self, changes): # Subscribe to selected securities from universe filter for security in changes.AddedSecurities: symbol = security.Symbol # Initialize ATR and fundamental data tracking atr = self.atr(symbol, 14, Resolution.DAILY) # Populate ATR with 14 days of historical data indicator_history = self.indicator_history(atr, symbol, 14, Resolution.DAILY) if indicator_history.Current: atr_value = indicator_history.Current[0].value else: atr_value = None self.symbol_data[symbol] = { 'ATR': atr_value, } # remove atr until next occurence for security in changes.RemovedSecurities: symbol = security.Symbol del self.symbol_data[symbol] # this is where data comes in, in this case daily data def on_data(self, data): # Process earnings and ATR conditions for filtered stocks for symbol, sym_data in self.symbol_data.items(): if symbol in data.Bars and sym_data['ATR'] is not None: # Get the current open, close, and ATR values open_price = data.Bars[symbol].Open prev_close = self.prev_close[symbol.value] atr_value = sym_data['ATR'] # Check if the stock had earnings yesterday and opened >2 ATRs above the close if open_price > (prev_close + 2 * atr_value): self.filtered_symbols.append({ 'Symbol': symbol.Value, 'Date': self.Time, 'ATR': atr_value, 'PrevClose': prev_close, 'Open': open_price }) # this is called at the end of the algo def on_end_of_algorithm(self): # Display filtered results at the end of the backtest for result in self.filtered_symbols: # self.log(f"{result['Date']} - {result['Symbol']} - ATR: {result['ATR']} - prev close {result['PrevClose']} - Open {result['Open']} - opened more than 2 ATRs above the previous close.") self.log(f"{result['Date']},{result['Symbol']},{result['ATR']},{result['PrevClose']},{result['Open']}")