QuantConnect: Create an Indicator – Awesome Oscillator

Continuing with the QuantConnect series, we shall turn our attention to indicators. There are actually a lot of cool things we can do with indicators on QuantConnect such as chaining the existing library of 100 indicators together to create some unique results. However, in this tutorial we will focus on creating a new indicator from scratch, importing it into our algorithm and finally plotting the output.

Awesome Oscillator

In the code example for this post, we will create an Awesome Oscillator. This was selected as it is a fairly simple indicator to calculate and we can build it on the back of an excellent example from QuantConnect’s own Alex Catarino. In short, the Awesome Oscillator aims to measure market momentum by calculating the difference between two simple moving averages. Specifically, it uses 34 Period and 5 Period SMA’s for this purpose.
Awesome Oscillator = 5 Period SMA – 34 Period SMA
Note: That in the example below, we will copy/port the formula from Tradingview’s Awesome Oscillator.

Example Code

import numpy as np
from collections import deque
from datetime import datetime 

### <summary>
### Basic template algorithm simply initializes the date range and cash. This is a skeleton
### framework you can use for designing an algorithm.
### </summary>

class AwesomeOscillator:
    '''
    //@version=3
    study(title="Awesome Oscillator", shorttitle="AO")
    ao = sma(hl2,5) - sma(hl2,34)
    plot(ao, color = change(ao) <= 0 ? red : green, style=histogram)
    '''

    def __init__(self, period_fast=5, period_slow=34):
        
        self.Name = "Awesome Osc - {}, {}".format(period_fast, period_slow)
        self.Time = datetime.min
        self.Value = 0
        self.IsReady = False
        
        self.fast_sma_queue = deque(maxlen=period_fast)
        self.slow_sma_queue = deque(maxlen=period_slow)
        

    def __repr__(self):
        return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value)

    # Update method is mandatory
    def Update(self, input):
        
        # Fill the queues
        hl2 = (input.High + input.Low) / 2
        self.fast_sma_queue.appendleft(hl2)
        self.slow_sma_queue.appendleft(hl2)
        
        # Calc the SMA's
        fast_count = len(self.fast_sma_queue)
        fast_sma = sum(self.fast_sma_queue) / fast_count
    
        slow_count = len(self.slow_sma_queue)
        slow_sma = sum(self.slow_sma_queue) / slow_count
        
        self.Value = fast_sma - slow_sma
        
        self.Time = input.EndTime
        self.IsReady = slow_count == self.slow_sma_queue.maxlen
        
        
