Overall Statistics |
Total Orders 39 Average Win 0.43% Average Loss -5.23% Compounding Annual Return -18.174% Drawdown 79.100% Expectancy -0.958 Start Equity 100000 End Equity 24928.72 Net Profit -75.071% Sharpe Ratio -0.662 Sortino Ratio -0.83 Probabilistic Sharpe Ratio 0.000% Loss Rate 96% Win Rate 4% Profit-Loss Ratio 0.08 Alpha -0.07 Beta -0.79 Annual Standard Deviation 0.207 Annual Variance 0.043 Information Ratio -0.668 Tracking Error 0.332 Treynor Ratio 0.173 Total Fees $11.79 Estimated Strategy Capacity $20000000.00 Lowest Capacity Asset BL WF3KPI1IVHID Portfolio Turnover 0.15% |
# https://quantpedia.com/strategies/shorting-goodwill/ # # The investment universe consists of stocks listed on NYSE, AMEX, and NASDAQ. Every year, stocks are sorted into deciles based on their Return on Assets during the previous # fiscal year. Stocks with the lowest ROA are then sorted into deciles based on their goodwill divided by total assets. The investor goes short on stocks with the highest # goodwill and lowest ROA and goes long on stocks with the same market cap. The portfolio is equally weighted and is rebalanced yearly. # # QC implementation changes: # - Universe consists of 3000 largest stocks traded on NYSE, AMEX, or NASDAQ. from AlgorithmImports import * class ShortingGoodwill(QCAlgorithm): def Initialize(self): # self.SetStartDate(2010, 1, 1) self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.symbol:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol self.short:List[Symbol] = [] self.fundamental_count:int = 3000 self.fundamental_sorting_key = lambda x: x.MarketCap self.quantile:int = 10 self.rebalance_month:int = 7 # self.leverage:int = 5 self.leverage:int = 1 self.selection_flag:bool = True self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.FundamentalSelectionFunction) self.Settings.MinimumOrderMarginPortfolioPercentage = 0. self.settings.daily_precise_end_time = False self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection) def OnSecuritiesChanged(self, changes: SecurityChanges) -> None: for security in changes.AddedSecurities: security.SetFeeModel(CustomFeeModel()) security.SetLeverage(self.leverage) def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> None: if not self.selection_flag: return Universe.Unchanged # selected:List[Fundamental] = [x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and \ # ((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE")) and \ # x.OperationRatios.ROA.OneYear != 0 and not np.isnan(x.OperationRatios.ROA.OneYear) and \ # x.FinancialStatements.BalanceSheet.Goodwill.TwelveMonths != 0 and not np.isnan(x.FinancialStatements.BalanceSheet.Goodwill.TwelveMonths) and \ # x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths != 0 and not np.isnan(x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths) # ] selected:List[Fundamental] = [x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and \ ((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE")) and \ x.OperationRatios.ROA.OneYear != 0 and not np.isnan(x.OperationRatios.ROA.OneYear) and \ x.FinancialStatements.BalanceSheet.Goodwill.TwelveMonths != 0 and not np.isnan(x.FinancialStatements.BalanceSheet.Goodwill.TwelveMonths) and \ x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths != 0 and not np.isnan(x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths) and \ x.dollar_volume > 10_000_000 ] if len(selected) > self.fundamental_count: # # Select the 100 most liquid assets. # selected = sorted(selected, key=lambda x: x.dollar_volume, reverse=True)[:100] selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]] if len(selected) >= self.quantile * 2: quantile:int = len(selected) // self.quantile sorted_by_roa:List[Fundamental] = sorted(selected, key = lambda x: x.OperationRatios.ROA.OneYear, reverse=False)[:quantile] quantile:int = len(sorted_by_roa) // self.quantile top_by_goodwill:List[Fundamental] = sorted(sorted_by_roa, key = lambda x: x.FinancialStatements.BalanceSheet.Goodwill.TwelveMonths / x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths, reverse=False)[-quantile:] self.short = list(map(lambda x: x.Symbol, top_by_goodwill)) return self.short def OnData(self, data: Slice) -> None: if not self.selection_flag: return self.selection_flag = False # Trade execution. targets:List[PortfolioTarget] = [PortfolioTarget(symbol, -1 / len(self.short)) for symbol in self.short if symbol in data and data[symbol]] self.SetHoldings(targets, True) self.short.clear() def Selection(self) -> None: if self.Time.year == self.rebalance_month: self.selection_flag = True # self.selection_flag = True # Custom fee model class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD"))
# region imports from AlgorithmImports import * # endregion class ShortingGoodwill(QCAlgorithm): def initialize(self): self.set_start_date(2018, 1, 1) self.set_cash(100000) self.symbol = self.add_equity('SPY', Resolution.DAILY).Symbol self.short = [] self.fundamental_count = 3000 self.fundamental_sorting_key = lambda x: x.market_cap self.quantile = 10 self.leverage = 1 self.selection_flag = True self.universe_settings.resolution = Resolution.DAILY self.add_universe(self.fundamental_selection_function) self.settings.minimum_order_margin_portfolio_percentage = 0. self.settings.daily_precise_end_time = False self.schedule.on(self.date_rules.month_start(self.symbol), self.time_rules.after_market_open(self.symbol), self.selection) def on_securities_changed(self, changes: SecurityChanges) -> None: for security in changes.added_securities: security.set_fee_model(CustomFeeModel()) security.set_leverage(self.leverage) def fundamental_selection_function(self, fundamental: List[Fundamental]) -> List[Symbol]: if not self.selection_flag: return Universe.UNCHANGED selected = [x for x in fundamental if x.has_fundamental_data and x.market == 'usa' and ((x.security_reference.exchange_id == "NYS") or (x.security_reference.exchange_id == "NAS") or (x.security_reference.exchange_id == "ASE")) and x.operation_ratios.ROA.one_year != 0 and not np.isnan(x.operation_ratios.ROA.one_year) and x.financial_statements.balance_sheet.goodwill.twelve_months != 0 and not np.isnan(x.financial_statements.balance_sheet.goodwill.twelve_months) and x.financial_statements.balance_sheet.total_assets.twelve_months != 0 and not np.isnan(x.financial_statements.balance_sheet.total_assets.twelve_months) ] if len(selected) > self.fundamental_count: selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]] if len(selected) >= self.quantile * 2: quantile = len(selected) // self.quantile sorted_by_roa = sorted(selected, key=lambda x: x.operation_ratios.ROA.one_year, reverse=False)[:quantile] quantile = len(sorted_by_roa) // self.quantile top_by_goodwill = sorted(sorted_by_roa, key=lambda x: x.financial_statements.balance_sheet.goodwill.twelve_months / x.financial_statements.balance_sheet.total_assets.twelve_months, reverse=False)[-quantile:] self.short = list(map(lambda x: x.symbol, top_by_goodwill)) return self.short def on_data(self, data: Slice) -> None: if not self.selection_flag: return self.selection_flag = False targets = [PortfolioTarget(symbol, -1 / len(self.short)) for symbol in self.short if symbol in data and data[symbol]] self.set_holdings(targets, True) self.short.clear() def selection(self) -> None: self.selection_flag = True class CustomFeeModel(FeeModel): def get_order_fee(self, parameters): fee = parameters.Security.PRICE * parameters.Order.ABSOLUTE_QUANTITY * 0.00005 return OrderFee(CashAmount(fee, "USD"))