Overall Statistics
Total Trades
7687
Average Win
0.17%
Average Loss
-0.24%
Compounding Annual Return
8.391%
Drawdown
57.500%
Expectancy
0.126
Net Profit
293.817%
Sharpe Ratio
0.48
Probabilistic Sharpe Ratio
0.752%
Loss Rate
34%
Win Rate
66%
Profit-Loss Ratio
0.71
Alpha
0.094
Beta
-0.084
Annual Standard Deviation
0.179
Annual Variance
0.032
Information Ratio
-0.041
Tracking Error
0.248
Treynor Ratio
-1.028
Total Fees
$8223.67
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel

class QuantumTransdimensionalComputer(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2003, 1, 1)  # Set Start Date
        self.SetEndDate(2019, 12, 31)  # Set End Date
        self.SetCash(100000)  # Set Strategy Cash
        self.UniverseSettings.Resolution = Resolution.Daily
        self.SetExecution(ImmediateExecutionModel())
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        # Momentum parameters
        self.mom = {}           # Dict of Momentum indicator keyed by Symbol
        self.lookback = 90     # Momentum indicator lookback period
        self.num_long = 30       # Number of symbols with open positions
        # Trade control Parameters
        self.month = -1     # month indicator
        self.rebalance = False # rebalance indicator


    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''
        # Update the indicator
        for symbol, mom in self.mom.items():
            # Update also works for an IndicatorDataPoint object
            mom.Update(self.Time, self.Securities[symbol].Close)

        if not self.rebalance:
            return

        # Selects the securities with highest momentum
        # Note that sorted_mom and selected are lists of symbols
        sorted_mom = sorted([k for k,v in self.mom.items() if v.IsReady],
            key=lambda x: self.mom[x].Current.Value, reverse=True)
        selected = sorted_mom[:self.num_long]

        # Liquidate securities that are not in the list
        for symbol, mom in self.mom.items():
            if symbol not in selected:
                self.Liquidate(symbol, 'Not selected')

        # Buy selected securities
        for symbol in selected:
            self.SetHoldings(symbol, 1/self.num_long)

        self.rebalance = False


    def CoarseSelectionFunction(self, coarse):
        '''Drop securities which have no fundamental data or have too low prices.
        Select those with highest by dollar volume'''
        # Only one rebalance per mounth
        if self.month == self.Time.month:
            return Universe.Unchanged
        # Set rebalance indicator and reset month variable
        self.rebalance = True
        self.month = self.Time.month

        selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5],
            key=lambda x: x.DollarVolume, reverse=True)

        return [x.Symbol for x in selected]
        
    
    def FineSelectionFunction(self, fine):
        '''Select security with highest market cap'''

        fine = [f for f in fine if f.ValuationRatios.PERatio > 0
                               and f.EarningReports.BasicEPS.TwelveMonths > 0
                               and f.EarningReports.BasicAverageShares.ThreeMonths > 0]
        # This is how Market Cap is calculated
        selected = [ f for f in fine if f.ValuationRatios.PERatio *
                                      f.EarningReports.BasicEPS.TwelveMonths *
                                      f.EarningReports.BasicAverageShares.ThreeMonths > 8200000000]
        return [x.Symbol for x in selected]
        
        
    def OnSecuritiesChanged(self, changes):
        # Clear removed securities
        for security in changes.RemovedSecurities:
            symbol = security.Symbol
            if self.mom.pop(symbol, None) is not None:
                self.Liquidate(symbol, 'Removed from universe')
                
        # Create indicators for the new secuirities (NOT UPDATING YET)
        for security in changes.AddedSecurities:
            symbol = security.Symbol
            if symbol not in self.mom:
                self.mom[symbol] = Momentum(self.lookback)
                
        # Warm up the indicator with history price if it is not ready
        addedSymbols = [k for k,v in self.mom.items() if not v.IsReady]
        # Get historical prices for the newly added securities
        # Use 1 + self.lookback as the window to be safer
        history = self.History(addedSymbols, 1 + self.lookback, Resolution.Daily)
        # The unstack changes a multiindex data frame to a single index data frame
        # Level = 0 keeps inner index and create columns based on the outer index
        history = history.close.unstack(level=0)
        # Manually update the indicator for the new securiites
        for symbol in addedSymbols:
            ticker = str(symbol)
            if ticker in history:
                # history[ticker] selects a *column*(result of unstack) based on the ticker
                for time, value in history[ticker].items():
                    # Manually create an indicator data point object and update with it
                    item = IndicatorDataPoint(symbol, time, value)
                    self.mom[symbol].Update(item)