Overall Statistics
Total Trades
62
Average Win
3.23%
Average Loss
-1.34%
Compounding Annual Return
4.575%
Drawdown
8.000%
Expectancy
0.543
Net Profit
23.947%
Sharpe Ratio
0.568
Probabilistic Sharpe Ratio
12.496%
Loss Rate
55%
Win Rate
45%
Profit-Loss Ratio
2.42
Alpha
0.018
Beta
0.194
Annual Standard Deviation
0.058
Annual Variance
0.003
Information Ratio
-0.284
Tracking Error
0.151
Treynor Ratio
0.171
Total Fees
$62.00
Estimated Strategy Capacity
$17000000.00
Lowest Capacity Asset
AMZN R735QTJ8XC9X
# Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
from AlgorithmImports import *
import numpy as np

## <summary>
## Example structure for structuring an algorithm with indicator and consolidator data for many tickers.
## Using the trend0 indicator together with the monthly price, to see if we need to buy or sell a stock
## In the future, perhaps we can look for a crossover in the trend0 of normal prices and a crossover of ema prices
## Here we could even include a statement which checks if the 30 day high has been passed at the time we want to sell, to see if it's going down and to see if we can still sell with profits
## </summary>
## <meta name="tag" content="consolidating data" />
## <meta name="tag" content="indicators" />
## <meta name="tag" content="using data" />
## <meta name="tag" content="strategy example" />
class MultipleSymbolConsolidationAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2018, 1, 1)   # Set a backtest starting date
        self.SetEndDate(datetime.now()) # Set End Date to Now
        self.SetCash(self.GetParameter('Cash'))    # Set investment cash available
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash) ## Margin accounts allow you to use leverage that goes above 2 to 4 times your capital
        self.SetBrokerageModel(MinimumAccountBalanceBrokerageModel(self, 100.00))           # Always keep 100 cash in your brokerage account! 
        
        # This is the period of bars we'll be creating
        BarPeriod = TimeSpan.FromHours(3)
        # This is the period of our ema indicators
        ExponentialMovingAveragePeriod = 10
        # This is the number of consolidated bars we'll hold in symbol data for reference
        self.RollingWindowSize = 20
        # Holds all of our data keyed by each symbol
        self.Data = {}
        self.WeightedMomentum = {}

        ### Add the SPY and set it as a benchmark
        self.spy = self.AddEquity("SPY", Resolution.Hour).Symbol                          # Minute Resolution
        self.SetBenchmark(self.spy)

        # Contains all of our equity symbols
        self.EquitySymbols = ['MSFT', 'AAPL', 'GOOG', 'AMZN'] #'TSLA', 'NVDA', 'META', 'UNH', 'BRK.B']
    
        
        ### to check if we are moving towards a lowest point
        self.MovingTowardLowPoint = False
        self.LowestPoint = 0

        self.BuyPrice = {}
        self.MovingTowardHighPoint = False
        self.HighestPoint = 0

        ### All tools needed to check if a stop loss order needs to be executed
        self.StopPercentage = 0.05      # Stop loss percentage
        self.StopLossOrder = {}         # Stop loss price dictionary
        self.CurrentPrice = {}           # The current price we want to track
        self.HighestPrice = {}       

        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))      # This line of code gets the last known prices of data, if data is missing for the openingbar

        
        ### For the custom indicator for multiple stocks, check this out: https://backtest-rookies.com/2018/09/07/quantconnect-trading-multiple-stocks-or-assets/
        # initialize our equity data
        for symbol in self.EquitySymbols:
            equity = self.AddEquity(symbol)
            self.Data[symbol] = SymbolData(self, equity.Symbol, BarPeriod, self.RollingWindowSize)
            self.WeightedMomentum[symbol] = Trend0('Trend0', period=self.RollingWindowSize, exponent=1.5)
            self.RegisterIndicator(symbol, self.WeightedMomentum[symbol], Resolution.Minute)
            self.StopLossOrder[symbol] = 0
            self.HighestPrice[symbol] = 0
            self.CurrentPrice[symbol] = 0
            self.BuyPrice[symbol] = 0
        # loop through all our symbols and request data subscriptions and initialize indicator
        for symbol, SymData in self.Data.items():
            # define the indicator
            SymData.EMA = ExponentialMovingAverage(self.CreateIndicatorName(symbol, "EMA" + str(ExponentialMovingAveragePeriod), Resolution.Hour), ExponentialMovingAveragePeriod)
            SymData.MonthHigh = self.MAX(symbol, 30, Resolution.Daily, Field.High)  ### Getting the average 30 day high
            SymData.MonthLow = self.MIN(symbol, 30, Resolution.Daily, Field.Low)    ### Getting the 30 day low

            # define a consolidator to consolidate data for this symbol on the requested period
            consolidator = TradeBarConsolidator(BarPeriod)
            # write up our consolidator to update the indicator
            consolidator.DataConsolidated += self.OnDataConsolidated
            # we need to add this consolidator so it gets auto updates
            self.SubscriptionManager.AddConsolidator(SymData.Symbol, consolidator)

        ### As we are using a static universe right now we can use the warmup method and return in the OnData function if we are still warming up
        ### We are using a 30 day high and low indicator
        self.SetWarmUp(timedelta(days=30))

    def OnDataConsolidated(self, sender, bar):

        self.Data[bar.Symbol.Value].EMA.Update(bar.Time, bar.Close)
        self.Data[bar.Symbol.Value].Bars.Add(bar)

    # OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
    # Argument "data": Slice object, dictionary object with your stock data 
    