class BasicTemplateAlgorithm(QCAlgorithm):
    '''Basic template algorithm simply initializes the date range and cash'''

    def Initialize(self):
        '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''

        self.SetStartDate(2018,1,1)    #Set Start Date
        self.SetEndDate(2019,1,1)      #Set End Date
        self.SetCash(100000)           #Set Strategy Cash
        # Find more symbols here: http://quantconnect.com/data
        self.AddEquity("SPY", Resolution.Daily)
        
        self.AO = AwesomeOscillator()
        self.RegisterIndicator("SPY", self.AO, Resolution.Daily)
        
        # Create a chart for the indicator
        AOChart = Chart("Awesome", ChartType.Stacked)
        AOChart.AddSeries(Series('AO', SeriesType.Line))
        
 
    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.

        Arguments:
            data: Slice object keyed by symbol containing the stock data
        '''
        
        self.Debug(self.AO)
        
        if not self.AO.IsReady: return
        
        self.Plot('Awesome', 'AO', self.AO.Value)

Code Commentary

If you have followed other posts in the getting started series, you might notice right away that we have a new import. from collections import deque A deque is like a list but it has a fixed size. This means that it is more like a “container”. Once it is full, anything you put into the container will result in something else getting pushed out of the other side. This makes it perfect for storing values needed to calculate our simple moving average value. For more information see here: https://docs.python.org/3.7/library/collections.html#collections.deque After the imports, we move onto the Indicator and Algorithm classes. This is actually our first example on QuantConnect to contain two classes. As such, we will break the commentary up into two parts to cover the indicator and algorithm separately. Note that we will be talking a little bit about classes over the following few paragraphs. The jargon will be as light as possible but if you are lost or completely new to programming, it might be an idea to take a look at some introductory Python tutorials on classes.

Indicator

To create a custom indicator on QuantConnect, all we need to do is create a new python class and make sure that it has some specific methods (functions) inside it.

__init__()

The first method needed is __init__(). This is called (run/executed) when a new instance of the indicator is made from the class blueprints. It is during the initialization of the class (when we create the indicator/instance) that we set a few attributes (variables) to be used later. Setting them when the indicator is created means that the attributes exist and have valid values before any data is piped into the indicator.  These attributes we set are:
  • Name: Wich can be used to identify the indicator when debugging or printing the output (more on this later)
  • Time: Will be used for logging the last time that the indicator was updated.  Here we start withdatetime.min. This actually just gives us the earliest valid datetime value possible.
  • Value: Is the actual indicator value to be returned. Here we set it to 0 as we don’t have any data when we first create the indicator.
  • IsReady: This an important attribute. It will be used by many algorithms to detect whether the indicator has enough data to make accurate calculations. For example, if you have a 200-day moving average, IsReadywould be Falseuntil the indicator has 200 days of data to make the calculation.
  • fast_sma_queue and slow_sma_queue: These are two dequecontainers that we will use for storing data. They will allow us to create the SMA values needed to create the final Awesome Oscillator Value.
Introduction to classes: https://www.pythoncentral.io/introduction-to-python-classes/

__repr__()

Next, we have the __repr__() method. This is not absolutely needed. However, it can be used to provide some useful debug information if you try to use self.Debug()on the indicator. In fact, we actually do this in the example algorithm.

update()

Finally, there is the update()method. This method IS absolutely required. It will be called and passed some new data every time a new bar or tick of data comes in. This is where we do our number crunching and arrive at the final indicator value. In our example, we are adding HL2data to each of the queues as new data arrives. Then we simply get the average value of each queue and subtract one average from the other. Once the queue is full, we set IsReady to true.

Algorithm

The algorithm in this example is only used for testing that the indicator is working and to plot the final results. There are more in-depth articles on both of these topics here: To load our custom indicator into our algorithm, we just need to create an instance of the indicator inside the algorithm’s __init__()method. Then we register the indicator with an asset and data feed (in this case daily SPY).
self.AO = AwesomeOscillator()
self.RegisterIndicator("SPY", self.AO, Resolution.Daily)
Once we have done that, our indicator will be fed data which we can access in OnData().  It is here we check to see if our indicator IsReadyand if so, check the Value with self.AO.Value. This can the be used to update the custom plot we created. Note: Plotting  is covered in the article above. Second Note: At the time of writing using PlotIndicator()within the Initialize()method does not work. As such, users must manually create a plot and update it in OnData()as shown in the example code. See this GitHub issue for more details.

Running The Algorithm

When running the algorithm, you will be able to see debugs printed thanks to the __repr__method we created. Awesome Oscillator Terminal Output on QuantConnect Also, once the results are in, we will be able to plot the final chart. Awesome Oscillator Plot Output on QuantConnect Note that we are just representing the chart as a simple line chart. The indicator on Tradingview (and possibly other platforms) uses a histogram and some dynamic coloring for styling. A good followup exercise would be to dive deeper into plotting to replicate the look and feel.

Find This Post Useful?

If this post saved you time and effort, please consider support the site! There are many ways to support us and some won’t even cost you a penny. 

Backtest Rookies is a registered with Brave publisher!

Brave users can drop us a tip.

Alternatively, support us by switching to Brave using this referral link and we will receive some BAT! 


https://brave.com/bac691

Referral Link

Enjoying the content and thinking of subscribing to Tradingview? Support this site by clicking the referral link before you sign up! 

https://tradingview.go2cloud.org/SHpB


Donate with PayPal using any payment method you are comfortable with! 


3HxNVyh5729ieTfPnTybrtK7J7QxJD9gjD

0x9a2f88198224d59e5749bacfc23d79507da3d431

M8iUBnb8KamHR18gpgi2sSG7jopddFJi8S