Welcome to Quantopian! The Getting Started Tutorial serves as an introduction to writing and backtesting an algorithm on Quantopian. This tutorial covers many of the basics of the Quantopian API, and is designed for those who are new to Quantopian. This tutorial is divided into a series of lessons, with each one focusing on a different part of the API. By the end, we will work up to a simple mean reversion algorithm. The video series also covers the same material.
What is a Trading Algorithm?
On Quantopian, a trading algorithm is a Python program that defines two special functions: initialize() and handle_data(). initialize() is called when the program is started, and handle_data() is called once per minute during simulation or live-trading in events that we'll refer to as 'bars'. The job of initialize() is to perform any one-time startup logic. The job of handle_data() is to decide what orders, if any, should be placed each minute.
The following is an example of an algorithm that allocates 100% of its portfolio in AAPL:
def initialize(context):
    # Reference to AAPL
    context.aapl = sid(24)

def handle_data(context, data):
    # Position 100% of our portfolio to be long in AAPL
    order_target_percent(context.aapl, 1.00)
To run this example algorithm, create a copy by clicking the "Clone" button in the upper right hand corner. Run a backtest by clicking "Build Algorithm" (top left) or "Run Full Algorithm" (top right).
Datasets
When writing an algorithm on Quantopian, you have access to free minute bar historical pricing and volume data for US equities (covered in this tutorial) as well as free Morningstar fundamentals data, and third-party datasets such as news sentiment, and earnings calendars. Futures data is coming soon but is not yet available. For more information on futures, click here.
Continue on to lesson 2 to begin writing your first algorithm.
Core Functions
An algorithm on Quantopian has 3 core functions: initialize(), handle_data(), and before_trading_start(). The first one, initialize(), must be implemented in every algorithm while handle_data() and before_trading_start() are optional. These functions each have a different role:
initialize()
initialize() is called exactly once when our algorithm starts and requires context as input.
context is an augmented Python dictionary used for maintaining state during our backtest or live trading, and can be referenced in different parts of our algorithm. context should be used instead of global variables in the algorithm. Properties can be accessed using dot notation (context.some_property).
handle_data()
handle_data() is called once at the end of each minute and requires context and data as input. context is a reference to the same dictionary in initialize() and data is an object that stores several API functions which we will discuss in a later lesson.
The following example stores a string variable with the value 'hello' in context, and prints it out each minute.
def initialize(context):
    context.message = 'hello'

def handle_data(context, data):
    print context.message

before_trading_start()
before_trading_start() is called once per day before the market opens and requires context and data as input. It is frequently used when selecting securities to order. This will be explored in a later tutorial.
Referencing Securities
The best algorithms select securities to trade dynamically using pipeline. Pipeline is an advanced tool and will be covered in a later tutorial. To keep things simple, we'll start by manually referencing hand-picked securities. There are two ways to manually select securities on Quantopian: the sid() function, and the symbol() function.
sid()
The sid() function returns a security given a unique integer security ID (SID). sid() is a robust way to refrence a security. The SID of a security remains constant despite ticker changes and as such, is a reliable way to reference a stock. A security's SID never changes.
For example, if we want to reference AAPL, we can use sid(), which brings up a text prompt allowing us to fill in a ticker symbol and gives us the corresponding security ID.
Help sid
We can store the reference to AAPL in a context variable:
context.aapl = sid(24)
To reference this later in our code, we can simply refer to context.aapl. For example, to print out the security reference each minute, we can add print context.aapl to handle_data():
def initialize(context):
    context.aapl = sid(24)

def handle_data(context, data):
    print context.aapl

symbol()
Alternatively, the symbol() function returns a security given a ticker symbol. symbol() is not robust to ticker changes unless you specify a reference date with set_symbol_lookup_date('YYYY-MM-DD'). It is strongly discouraged to us symbol() to reference a security without setting a reference date.
Ordering Securities
There are several functions that can be used to order securities in the Quantopian API. In this tutorial, we are going to focus on the order_target_percent() function. order_target_percent() allows us to order securities to a target percent of our portfolio value (sum of open positions value + cash balance).
order_target_percent() requires two arguments: the asset being ordered, and the target percent of the portfolio. For example, the following line would order 50% of our portfolio value worth of AAPL:
order_target_percent(sid(24), 0.50)
To open a short position (a position that profits when the security will drop in price), we can simply enter a negative target percent:
order_target_percent(sid(24), -0.50)
The following example takes a long position in Apple stock worth 60% of our portfolio value, and takes a short position in the SPY ETF worth 40% of our portfolio value:
def initialize(context):
    context.aapl = sid(24)
    context.spy = sid(8554)

