Overall Statistics |
Total Trades 3241 Average Win 0.07% Average Loss -0.05% Compounding Annual Return 2.783% Drawdown 5.200% Expectancy 0.093 Net Profit 15.005% Sharpe Ratio 0.627 Probabilistic Sharpe Ratio 18.004% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 1.24 Alpha 0.024 Beta -0.003 Annual Standard Deviation 0.038 Annual Variance 0.001 Information Ratio -0.653 Tracking Error 0.175 Treynor Ratio -6.828 Total Fees $3610.15 |
import pandas as pd from universe import LargeLiquidUSEquities from symbol_data import SymbolData class ValueAndMomentumEverywhere(QCAlgorithm): def Initialize(self): self.SetStartDate(2016, 1, 1) self.SetEndDate(2021, 2, 1) self.SetCash(1000000) self.UniverseSettings.Resolution = Resolution.Daily self.Settings.FreePortfolioValuePercentage = 0.2 self.AddUniverseSelection(LargeLiquidUSEquities()) self.symbol_data_by_symbol = {} self.rebalance_flag = True self.Schedule.On(self.DateRules.MonthStart(0), self.TimeRules.At(0, 0), self.rebalance) def rebalance(self): self.rebalance_flag = True def OnData(self, data): if not self.rebalance_flag: return # Gather value and momentum factor values df = pd.DataFrame() for symbol, symbol_data in self.symbol_data_by_symbol.items(): has_data = data.ContainsKey(symbol) and data[symbol] is not None if not (symbol_data.IsReady and has_data): continue df.loc[str(symbol), 'Value'] = symbol_data.value # Ensure some constituents are ready if df.empty: self.Debug('Consitituents not warm yet') for symbol in self.symbol_data_by_symbol.keys(): self.Liquidate(symbol) return self.rebalance_flag = False # Rank the securities on their factor values rank = df['Value'].rank(ascending=False) # Weight securities based on their factor rankings; Make dollar-neutral weight_by_symbol = rank - rank.mean() # Scale weights down so the portfolio stays within leverage constraints weight_by_symbol /= (abs(weight_by_symbol).sum()) # Place trades for symbol, weight in weight_by_symbol.iteritems(): self.SetHoldings(symbol, weight) def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: self.symbol_data_by_symbol[security.Symbol] = SymbolData(security) for security in changes.RemovedSecurities: self.Liquidate(security.Symbol) self.symbol_data_by_symbol.pop(security.Symbol, None)
class SymbolData: # Value Indicator: Book-to-market ratio def __init__(self, security): self.security = security @property def value(self): return 1 / self.security.Fundamentals.ValuationRatios.PBRatio @property def IsReady(self): return self.security.Fundamentals.ValuationRatios.PBRatio != 0
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel class LargeLiquidUSEquities(FundamentalUniverseSelectionModel): def __init__(self, min_price = 1): self.month = -1 self.min_price = min_price super().__init__(True) def SelectCoarse(self, algorithm, coarse): """ Coarse universe selection is called each day at midnight. Input: - algorithm Algorithm instance running the backtest - coarse List of CoarseFundamental objects Returns the symbols that have fundamental data, excluding penny stocks. """ # Refresh monthly if self.month == algorithm.Time.month: return Universe.Unchanged # Select securities with fundamental data (exclude penny stocks) selected = [c for c in coarse if c.HasFundamentalData and c.Price >= self.min_price] sorted_by_dollar_volume = sorted(selected, key=lambda x: x.DollarVolume, reverse=True) return [c.Symbol for c in sorted_by_dollar_volume[:500]] def SelectFine(self, algorithm, fine): """ Fine universe selection is performed each day at midnight after `SelectCoarse`. Input: - algorithm Algorithm instance running the backtest - fine List of FineFundamental objects that result from `SelectCoarse` processing Returns a list of the largest symbols from SelectCoarse that make up 90% of the total market cap, exluding depositary receipts, REITs, and financial firms. """ self.month = algorithm.Time.month # Restrict to common stock; Remove ADRs, REITs, and Financials selected = [f for f in fine if f.SecurityReference.SecurityType == 'ST00000001' and not f.SecurityReference.IsDepositaryReceipt and not f.CompanyReference.IsREIT and f.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices] # Select the largest stocks top_market_cap = sorted(selected, key=lambda x: x.MarketCap, reverse=True) return [f.Symbol for f in top_market_cap[:50]]