Overall Statistics
Total Trades
44
Average Win
1.03%
Average Loss
-0.95%
Compounding Annual Return
22.892%
Drawdown
6.500%
Expectancy
0.473
Net Profit
11.016%
Sharpe Ratio
1.437
Probabilistic Sharpe Ratio
59.212%
Loss Rate
29%
Win Rate
71%
Profit-Loss Ratio
1.08
Alpha
0.205
Beta
-0.026
Annual Standard Deviation
0.136
Annual Variance
0.018
Information Ratio
-0.913
Tracking Error
0.204
Treynor Ratio
-7.59
Total Fees
$44.00
from CandleStickAlphaModel import PierceAlpha # Alpha Model
from ImmediateExecution import ImmediateExecutionModel # Execution Model
from TrailingStopModel import TrailingStopRiskModel # Risk Model
from InsightWeightingPortfolioConstructionModel import InsightWeightingPortfolioConstructionModel # Portfolio Construction
from datetime import timedelta

# https://www.quantconnect.com/forum/discussion/7684/creating-consolidated-data-from-universe-filters/p1

class DynamicNadionCoil(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 7, 7)  # Set Start Date
        self.SetCash(10000)  # Set Strategy Cash
        self.SetWarmUp(20)
        
        # Manual Universe Selection
        # self.symbol = self.AddEquity("TSLA", Resolution.Minute, Market.USA).Symbol
        # self.consolidated = self.Consolidate(self.symbol, timedelta(days = 2), self.OneDayBarHandler)
        
        self.numberOfSymbolsCoarse = 50
        self.numberOfSymbolsFine = 30
        self.dollarVolumeBySymbol = {}
        self.lastMonth = -1
        
        self.symbol = []
        self.UniverseSettings.Resolution = Resolution.Daily
        self.SetUniverseSelection(CoarseFundamentalUniverseSelectionModel(self.CoarseSelectionFunction))
        
        self.AddAlpha(PierceAlpha())
        
        self.SetExecution(ImmediateExecutionModel())
        
        self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel())
        
        self.SetRiskManagement(TrailingStopRiskModel(self, maximumDrawdownPercent = .1))
        


    def CoarseSelectionFunction(self, coarse):
        # The stocks must have fundamental data
        # The stock must have positive previous-day close price
        # The stock must have positive volume on the previous trading day
        if self.Time.month == self.lastMonth:
            return Universe.Unchanged
        self.lastMonth = self.Time.month

        sortedByDollarVolume = sorted([x for x in coarse if x.HasFundamentalData and x.Volume > 0 and x.Price > 0],
                                     key = lambda x: x.DollarVolume, reverse=True)[:self.numberOfSymbolsCoarse]

        self.dollarVolumeBySymbol = {x.Symbol:x.DollarVolume for x in sortedByDollarVolume}

        # If no security has met the QC500 criteria, the universe is unchanged.
        # A new selection will be attempted on the next trading day as self.lastMonth is not updated
        if len(self.dollarVolumeBySymbol) == 0:
            return Universe.Unchanged

        # return the symbol objects our sorted collection
        return list(self.dollarVolumeBySymbol.keys())
from QuantConnect.Indicators.CandlestickPatterns import Piercing

class PierceAlpha(AlphaModel):

    symbol_data_by_symbol = {}
   
    def Update(self, algorithm, slice):
        self.insights = []
        
        for symbol, symbol_data in self.symbol_data_by_symbol.items():
            
            ## Check for all Symbols in current data Slice
            if not (slice.ContainsKey(symbol) and slice[symbol] is not None):
                continue
            
            
            
            if symbol_data.indicator.IsReady:

                indicator_value = symbol_data.indicator.Current.Value
                algorithm.Plot("Indicator", str(symbol), indicator_value)
                
                if indicator_value == 1:
                    #self.algorithm.Debug(f"Time: {slice.Time} pattern is 1 going long on {symbol}")
                    self.insights.append(Insight.Price(symbol, timedelta(days = 5), InsightDirection.Up, None, None, None, 1))
    
                elif indicator_value == -1:    
                    #self.algorithm.Debug(f"{symbol} pattern is -1 going short")
                    self.insights.append(Insight.Price(symbol, timedelta(days = 5), InsightDirection.Down))
                
        return self.insights
        
    
    def OnSecuritiesChanged(self, algorithm, changes):
        for security in changes.AddedSecurities:
            symbol = security.Symbol
            self.symbol_data_by_symbol[symbol] = SymbolData(symbol, algorithm)
        
        for security in changes.RemovedSecurities:
            symbol_data = self.symbol_data_by_symbol.pop(security.Symbol, None)
            if symbol_data:
                symbol_data.dispose()
        
        
class SymbolData:
    def __init__(self, symbol, algorithm):
        self.symbol = symbol
        self.algorithm = algorithm
        self.indicator = Piercing()
       
        # Setup consolidator to update indicator 
        self.consolidator = TradeBarConsolidator(1)
        self.consolidator.DataConsolidated += self.consolidation_handler
        algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)
        

    def consolidation_handler(self, sender, consolidated):
        self.indicator.Update(consolidated)
    
    def dispose(self):
        self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.consolidator)