def handle_data(context, data):
    # Note: data.can_trade() is explained in the next lesson
    if data.can_trade(context.aapl):
        order_target_percent(context.aapl, 0.60)
    if data.can_trade(context.spy):
        order_target_percent(context.spy, -0.40)

The data Object
The data object contains functions that allow us to look up current or historical pricing and volume data for any security. data is available in handle_data() and before_trading_start(), as well as any scheduled functions. Let's have a look at some of data's functions:
data.current()
data.current() can be used to retrieve the most recent value of a given field(s) for a given asset(s). data.current() requires two arguments: the asset or list of assets, and the field or list of fields being queried. Possible fields include 'price', 'open', 'high', 'low', 'close', and 'volume'. The output type will depend on the input types. To get the most recent price of AAPL, we can use:
data.current(sid(24), 'price')
The following line of code gets the most recent price of AAPL and SPY and returns a pandas Series indexed by asset:
data.current([sid(24), sid(8554)], 'price')
The following line will get the last known low and high prices of AAPL and SPY from and returns a pandas DataFrame (indexed by assets, fields as columns):
data.current([sid(24), sid(8554)], ['low', 'high'])
data.can_trade()
data.can_trade() is used to determine if an asset(s) is currently listed on a supported exchange and can be ordered. If data.can_trade() returns True for a particular asset in a given minute bar, we are able to place an order for that asset in that minute. This is an important guard to have in our algorithm if we hand-pick the securities that we want to trade. It requires a single argument: an asset or a list of assets. The following example checks if AAPL is currently listed on a major exchange:
data.can_trade(sid(24))
data.history()
data.history() is covered in the next lesson.
The history() Function
The data object has a function history() that allows us to get trailing windows of historical pricing or volume data. data.history() requires 4 arguments: an asset or list of assets, a field or list of fields, an integer lookback window length, and a lookback frequency. Possible fields include 'price', 'open', 'high', 'low', 'close', and 'volume'. Possible frequencies are '1d' for daily and '1m' for minutely. The following example returns a pandas Series containing the price history of AAPL over the last 10 days and uses pandas.Series.mean() to calculate the mean.
# Get the 10-day trailing price history of AAPL in the form of a Series.
hist = data.history(sid(24), 'price', 10, '1d')

# Mean price over the last 10 days.
mean_price = hist.mean()
Note: With '1d' frequency, the most recent value in the result from data.history() will include a value for the current date in the simulation, which can sometimes be a value for a partial day. For example, if data.history() is called in the first minute of the day, the last row of the returned DataFrame will represent values from 9:31AM, whereas the previous 9 rows will represent end-of-day values. To get the past 10 complete days of data, we can get an extra day of data, and drop the most recent row. The following example gets the trading volume of SPY from the last 10 complete days:
data.history(sid(8554), 'volume', 11, '1d')[:-1].mean()
Similar to the other data functions, the return type of data.history() depends on the input types. In the next example, the return type is a pandas DataFrame indexed by date, with assets as columns:
# Get the last 5 minutes of volume data for each security in our list.
hist = data.history([sid(24), sid(8554), sid(5061)], 'volume', 5, '1m')

# Calculate the mean volume for each security in our DataFrame.
mean_volumes = hist.mean(axis=0)
And if we pass a list of fields, we get a pandas Panel indexed by field, having date as the major axis, and assets as the minor axis::
# Low and high minute bar history for each of our securities.
hist = data.history([sid(24), sid(8554), sid(5061)], ['low', 'high'], 5, '1m')

# Calculate the mean low and high over the last 5 minutes
means = hist.mean()
mean_lows = means['low']
mean_highs = means['high']
The following example is clonable and can be backtested in the IDE. It prints the mean trading volume of a list of securities over the last 10 minutes:
def initialize(context):
    # AAPL, MSFT, SPY
    context.security_list = [sid(24), sid(8554), sid(5061)]

def handle_data(context, data):
    hist = data.history(context.security_list, 'volume', 10, '1m').mean()
    print hist.mean()

