| 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"))