Overall Statistics |
Total Orders 324 Average Win 1.70% Average Loss -4.24% Compounding Annual Return 56.657% Drawdown 9.400% Expectancy 0.294 Start Equity 100000 End Equity 1278419.56 Net Profit 1178.420% Sharpe Ratio 2.426 Sortino Ratio 1.132 Probabilistic Sharpe Ratio 99.996% Loss Rate 8% Win Rate 92% Profit-Loss Ratio 0.40 Alpha 0.348 Beta 0.015 Annual Standard Deviation 0.144 Annual Variance 0.021 Information Ratio 1.215 Tracking Error 0.225 Treynor Ratio 22.809 Total Fees $7692.87 Estimated Strategy Capacity $260000.00 Lowest Capacity Asset INTU R735QTJ8XC9X Portfolio Turnover 10.59% |
#region imports from AlgorithmImports import * #endregion ############################################################ BACKTEST SETTINGS ######################################################################## # !!! Right now the historical earnings data only goes back to 2017 BACKTEST_START_YEAR = 2019 # Set start Year of the Backtest BACKTEST_START_MONTH = 8 # Set start Month of the Backtest BACKTEST_START_DAY = 10 # Set start Day of the Backtest BACKTEST_END_YEAR = 2026 # Set end Year of the Backtest BACKTEST_END_MONTH = 2 # Set end Month of the Backtest BACKTEST_END_DAY = 1 # Set end Day of the Backtest BACKTEST_ACCOUNT_CASH = 100000 # Set Backtest Strategy Cash # Set this to "BACKTEST" when backtesting and to "LIVE" when live trading MODE = "BACKTEST" # In this version I have set it to only allow for one trade at a time with 100% cash allocated for that trade. # Percentage of cash allocated per position, 100 = 100% CASH_PERCENTAGE_PER_POSITION = 100 # Pick between "NUMERIC", "ATR", "STANDARD_DEVIATION" EXIT_PERCENTAGE_TYPE = "NUMERIC" # Required upwards price move before entering, 1 = 1% PRICE_MOVE_BEFORE_ENTRY = 1 # Profit percentage, 3 = 3% PROFIT_PERCENTAGE = 3 # Stop percentage, 5 = 5% STOP_PERCENTAGE = 5 # Trailing percentage, 1 = 1% TRAIL_PERCENTAGE = 1 # Set this to 1 to include beating eps estimate as a filter requirment, set to 0 to not include it as filter USE_BEAT_EPS = 1 # Set this to 1 to include beating revenue estimate as a filter requirment, set to 0 to not include it as filter USE_BEAT_REVENUE = 0 # Percentage of earnings per stock that must have beaten the EPS estimate when USE_BEAT_EPS = 1, 0.66 = 66% BEAT_EPS_PERCENTAGE = 0.66 # Percentage of earnings per stock that must have beaten the Revenue estimate when USE_BEAT_REVENUE = 1, 0.66 = 66% BEAT_REVENUE_PERCENTAGE = 0.66 # Percentage of earnings per stock that must have included a raised guidance, 0.3 = 30% GUIDANCE_RAISED_PERCENTAGE = 0.1 # Percentage that the actual eps has to be higher than the estimate, 2 = 2% EPS_SURPRISE_PERCENTAGE = 2 # Percentage that the actual revenue has to be higher than the estimate, 2 = 2% REVENUE_SURPRISE_PERCENTAGE = 1
# region imports from AlgorithmImports import * # endregion from io import StringIO import pandas as pd from symboldata import SymbolData import config import math class EmotionalBlackChinchilla(QCAlgorithm): def Initialize(self): self.SetStartDate(config.BACKTEST_START_YEAR, config.BACKTEST_START_MONTH, config.BACKTEST_START_DAY) # Set Backtest Start Date self.SetEndDate(config.BACKTEST_END_YEAR, config.BACKTEST_END_MONTH, config.BACKTEST_END_DAY) # Set Backtest End Date self.SetCash(config.BACKTEST_ACCOUNT_CASH) self.mode = config.MODE self.cash_percent = config.CASH_PERCENTAGE_PER_POSITION/100 self.percent_exit_type = config.EXIT_PERCENTAGE_TYPE self.move_pre_entry = 1 + (config.PRICE_MOVE_BEFORE_ENTRY/100) self.SetSecurityInitializer(self.CustomSecurityInitializer) self.profit_percentage = 1 + (config.PROFIT_PERCENTAGE/100) self.stop_percentage = 1 - (config.STOP_PERCENTAGE/100) self.trail_percentage = 1 + (config.TRAIL_PERCENTAGE/100) self.AddUniverse(self.CoarseFilterFunction, self.FineFilterFunction) self.UniverseSettings.Resolution = Resolution.Minute self.UniverseSettings.ExtendedMarketHours = True self.UniverseSettings.Leverage = 4 self.symbol_instances = {} # -------------------------------------------------------------------- # LIVE MODE # -------------------------------------------------------------------- if self.mode == "LIVE": x = self.Download("https://docs.google.com/spreadsheets/d/1KoL8P32yXQZiMbnsDAfQ7F4qT8fL73L5/export?format=csv") # Schedules self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 0), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 5), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 10), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 15), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 20), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 25), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 30), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 35), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 40), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 45), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 50), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 55), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(17, 0), self.get_updated_earnings) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(8, 0), self.get_updated_database) x = self.Download("https://docs.google.com/spreadsheets/d/1KoL8P32yXQZiMbnsDAfQ7F4qT8fL73L5/export?format=csv") # Read CSV data into a DataFrame df = pd.read_csv(StringIO(x)) self.Debug(f"[LIVE] Columns in CSV: {df.columns}") # Apply the filter conditions directly on the DataFrame self.df = df.groupby('Symbol').filter(lambda x: len(x) >= 10 and (x['Beat_EPS'].mean() >= config.BEAT_EPS_PERCENTAGE if config.USE_BEAT_EPS else True) and (x['Beat_Revenue'].mean() >= config.BEAT_REVENUE_PERCENTAGE if config.USE_BEAT_REVENUE else True) and (x['Raised_EPS_Guidance'].sum() > 0 or x['Raised_REVENUE_Guidance'].sum() > 0).mean() >= config.GUIDANCE_RAISED_PERCENTAGE and (x['Price_Change'] >= 0.01).mean() >= 0.5 and (x['Price_Change'] >= 0.01) and (x['EPS_Surprise_Earnings'] >= config.EPS_SURPRISE_PERCENTAGE) and (x['Revenue_Surprise_Earnings'] >= config.REVENUE_SURPRISE_PERCENTAGE)) # Get unique symbols self.symbol_list = self.df['Symbol'].unique().tolist() self.Debug(f"[LIVE] After filter, symbol_list = {self.symbol_list}") # -------------------------------------------------------------------- # BACKTEST MODE # -------------------------------------------------------------------- else: x = self.Download("https://docs.google.com/spreadsheets/d/1A2oY8FA9197O9LpZWtae5QdAC0jrIT8s9N5fzsUJg4Y/export?format=csv") df = pd.read_csv(StringIO(x)) self.Debug(f"### DEBUG ### CSV columns: {df.columns.tolist()}") self.Debug(f"### DEBUG ### First few rows:\n{df.head()}") # Ensure numeric columns are truly numeric numeric_columns = [ 'Price_Change', 'Beat_EPS', 'Beat_Revenue', 'EPS_Surprise_Earnings', 'Revenue_Surprise_Earnings' ] for col in numeric_columns: df[col] = pd.to_numeric(df[col], errors='coerce') # 1) Condition: Beat_EPS if config.USE_BEAT_EPS: cond_beat_eps = df.groupby('Symbol')['Beat_EPS'].transform(lambda x: x.sum() / len(x)) >= config.BEAT_EPS_PERCENTAGE else: cond_beat_eps = pd.Series([True]*len(df), index=df.index) self.Debug(f"### DEBUG ### cond_beat_eps passing rows: {cond_beat_eps.sum()} / {len(cond_beat_eps)}") # 2) Condition: Beat_Revenue if config.USE_BEAT_REVENUE: cond_beat_revenue = df.groupby('Symbol')['Beat_Revenue'].transform(lambda x: x.sum() / len(x)) >= config.BEAT_REVENUE_PERCENTAGE else: cond_beat_revenue = pd.Series([True]*len(df), index=df.index) self.Debug(f"### DEBUG ### cond_beat_revenue passing rows: {cond_beat_revenue.sum()} / {len(cond_beat_revenue)}") # 3) Condition: Guidance cond_guidance = ( (df['Raised_EPS_Guidance'] == 1).groupby(df['Symbol']).transform('any') | (df['Raised_REVENUE_Guidance'] == 1).groupby(df['Symbol']).transform('any') ) self.Debug(f"### DEBUG ### cond_guidance passing rows: {cond_guidance.sum()} / {len(cond_guidance)}") # 4) Condition: Price_Change ratio >= 0.52 cond_price_change_ratio = df.groupby('Symbol')['Price_Change'] \ .transform(lambda x: (x >= 0.01).sum() / len(x)) >= 0.52 self.Debug(f"### DEBUG ### cond_price_change_ratio passing rows: {cond_price_change_ratio.sum()} / {len(cond_price_change_ratio)}") # 5) Condition: EPS_Surprise_Earnings cond_eps_surprise = df.groupby('Symbol')['EPS_Surprise_Earnings'].transform('mean') >= config.EPS_SURPRISE_PERCENTAGE self.Debug(f"### DEBUG ### cond_eps_surprise passing rows: {cond_eps_surprise.sum()} / {len(cond_eps_surprise)}") # 6) Condition: Revenue_Surprise_Earnings cond_rev_surprise = df.groupby('Symbol')['Revenue_Surprise_Earnings'].transform('mean') >= config.REVENUE_SURPRISE_PERCENTAGE self.Debug(f"### DEBUG ### cond_rev_surprise passing rows: {cond_rev_surprise.sum()} / {len(cond_rev_surprise)}") # 7) Condition: row-level Price_Change >= 0.01 cond_price_change = df['Price_Change'] >= 0.01 self.Debug(f"### DEBUG ### cond_price_change passing rows: {cond_price_change.sum()} / {len(cond_price_change)}") # Combine them all combined_mask = ( cond_beat_eps & cond_beat_revenue & cond_guidance & cond_price_change_ratio & cond_eps_surprise & cond_rev_surprise & cond_price_change ) self.df = df[combined_mask] self.Debug(f"### DEBUG ### Combined mask final row count: {len(self.df)}") # 3) Symbol list self.symbol_list = self.df['Symbol'].unique().tolist() self.Debug(f"### DEBUG ### Final symbol_list count: {len(self.symbol_list)} => {self.symbol_list}") def CustomSecurityInitializer(self, security): security.SetLeverage(4) def get_updated_database(self): x = self.Download("https://docs.google.com/spreadsheets/d/1KoL8P32yXQZiMbnsDAfQ7F4qT8fL73L5/export?format=csv") df = pd.read_csv(StringIO(x)) # Apply the filter conditions directly on the DataFrame self.df = df.groupby('Symbol').filter(lambda x: len(x) >= 10 and (x['Beat_EPS'].mean() >= config.BEAT_EPS_PERCENTAGE if config.USE_BEAT_EPS else True) and (x['Beat_Revenue'].mean() >= config.BEAT_REVENUE_PERCENTAGE if config.USE_BEAT_REVENUE else True) and (x['Raised_EPS_Guidance'].sum() > 0 or x['Raised_REVENUE_Guidance'].sum() > 0).mean() >= config.GUIDANCE_RAISED_PERCENTAGE and (x['Price_Change'] >= 0.01).mean() >= 0.5 and (x['Price_Change'] >= 0.01) and (x['EPS_Surprise_Earnings'] >= config.EPS_SURPRISE_PERCENTAGE) and (x['Revenue_Surprise_Earnings'] >= config.REVENUE_SURPRISE_PERCENTAGE)) # Get unique symbols self.symbol_list = self.df['Symbol'].unique().tolist() self.Debug(f"[LIVE] get_updated_database => symbol_list now has {len(self.symbol_list)} symbols") def get_updated_earnings(self): x = self.Download("https://docs.google.com/spreadsheets/d/1KoL8P32yXQZiMbnsDAfQ7F4qT8fL73L5/export?format=csv") self.earnings_today_df = pd.read_csv(StringIO(x)) # Debug: fix the loop to iterate properly self.Debug("[LIVE] get_updated_earnings => First few rows from earnings_today_df:") for i, row in self.earnings_today_df.head(5).iterrows(): self.Debug(str(row)) def OnSecuritiesChanged(self, changes): for security in changes.RemovedSecurities: symbol = security.Symbol if symbol in self.symbol_instances: self.symbol_instances.pop(symbol, None) for security in changes.AddedSecurities: symbol = security.Symbol if symbol not in self.symbol_instances: self.symbol_instances[symbol] = SymbolData(self, symbol) # Debug: Show the new_string or the actual ticker self.Debug(f"### OnSecuritiesChanged ### Created SymbolData for {symbol} => new_string={self.symbol_instances[symbol].new_string}") def CoarseFilterFunction(self, coarse: List[CoarseFundamental]) -> List[Symbol]: filtered = [c.Symbol for c in coarse if c.HasFundamentalData and c.Volume >= 1_000_000 and c.Symbol.Value in self.symbol_list] if len(filtered) == 0: self.Debug("WARNING: CoarseFilter returned 0 symbols. Potential mismatch with symbol_list or volume filter.") invested = [i.Key for i in self.Portfolio if i.Value.Invested] unique = list(set(invested + filtered)) return unique def FineFilterFunction(self, fine: List[FineFundamental]) -> List[Symbol]: filtered = [f.Symbol for f in fine if f.MarketCap >= 5_000_000_000] if len(filtered) == 0: self.Debug("WARNING: FineFilter returned 0 symbols. Potential mismatch with MarketCap filter.") invested = [i.Key for i in self.Portfolio if i.Value.Invested] unique = list(set(invested + filtered)) return unique def OnData(self, data: Slice): # ---------------------------- # BACKTEST MODE OnData # ---------------------------- if self.mode == "BACKTEST": if self.Portfolio.Invested: for symbol, symbolData in self.symbol_instances.items(): if self.percent_exit_type == "ATR": self.move_pre_entry = 1 + (symbolData.natr.Current.Value/100) self.profit_percentage = 1 + (symbolData.natr.Current.Value/100) self.stop_percentage = 1 - (symbolData.natr.Current.Value/100) self.trail_percentage = 1 + (symbolData.natr.Current.Value/100) elif self.percent_exit_type == "STANDARD_DEVIATION": self.move_pre_entry = 1 + (symbolData.stdev_percent) self.profit_percentage = 1 + (symbolData.stdev_percent) self.stop_percentage = 1 - (symbolData.stdev_percent) self.trail_percentage = 1 + (symbolData.stdev_percent) if self.Portfolio[symbol].Invested: # profit if (self.Securities[symbol].Price > (self.Portfolio[symbol].AveragePrice * self.profit_percentage) and not symbolData.sold_half and not symbolData.open_profit_order): self.LimitOrder(symbol, -round(self.Portfolio[symbol].Quantity/2), 0, tag="PROFIT") symbolData.sold_half = True symbolData.trail_start = self.Securities[symbol].Price symbolData.open_profit_order = True # stop elif (self.Securities[symbol].Price < (self.Portfolio[symbol].AveragePrice * self.stop_percentage) and not symbolData.open_stop_order): self.LimitOrder(symbol, -self.Portfolio[symbol].Quantity, 0, tag="STOP") symbolData.sold_half = False symbolData.trail_start = None symbolData.open_stop_order = True else: # trailing if symbolData.trail_start is not None and self.Securities[symbol].Price > symbolData.trail_start: symbolData.trail_start = self.Securities[symbol].Price if (symbolData.trail_start is not None and self.Securities[symbol].Price * self.trail_percentage <= symbolData.trail_start and not symbolData.open_trail_order): self.LimitOrder(symbol, -self.Portfolio[symbol].Quantity, 0, tag="TRAIL STOP") symbolData.sold_half = False symbolData.trail_start = None symbolData.open_trail_order = True else: # reset symbolData.open_profit_order = False symbolData.open_trail_order = False symbolData.open_stop_order = False symbolData.open_entry_order = False # At 16:00, cancel orders, reset ten_am_price if self.Time.hour == 16 and self.Time.minute == 0: self.Transactions.CancelOpenOrders() for symbol, symbolData in self.symbol_instances.items(): symbolData.ten_am_price = self.Securities[symbol].Price symbolData.open_entry_order = False # After 16:00, check for new trades if self.Time.hour == 16 and self.Time.minute >= 1: formatted_date = self.Time.strftime("%Y-%m-%d") # -- Debug Check: does formatted_date exist in df["Date_Earnings"]? if formatted_date not in self.df["Date_Earnings"].unique(): self.Debug(f"MISMATCH WARNING: {formatted_date} not found in df['Date_Earnings'].unique() => {self.df['Date_Earnings'].unique()}") # -- Debug Check: does 'amc' exist in Time_Earnings? unique_times = self.df["Time_Earnings"].unique().tolist() if "amc" not in unique_times: self.Debug(f"MISMATCH WARNING: 'amc' not found in df['Time_Earnings'].unique() => {unique_times}") symbols = self.df.loc[ (self.df["Date_Earnings"] == formatted_date) & (self.df["Time_Earnings"] == "amc"), "Symbol" ] if len(symbols) > 0: # Debug: check for symbol mismatch for csv_symbol in symbols: # Do we have any SymbolData with new_string = csv_symbol? if csv_symbol not in [sd.new_string for sd in self.symbol_instances.values()]: self.Debug(f"MISMATCH WARNING: CSV symbol '{csv_symbol}' not found among symbolData.new_string " f"=> {[sd.new_string for sd in self.symbol_instances.values()]}") for symbol, symbolData in self.symbol_instances.items(): if not self.Portfolio.Invested and self.Securities[symbol].Price != 0: cash = self.Portfolio.Cash * self.cash_percent quantity = math.floor(cash / self.Securities[symbol].Price) # Check if symbol is in 'symbols' if symbols.isin([symbolData.new_string]).any(): if (not self.Portfolio.Invested and self.Securities[symbol].Price >= symbolData.ten_am_price * self.move_pre_entry and not symbolData.open_entry_order and quantity > 1): self.LimitOrder(symbol, quantity, self.Securities[symbol].Price) symbolData.open_entry_order = True if symbolData.open_entry_order: break # ---------------------------- # LIVE MODE OnData # ---------------------------- else: if self.Portfolio.Invested: for symbol, symbolData in self.symbol_instances.items(): if self.percent_exit_type == "ATR": self.move_pre_entry = 1 + (symbolData.natr.Current.Value/100) self.profit_percentage = 1 + (symbolData.natr.Current.Value/100) self.stop_percentage = 1 - (symbolData.natr.Current.Value/100) self.trail_percentage = 1 + (symbolData.natr.Current.Value/100) elif self.percent_exit_type == "STANDARD_DEVIATION": self.move_pre_entry = 1 + (symbolData.stdev_percent) self.profit_percentage = 1 + (symbolData.stdev_percent) self.stop_percentage = 1 - (symbolData.stdev_percent) self.trail_percentage = 1 + (symbolData.stdev_percent) if self.Portfolio[symbol].Invested: if (self.Securities[symbol].Price > (self.Portfolio[symbol].AveragePrice * self.profit_percentage) and not symbolData.sold_half and not symbolData.open_profit_order): self.LimitOrder(symbol, -round(self.Portfolio[symbol].Quantity/2), 0, tag="PROFIT") symbolData.sold_half = True symbolData.trail_start = self.Securities[symbol].Price symbolData.open_profit_order = True elif (self.Securities[symbol].Price < (self.Portfolio[symbol].AveragePrice * self.stop_percentage) and not symbolData.open_stop_order): self.LimitOrder(symbol, -self.Portfolio[symbol].Quantity, 0, tag="STOP") symbolData.sold_half = False symbolData.trail_start = None symbolData.open_stop_order = True else: if symbolData.trail_start is not None and self.Securities[symbol].Price > symbolData.trail_start: symbolData.trail_start = self.Securities[symbol].Price if (symbolData.trail_start is not None and self.Securities[symbol].Price * self.trail_percentage <= symbolData.trail_start and not symbolData.open_trail_order): self.LimitOrder(symbol, -self.Portfolio[symbol].Quantity, 0, tag="TRAIL STOP") symbolData.sold_half = False symbolData.trail_start = None symbolData.open_trail_order = True else: symbolData.open_profit_order = False symbolData.open_trail_order = False symbolData.open_stop_order = False symbolData.open_entry_order = False if self.Time.hour == 16 and self.Time.minute == 0: for symbol, symbolData in self.symbol_instances.items(): symbolData.ten_am_price = self.Securities[symbol].Price if self.Time.hour == 16 and self.Time.minute >= 1: formatted_date = self.Time.strftime("%Y-%m-%d") # Quick mismatch debug if formatted_date not in self.earnings_today_df["Date"].unique(): self.Debug(f"[LIVE] MISMATCH: {formatted_date} not in earnings_today_df['Date'].unique() => " f"{self.earnings_today_df['Date'].unique()}") # Example logic (depends on your config) if config.USE_BEAT_EPS and config.USE_BEAT_REVENUE: symbols = self.earnings_today_df.loc[ (self.earnings_today_df["Date"] == formatted_date) & (self.earnings_today_df["Actual"] > self.earnings_today_df["ActEstimate"]) & (self.earnings_today_df["RevActual"] > self.earnings_today_df["ActRevEst"]) & (self.earnings_today_df["Guidance"] == "pos"), "Symbol" ] elif config.USE_BEAT_EPS: symbols = self.earnings_today_df.loc[ (self.earnings_today_df["Date"] == formatted_date) & (self.earnings_today_df["Actual"] > self.earnings_today_df["ActEstimate"]) & (self.earnings_today_df["Guidance"] == "pos"), "Symbol" ] elif config.USE_BEAT_REVENUE: symbols = self.earnings_today_df.loc[ (self.earnings_today_df["Date"] == formatted_date) & (self.earnings_today_df["RevActual"] > self.earnings_today_df["ActRevEst"]) & (self.earnings_today_df["Guidance"] == "pos"), "Symbol" ] else: symbols = self.earnings_today_df.loc[ (self.earnings_today_df["Date"] == formatted_date) & (self.earnings_today_df["Guidance"] == "pos"), "Symbol" ] if len(symbols) > 0: # Debug check for symbol mismatch for csv_symbol in symbols: if csv_symbol not in [sd.new_string for sd in self.symbol_instances.values()]: self.Debug(f"[LIVE] MISMATCH WARNING: CSV symbol '{csv_symbol}' " f"not found among symbolData.new_string => {[sd.new_string for sd in self.symbol_instances.values()]}") for symbol, symbolData in self.symbol_instances.items(): cash = self.Portfolio.Cash * self.cash_percent quantity = math.floor(cash / self.Securities[symbol].Price) if symbols.isin([symbolData.new_string]).any(): if (not self.Portfolio[symbol].Invested and self.Securities[symbol].Price >= symbolData.ten_am_price * self.move_pre_entry and not symbolData.open_entry_order and quantity > 1): self.LimitOrder(symbol, quantity, self.Securities[symbol].Price) symbolData.open_entry_order = True
#region imports from AlgorithmImports import * #endregion class SymbolData(): def __init__(self, algo, symbol): self.symbol = symbol self.algo = algo self.new_string = str(symbol).split(" ")[0] self.natr = NormalizedAverageTrueRange(14) self.standard_deviation = StandardDeviation(14) self.stdev_percent = None self.bar_consolidator = TradeBarConsolidator(timedelta(hours=1)) self.algo.SubscriptionManager.AddConsolidator(symbol, self.bar_consolidator) self.bar_consolidator.DataConsolidated += self.receive_bars history = self.algo.History[TradeBar](self.symbol, 1000, Resolution.Minute) for bar in history: self.bar_consolidator.Update(bar) self.ten_am_price = None self.algo = algo self.sold_half = False self.trail_start = None self.open_entry_order = False self.open_profit_order = False self.open_trail_order = False self.open_stop_order = False def receive_bars(self, sender, bar): self.natr.Update(bar) self.standard_deviation.Update(IndicatorDataPoint(bar.EndTime, bar.Close)) if self.standard_deviation.IsReady: self.stdev_percent = self.standard_deviation.Current.Value / bar.Close