Trading Calendars
Before running an algorithm, we need to select the trading calendar on which we want our algorithm to run. Currently, there are two options: the US Equities Calendar and the US Futures Calendar.
The trading calendar can be selected from a dropdown menu before running a backtest.
Help calendar dropdown
If you are only trading equities, you will want to use the US Equities Calendar. If you would like to include futures in your algorithm, you will need to use the US Futures Calendar. For the rest of this tutorial, we will use the US Equities Calendar, which runs from 9:30AM-4PM Eastern time on regular trading days and follows the NYSE holiday and early close schedule.
Scheduling Functions
So far we have seen that trade logic can exist in handle_data() which runs every minute. But what if we want to trade at a different frequency like once per day, or once per month? schedule_function() allows us to schedule custom functions at regular intervals and allows us to specify interday frequency as well as intraday timing.
The following example schedules a function called rebalance() to run once per day, one hour after market open (usually 10:30AM ET).
schedule_function(func=rebalance,
                  date_rules=date_rules.every_day(),
                  time_rules=time_rules.market_open(hours=1))
rebalance() must be a function requiring context and data as arguments that we can define later in our algorithm.
Note: The relative date and time rules that we specify will follow the trading calendar that we selected. For example, date_rules.market_open() will usually run in the 9:30AM ET minute bar if we selected the US equity calendar.
In another example, let's say we want to run a custom function weekly_trades(), on the last trading day of each week, 30 minutes before market close. We can use:
schedule_function(weekly_trades, date_rules.week_end(), time_rules.market_close(minutes=30))
The following clonable example takes a long position in SPY at the start of the week, and closes out the position at 3:30pm on the last day of the week:
def initialize(context):
    context.spy = sid(8554)

    schedule_function(open_positions, date_rules.week_start(), time_rules.market_open())
    schedule_function(close_positions, date_rules.week_end(), time_rules.market_close(minutes=30))

def open_positions(context, data):
    order_target_percent(context.spy, 0.10)

def close_positions(context, data):
    order_target_percent(context.spy, 0)

schedule_function() uses relative times and dates to account for market holidays. A backtest or live algorithm only ever runs on trading days. Market half-days can be skipped by passing half_days=False as a final keyword argument to schedule_function(). For more information, and a full list of date_rules and time_rules, check out the documentation.
Managing Your Portfolio and Plotting Variables
The portfolio object stores important information about our portfolio. The portfolio object is stored in context, and as such, is accessible in each of our core functions and our scheduled functions. In this lesson, we are going to focus on the positions attribute of the portfolio object.
Positions
Our current positions are stored in context.portfolio.positions. which is similar to a Python dictionary having assets as keys, and Position objects (including information such as the number of shares and price paid) as values.
One example of when it can be useful to reference our current positions, is if we want to close out all of our open positions. To do so, we can iterate over the keys in context.portfolio.positions, and close out each position:
for security in context.portfolio.positions:
  order_target_percent(security, 0)
Plotting Variables
In the IDE, the record() function allows us to plot time series charts updated as frequently as daily in backtesting or as frequently as minutely in live trading. Up to 5 series can be recorded and plotted. To record a variable, we can pass it as a keyword argument to record(). The name of the argument will be the name of the series in the plot. Recorded time series are then displayed in a chart below the returns chart.
The following example plots the number of long positions in our portfolio as a series called 'num_long', and the number of short positions as 'num_short'.
def initialize(context):
    context.aapl = sid(24)
    context.spy = sid(8554)

    schedule_function(rebalance, date_rules.every_day(), time_rules.market_open())
    schedule_function(record_vars, date_rules.every_day(), time_rules.market_close())

def rebalance(context, data):
    order_target_percent(context.aapl, 0.50)
    order_target_percent(context.spy, -0.50)

def record_vars(context, data):

    long_count = 0
    short_count = 0

    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            long_count += 1
        if position.amount < 0:
            short_count += 1

    # Plot the counts
    record(num_long=long_count, num_short=short_count)