### After updating the stopLimitOrder price with the UpdateOrderField() method, somehow the ondata function can't access the 
### Data.keys() object anymore?! Which I find strange
    def OnData(self,data):
        ### If we are still warming up our algorithm, return
        if self.IsWarmingUp:
            return

        # loop through each symbol in our structure

        for symbol in self.Data.keys():
####    \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-    
#       Testing purposes:

            # self.Debug(f'Portfolio Invested = {str(self.Portfolio[symbol].Invested)}')
            # self.Debug(f'symbol = {str(symbol)}')
            # self.Debug(f'Moving Toward Low Point = {str(self.MovingTowardLowPoint)}')
            # self.Debug(f'WeightedSymbol Momentum = {str(self.WeightedMomentum[symbol].Value < 0)}')
            # self.Debug(f'High = {str(self.Data[symbol].MonthHigh)} and Low = {self.Data[symbol].MonthLow}')
            # self.Debug(f'Securities current price = {str(self.Securities[symbol].Price)}')
####    \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-

            if not (data.ContainsKey(symbol) and data[symbol] is not None):
                self.Debug(f'No data available for {str(symbol)} on {str(self.Time)}!')
                return
            
            if not self.WeightedMomentum[symbol].IsReady:
                self.Debug('Indicator is not yet ready')
                return

            self.CurrentPrice[symbol] = data[symbol].Close
            # # self.Log(f'The function: {str(self.WeightedMomentum[symbol])} and the time is {str(self.Time)}')
            # # self.Debug(f'The function: {str(self.WeightedMomentum[symbol])} and the time is {str(self.Time)}')
            symbolData = self.Data[symbol]


            # this check proves that this symbol was JUST updated prior to this OnData function being called
            if symbolData.IsReady() and symbolData.WasJustUpdated(self.Time):
                symbolData.emaWin.Add(symbolData.EMA.Current.Value)             ### Add data to the rolling window of the EMA
                # self.Log(f'The monthly high is: {str(symbolData.MonthHigh)}')
                # self.Debug(f'Monthly high = {str(symbolData.MonthLow)} and Low = {str(symbolData.MonthHigh)}')
                # self.Log(f'The monthly low is: {str(symbolData.MonthLow)}')

                if symbolData.emaWin.Count == self.RollingWindowSize:
                    # self.Log(f'MovingTowardLowPoint = {str(self.MovingTowardLowPoint)} and Moving Toward High point = {str(self.MovingTowardHighPoint)}, the time is {str(self.Time)}')
                    emaWindowList = [i for i in symbolData.emaWin]              ### With this list we can check if the prices over time have been going up and if it reached a 52 week high

                    if not self.Portfolio[symbol].Invested: ### check if we should create a buy Order or not if we haven't invested in the stock yet.

                        if self.MovingTowardLowPoint == False and self.WeightedMomentum[symbol].Value < 0:   ## Checking if we have downwards momentum and checking if the price exceeds the lowest point we've had so far. If so, we know we are moving towards a lowest point
                            self.MovingTowardLowPoint = True                        ### We are moving towards a low point
                            self.LowestPoint = self.Securities[symbol].Price        ### Store the current lowest price

                        elif self.MovingTowardLowPoint == True and self.Securities[symbol].Price < self.LowestPoint:
                            self.LowestPoint = self.Securities[symbol].Price        ### We want to keep refreshing the lowest point if it's going downward

                        ### If we are moving towards a low point, check if we are going up once again compared to the last value, to make sure it was a low point.
                        elif self.MovingTowardLowPoint == True and self.WeightedMomentum[symbol].Value > 0 and self.Securities[symbol].Price >= self.LowestPoint and self.Securities[symbol].Price <= symbolData.MonthLow.Current.Value: # Check if the stock is moving up again after moving downwards, we will buy just after the lowest point has been reached
                            self.SetHoldings(symbol, 1/len(self.EquitySymbols))
                            self.BuyPrice[symbol] = self.Securities[symbol].Price
                            self.StopLossOrder[symbol] = self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, self.Securities[symbol].Price * (1 - self.StopPercentage))
                            self.Debug(f'Buying {str(symbol)} at {str(self.Securities[symbol].Price)} on {str(self.Time)}, the TrailingStopLoss price is {str(self.StopLossOrder[symbol])}')
                            # Reset the booleans, as we just bought new stock
                            self.MovingTowardLowPoint = False
                            self.LowestPoint = 0
                        
                        else:
                            pass
                            # self.Log(f'No buy action has to be undertaken')


                    else:   # If self.Portfolio[symbol].Invested: we have invested in the stock, we want to check if we should create a sell order
                        ### trailing stop loss update check, to see if we need to update
                        if self.CurrentPrice[symbol] > self.HighestPrice[symbol]:
                            self.HighestPrice[symbol] = self.CurrentPrice[symbol]
                            ## https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-management/order-tickets
                            # NewPrice = self.CurrentPrice[symbol] * (1 - self.StopPercentage)
                            # response = self.StopLossOrder[symbol].UpdateLimitPrice(NewPrice, symbol)

                            # if response.IsSuccess:
                            #     self.Debug("Stop order price updated successfully")
                            # else:
                            #     self.Debug('Something is going wrong with updating the stoplimitprice!!')

                        ### Checking the momentum and seeing if 
                        if self.MovingTowardHighPoint == False and self.WeightedMomentum[symbol].Value > 0 and self.Securities[symbol].Price >= symbolData.MonthHigh.Current.Value and self.Securities[symbol].Price > self.BuyPrice[symbol]:
                            self.MovingTowardHighPoint = True
                            self.HighestPoint = self.Securities[symbol].Price
                        
                        elif self.MovingTowardHighPoint == True and self.Securities[symbol].Price > self.HighestPoint:
                            self.HighestPoint = self.Securities[symbol].Price        ### We want to keep refreshing the lowest point if it's going downward

                        
                        ### !!!! Maybe add in the self.ReachedLowPoint
                        elif self.MovingTowardHighPoint == True and self.Portfolio[symbol].Price > self.BuyPrice[symbol] and self.WeightedMomentum[symbol].Value < 0 and self.Securities[symbol].Price < self.HighestPoint:   # If the stock is exceeding it's buying price, moving towards a high point and the current value is below the hight point, then execute a sell order
                            self.Liquidate(symbol)
                            self.Debug(f'Selling {str(symbol)} at {str(self.Securities[symbol].Price)} on {str(self.Time)}')
                            self.MovingTowardHighPoint = False
                            self.HighestPoint = 0
                            self.BuyPrice[symbol] = None

                            ### Resetting all the open orders
                            if self.StopLossOrder[symbol].Status != OrderStatus.Filled:
                                self.StopLossOrder[symbol].Cancel()
                                self.StopLossOrder[symbol] = None

                        ## Add in function that checks if our price is dropping below a target price, to prevent heavy losses!
                        ## If the price is going up, update the forced sell price with it accordingly!!!
                        ## elif self.MovingTowardHighPoint == True and self.Securities[symbol].Price <= self.ForcedSellPrice[symbol]:
                        ##     self.Liquidate(symbol)
                        ##     self.Log(f'We had to do a forced sell, because {str(symbol)} has a current price of {str(self.Securities.Price)}, which is lower than the forced sell price of {str(self.ForcedSellPrice[symbol])}!!!')

                        else:
                            pass
                            # self.Log('No sell action has to be undertaken')

                        # !!!!! Future functions, a function that checks if the momentum is still going down and if the price is below a critical selling point
                        # elif emaMomentum < 0 and self.Portfolio[symbol].Price > self.BuyPrice[symbol]       ### Future function
                        # Def GoShortIfBelowPrice:
                        # #


    # End of a trading day event handler. This method is called at the end of the algorithm day (or multiple times if trading multiple assets).
    # Method is called 10 minutes before closing to allow user to close out position.
    def OnEndOfDay(self, symbol):
        pass
        # i = 0
        # for symbol in sorted(self.Data.keys()):
        #     symbolData = self.Data[symbol]

    def OnEndOfAlgorithm(self):
        self.Debug(f'Algorithm finished, jippie kay yey motherfucker:)')
    
