Overall Statistics
Total Trades
19996
Average Win
0.44%
Average Loss
-0.32%
Compounding Annual Return
99.395%
Drawdown
38.300%
Expectancy
0.167
Net Profit
21459.215%
Sharpe Ratio
2.08
Probabilistic Sharpe Ratio
97.424%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
1.39
Alpha
0.801
Beta
-1.26
Annual Standard Deviation
0.339
Annual Variance
0.115
Information Ratio
1.416
Tracking Error
0.445
Treynor Ratio
-0.56
Total Fees
$68169.01
Estimated Strategy Capacity
$610000.00
Lowest Capacity Asset
DIST R735QTJ8XC9X
#region imports
from AlgorithmImports import *
#endregion
# 11613  trend0 custom python indicator by Derek Melchin
# inspired by Warren Harding https://www.quantconnect.com/forum/discussion/11613/algo-trend0/p1


class TrendPower(PythonIndicator):
    def __init__(self, name, period, power):
        self.Name = name
        self.period = period
        self.power = power
        self.Time = datetime.min
        self.Value = 0
        self.prices = np.array([])
        self.times = np.array([])
        self.IndicatorIsReady  = False

    # def Update(self, input):
    def Update(self, timeIndex, value):
        self.prices = np.append(self.prices, value)[-self.period:]
        
        timeStr = f"{timeIndex.hour}:{timeIndex.minute}:{timeIndex.second}" 
        self.times = np.append(self.times, timeStr)[-self.period:]
        
        if len(self.prices) != self.period:
            self.Value = 0
            return False
        
        self.Value = self.calc_trend()
        self.IndicatorIsReady = True
        return True
        
    
    def calc_trend(self):
        changes = np.array([])
        for i in range(len(self.prices) - 1):
            _return = (self.prices[i + 1] - self.prices[i]) / self.prices[i]
            changes = np.append(changes, _return)
        return self.power_weighted_moving_average(changes)
        
    
    def power_weighted_moving_average(self, changes):
        return self.weighted_average(changes, self.power_weights(len(changes)))
        
        
    def power_weights(self, length):
        weights = np.array([])
        for i in range(length):
            w = i + 1
            weights = np.append(weights, w**self.power)
        return weights
        
        
    def weighted_average(self, changes, weights):
        products = []
        for i in range(len(changes)):
            products.append(changes[i] * weights[i])
        return sum(products) / sum(weights)
#region imports
from AlgorithmImports import *
#endregion
######
# The Trend Power Universe Selector
# ----------------------------------------------------
# Ikezi Kamanu
# 
# Entry:
# -------
# Short Stock with the higest positive Trend Power (measured over last 10 hours)
#
# Exit:
# -------
# Exit when Trend power goes negative
#
########################################################################################