Slippage and Commission
Slippage
Slippage is where a simulation estimates the impact of orders on the fill rate and execution price they receive. When an order is placed for a trade, the market is affected. Buy orders drive prices up, and sell orders drive prices down; this is generally referred to as the price_impact of a trade. Additionally, trade orders do not necessarily fill instantaneously. Fill rates are dependent on the order size and current trading volume of the ordered security. The volume_limit determines the fraction of a security's trading volume that can be used by your algorithm.
In backtesting and non-brokerage paper trading (Quantopian paper trading), a slippage model can be specified in initialize() using set_slippage(). There are different builtin slippage models that can be used, as well as the option to set a custom model. By default (if a slippage model is not specified), the following volume share slippage model is used:
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.1))
Using the default model, if an order of 60 shares is placed for a given stock, then 1000 shares of that stock trade in each of the next several minutes and the volume_limit is 0.025, then our trade order will be split into three orders (25 shares, 25 shares, and 10 shares) that execute over the next 3 minutes.
At the end of each day, all open orders are canceled, so trading liquid stocks is generally a good idea. Additionally, orders placed exactly at market close will not have time to fill, and will be canceled.
Commission
To set the cost of trades, we can specify a commission model in initialize() using set_commission(). By default (if a commission model is not specified), the following commission model is used:
set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1))
The default commission model charges $0.0075 per share, with a minimum trade cost of $1.
Slippage and commission models can have an impact on the performance of a backtest. The default models used by Quantopian are fairly realistic, and it is highly recommended that you use them.
Managing Orders
Orders do not always fill instantaneously. Large orders, or orders placed for illiquid securities can take some time to fill. On Quantopian, the time it takes for an order to fill is determined by the slippage model being used. If an order takes more than one minute to fill, it's considered open until it fills. When placing new orders, it's sometimes necessary to consider open orders.
On Quantopian, there is no limit to the amount of money that can be invested by an algorithm, regardless of its starting capital, so controlling cash and leverage is necessary. At the end of each trading day, all open orders are canceled. However, when placing orders multiple times in the same day, open orders need to be taken into account each time a new order is placed. order_target_percent() doesn't consider open orders when calculating the number of shares to order. Placing a new order for a security that has an outstanding open order can lead to over-ordering (ordering past the target). This can lead to an algorithm spending more money than intended.
To avoid over-ordering, we can look at open orders using get_open_orders() which returns a dictionary of open orders keyed by assets. We can use this to ensure that we don't have an open order for a security before we place a new order for it.
The following example checks for open orders before opening new ones:
def initialize(context):
    # Relatively illiquid stock.
    context.xtl = sid(40768)

def handle_data(context, data):
    # Get all open orders.
    open_orders = get_open_orders()

    if context.xtl not in open_orders and data.can_trade(context.xtl):
        order_target_percent(context.xtl, 1.0)

Putting It All Together
At this point, we have looked at different components of the Quantopian API individually. Now it's time to put them all together in a basic trading algorithm. In this lesson, we're going to use the functions that we've seen so far to construct a simple mean reversion strategy.
Planning the Strategy
To start out, let's plan a strategy to implement.
Hypothesis: If the 10-day simple moving average (short SMA) of a security is higher than its 30-day simple moving average (long SMA), the price of the security will drop. Conversely, if its short SMA is lower than its long SMA, the price will go up. This is referred to as mean reversion.
Selecting Assets to Trade
This type of strategy is usually implemented using a dynamic set of securities selected by pipeline, but since we haven't covered that yet, we're going to hand-pick a list of securities to trade:
# MSFT, UNH, CTAS, JNS, COG
context.security_list = [sid(5061), sid(7792), sid(1941), sid(24556), sid(1746)]

Note: The securities selected have no significance to the strategy. They were chosen arbitrarily.


Setting a Rebalance Schedule
In this implementation, we will rebalance once at the start of each week at market open. To do this, we can schedule a rebalance function in initialize().
schedule_function(rebalance,
                  date_rules.week_start(days_offset=0),
                  time_rules.market_open())
Computing Weights
Next, we want to decide how much of our portfolio we want to allocate to each security in our list and whether we want to open long or short positions for each of them. In this example, determining whether we want to open a long or short position is simple. If the 10-day SMA is below the 30-day SMA, we take a long position, predicting that the price will go back up. If the 10-day SMA is above the 30-day SMA, we take a short position, predicting the price will drop.
Let's also assume that the greater the relative difference between a security's 10-day and 30-day SMA, the more likely it is to revert. Under this hypothesis, we want to take a bigger position in securities that have a greater relative difference between their short and long SMAs.
Let's define this in a function:
def compute_weights(context, data):

  # Get the 30-day price history for each security in our list.
  hist = data.history(context.security_list, 'price', 30, '1d')

  # Create 10-day and 30-day trailing windows.
  prices_10 = hist[-10:]
  prices_30 = hist

  # 10-day and 30-day simple moving average (SMA)
  sma_10 = prices_10.mean()
  sma_30 = prices_30.mean()

  # Weights are based on the relative difference between the short and long SMAs
  raw_weights = (sma_30 - sma_10) / sma_30

  # Normalize our weights
  normalized_weights = raw_weights / raw_weights.abs().sum()

  # Return our normalized weights. These will be used when placing orders later.
  return normalized_weights
