Overall Statistics
Total Trades
640
Average Win
0.10%
Average Loss
-0.11%
Compounding Annual Return
-0.594%
Drawdown
2.200%
Expectancy
-0.021
Net Profit
-0.759%
Sharpe Ratio
-0.236
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
0.97
Alpha
-0.003
Beta
-0.125
Annual Standard Deviation
0.023
Annual Variance
0.001
Information Ratio
-1.065
Tracking Error
0.023
Treynor Ratio
0.044
Total Fees
$724.20
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel

class VerticalTachyonRegulators(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018, 1, 1)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        
        ## Set execution model to mimic market orders
        self.SetExecution(ImmediateExecutionModel())
        
        ## set equal weighting protfolio construction to mimic intital algorithm weighting scheme
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())

        ## Helper variables for universe selection and checking for conditions to update only at the
        ## beginning of each month
        self.num_coarse = 250
        self.num_fine = 10
        self.lastMonth = -1
        self.symbols = []
        
        ## Coarse/Fine universe selection model
        self.UniverseSettings.Resolution = Resolution.Daily
        self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.CoarseSelectionFunction, self.FineSelectionFunction, None, None))

        ## Custom Alpha model where we can perform the trading logic and much of the filtering previously done in the Fine function and OnData methods
        self.AddAlpha(CustomAlphaModel(self.lastMonth, self.num_fine, self.num_coarse))


    def CoarseSelectionFunction(self, coarse):

        ## If not time to rebalance, return an empty list
        if self.Time.month == self.lastMonth: 
            return []

        selected = [x for x in coarse if (x.HasFundamentalData) 
                    and (float(x.Price) > 5)]
        
        sortedByDollarVolume = sorted(selected, key=lambda x: x.DollarVolume, reverse=True) 
        top = sortedByDollarVolume[:self.num_coarse]
        self.symbols = [i.Symbol for i in top]

        return self.symbols
    
    def FineSelectionFunction(self, fine):
        
        ## If not time to rebalance, return an empty list
        if self.Time.month == self.lastMonth: 
            return []
            
        ## Else reassign the month variable and filter
        self.lastMonth = self.Time.month       
        
        filtered_fine = [x.Symbol for x in fine if x.OperationRatios.OperationMargin.Value
                                        and x.ValuationRatios.PriceChange1M 
                                        and x.ValuationRatios.BookValuePerShare]
        
        ## Sanity check to examine PB Ratios
        for x in fine:
            self.Log(x.Symbol.Value + ' PB Ratio: ' + str(x.ValuationRatios.PBRatio))

        self.symbols = filtered_fine
        return self.symbols


class CustomAlphaModel:
    
    def __init__(self, lastMonth, num_fine, num_coarse):
        
        ## Initialize the various variables/helpers we'll need
        self.lastMonth = lastMonth
        self.longs = []
        self.shorts = []
        self.num_fine = num_fine
        self.num_coarse = num_coarse
        
    def Update(self, algorithm, data):
        
        ## Create empty list of insights
        insights = []
        
        ## Return no insights if it's not the month to rebalance
        if algorithm.Time.month == self.lastMonth: 
            return []
        
        ## We will liquidate any securities we're still invested in that we don't want to hold a position
        ## for the next month
        for i in algorithm.ActiveSecurities:
            symbol = i.Value.Symbol
            if algorithm.Securities[symbol].Invested and (symbol not in self.longs) and (symbol not in self.shorts):
                insights.append(Insight(symbol, TimeSpan.FromDays(30), InsightType.Price, InsightDirection.Flat, None, None))
        
        ## Emit Up (buy) Insights for our desired long positions
        for symbol in self.longs:
            insights.append(Insight(symbol, TimeSpan.FromDays(30), InsightType.Price, InsightDirection.Up, 0.01, None))
            
        ## Emit Down (sell) Insights for our desired short positions
        for symbol in self.shorts:
            algorithm.Log(symbol)
            insights.append(Insight(symbol, TimeSpan.FromDays(30), InsightType.Price, InsightDirection.Down, 0.01, None))
            
        
        return insights

    def OnSecuritiesChanged(self, algorithm, changes):
        
        ## Get symbols from the universe
        symbols = [ x.Symbol for x in changes.AddedSecurities ]
        
        ## Get the security objects
        filtered_fine = [algorithm.Securities[symbol] for symbol in symbols]
        
        ## Perform filtering/sorting that was previously done in the Coarse/Fine functions -- these lists could not
        ## be passed into the Alpha model as before, so we access the Fundamental data here instead
        sortedByfactor1 = sorted(filtered_fine, key=lambda x: x.Fundamentals.OperationRatios.OperationMargin.Value, reverse=True)
        sortedByfactor2 = sorted(filtered_fine, key=lambda x: x.Fundamentals.ValuationRatios.PriceChange1M, reverse=True)
        sortedByfactor3 = sorted(filtered_fine, key=lambda x: x.Fundamentals.ValuationRatios.BookValuePerShare, reverse=True)
        
        stock_dict = {}
        
        ## Assign a score to each stock, you can also change the rule of scoring here.
        for i,ele in enumerate(sortedByfactor1):
            rank1 = i
            rank2 = sortedByfactor2.index(ele)
            rank3 = sortedByfactor3.index(ele)
            score = sum([rank1*0.2,rank2*0.4,rank3*0.4])
            stock_dict[ele] = score
        
        ## Sort the stocks by their scores
        self.sorted_stock = sorted(stock_dict.items(), key=lambda d:d[1],reverse=False)
        sorted_symbol = [x[0] for x in self.sorted_stock]

        ## Sort the top stocks into the long_list and the bottom ones into the short_list
        self.longs = [x.Symbol for x in sorted_symbol[:self.num_fine]]
        self.shorts = [x.Symbol for x in sorted_symbol[-self.num_fine:]]