class InsightWeightingPortfolioConstructionModel(EqualWeightingPortfolioConstructionModel):
    '''Provides an implementation of IPortfolioConstructionModel that generates percent targets based on the
    Insight.Weight. The target percent holdings of each Symbol is given by the Insight.Weight from the last
    active Insight for that symbol.
    For insights of direction InsightDirection.Up, long targets are returned and for insights of direction
    InsightDirection.Down, short targets are returned.
    If the sum of all the last active Insight per symbol is bigger than 1, it will factor down each target
    percent holdings proportionally so the sum is 1.
    It will ignore Insight that have no Insight.Weight value.'''

    def __init__(self, rebalance = Resolution.Daily, portfolioBias = PortfolioBias.LongShort):
        '''Initialize a new instance of InsightWeightingPortfolioConstructionModel
        Args:
            rebalance: Rebalancing parameter. If it is a timedelta, date rules or Resolution, it will be converted into a function.
                              If None will be ignored.
                              The function returns the next expected rebalance time for a given algorithm UTC DateTime.
                              The function returns null if unknown, in which case the function will be called again in the
                              next loop. Returning current time will trigger rebalance.
            portfolioBias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
        super().__init__(rebalance, portfolioBias)

    def ShouldCreateTargetForInsight(self, insight):
        '''Method that will determine if the portfolio construction model should create a
        target for this insight
        Args:
            insight: The insight to create a target for'''
        # Ignore insights that don't have Weight value
        return insight.Weight is not None

    def DetermineTargetPercent(self, activeInsights):
        '''Will determine the target percent for each insight
        Args:
            activeInsights: The active insights to generate a target for'''
        result = {}

        # We will adjust weights proportionally in case the sum is > 1 so it sums to 1.
        weightSums = sum(self.GetValue(insight) for insight in activeInsights if self.RespectPortfolioBias(insight))
        weightFactor = 1.0
        if weightSums > 1:
            weightFactor = 1 / weightSums
        for insight in activeInsights:
            result[insight] = (insight.Direction if self.RespectPortfolioBias(insight) else InsightDirection.Flat) * self.GetValue(insight) * weightFactor
        return result

    def GetValue(self, insight):
        '''Method that will determine which member will be used to compute the weights and gets its value
        Args:
            insight: The insight to create a target for
        Returns:
            The value of the selected insight member'''
        return insight.Weight
class TrailingStopRiskModel(RiskManagementModel):
    '''Provides an implementation of IRiskManagementModel that limits the maximum possible loss
    measured from the highest unrealized profit'''
    def __init__(self, algorithm, maximumDrawdownPercent):
        '''Initializes a new instance of the TrailingStopRiskManagementModel class
        Args:
            maximumDrawdownPercent: The maximum percentage drawdown allowed for algorithm portfolio compared with the highest unrealized profit, defaults to 5% drawdown'''
        self.maximumDrawdownPercent = -abs(maximumDrawdownPercent)
        self.trailingHighs = dict()
        self.algorithm = algorithm

    def ManageRisk(self, algorithm, targets):
        '''Manages the algorithm's risk at each time step
        Args:
            algorithm: The algorithm instance
            targets: The current portfolio targets to be assessed for risk'''
        riskAdjustedTargets = list()

        for kvp in algorithm.Securities:
            symbol = kvp.Key
            security = kvp.Value

            # Remove if not invested
            if not security.Invested:
                self.trailingHighs.pop(symbol, None)
                continue

            # Add newly invested securities
            if symbol not in self.trailingHighs:
                self.trailingHighs[symbol] = security.Holdings.AveragePrice   # Set to average holding cost
                continue

            # Check for new highs and update - set to tradebar high
            if self.trailingHighs[symbol] < security.High:
                self.trailingHighs[symbol] = security.High
                continue

            # Check for securities past the drawdown limit
            securityHigh = self.trailingHighs[symbol]
            drawdown = (security.Low / securityHigh) - 1

            if drawdown < self.maximumDrawdownPercent:
                # liquidate
                #self.algorithm.Debug(f"{algorithm.Time}Past the drawdown limit... Liquidating")
                riskAdjustedTargets.append(PortfolioTarget(symbol, 0))
                


        return riskAdjustedTargets
class ImmediateExecutionModel(ExecutionModel):
    '''Provides an implementation of IExecutionModel that immediately submits market orders to achieve the desired portfolio targets'''

    def __init__(self):
        '''Initializes a new instance of the ImmediateExecutionModel class'''
        self.targetsCollection = PortfolioTargetCollection()

    def Execute(self, algorithm, targets):
        '''Immediately submits orders for the specified portfolio targets.
        Args:
            algorithm: The algorithm instance
            targets: The portfolio targets to be ordered'''

        # for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
        self.targetsCollection.AddRange(targets)
        if self.targetsCollection.Count > 0:
            for target in self.targetsCollection.OrderByMarginImpact(algorithm):
                # calculate remaining quantity to be ordered
                quantity = OrderSizing.GetUnorderedQuantity(algorithm, target)
                if quantity != 0:
                    algorithm.MarketOrder(target.Symbol, quantity)

            self.targetsCollection.ClearFulfilled(algorithm)