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