class SymbolData(object):
    def __init__(self, algorithm, symbol, barPeriod, windowSize):
        self.Symbol = symbol
        self.algorithm = algorithm
        # The period used when population the Bars rolling window
        self.BarPeriod = barPeriod
        # A rolling window of data, data needs to be pumped into Bars by using Bars.Update( tradeBar ) and can be accessed like:
        # mySymbolData.Bars[0] - most first recent piece of data
        # mySymbolData.Bars[5] - the sixth most recent piece of data (zero based indexing)
        self.Bars = RollingWindow[IBaseDataBar](windowSize)
        # The exponential moving average indicator for our symbol
        self.EMA = None
        self.emaWin = RollingWindow[float](windowSize)

        self.MonthHigh = None
        self.MonthLow = None
      
  
    # Returns true if all the data in this instance is ready (indicators, rolling windows, ect...)
    def IsReady(self):
        return self.Bars.IsReady and self.EMA.IsReady and self.MonthHigh.IsReady and self.MonthLow.IsReady

    # Returns true if the most recent trade bar time matches the current time minus the bar's period, this
    # indicates that update was just called on this instance
    def WasJustUpdated(self, current):
        return self.Bars.Count > 0 and self.Bars[0].Time == current - self.BarPeriod

class MinimumAccountBalanceBrokerageModel(DefaultBrokerageModel):
    '''Custom brokerage model that requires clients to maintain a minimum cash balance'''
    def __init__(self, algorithm, minimumAccountBalance):
        self.algorithm = algorithm
        self.minimumAccountBalance = minimumAccountBalance


