Overall Statistics |
Total Orders
2830
Average Win
0.51%
Average Loss
-0.30%
Compounding Annual Return
4.802%
Drawdown
41.200%
Expectancy
0.339
Start Equity
100000
End Equity
352397.72
Net Profit
252.398%
Sharpe Ratio
0.173
Sortino Ratio
0.197
Probabilistic Sharpe Ratio
0.019%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
1.69
Alpha
0.01
Beta
0.092
Annual Standard Deviation
0.083
Annual Variance
0.007
Information Ratio
-0.219
Tracking Error
0.166
Treynor Ratio
0.156
Total Fees
$314.99
Estimated Strategy Capacity
$48000000.00
Lowest Capacity Asset
SYNA SBSAXXUERZ39
Portfolio Turnover
0.30%
|
# https://quantpedia.com/strategies/rd-expenditures-and-stock-returns/ # # The investment universe consists of stocks that are listed on NYSE NASDAQ or AMEX. At the end of April, for each stock in the universe, calculate a measure of total R&D expenditures in the past 5 years scaled by the firm’s Market cap (defined on page 7, eq. 1). # Go long (short) on the quintile of firms with the highest (lowest) R&D expenditures relative to their Market Cap. Weight the portfolio equally and rebalance next year. The backtested performance of the paper is substituted by our more recent backtest in Quantconnect. # # QC implementation changes: # - The investment universe consists of 500 most liquid stocks that are listed on NYSE NASDAQ or AMEX. #region imports from AlgorithmImports import * from numpy import log, average from scipy import stats import numpy as np #endregion class RDExpendituresandStockReturns(QCAlgorithm): def Initialize(self) -> None: self.SetStartDate(1998, 1, 1) self.SetCash(100000) self.fundamental_count:int = 500 self.fundamental_sorting_key = lambda x: x.DollarVolume self.rebalance_month:int = 4 self.quantile:int = 5 self.leverage:int = 5 self.min_share_price:float = 5. self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE'] # R&D history. self.RD:Dict[Symbol, float] = {} self.rd_period:int = 5 self.long:List[Symbol] = [] self.short:List[Symbol] = [] data:Equity = self.AddEquity('XLK', Resolution.Daily) data.SetLeverage(self.leverage) self.technology_sector:Symbol = data.Symbol market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol self.selection_flag:bool = False self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.FundamentalSelectionFunction) self.Settings.MinimumOrderMarginPortfolioPercentage = 0. self.Schedule.On(self.DateRules.MonthEnd(market), self.TimeRules.AfterMarketOpen(market), self.Selection) self.settings.daily_precise_end_time = False def OnSecuritiesChanged(self, changes: SecurityChanges) -> None: for security in changes.AddedSecurities: security.SetFeeModel(CustomFeeModel()) security.SetLeverage(self.leverage) def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]: if not self.selection_flag: return Universe.Unchanged selected:List[Fundamental] = [ x for x in fundamental if x.HasFundamentalData and x.Price > self.min_share_price and x.SecurityReference.ExchangeId in self.exchange_codes and x.MarketCap != 0 and \ not np.isnan(x.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths) and x.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths != 0 ] if len(selected) > self.fundamental_count: selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]] selected_symbols:List[Symbol] = list(map(lambda x: x.Symbol, selected)) ability:Dict[Fundamental, float] = {} updated_flag:List[Symbol] = [] # updated this year already for stock in selected: symbol:Symbol = stock.Symbol # prevent storing duplicated value for the same stock in one year if symbol not in updated_flag: # Update RD. if symbol not in self.RD: self.RD[symbol] = RollingWindow[float](self.rd_period) if self.RD[symbol].IsReady: coefs:np.ndarray = np.array([1, 0.8, 0.6, 0.4, 0.2]) rds:np.ndarray = np.array([x for x in self.RD[symbol]]) rdc:float = sum(coefs * rds) ability[stock] = rdc / stock.MarketCap rd:float = stock.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths self.RD[symbol].Add(rd) # prevent storing duplicated value for the same stock in one year if selected_symbols.count(symbol) > 1: updated_flag.append(symbol) # Remove not updated symbols symbols_to_delete:List[Symbol] = [] for symbol in self.RD.keys(): if symbol not in selected_symbols: symbols_to_delete.append(symbol) for symbol in symbols_to_delete: if symbol in self.RD: del self.RD[symbol] # starts trading after data storing period if len(ability) >= self.quantile: # Ability sorting. sorted_by_ability:List = sorted(ability.items(), key = lambda x: x[1], reverse = True) quantile:int = int(len(sorted_by_ability) / self.quantile) high_by_ability:List[Symbol] = [x[0].Symbol for x in sorted_by_ability[:quantile]] low_by_ability:List[Symbol] = [x[0].Symbol for x in sorted_by_ability[-quantile:]] self.long = high_by_ability self.short = low_by_ability return self.long + self.short def Selection(self) -> None: if self.Time.month == self.rebalance_month: self.selection_flag = True def OnData(self, data: Slice) -> None: if not self.selection_flag: return self.selection_flag = False # order execution targets:List[PortfolioTarget] = [] for i, portfolio in enumerate([self.long, self.short]): for symbol in portfolio: if symbol in data and data[symbol]: targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio))) self.SetHoldings(targets, True) self.long.clear() self.short.clear() class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD"))