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:
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.
# 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
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
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.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.
self.TimeRules.AfterMarketOpen(self.benchmark, 10), \
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
#Do nothing if outstanding orders exist
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.
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()
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.
((spy_mean_short *2 / spy_mean_long) *.25) / \
risk_off_pct = (spy_close/spy_mean_short) * \
((spy_mean_long *2 / spy_mean_short) *.25) / \
for sid in self.risk_on_symbols:
for sid in self.risk_off_symbols:
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.