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]]