Learning Quantconnect
After some investigation on how to get started on algo trading, I learned that the important thing is to get started with the bare minimum as soon as possible. A lot of posts on reddit suggest that many spend tons of time reading books, watching videos, but never get their hands dirty with trading. That is the biggest mistake, as you learn best from doing.
So instead of building my own trading engine and backtesting framework as I initially planned, I have decided to jump in with QuantConnect to get a taste of what strategy development and testing feels like.
What is QuantConnect
QuantConnect is an open-source algorithmic trading platform that provides access to financial data, cloud computing, and a coding environment for designing algorithms.
Why QuantConnect
The main reasons I chose QuantConnect are
- It’s free and open source
- It uses Python and provides capabilities like backtesting, free data source, and paper trading
- It has a nice tutorial for learning
Obviously there are plenty of good platforms for algo trading, so there’s no right or wrong in which platform one goes with. I just happen to use QuantConnect.
Notes On Learning QuantConnect
Bootcamp 101: US Equities
Taking some notes while I go through the Bootcamp 101 on US Equities.
Buy and Hold Equities
- Each algorithm is a class that inherits from the
QCAlgorithm
class. - Each algorithm has an
initialize()
function which runs once everytime the algo starts. One can set different properties inside theinitialize()
function:- Starting cash:
self.set_cash(300)
- Equity:
self.add_equity("SPY", Resolution.DAILY)
. Theadd_equity
method is used for manually requesting assets. It returns a security object. This gives you a reference to the security object you can use later. - Date range:
self.set_start_date(2017, 1, 1)
self.set_end_date(2017, 1, 1)
- Starting cash:
def OnData(self, data)
is activated each time new data is passed to your algorithm, so this can be hourly, daily or weekly, etc. depending on the data resolution you request ininitialization(self)
.- Securities in your algorithm can be accessed via the
self.securities[symbol]
dictionary. - The algorithm portfolio dictionary also has helper properties for quick look ups of things like:
invested
,total_unrealized_profit
,total_portfolio_value
,total_margin_used
. - Entries in the Portfolio dictionary are SecurityHolding objects with many properties about your holdings, such as:
invested
,quantity
,average_price
andunrealized_profit
. For instance,self.portfolio["IBM"].invested
checks if the portfolio has a position in IBM. - To submit a market order, use
self.market_order("AAPL", 200)
.
Buy And Hold With A Trailing Stop
- To submit a stop-market order. The self.stop_market_order() method has the following arguments: ticker, quantity, and stop_price.
1
self.stop_market_order("IBM", -300, 0.95 * self.securities["IBM"].close)
- Order events are updates on the status of your order. Every order event is sent to the
def on_order_event()
event handler, with information about the order status held in anOrderEvent
object. - The OrderEvent object has a status property with the OrderStatus enum values SUBMITTED, PARTIALLY_FILLED, FILLED, CANCELED, and INVALID. It also contains an order_id property which is a unique number representing the order.
- When placing an order, QuantConnect returns an
OrderTicket
object which can be used to update an order’s properties, request that it is cancelled, or fetch itsorder_id
. For instance,1
self.stop_market_ticket = self.stop_market_order("IBM", -300, ibmStockPrice * 0.9)
Momentum-Based Tactical Allocation
A tactical asset allocation (TAA) strategy allows us to move the portfolio between assets depending on the market conditions. - Tracking multiple assets in your portfolio can easily be done with QuantConnect by simply calling self
add_equity()
twice. Data requested is passed into youron_data()
method synchronized in time. - The indicator MomentumPercent computes the n-period percent change in the security. The indicator will be automatically updated on the resolution frequency. The
momp()
method has the parameters symbol, period, and resolution. For instance,1
self.ibm_momp = self.momp("IBM", 30, Resolution.MINUTE)
- We prepare our algorithm with a “fast-forward” system called “Warm Up” which automatically pumps historical data to an indicator using the
self.set_warm_up()
method. Warm up requests should be sent in your algorithminitialize()
method. - All indicators have a property
is_ready
to let you know they’re ready to be used. Before using an indicator you should make sure its initialized. - A benchmark asset should be representative of the portfolio you are trading. The
self.set_benchmark()
helper should be called in your Initialize method and takes a single parameter, ticker. - To retrieve the numerical value of any indicator, you can use the
indicator.current.value
attribute of the indicator. For instance,1
if self.spy_momentum.current.value > 5:
- We can use this indicator value to dynamically allocate our holdings with a combination of the
self.liquidate()
method andself.set_holdings()
method. - We can use the algorithm
self.time
property to track the current simulation time. In backtesting this fast-forwards through historical data, in live trading this is always the current time (now). Theself.time
property is a pythondatetime
object.
An Example Momentum-based Tactical Allocation Strategy
Below is an example momentum-based tactical allocation algo, where we switch between two equities, namely SPY and BND, based on which one has the higher (50 day percent change) momentum currently:
1 | class MomentumBasedTacticalAllocation(QCAlgorithm): |
Opening Range Breakout
Opening range breakout uses a defined period of time to set a price-range, and trades on leaving that range.
- Consolidators: Consolidators are used to combine smaller data points into larger bars. We commonly use consolidators to create 5, 10 or 15-minute bars from minute data.
- The
self.consolidate()
method takes aticker
,timedelta
, andevent handler
. For instance,1
self.consolidate("SPY", timedelta(minutes=45), self.on_data_consolidated)
- self.consolidate should be called in your
initialize()
method so it is initialized only once. You must request data smaller than the bar you aim to consolidate. For example, it is not possible to consolidate daily data into minute bars. - Consolidators require an accompanying
event handler
to receive the output data. The consolidatorevent handlers
are functions which are called when a new bar is created. - Regardless of the resulting bar type, all data contains a few common properties:
bar.time
, andbar.price
. You can usebar.time
to get the current moment the bar was created andbar.end_time
to get the time at the end of the bar. - As we have requested equities data, the consolidated bar’s type is
TradeBar
. It has the properties open, high, low, close and volume for a given period of time.1
2
3
4
5
6def on_data_consolidated(self, bar):
# bar.time
# bar.open
# bar.high
# bar.low
# bar.close - We can use a scale of
1
to-1
inself.set_holdings
to place a long or short order. - Scheduled events allow you to trigger code blocks for execution at specific times according to rules you set. We initialize scheduled events in the
initialize
method so they are only executed once. - We use
self.schedule.on()
to coordinate your algorithm activities and perform analysis at regular intervals while letting the trading engine take care of market holidays. - Scheduled events need a
DateRules
andTimeRules
pair to set a specific time, and an action that you want to complete.1
self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(13,30), self.close_positions)
- We include our asset ticker in our
every_day
(“ticker”) object to specifify that we are calling days there is data for the ticker. - The call
self.liquidate("ticker")
closes any open positions with a market order, and closes any open limit orders.
An example Opening Range Breakout Strategy
Below is an example of an opening range breakout strategy. It first consolidates the data of the equity (Tesla in this case) into 30 minutes bars. If the stock price (closing price on each minute) is greater than the opening 30 minutes bar’s high price, we all in on the stock. On the other hand, if the stock price dips under the opening bar’s low price, we liquidate all of our position in the stock. Since we think the strategy is most effective before 1:30 PM ET, we scheduled a event to trigger a function which will close all of our positio in the stock.
1 | class OpeningRangeBreakout(QCAlgorithm): |
Liquid Universe Selection
We use universe selection to algorithmically reduce the number of assets added to our algorithm (the investment “universe”). We want to choose our assets with formulas, not by manually selecting our favorite assets.