Overall Statistics |
Total Orders 547 Average Win 1.16% Average Loss -0.67% Compounding Annual Return 17.668% Drawdown 26.300% Expectancy 0.610 Start Equity 100000 End Equity 290308.64 Net Profit 190.309% Sharpe Ratio 0.73 Sortino Ratio 0.793 Probabilistic Sharpe Ratio 20.062% Loss Rate 41% Win Rate 59% Profit-Loss Ratio 1.74 Alpha 0.085 Beta 0.66 Annual Standard Deviation 0.173 Annual Variance 0.03 Information Ratio 0.463 Tracking Error 0.137 Treynor Ratio 0.191 Total Fees $10948.13 Estimated Strategy Capacity $78000.00 Lowest Capacity Asset KF R735QTJ8XC9X Portfolio Turnover 3.79% |
from AlgorithmImports import * class CustomDataEMFAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2008, 1, 8) self.set_end_date(2014, 7, 25) self.set_cash(100000) self.activeSecurities = set() universe = self.add_universe(MyCustomUniverseDataClass, "myCustomUniverse", self.selector_function) self.universe_settings.resolution = Resolution.DAILY self.portfolioTargets = [] self.aro_data = {1: {}, 3: {}, 5: {}} # Add this line to initialize the dictionary self.cef_discount_data = {} self.cef_purchase_discount = {} ### Define discount percentile upper and lower limits below self.aro_dptl_lower_limit = float(self.get_parameter("dptl_lower_limit")) self.aro_dptl_upper_limit = float(self.get_parameter("dptl_upper_limit")) ###Define period whether, 1, 3, or 5 year below self.period = 1 # Define the selector function def selector_function(self, data): sorted_data = sorted([ x for x in data if x["CEF_Discount"] <= 0 ], key=lambda x: x.end_time) #, # reverse=True) self.Log('Stocks trading at a discount to NAV: ' + str(len(sorted_data))) for x in sorted_data: self.aro_data[5][x.symbol] = x["ARO_DPTL_5YR"] self.aro_data[3][x.symbol] = x["ARO_DPTL_3YR"] self.aro_data[1][x.symbol] = x["ARO_DPTL_1YR"] self.cef_discount_data[x.symbol] = x["CEF_Discount"] return [x.symbol for x in sorted_data] def on_securities_changed(self, changes): # close positions in removed securities for x in changes.RemovedSecurities: self.Debug(f"{self.Time}: Removed {x.Symbol}") # self.Debug(f"ARO_DPTL_{self.period}YR for {x.Symbol} when removed: {self.aro_data[self.period][x.Symbol]}") self.Liquidate(x.Symbol) if x.Symbol in self.activeSecurities: self.Debug(f"ARO_DPTL_{self.period}YR for {x.Symbol} when removed: {self.aro_data[self.period][x.Symbol]}") self.activeSecurities.remove(x.Symbol) del self.aro_data[self.period][x.Symbol] # can't open positions here since data might not be added correctly yet for x in changes.AddedSecurities: self.Debug(f"{self.Time}: Added {x.Symbol}") self.Debug(f"ARO_DPTL_{self.period}YR for {x.Symbol} when added: {self.aro_data[self.period][x.Symbol]}") self.activeSecurities.add(x.Symbol) # adjust targets if universe has changed self.portfolioTargets = [PortfolioTarget(symbol, 1/len(self.activeSecurities)) for symbol in self.activeSecurities] def OnData(self, data): if self.portfolioTargets == []: return for target in self.portfolioTargets: symbol = target.Symbol price = self.Securities[symbol].Price # Get the current price aro_value = self.aro_data[self.period][symbol] cef_discount_value = self.cef_discount_data[symbol] # Get the CEF discount value if aro_value < self.aro_dptl_lower_limit and not self.Portfolio[symbol].Invested: self.cef_purchase_discount[symbol] = self.cef_discount_data[symbol] self.SetHoldings(self.portfolioTargets) # Buy self.Debug(f"Buying {symbol} at {self.Time}, Price: {price}, ARO_DPTL_{self.period}YR: {aro_value}, Purchase Discount: {self.cef_purchase_discount[symbol]}") elif aro_value > self.aro_dptl_upper_limit and self.Portfolio[symbol].Invested: self.Liquidate(symbol) # Sell self.Debug(f"Selling {symbol} at {self.Time}, Price: {price}, ARO_DPTL_{self.period}YR: {aro_value}") self.Plot("ARO Data", f"ARO_DPTL_{self.period}YR", self.aro_data[self.period][symbol]) self.Plot("ARO Data", "CEF_Discount", cef_discount_value) self.Plot("ARO Data", "Price", price) # self.portfolioTargets = [] # Example custom universe data; it is virtually identical to other custom data types. class MyCustomUniverseDataClass(PythonData): def get_source(self, config, date, is_live_mode): source = "https://raw.githubusercontent.com/jabertech/backtest-qc/main/sorted_5_stocks2.csv" return SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile); def Reader(self, config, line, date, isLive): if not (line.strip() and line[0].isalnum()): return None items = line.split(",") # Generate required data, then return an instance of your class. data = MyCustomUniverseDataClass() try: data.end_time = datetime.strptime(items[1], '%Y-%m-%d')+timedelta(hours=20) # define Time as exactly 1 day earlier Time data.time = data.end_time - timedelta(1) data.symbol = Symbol.create(items[0], SecurityType.EQUITY, Market.USA) data.value = float(items[5]) data["Open"] = float(items[2]) data["High"] = float(items[3]) data["Low"] = float(items[4]) data["Close"] = float(items[5]) data["Volume"] = int(items[6]) data["CEF_NAV"] = float(items[7]) data["CEF_Discount"] = float(items[8]) data["CEF_Price"] = float(items[9]) data["ARO_DPTL_1YR"] = float(items[10]) data["ARO_DPTL_3YR"] = float(items[12]) data["ARO_DPTL_5YR"] = float(items[14]) except ValueError: return None return data