### Custom indicator, made by 
class Trend0(PythonIndicator):
    def __init__(self, name, period, exponent):
        self.Name = name
        self.period = period
        self.exponent = exponent
        self.Time = datetime.min
        self.Value = 0
        self.prices = np.array([])


    def Update(self, input):
        self.prices = np.append(self.prices, input.Close)[-self.period:]
        
        # IsReady?
        if len(self.prices) != self.period:
            self.Value = 0
            return False
        
        self.Value = self.calc_trend()
        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.exponential_weights(len(changes)))
        
    def exponential_weights(self, length):
        weights = np.array([])
        for i in range(length):
            w = i + 1
            weights = np.append(weights, w**self.exponent)
        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)

# class HighestAndDirection(QCAlgorithm):

#     def Initialize(self):
#         self.SetStartDate(2018, 1, 1)   # Set a backtest starting date
#         self.SetEndDate(datetime.now()) # Set End Date to Now
#         self.SetCash(self.GetParameter('Cash'))    # Set investment cash available
#         self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash) ## Margin accounts allow you to use leverage that goes above 2 to 4 times your capital
#         self.SetBrokerageModel(MinimumAccountBalanceBrokerageModel(self, 100.00))           # Always keep 100 cash in your brokerage account! 

# class MinimumAccountBalanceBrokerageModel(DefaultBrokerageModel):
#     '''Custom brokerage model that requires clients to maintain a minimum cash balance'''
#     def __init__(self, algorithm, minimumAccountBalance):
#         self.algorithm = algorithm
#         self.minimumAccountBalance = minimumAccountBalance