This post will guide you through developing your very own trading algorithm in QuantConnect. A familiarity in python and basic finance knowledge is assumed, but I’ll be gentle — promise! Already an expert? Skip to the code.

More comfortable with C#? View the alternate tutorial.

The algorithm we’ll build is based on the principle of a proportionated simple moving average (P-SMA). We will choose a benchmark (SPY in this example) and, based on its simple moving average, decide if the market will go up or down. If we predict the market will go up, we will invest in equities that provide fast growth but increased risk. Otherwise, we invest in safe assets, such as treasury bonds. Proportionated means the decision is not binary. For example, we may calculate 30% of our portfolio should be relatively risk-less and allocate 70% for high growth equities.

Time to code! Any algorithm in QuantConnect starts the same way:

class ProportionalSMAFast(QCAlgorithm):
    def Initialize(self):
        pass

First, we instantiate the class. The name can be anything you like, but it’s important to extend QCAlgorithm. Whenever an algorithm is started, Initialize is called exactly once and allows us to setup the properties of our algorithm. Let’s begin to flesh out initialize.

def Initialize(self):
    self.SetCash(10000)

    self.SetStartDate(2016,01,01)
    self.SetEndDate(2016,10,14)

    # Add all assets you plan on using later
    self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
    self.qqq = self.AddEquity("QQQ", Resolution.Daily).Symbol
    self.tlt = self.AddEquity("TLT", Resolution.Daily).Symbol
    self.agg = self.AddEquity("AGG", Resolution.Daily).Symbol

    self.benchmark = self.spy

    self.risk_on_symbols = [self.spy, self.qqq]
    self.risk_off_symbols = [self.tlt, self.agg]

Methods such as SetCash, SetStartDate, and SetEndDate are only applicable when running a back-test. They are completely ignored during live trading.

AddEquity is essential to any algorithm you write. By adding the equity in the initialize method, the relevant equity data will be made available throughout your algorithm. Resolution.Daily specifies data will be given with a daily window. Other options are tick, second, minute, and hour.

So what about the .Symbol and self.my_equity? Why assign the variable? This is not strictly necessary. Specifically, the following code is all that is required to make the equity data available.

self.AddEquity("SPY", Resolution.Daily)

By assigning self.spy, we can prevent hard coding the string "SPY" everywhere and use the variable instead. If you’re a little confused about this point, don’t worry. It will become more apparent later on.

And the final two lines? Remember, we want to invest in either high growth or low risk assets depending on the market. risk_on_symbols will be invested when we want to add risk to our portfolio — predicting an upswing. risk_off_symbols are our low risk investments. You should feel free to experiment with different symbols. You can add as many or as few equities as you like to either list.

#Schedule every day SPY is trading
self.Schedule.On(self.DateRules.EveryDay(), \
                 self.TimeRules.AfterMarketOpen(self.benchmark, 10), \
                 Action(self.EveryDayOnMarketOpen))

The snippet above will complete our Initialize method. This is the main driver of your algorithm. It schedules a method called EveryDayOnMarketOpen to run every day that SPY (our benchmark) is trading, 10 minutes after market open.

Since setup is over with, let’s move on to the heart of the algorithm by defining EveryDayOnMarketOpen.

def EveryDayOnMarketOpen(self):
    #Do nothing if outstanding orders exist
    if self.Transactions.GetOpenOrders():
        return

Nothing groundbreaking here. We just return immediately if there are any open orders. In theory, this should never happen. Our algorithm will submit market orders 10 minutes after market open, and is run once per trading day. If this block does execute, it’s likely an indicator of a more serious, underlying problem. Nevertheless, better safe than sorry.

#Lookup last 84 days
slices = self.History(self.spy, 84)
#Get close of last (yesterday's) slice
spy_close = slices["close"][-1]

#Get mean over last 21 days
spy_prices_short = slices["close"][-21:]
spy_mean_short = spy_prices_short.mean()

#Get mean over last 84 days
spy_prices_long = slices["close"]
spy_mean_long = spy_prices_long.mean()

The self.History method returns a pandas data frame, representing data on the specified equity for the previous 84 days. Our algorithm compares moving averages over two different window sizes, 21 and 84 days. These are arbitrary (but common) intervals. I encourage you to experiment by changing these values. The next two blocks splice the last 21 and 84 closing prices from the data frame and calculate the average.

risk_on_pct  = (spy_mean_short/spy_close) * \
               ((spy_mean_short *2 / spy_mean_long) *.25) / \
               len(self.risk_on_symbols)
risk_off_pct = (spy_close/spy_mean_short) * \
               ((spy_mean_long *2 / spy_mean_short) *.25) / \
               len(self.risk_off_symbols)

#Submit orders
for sid in self.risk_on_symbols:
    self.SetHoldings(sid, risk_on_pct)
for sid in self.risk_off_symbols:
    self.SetHoldings(sid, risk_off_pct)

Finally, the exciting stuff! The “risk on” and “risk off” percentages are calculated using our history data. self.SetHoldings will allocate a percentage of your portfolio to the specified equity. For instance, self.SetHoldings("SPY", 1) will buy as much SPY as you can afford, 100% of your portfolio. If you have a margin account and want to leverage your position, simply allocate more than 100%. self.SetHoldings("SPY", 2) will buy twice as many SPY shares as you can actually afford.

That’s it! You now have an algorithm that can trade automatically on your behalf. I encourage you to experiment changing/improving the algorithm on your own.

This example is also available on GitHub.

Last Updated on

2 thoughts on “Building your first algorithm in QuantConnect (Python)

  1. Should the 84 period code call look like the 21 day? If so there should be [-84:] appended to this line .. bench_prices_long = slices[“close”]

  2. Hi Tt,

    Excellent question! The [-n:] syntax gets a subset of the list, from the nth to last item to the very end. For the 21 day code, this is required because the list has the last 84 days in it, and for the short time period we only want to evaluate the most recent 21 days (the 21st to last element to the very end).

    Because the entire list consists of 84 days, this is not necessary for the long period. In this case, we can use the entire list, not just a subset.

    `bench_prices_long = slices[“close”]` and `bench_prices_long = slices[“close”][-84:]` will result in exactly the same list, since there are 84 elements total, and you specify to get the last 84 items of the list.

    How do I know `slices` contains exactly 84 days? It was specified in this line: `slices = self.History(self.benchmark, 84)`

    I hope this helps! Let me know if you have any other questions.

    Best,
    ~Eddie

Leave a Reply