Note that we also normalized our weights to ensure that they add up to 100%. We then return normalized_weights which is a Series indexed by our securities.
Order Execution
The next step is to define how we want to order our securities. Earlier, we decided to rebalance once per week at market open using a function called rebalance(). Now, let's define rebalance() to place an order for each of our securities to the target weight that we calculated in compute_weights():
def rebalance(context, data):

  # Calculate our target weights.
  weights = compute_weights(context, data)

  # Place orders for each of our securities.
  for security in context.security_list:
    if data.can_trade(security):
      order_target_percent(security, weights[security])
Note that we use order_target_percent() to order to our target weights. We also use data.can_trade() to guard against ordering delisted securities since we hard-coded a static list of securities.
Recording and Plotting
To learn more about what our algorithm is actually doing, let's track some variables that we might be interested in. Let's first schedule another function in initialize() called record_vars() to run at the end of each day:
schedule_function(record_vars,
                  date_rules.every_day(),
                  time_rules.market_close())
And then we'll define record_vars() to track our leverage, as well as the number of long and short positions we are holding. We can count the number of long and short positions each day using the context.portfolio.positions object:
def record_vars(context, data):

  # Check how many long and short positions we have.
  longs = shorts = 0
  for position in context.portfolio.positions.itervalues():
    if position.amount > 0:
      longs += 1
    elif position.amount < 0:
      shorts += 1

  # Record our variables.
  record(leverage=context.account.leverage, long_count=longs, short_count=shorts)
Putting It Together
Since there's nothing that we want to do every minute, nor anything that we want to do before the market opens, we don't need to implement either handle_data() or before_trading_start(). The final algorithm looks like this (note that some comments, as well as two print statements in compute_weights() were added):
def initialize(context):
    """
    initialize() is called once at the start of the program. Any one-time
    startup logic goes here.
    """

    # An assortment of securities from different sectors:
    # MSFT, UNH, CTAS, JNS, COG
    context.security_list = [sid(5061), sid(7792), sid(1941), sid(24556), sid(1746)]

    # Rebalance every Monday (or the first trading day if it's a holiday)
    # at market open.
    schedule_function(rebalance,
                      date_rules.week_start(days_offset=0),
                      time_rules.market_open())

    # Record variables at the end of each day.
    schedule_function(record_vars,
                      date_rules.every_day(),
                      time_rules.market_close())

def compute_weights(context, data):
    """
    Compute weights for each security that we want to order.
    """

    # Get the 30-day price history for each security in our list.
    hist = data.history(context.security_list, 'price', 30, '1d')

    # Create 10-day and 30-day trailing windows.
    prices_10 = hist[-10:]
    prices_30 = hist

    # 10-day and 30-day simple moving average (SMA)
    sma_10 = prices_10.mean()
    sma_30 = prices_30.mean()

    # Weights are based on the relative difference between the short and long SMAs
    raw_weights = (sma_30 - sma_10) / sma_30

    # Normalize our weights
    normalized_weights = raw_weights / raw_weights.abs().sum()

    # Determine and log our long and short positions.
    short_secs = normalized_weights.index[normalized_weights < 0]
    long_secs = normalized_weights.index[normalized_weights > 0]

    log.info("This week's longs: " + ", ".join([long_.symbol for long_ in long_secs]))
    log.info("This week's shorts: " + ", ".join([short_.symbol for short_ in short_secs]))

    # Return our normalized weights. These will be used when placing orders later.
    return normalized_weights

def rebalance(context, data):
    """
    This function is called according to our schedule_function settings and calls
    order_target_percent() on every security in weights.
    """

    # Calculate our target weights.
    weights = compute_weights(context, data)

    # Place orders for each of our securities.
    for security in context.security_list:
        if data.can_trade(security):
            order_target_percent(security, weights[security])

def record_vars(context, data):
    """
    This function is called at the end of each day and plots our leverage as well
    as the number of long and short positions we are holding.
    """

    # Check how many long and short positions we have.
    longs = shorts = 0
    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            longs += 1
        elif position.amount < 0:
            shorts += 1

    # Record our variables.
    record(leverage=context.account.leverage, long_count=longs, short_count=shorts)
Congratulations on completing the first Quantopian tutorial! Try coming up with your own strategy and implementing it yourself.
The next tutorial will cover the Pipeline API and teach you how to select securities dynamically.
PREVIOUS
NEXT

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 or other services by Quantopian. In addition, the content of the website offers no opinion with respect to the suitability of any security or any specific investment.

Quantopian makes no guarantees as to 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.