from TrendPowerIndicator import *
class TrendPowerUniverseStrategy(QCAlgorithm):
    
    # =====================================
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(10000)

        self.SPY = self.AddEquity("SPY", Resolution.Hour)

        self.UniverseSettings.Resolution = Resolution.Hour
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) 

        self.symDataDict = { }
        # self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
        self.maxHoldings    = int(self.GetParameter("maxHoldings"))
        self.pctPerHolding  = float(self.GetParameter("pctPerHolding"))/100

    # =====================================
    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            security.SetLeverage(10)

    def OnData(self, dataSlice):

        # Update Indicators
        # ----------------------------
        for symbol in dataSlice.Keys:
            if symbol in self.symDataDict:
                symbolData = self.symDataDict[symbol] 
                symbolData.UpdateIndicatorsWithBars(dataSlice[symbol])

                if( self.Portfolio[symbol].Invested and symbolData.ExitSignalFired() ):
                    self.Liquidate(symbol)


        # If during market hours, Look for opportunity 
        # ---------------------------------------------
        if(self.Securities["SPY"].Exchange.DateTimeIsOpen(self.Time)):
            
            numHoldings = len([x.Key for x in self.Portfolio if x.Value.Invested])

            # IF we have less than our max holdings, add more, up to max. 
            if( numHoldings < self.maxHoldings):
                addHoldingsCount = self.maxHoldings - numHoldings     
                trendersSymData  = [self.symDataDict[symb] for symb in self.symDataDict \
                                    if (dataSlice.ContainsKey(symb) and \
                                       (self.symDataDict[symb].TrendPowerValue is not None) and \
                                       (self.symDataDict[symb].TrendPowerValue > 0))]
                topTrendersSymData = sorted(trendersSymData, key=lambda symbolData: symbolData.TrendPowerValue, reverse=True)  
                topToBuy    = topTrendersSymData[:addHoldingsCount]
                
                for symbolData in topToBuy:

                    orderMsg = f"self.indicator.Value = {symbolData.TrendPowerValue}"
                    self.Log (f"{self.Time} - {orderMsg}")
                    self.SetHoldings(symbolData.symbol, -self.pctPerHolding, False, orderMsg)
                    # self.SetHoldings(symbolData.symbol, 0.10)

    # =====================================
    def CoarseSelectionFunction(self, universe):  
        coarseuniverse = sorted(universe, key=lambda c: c.DollarVolume, reverse=True)  
        coarseuniverse = [c for c in coarseuniverse if c.Price > 200][:200]

        return [x.Symbol for x in coarseuniverse]


    # =====================================
    def FineSelectionFunction(self, universe):
        
        self.symDataDict = {} # reset the dictionary
        
        fineUniverse = [x for x in universe if x.SecurityReference.IsPrimaryShare
                        and x.SecurityReference.SecurityType == "ST00000001"
                        and x.SecurityReference.IsDepositaryReceipt == 0
                        and x.CompanyReference.IsLimitedPartnership == 0]


        # -------------
        # Debug Block: 
        # -------------
        # Force universe to be just AMZN, for Performance comparison
        # --------------------------------------------------------------
        # amzn = self.AddEquity("AMZN", Resolution.Hour).Symbol
        # history = self.History(amzn, 10, Resolution.Hour)
        # tmpSymbolData = SymbolData(amzn, history, self) 
        # if amzn not in self.symDataDict:
        #     self.symDataDict[amzn] = tmpSymbolData
        # return [amzn]        
        # ---------------------------------------------------------------
        
        
        selected     = []  
        
        for element in fineUniverse:  
            symbol = element.Symbol
        
            history = self.History(symbol, 10, Resolution.Hour)
            tmpSymbolData = SymbolData(symbol, history, self) 
            
            # Store data for this symbol, seed it with some history     
            # -----------------------------------------------------            
            if symbol not in self.symDataDict:
                self.symDataDict[symbol] = tmpSymbolData

            # If the indicators are ready for this symbol, add to universe     
            # --------------------------------------------------------------       
            if tmpSymbolData.IsReady() and tmpSymbolData.EntrySignalFired():
                selected.append(symbol)
                self.symDataDict[symbol] = tmpSymbolData
        return selected        

##################################
# 
# SymbolData Class
#
##################################
class SymbolData():
    
    # ==========================================
    # Constructor. Accepts History array
    # ==========================================
    def __init__(self, theSymbol, history, algo):

        # Algo / Symbol / Price reference
        # ----------------------------------------
        self.algo                 = algo
        self.symbol               = theSymbol
        self.lastPrice            = 0

        # Initialize our trend power indicator
        # ----------------------------------------
        self.TrendPower           = TrendPower('TrendPower', period = 10, power = 1.5)
        
        # Loop over the history data and update the indicator
        # -------------------------------------------------------------
        if history.empty or 'close' not in history.columns:
            return

        for index, row in history.loc[theSymbol].iterrows():
            tradeBar        = TradeBar()
            tradeBar.Close  = row['close']
            tradeBar.Open   = row['open']
            tradeBar.High   = row['high']
            tradeBar.Low    = row['low']
            tradeBar.Volume = row['volume']
            tradeBar.Time   = index
            tradeBar.Period = timedelta(hours=1) # todo: address this. it may not always be correct.
            tradeBar.Symbol = theSymbol
            
            
            self.UpdateIndicatorsWithBars(tradeBar)
            
    # =====================================    
    def UpdateIndicatorsWithBars(self, tradeBar):
        if((tradeBar is not None) and (self.TrendPower is not None)): 
            self.TrendPower.Update(tradeBar.Time, tradeBar.Close)

    # =====================================    
    @property
    def TrendPowerValue(self):
        if(self.TrendPower.IndicatorIsReady):
            return self.TrendPower.Value
        else:
            return None
    
    # =====================================            
    def IsReady(self):
        return self.TrendPower.IndicatorIsReady
        
    # =====================================
    def EntrySignalFired(self):
        return (self.TrendPower.IndicatorIsReady) and (self.TrendPower.Value > 0)
        
    # =====================================
    def ExitSignalFired(self):
        return (self.TrendPower.IndicatorIsReady) and (self.TrendPower.Value < 0)