Buy and hold may be the simplest algorithm possible yet covers many aspects of building an algorithm. In this lesson we walk through the steps of initializing an algorithm to run a buy and hold strategy along with some nuances of getting the data you want.
What You'll Learn- Initializing algorithms date range and starting portfolio value.
- Requesting asset data and configuring your data resolution.
- Differences in data normalization techniques and how to request raw data.
- Accessing algorithm portfolio properties.
- Buy and Hold / Equities
- Set Starting Cash
At the very start of your algorithm we call the Initialize()
method to set up your strategy. It is important to set up the state of your algorithm here so it can be restarted easily.
The self.SetCash()
method sets the starting capital for your strategy. In backtesting we use the value you set to initialize your capital; in live trading we get this value from your brokerage account. By default we use a starting cash of $100,000 in backtesting and paper trading.
Task Objectives CompletedContinue
- Set starting cash for the algorithm to $25,000
class BootCampTask(QCAlgorithm):
def Initialize(self):
self.AddEquity("SPY", Resolution.Daily)
# 1. Set Starting Cash
self.SetCash(25000)
def OnData(self, data):
pass
-----------------------------
Set Date Range- Buy and Hold / Equities
- Set Date Range
The date range for your backtest is defined in your Initialize method. You can set this with fixed dates or a datetime object. These methods are called self.SetStartDate()
and self.SetEndDate()
.
Task Objectives CompletedContinue
- Set start date for the backtest to January 1st, 2017
- Set ending date for the backtest to October 31st, 2017
int year
, int month
and int day
. You should replace the numbers in the algorithm code with the right dates. class BootCampTask(QCAlgorithm):
def Initialize(self):
#1-2. Set Date Range
self.SetStartDate(2017, 1, 1)
self.SetEndDate(2017, 10, 31)
self.AddEquity("SPY", Resolution.Daily)
def OnData(self, data):
pass
-----------------------
- Buy and Hold / Equities
- Manually Selecting Data
There are many ways to request data in QuantConnect. To start we're going to demonstrate how to ask for data manually.
Adding DataThe self.AddEquity()
method is used for manually requesting assets. It takes a string ticker of the current asset name and the resolution. For more information see the documentation.
The AddEquity (and all other AddAsset methods) return a security object. This gives you a reference to the security object you can use later.
You can control the resolution of the data with the Resolution
enum. It has the values Tick
, Second
, Minute
, Hour
and Daily
. e.g. Resolution.Minute
. Not all data is available in all resolutions. You should check the Data Library to make sure your resolution is available.
You can change leverage, request pre-market data and disable fill-forward. You can see the API calls in the documentation. We'll discuss these next.
Task Objectives CompletedContinue
- Update the code to request minute resolution data for SPY.
- Add minute resolution data for the Russel 2000 ETF (ticker: IWM).
AddEquity(string ticker, Resolution resolution)
method with the ticker and resolution set. class BootCampTask(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2017, 6, 1)
self.SetEndDate(2017, 6, 15)
# Manually Select Data
self.spy = self.AddEquity("SPY", Resolution.Minute)
self.iwm = self.AddEquity("IWM", Resolution.Minute)
def OnData(self, data):
pass
-------------------------
- Buy and Hold / Equities
- Set Data Normalization Mode
In QuantConnect, historical data is adjusted by default. This means the historical values are changed to account for share splits and dividend payments. Historical prices can look a little bit strange if you don't understand why it was done: e.g. instead of $10.23 the price might read $1.21216.
You can control the Data Normalization Mode for each asset individually. This is done with the: security.SetDataNormalizationMode()
method. The accepted values are: Raw
, Adjusted
, SplitAdjusted
and TotalReturn
.
Securities in your algorithm can be accessed via the self.Securities[symbol]
dictionary. It looks up a security by its symbol object or ticker string. Using this accessor you can look up a security and alter its models or data normalization mode.
Task Objectives CompletedContinue
- Change the data normalization mode of SPY to Raw.
- Using the security.SetLeverage method; set the leverage for the IWM Equity to 1.0.
class BootCampTask(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2017, 6, 1)
self.SetEndDate(2017, 6, 15)
#1-2. Change the data normalization mode for SPY and set the leverage for the IWM Equity
self.spy = self.AddEquity("SPY", Resolution.Daily)
self.iwm = self.AddEquity("IWM", Resolution.Daily)
self.spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
self.iwm.SetLeverage(1.0)
def OnData(self, data):
pass
------------------------------------------------------
Checking Holdings- Buy and Hold / Equities
- Checking Holdings
The algorithm Portfolio
dictionary also has helper properties for quick look ups of things like: Invested
, TotalUnrealizedProfit
, TotalPortfolioValue
, TotalMarginUsed
. You can see more properties in the documentation. e.g:
Individual asset holdings are held in your Portfolio property. This can be accessed via the self.Portfolio[symbol]
dictionary. Entries in the Portfolio dictionary are SecurityHolding objects with many properties about your holdings, such as: Invested
, Quantity
, AveragePrice
and
UnrealizedProfit
. e.g:
Task Objectives CompletedContinue
- Update the AddEquity command to request IBM data instead of SPY.
- Fix the compiler error on the Debug statement to display the quantity of IBM shares you own.
Don't forget to update the AddEquity line to IBM!
Remember you cannot concatenate floats with strings. You will need to wrap the Quantity with the python str() method.
class BootCampTask(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2017, 6, 1)
self.SetEndDate(2017, 6, 2)
#1. Update the AddEquity command to request IBM data
self.ibm = self.AddEquity("IBM", Resolution.Daily)
def OnData(self, data):
#2. Display the Quantity of IBM Shares You Own
self.Debug("Number of IBM Shares: " + str(self.Portfolio["IBM"].Quantity))
--------------------
- Buy and Hold / Equities
- Placing Orders
In this task we're going to pull together everything we've learned so far; requesting data, setting data normalization, using your portfolio and printing a debug statement.
Submitting an OrderLets place our first trade! There are many ways to submit orders through LEAN but we're going to start with a MarketOrder. It can be used like this:
self.MarketOrder("AAPL", 200)Market orders are filled immediately when the market is open. If you are using daily data, the order isn't processed until the next morning. Daily bars only arrive at your algorithm after the market has closed.
Fill PricesThe average fill price of your asset is available in the Portfolio class. You can access it like this: Portfolio["IBM"].AveragePrice
. In backtesting this is a modelled price. In live trading this is taken from your brokerage fill event.
Task Objectives CompletedContinue
- Insert
AddEquity
in the Initialize() method to request IWM data at minute resolution. - Set IWM to
DataNormalizationMode.Raw
. - In the OnData() method place a self.MarketOrder() for 100 shares of IWM.
- Using self.Debug() print the AveragePrice of IWM to the console.
Use self.Portfolio["IWM"].AveragePrice
or self.Portfolio.Invested
to only send 1 debug message to the console.
You might be able to use something like this in the OnData method...
if not self.Portfolio.Invested: // ...class BootCampTask(QCAlgorithm): def Initialize(self): self.SetStartDate(2017, 6, 1) self.SetEndDate(2017, 6, 15) #1,2. Select IWM minute resolution data and set it to Raw normalization mode self.iwm = self.AddEquity("IWM", Resolution.Minute) self.iwm.SetDataNormalizationMode(DataNormalizationMode.Raw) def OnData(self, data): #3. Place an order for 100 shares of IWM and print the average fill price #4. Debug the AveragePrice of IWM if not self.Portfolio.Invested: self.MarketOrder("IWM", 100) self.Debug(str(self.Portfolio["IWM"].AveragePrice)) ------------------------
Greg Kendall
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!