Overall Statistics
Total Trades
1531
Average Win
0.34%
Average Loss
-0.37%
Compounding Annual Return
13.685%
Drawdown
27.200%
Expectancy
0.183
Net Profit
90.031%
Sharpe Ratio
0.707
Probabilistic Sharpe Ratio
20.784%
Loss Rate
38%
Win Rate
62%
Profit-Loss Ratio
0.90
Alpha
0.157
Beta
-0.173
Annual Standard Deviation
0.185
Annual Variance
0.034
Information Ratio
-0.079
Tracking Error
0.27
Treynor Ratio
-0.755
Total Fees
$1574.58
Estimated Strategy Capacity
$490000.00
from QuantConnect.Data.UniverseSelection import *
import operator
from math import ceil,floor
import pandas as pd


class CoarseFineFundamentalComboAlgorithm(QCAlgorithm):
    def Initialize(self):

        self.SetStartDate(2016, 3, 1)  #Set Start Date
        self.SetEndDate(2021, 3, 1)    #Set End Date
        self.SetCash(100000)           #Set Strategy Cash

        self.UniverseSettings.Resolution = Resolution.Daily        
        
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        self.__numberOfSymbols = 100
        self.__numberOfSymbolsFine = 50
        self.num_portfolios = 5
        self.symbols = []
        self.month = 0


    def CoarseSelectionFunction(self, coarse):
        if self.Time.month == self.month:
            return Universe.Unchanged
        self.month = self.Time.month
        
        sortedByDollarVolume = sorted([x for x in coarse if x.HasFundamentalData], key=lambda x: x.DollarVolume, reverse=True) 
        top = sortedByDollarVolume[:self.__numberOfSymbols]
        return [x.Symbol for x in top]

    def FineSelectionFunction(self, fine):
        selected = [x for x in fine if x.OperationRatios.OperationMargin.Value
                                        and x.ValuationRatios.PriceChange1M 
                                        and x.ValuationRatios.BookValuePerShare]

        # Gather factor values
        factors = pd.DataFrame()
        symbol_by_str_symbol = {}
        for f in selected:
            str_symbol = str(f.Symbol)
            symbol_by_str_symbol[str_symbol] = f.Symbol
            factors.loc[str_symbol, 'OperationMargin'] = f.OperationRatios.OperationMargin.Value
            factors.loc[str_symbol, 'PriceChange1M'] = f.ValuationRatios.PriceChange1M
            factors.loc[str_symbol, 'BookValuePerShare'] = f.ValuationRatios.BookValuePerShare

        # Rank symbols by their factor values
        factors_rank = factors.rank()
        
        # Calculate score of each symbol
        factors['score'] = factors_rank['OperationMargin'] * 0.2 + factors_rank['PriceChange1M'] * 0.4 + factors_rank['BookValuePerShare'] * 0.4
        
        # Sort symbols by their score
        sorted_by_score = factors['score'].sort_values(ascending=False).index

        num_stocks = floor(len(selected)/self.num_portfolios)
        self.symbols = [symbol_by_str_symbol[symbol] for symbol in sorted_by_score[:num_stocks]]
        return self.symbols


    def OnData(self, data):
        if not self.symbols:
            return
        
        weight = 1 / len(self.symbols)
        for symbol in self.symbols:
            self.SetHoldings(symbol, weight)
        self.symbols.clear()
            

    def OnSecuritiesChanged(self, changes):
        for security in changes.RemovedSecurities:
            self.Liquidate(security.Symbol)