The Getting Started with Futures Tutorial will walk you through the process of researching a quantitative strategy using futures, implementing that strategy in an algorithm, and backtesting it on Quantopian. It covers many of the basics of the Quantopian API, and is designed for those who are new to the platform. Each lesson focuses on a different part of the API and by the end, we will work up to a simple pairs trading algorithm.
If you are not familiar with Futures trading, check out the Introduction to Futures lecture from our Lecture Series.
What is a Trading Algorithm?
On Quantopian, a trading algorithm is a Python program that defines a specific set of instructions on how to analyze, order, and manage assets. Most trading algorithms make decisions based on mathematical or statistical hypotheses that are derived by conducting research on historical data.
Coming Up With a Strategy
The first step to writing a trading algorithm is to find an economic relationship on which we can base our strategy. To do this, we can use Research Notebooks to inspect and analyze pricing and volume data for 72 different US futures going as far back as 2002. Research is an IPython Notebook environment that allows us to run python code in units called 'cells'. The following code gets pricing and volume data for the Light Sweet Crude Oil contract with delivery in January 2016 and plots it:
# Import the history function
from quantopian.research.experimental import history

# Create a reference to the Light Sweet Crude Oil contract
# with delivery in January 2016
clf16 = symbols('CLF16')

# Query historical pricing and volume data for 
# the contract from October 21st, 2015 to December 21st, 2015
clf16_data = history(
    clf16, 
    fields=['price', 'volume'], 
    frequency='daily', 
    start='2015-10-21', 
    end='2015-12-21'
)

# Plot the data
clf16_data.plot(subplots=True);
To use the above code copy and paste it in a new notebook in Research, or click the Get Notebook button at the top right corner of this lesson. Once you are in Research, press Shift+Enter to run the cell. The output should look like this:
Futures getting started1 l3 screenshot5
Research Notebooks is the perfect tool to test a hypothesis, so let's come up with an idea to test.
Strategy: If a pair of commodities exist on the same supply chain, we expect the spread between the prices of their futures to remain consistent. As the spread increases or decreases, we can trade on the expectation that it will revert to the steady state.
In the next few lessons we will learn how to use Research to test our hypothesis. If we find our hypothesis to be valid (spoiler alert: we will), we can use it as a starting point for implementing and backetsting an algorithm in the Interactive Development Environment (IDE).
Lessons 2-5 will be conducted in the research environment. To get set up in research, create a new IPython Notebook or clone the notebook version of this lesson by clicking Get Notebook. If you're not yet familiar with research, we recommend that you go through the Research Jupyter Notebook Tutorial.
Futures Data
Quantopian has open, high, low, close, and volume (OHLCV) data for 72 US futures from the beginning of 2002 to the current date. This dataset contains both day and minute frequency data for 24 hours x 5 days a week, and is collected from electronic trade data.
The list of US futures currently available on Quantopian can be found here.
Future Object
On Quantopian, a Future is an instance of a specific futures contract denoted by a base symbol + a code for month/year of delivery. For example, CLF16 is a contract for crude oil with delivery date in January (F) 2016 (16). Here is a reference for delivery months and their corresponding codes:
Futures getting started1 l2 screenshot1
Let's start by looking at a particular contract of the Light Sweet Crude Oil future (CL). In Research, a reference to a futures contract is obtained via the symbols function. Run the following code in a new cell to output the Future object corresponding to CLF16.
clf16 = symbols('CLF16')
clf16
Futures getting started1 l2 screenshot2
As you can see, the Future object has several attributes. Here is a brief description of some of them:
  • root_symbol: The root symbol of the underlying asset. For example, CL corresponds to crude oil.
  • start_date: The date the contract becomes available on Quantopian. Note that the price of a contract might be NaN near the start_date, as it may not be actively traded until it gets closer to its delivery date.
  • end_date: The last date the contract can be traded or closed before delivery.
  • notice_date: The date in which the exchange can start assigning delivery to accounts holding long positions on the contract.
  • auto_close_date: This is two days prior to either notice_date or end_date, whichever is earlier. In backtesting, positions in contracts will be automatically closed out on their auto_close_date.
  • tick_size: The increment in which the price of the future can change. For example, CL changes in increments of $0.01.
  • multiplier: The number of units per contract. For example, a contract for CL corresponds to 1000 barrels of oil.
In the following lesson, we'll take a look at how to get pricing and volume data for a particular futures contract.
The history Function
The history function allows us to get trailing windows of historical pricing and volume data in Research. It requires 5 arguments: the contracts for which we want data, the data fields, a lookback frequency, and the start and end dates for the lookback window. Possible fields include 'price', 'open_price', 'high', 'low', 'close_price', 'volume' and 'contract'. Possible frequencies are 'daily' and 'minute'.
Let's start by importing the history function from research's experimental library:
from quantopian.research.experimental import history
The return type of history depends on the input types of the symbols and fields parameters. If a single Future and a single field are specified, history returns a pandas Series:
clf16_price = history(
  'CLF16', 
  fields='price', 
  frequency='daily', 
  start='2015-10-21', 
  end='2015-12-21'
)

clf16_price.head()
Futures getting started1 l3 screenshot1
When a list of Futures and a single field are specified, the return type is a pandas DataFrame indexed by date, with assets as columns:
cl_fgh_16_volume = history(
    symbols=['CLF16', 'CLG16', 'CLH16'], 
    fields='volume', 
    frequency='daily', 
    start='2015-10-21', 
    end='2015-12-21'
)

cl_fgh_16_volume.head()
Futures getting started1 l3 screenshot2
And if we pass a list of Futures and a list of fields, we get a pandas Panel indexed by field, having date as the major axis, and assets as the minor axis.
Futures Pricing & Volume
Now, let's use history to get the close price and volume for the crude oil contract with delivery in January 2016 (CLF16), for the 2 months leading up to its delivery.
clf16 = symbols('CLF16')

clf16_data = history(
    clf16, 
    fields=['price', 'volume'], 
    frequency='daily', 
    start='2015-10-21', 
    end='2015-12-21'
)

clf16_data.head()
Futures getting started1 l3 screenshot4
All pricing values for futures contracts correspond to unit prices. For a CL contract, this value denotes the price per barrel, and a single contract represents 1000 barrels (multiplier). In backtesting, if you hold a particular contract, the value of your portfolio will change by an amount equal to the change in price of the contract (unit price) * the multiplier.
Plotting price and volume will give us a better idea of how these values change over time for this particular contract.
clf16_data.plot(subplots=True);
Futures getting started1 l3 screenshot5
Notice the rise and subsequent drop in trading volume prior to the delivery date of the contract? This is typical behavior for futures. Let's see what the volume looks like for a chain of consecutive contracts.
cl_contracts = symbols(['CLF16', 'CLG16', 'CLH16', 'CLJ16', 'CLK16', 'CLM16'])

cl_consecutive_contract_volume = history(
    cl_contracts, 
    fields='volume', 
    frequency='daily', 
    start='2015-10-21', 
    end='2016-06-01'
)

cl_consecutive_contract_volume.plot(title='Consecutive Contracts Volume Over Time');
Futures getting started1 l3 screenshot6
Trading activity jumps from one contract to the next, and transitions happen just prior to the delivery of each contract.
As you might imagine, having to explicitly reference a series of transient contracts when trading or simulating futures can make it difficult to work with them. In the next lesson we introduce a solution to this problem in the form of Continuous Futures.
Continuous Futures
Continuous Futures are an abstraction of the chain of consecutive contracts for the same underlying commodity or asset. Additionally, they maintain an ongoing reference to the active contract on the chain. Continuous futures make it much easier to maintain a dynamic reference to contracts that you want to order, and get historical series of data. In this lesson, we will explore some of the ways in which we can use continuous futures to help us in our research.
In order to create an instance of a ContinuousFuture in Research, we need to use the continuous_future function. Similar to history, we need to import it from research's experimental library:
from quantopian.research.experimental import continuous_future, history
To create a continuous future, we just need to supply a root_symbol to the continuous_future function. The following cell creates a continuous future for Light Sweet Crude Oil.
cl = continuous_future('CL')
cl
Continuous Futures & history
We can use history to get pricing and volume data for a particular ContinuousFuture in the same way we do for Futures. Additionally, we can get the reference to its currently active Future contract by using the contractfield.
Running the next cell will get pricing data for our CL continuous future and plot it:
# Pricing data for CL `ContinuousFuture`.
cl_pricing = history(
    cl, 
    fields='price', 
    frequency='daily', 
    start='2015-10-21', 
    end='2016-06-01'
)

cl_pricing.plot()
Futures getting started1 l4 screenshot1
To better understand the need for continuous futures, let's use history to get pricing data for the chain of individual contracts we looked at in the previous lesson and plot it.
cl_contracts = symbols(['CLF16', 'CLG16', 'CLH16', 'CLJ16', 'CLK16', 'CLM16'])

# Pricing data for our consecutive contracts from earlier.
cl_consecutive_contract_pricing = history(
    cl_contracts, 
    fields='price', 
    frequency='daily', 
    start='2015-10-21', 
    end='2016-06-01'
)

cl_consecutive_contract_pricing.plot();
Futures getting started1 l4 screenshot2
The price difference between contracts at a given time is not considered to be an increase in value in the future. Instead, it is associated with the carrying cost and the opportunity cost of holding the underlying commodity or asset prior to delivery. This concept is covered more in depth in the Introduction to Futures lecture from our Lecture Series.
Next, let's look at the price history for active contracts separately. We will notice that this difference in price creates discontinuities when a contract expires and the reference moves to the next contract:
# Pricing and contract data for unadjusted CL `ContinuousFuture`.
# Adjustments are covered in the next section. 
cl_unadjusted = continuous_future('CL', adjustment=None)

cl_history = history(
    cl_unadjusted, 
    fields=['contract', 'price'], 
    frequency='daily', 
    start='2015-10-21', 
    end='2016-06-01'
)

cl_active_contract_pricing = cl_history.pivot(index=cl_history.index, columns='contract')
cl_active_contract_pricing.plot();
Futures getting started1 l4 screenshot3
Part of the job of our continuous future abstraction is to account for these discontinuities, as we will see next by plotting our CL continuous future price against the price history for individual active contracts.
cl_active_contract_pricing.plot()
cl_pricing.plot(style='k--')
Futures getting started1 l4 screenshot4
The above plot is adjusted for the price jumps that we see between contracts. This allows us to get a price series that reflects the changes in the price of the actual underlying commodity/asset.
In the next section, we will explore the options for adjusting historical lookback windows of continuous futures.
Adjustment Styles
As we just saw, continuous future historical data series are adjusted to account for price jumps between contracts by default. This can be overridden by specifying an adjustment argument when creating the continuous future. The adjustment argument has 3 options: 'mul' (default), 'add', and None.
The 'mul' option multiplies the prices series by the ratio of consecutive contract prices. The effect from each jump is only applied to prices further back in the lookback window.
Similarly, the 'add' technique adjusts by the difference between consecutive contract prices.
Finally, passing None means that no adjustments will be applied to the lookback window.
Roll Styles
In the previous lesson we saw that trading activity jumps from one contract in the chain to the next as they approach their delivery date. A continuous future changes its reference from the active contract to the next bassed on its roll attribute.
A 'calendar' roll means that the continuous future will point to the next contract in the chain when it reaches the auto_close_date of the current active contract.
The volume roll (default) means that the continuous future will begin pointing to the next contract when the trading volume of the next contract surpasses the volume of the current contract. The idea is to roll when the majority of traders have moved to the next contract. If the volume swap doesn't happen before the auto_close_date, the contract will roll at this date. Note: volume rolls will not occur earlier than 7 trading days before the auto_close_date.
Let's get the volume history of our CL continuous future and plot it against the individual contract volumes we saw before.
cl_consecutive_contract_data = history(
    cl_contracts, 
    fields='volume', 
    frequency='daily', 
    start='2015-10-21', 
    end='2016-06-01'
)

cl_continuous_volume = history(
    cl, 
    fields='volume', 
    frequency='daily', 
    start='2015-10-21', 
    end='2016-06-01'
)

cl_consecutive_contract_data.plot()

cl_continuous_volume.plot(style='k--');
Futures getting started1 l4 screenshot5
The volume for the CL ContinuousFuture is essentially the skyline of the individual contract volumes. As the volume moves from one contract to the next, the continuous future starts pointing to the next contract. Note that there are some points where the volume does not exactly match, most notably in the transition from CLK16 to CLM16 between April and May. This is because the rolls are currently computed daily, using only the previous day's volume to avoid lookahead bias.
Offset
The offset argument allows you to specify whether you want to maintain a reference to the front contract or to a back contract. Setting offset=0 (default) maintains a reference to the front contract, or the contract with the next soonest delivery. Setting offset=1 creates a continuous reference to the contract with the second closest date of delivery, etc.
Futures Analysis
Revisiting our strategy, we can use the tools we have covered so far in order to compare our crude oil future to a future in the same supply chain, RBOB Gasoline (XB). The idea is that because Gasoline is derived from oil, the changes in contract price for one future should be reflected on the other.
Let's import continuous_future and history, define continuous futures for both assets and get pricing data for both.
from quantopian.research.experimental import continuous_future, history

cl_future = continuous_future('CL')
xb_future = continuous_future('XB')

cl_price = history(cl_future, start='2014-01-01', end='2015-01-01', fields='price')
xb_price = history(xb_future, start='2014-01-01', end='2015-01-01', fields='price')
Now, contracts for these futures have different multipliers. Light Sweet Crude Oil has a multiplier of 1,000 barrels per contract, while RBOB Gasoline Futures has a multiplier of 42,000 gallons per contract. In order for the prices to have the same scale, we need to adjust one of them by the ratio of their multipliers. For example, we can adjust XB's price by multiplying it by 42.
With that in mind, let's plot the price history for both continuous futures:
cl_price.plot()
xb_price.multiply(42).plot()
Futures getting started1 l5 screenshot1
We can see in our plot that both prices follow a similar trend, so our strategy probably has some validity based on historical data.
As it turns out, this relationship can be explained by a statistical property called Cointegration. Essentially, if two time series are cointegrated, there is an equilibrium relationship between them in the long run, even if they seem to diverge substantially from that equilibrium in the short term. Pairs Trading strategies take advantage of this property by trading with the expectation that the spread will return to its equilibrium state.
We can do a quick test for cointegration using the coint function from Python's statsmodels library. If the second value of coint's output (pvalue) is small, this means the input time series are cointegrated.
import statsmodels.api as sm
import statsmodels.tsa as tsa

tsa.stattools.coint(cl_price, xb_price)[1]
Cointegration is covered in depth in our Integration, Cointegration, and Stationarity lecture.
Now that we have some evidence that our idea might be valid, let's build an algorithm that takes advantage of the relationship we discovered between crude oil and gasoline. The rest of the tutorial will cover the Algorithm API and the Interactive Development Environment (IDE). We will start by looking at the core functions available in the platform, and backtest a simple example.
Lessons 6-12 take place in the Interactive Development Environment (IDE). The IDE is where you can implement and backtest algorithms. To create a new algorithm, go to your algorithms page and click New Algorithm. Algorithms developed in the IDE are written in Python 2.7.
Algorithm API & Core Functions
As we mentioned before, a trading algorithm specifies instructions on how to process data, and what actions to take based on the results.
We can implement our own functions to carry out these instructions, in addition to using the core functions available in the platform: initialize and handle_data. Each of these functions has a different role and execution frequency. The first one, initialize, must be implemented in every algorithm while handle_data is optional.
Here is a brief description of these functions:
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 across live trading sessions, and can be referenced in different parts of our algorithm. Any variables that we want to persist between function calls should be stored in context instead of using global variables. 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 futures' in context, and prints it out each minute.
def initialize(context):
	# store a message in context
    context.message = 'hello futures'

def handle_data(context, data):
	# print the message every minute
    print context.message

To run this example algorithm, create a copy by clicking the "Clone" button. Once you are in the IDE, run a backtest by clicking "Build Algorithm" (top left) or "Run Full Algorithm" (top right). You can specify the start and end dates of the simulation as well as the starting capital base using the interface next to the calendar dropdown.
Futures getting started1 l6 screenshot1
Futures Calendar
In order to run a backtest that uses futures data, algorithms need to be simulated on the futures calendar (available via dropdown menu in the IDE). On the futures calendar, an algorithm has access to 24 hour futures data and can place trades anywhere between 6:30am-5:00pm ET, Monday through Friday. Currently, futures data is available for backtesting from the beginning of 2002 up to the current date.
Equities can still be traded on the futures calendar, but placing orders for equities outside of US equity market hours (9:30am - 4pm ET) is not supported.
Futures getting started1 l7 screenshot1
Referencing Futures Contracts
There are two ways to refrence futures in the IDE: the continuous_future function, and the future_symbol function.
continuous_future()
As discussed in lesson 4, the best way of maintaining an ongoing reference to futures contracts in your algorithm is by using continuous futures. The same continuous_future() function that we used in Research is available to us in the IDE. Let's create a crude oil continuous future.
Using continuous_future() in the IDE will bring up a text prompt allowing us to look up the future's name, giving in return the corresponding root symbol. For example, to instantiate a continuous future for Light Sweet Crude Oil (CL) we can type the following in the algorithm editor:
Help continuous future
We can store the reference to CL's continuous future in a context variable as follows:
context.primary_cl = continuous_future('CL')
To reference this later in our code, we can simply refer to context.primary_cl. For example, to print out the continuous future each minute, we can add print context.primary_cl to handle_data():
def initialize(context):
    context.primary_cl = continuous_future('CL')

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

future_symbol()
The future_symbol() function returns the Future object for a particular contract (CLF16 for example). Referencing a specific future contract is uncommon outside of research given the short lifetime of contracts.
Scheduling Functions
So far we have seen that trade logic can exist in handle_data(), but most algorithms do not want to perform all of their computations or trades every minute. To schedule parts of our algorithm to run at different times, or different frequencies, we can use schedule_function(). 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.
schedule_function(func = rebalance,
                  date_rule = date_rules.every_day(),
                  time_rule = time_rules.market_open(hours = 1))
Note: when using the futures calendar, market open is considered to be 6:30AM Eastern Time as this is the start of when contracts can be traded on Quantopian. Similarly, market close is at 5PM.
In another example, let's say we want to run the custom function weekly_trades() on the last trading day of each week, 30 minutes before market close. We can use:
schedule_function(func = weekly_trades, 
                  date_rule = date_rules.week_end(), 
                  time_rule = timetime_rules.market_close(minutes=30))
One important thing to keep in mind is that custom functions we define and use along with schedule_function() must take context and data as arguments. For example, the definition for the custom function weekly_trades used above must be:
def weekly_trades(context, data):
    # do something
    pass
The following clonable example keeps count of the number of trading days that have passed, and prints a daily message at market open. Then, it prints a different message at market close on the last day of the week:
def initialize(context):
    context.day_count = 0
    context.daily_message = "Day {}. Market opened, time to trade!"
    context.weekly_message = "End of the week"

    schedule_function(daily_output, 
        date_rule=date_rules.every_day(), 
        time_rule=time_rules.market_open())

    schedule_function(weekly_output, 
        date_rule=date_rules.week_end(), 
        time_rule=time_rules.market_close())

def daily_output(context, data):
    context.day_count += 1
    print context.daily_message.format(context.day_count)
        
def weekly_output(context, data):
    print context.weekly_message

schedule_function() uses relative times and dates to account for market holidays. A backtest or live algorithm only ever runs on trading days. For a full list of date_rules and time_rules, check out the documentation.
Some strategies might involve trading futures and equities together. As we mentioned before, placing orders for equities outside of trading hours is not supported, so in order to adjust date and time rules to account for the difference between trading calendars (9:30am-4pm for equities, 6:30am-5pm for futures), schedule_function() includes a calendar parameter that can be set to 'us_futures' or 'us_equities' (it defaults to 'us_futures' when selecting the futures calendar). For example, if our algorithm needs to rebalance futures at market open relative to the futures calendar, and rebalance equities at market open relative to the equities calendar, we can do the following:
schedule_function(func = rebalance_futures,
                  date_rule = date_rules.every_day(),
                  time_rule = time_rules.market_open())

schedule_function(func = rebalance_equities,
                  date_rule = date_rules.every_day(),
                  time_rule = time_rules.market_open(), 
                  calendar = calendars.us_equities)
The data Object
The data object contains functions that allow us to look up current or historical data for any Future contract or continuous future. data must be given as a parameter to handle_data(), as well as any scheduled functions. You can also pass it to any custom functions you implement in your algorithm, but this is optional. Let's have a look at some of data's functions:
data.history()
Similar to the history function in Research, this method allows us to get lookback windows of historical pricing and volume data. The difference is that we do not need to specify a start or end date. Instead, historical data is collected by looking back from the current day or minute in the simulation. The data.history() method gets a trailing window of data based on the assets, fields, bar_count, and frequency specified. For example, the following code would get volume data for the crude oil continuous future for the previous 10 days:
context.cl_future('CL')

hist = data.history(
    assets=context.cl_future, 
    fields='price', 
    bar_count=10, 
    frequency='1d'
)       
Possible fields include 'price', 'open', 'high', 'low', 'close' and 'volume'. Possible frequencies are '1d' for daily and '1m' for minutely.
Alternatively, we can pass these arguments positionally (without naming the arguments) like this:
hist = data.history(context.cl_future, 'price', 10, '1d')
The following example returns a pandas Series containing the price history of CL over the last 10 days and uses pandas.Series.mean() to calculate the mean.
# Get the 10-day trailing price history of the crude oil continuous future
context.cl_future = continuous_future('CL')
hist = data.history(context.cl_future, 'price', 10, '1d')

# Mean price over the last 10 days.
mean_price = hist.mean()
Note: The most recent value in the result of data.history() always corresponds to the current minute in the simulation. When using '1d' frequency, this means the last row in the result corresponds to a partial day. In order to get data for complete days only, we can increase the lookback window by one and drop the last row from the result. The following example gets the trading volume for Crude Oil for the last 10 complete days, up to the end of yesterday, and computes its mean:
context.cl_future = continuous_future('CL')

mean_volume = data.history(context.cl_future, 'price', 11, '1d')[:-1].mean()
The return type of data.history() depends on the inputs in the same way that the history() function depends on them in Research. To review these output types, see lesson 3.
data.current()
data.current() can be used to retrieve the most recent value of a given field(s) for a given Future contract(s) or ContinuousFuture(s). data.current() requires two arguments: the contract(s) we want data for, and the field(s) being queried. Possible fields include 'price', 'open', 'high', 'low', 'close' and 'volume'. The output type of data.current() also depends on the inputs, just like data.history(). To get the most recent price of Crude Oil, we can use:
context.cl_future = continuous_future('CL')

data.current(context.cl_future, 'price')
Continuous futures are not tradable assets. Instead, we need to use their reference to the current contract to place trades. data.current() can take a field exclusive to continuous futures, 'contract', that returns the current contract corresponding to the given ContinuousFuture. The following line of code returns the current contract for our crude oil ContinuousFuture:
data.current(context.cl_future, 'contract')
data.current_chain()
data.current_chain() takes a ContinuousFuture as input, and returns the forward looking chain of futures contracts associated with the same underlying commodity/asset that have begun trading as of the simulation date. This can be helpful, for example, if we want to trade contracts having delivery dates several months into the future. The following code returns the current chain of contracts for the Crude Oil continuous future, and stores the primary, secondary and terciary contracts into separate variables:
context.cl_future('CL')
cl_chain = data.current_chain(context.cl_future)

front_contract = cl_chain[0]
secondary_contract = cl_chain[1]
terciary_contract = cl_chain[2]
Ordering Futures
Placing trades is a key part of any algorithm. In this lesson we will learn how to place orders for Future contracts using the order_optimal_portfolio() function.
The order_optimal_portfolio function allows us to move our portfolio from one state to another by providing a portfolio objective. For example, our objective might be to hold a specific set of contracts, each at a particular percentage of our portfolio value. We can tell order_optimal_portfolio to target our desired weights using the TargetWeights objective.
targets = {contract_1: 0.5, contract_2: 0.25, contract_3: 0.25}
objective = TargetWeights(targets)
order_optimal_portfolio(objective, constraints=[])
In the example above, order_optimal_portfolio() takes our target weights and executes a set of orders designed to move our current portfolio as close as possible to our target.
To open a short position (a position that profits when the contract drops in price), we can simply use a negative target weight.
The constraints parameter allows us to impose additional restrictions on the orders emitted by order_optimal_portfolio. Imposing extra constraints isn't normally very useful with TargetWeights, but can be powerful when used with other objectives. See here for details.
To use order_optimal_portfolio, we need to import the optimize library:
import quantopian.optimize
The following example uses the TargetWeights objective to allocate our portfolio to crude oil and gasoline in equal parts. The crude oil position will be long, while the gasoline position will be short. Quantopian does not limit the amount of capital that your algorithm can invest, so in order to use only our base capital we want our target weights to add up to 1:
import pandas as pd
from quantopian.algorithm import order_optimal_portfolio
import quantopian.optimize as opt

def initialize(context):
    
    # Light Sweet Crude Oil
    context.cl_future = continuous_future('CL')
    # RBOB Gasoline Futures
    context.xb_future = continuous_future('XB')

    schedule_function(rebalance, 
        date_rule=date_rules.every_day(), 
        time_rule=time_rules.market_open())

def rebalance(context, data):
    # Extract primary contracts from our Continuous Futures
    cl_primary = data.current(context.cl_future, 'contract')
    xb_primary = data.current(context.xb_future, 'contract')
        
    # Distribute weights evenly between our contracts.
    # Go long in CL, and short in XB.
    weights = {cl_primary:0.5, xb_primary:-0.5}
    
    # Place orders for contracts according to weights.
    order_optimal_portfolio(objective=opt.TargetWeights(weights),
                           constraints=[])
Slippage and Commission
Slippage
Slippage models simulate the market's impact on trades, which determines the fill rate and execution price of orders. When an order is placed for a contract, 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, orders do not necessarily fill instantaneously. Fill rates are dependent on the order size and current trading volume of the ordered contract.
On Quantopian, slippage on futures contracts is calculated using a special volatility volume share model. The volatility volume share model uses trailing 20-day trading volume and volatility to compute the price impact and fill rate of an order. Each underlying commodity/asset has its own model fit to historical data. You can find an in-depth description of the default model in this community post.
Note: Orders that remain open (unfilled) by the end of the day are cancelled automatically at market close.
Commission
Commissions for futures contracts are modeled based on Interactive Brokers' Fixed Pricing Structure which can be found here.
The commission model on Quantopian includes commissions charged per contract as well as exchange fees charged per trade. The commissions are the same across all futures, but the fees vary per underlying asset/commodity.
Slippage and commission models can have an impact on the performance of a backtest. The default models used by Quantopian are fairly realistic.
Putting It All Together
Up until now, we have looked at different components of the Futures API individually. Now it's time to put them all together in a basic pairs trading algorithm.
Selecting Futures to Trade
We used Research to confirm a relationship between Light Sweet Crude Oil and RBOB Gasoline, so we will use this pair as our trading universe in the algorithm:
context.crude_oil = continuous_future('CL', roll='calendar')

context.gasoline = continuous_future('XB', roll='calendar')
Setting a Rebalance Schedule
In this implementation, we will calculate the spread daily, and rebalance our portfolio accordingly. To do this, we can schedule a rebalance_pairs function in initialize() to run daily, 30 minutes after market open (runs at 7am ET).
schedule_function(func=rebalance_pairs, 
                  date_rule=date_rules.every_day(), 
                  time_rule=time_rules.market_open(minutes=30))
Calculating the Spread
Next we want to define a function that calculates the zscore of the spread. Remember that the spread is the difference between the prices of the futures in our pair over a trailing window. We can do this by using the data.history() function as well as the pandas pct_change function and numpy.mean:
def calc_spread_zscore(context, data):

    prices = data.history([context.crude_oil, 
                           context.gasoline], 
                          'price', 
                          context.long_ma, 
                          '1d')
    
    cl_price = prices[context.crude_oil]
    xb_price = prices[context.gasoline]
        
    cl_returns = cl_price.pct_change()[1:]
    xb_returns = xb_price.pct_change()[1:]
    
    regression = sp.stats.linregress(
        xb_returns[-context.long_ma:],
        cl_returns[-context.long_ma:],
    )

    spreads = cl_returns - (regression.slope * xb_returns)
    zscore = (np.mean(spreads[-context.short_ma]) - np.mean(spreads)) / np.std(spreads, ddof=1)

    return zscore
Computing Target Weights
Now, let's use our zscore to determine whether or not we should be placing orders, and in what direction. When the zscore goes above a certain threshold, we want to open a long position on one leg of the pair and short the other. If the zscore indicates that the spread has gone back to its equilibrium, we exit our positions. For details on the entry/exit logic see the Pairs Trading section on the Mean Reversion on Futures lecture from our Lecture Series.
Let's define this in a function:
def get_target_weights(context, data, zscore):

    cl_contract, xb_contract = data.current(
        [context.crude_oil, context.gasoline], 
        'contract'
    )
    
    target_weights = {}  

    if context.currently_short_the_spread and zscore < 0.0:
        target_weights[cl_contract] = 0
        target_weights[xb_contract] = 0

        context.currently_long_the_spread = False
        context.currently_short_the_spread = False

    elif context.currently_long_the_spread and zscore > 0.0:
        target_weights[cl_contract] = 0
        target_weights[xb_contract] = 0

        context.currently_long_the_spread = False
        context.currently_short_the_spread = False

    elif zscore < -1.0 and (not context.currently_long_the_spread):
        target_weights[cl_contract] = 0.5
        target_weights[xb_contract] = -0.5
        
        context.currently_long_the_spread = True
        context.currently_short_the_spread = False

    elif zscore > 1.0 and (not context.currently_short_the_spread):
        target_weights[cl_contract] = -0.5
        target_weights[xb_contract] = 0.5

        context.currently_long_the_spread = False
        context.currently_short_the_spread = True

    return target_weights
Note that we also normalized our weights to ensure that they add up to 100%. We then return target_weights which is a Python dictionary indexed by the current contracts for our futures.
Order Execution
The next step is to define how to order our contracts. Earlier, we decided to rebalance our pair daily using a function called rebalance_pairs(). Now, let's define rebalance_pairs() to rebalance our portfolio using the target weights we get from get_target_weights():
def rebalance_pairs(context, data):

    zscore = calc_spread_zscore(context, data)
    
    target_weights = get_target_weights(context, data, zscore)
        
    if target_weights:
        order_optimal_portfolio(
            opt.TargetWeights(target_weights),
            constraints=[]
        )
Note that we first check if target_weights is empty. If it isn't, we use order_optimal_portfolio() to rebalance to our target weights.
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_price() to run daily at market open:
schedule_function(record_price, 
                      date_rules.every_day(), 
                      time_rules.market_open())
And then we'll define record_price() to track the prices of crude oil and gasoline using the record function:
def record_price(context, data):

    crude_oil_price = data.current(context.crude_oil, 'price')
    gasoline_price = data.current(context.gasoline, 'price')
      
    record(Crude_Oil=crude_oil_price, Gasoline=gasoline_price*42)
Putting It Together
The final algorithm looks like this (note that some comments were added):
import numpy as np
import scipy as sp
from quantopian.algorithm import order_optimal_portfolio
import quantopian.optimize as opt

def initialize(context):

    # Get continuous futures for Light Sweet Crude Oil...
    context.crude_oil = continuous_future('CL', roll='calendar')
    # ... and RBOB Gasoline
    context.gasoline = continuous_future('XB', roll='calendar')
    
    # Long and short moving average window lengths     
    context.long_ma = 65
    context.short_ma = 5
    
    # True if we currently hold a long position on the spread
    context.currently_long_the_spread = False
    # True if we currently hold a short position on the spread
    context.currently_short_the_spread = False
    
    # Rebalance pairs every day, 30 minutes after market open
    schedule_function(func=rebalance_pairs, 
                      date_rule=date_rules.every_day(), 
                      time_rule=time_rules.market_open(minutes=30))
    
    # Record Crude Oil and Gasoline Futures prices everyday
    schedule_function(record_price, 
                      date_rules.every_day(), 
                      time_rules.market_open())

def rebalance_pairs(context, data):

    # Calculate how far away the current spread is from its equilibrium
    zscore = calc_spread_zscore(context, data)
    
    # Get target weights to rebalance portfolio
    target_weights = get_target_weights(context, data, zscore)
        
    if target_weights:
        # If we have target weights, rebalance portfolio
        order_optimal_portfolio(
            opt.TargetWeights(target_weights),
            constraints=[]
        )

def calc_spread_zscore(context, data):

    # Get pricing data for our pair of continuous futures
    prices = data.history([context.crude_oil, 
                           context.gasoline], 
                          'price', 
                          context.long_ma, 
                          '1d')
    
    cl_price = prices[context.crude_oil]
    xb_price = prices[context.gasoline]
        
    # Calculate returns for each continuous future  
    cl_returns = cl_price.pct_change()[1:]
    xb_returns = xb_price.pct_change()[1:]
    
    # Calculate the spread
    regression = sp.stats.linregress(
        xb_returns[-context.long_ma:],
        cl_returns[-context.long_ma:],
    )
    spreads = cl_returns - (regression.slope * xb_returns)

    # Calculate zscore of current spread
    zscore = (np.mean(spreads[-context.short_ma]) - np.mean(spreads)) / np.std(spreads, ddof=1)

    return zscore

def get_target_weights(context, data, zscore):

    # Get current contracts for both continuous futures
    cl_contract, xb_contract = data.current(
        [context.crude_oil, context.gasoline], 
        'contract'
    )
    
    # Initialize target weights
    target_weights = {}  

    if context.currently_short_the_spread and zscore < 0.0:
        # Update target weights to exit position
        target_weights[cl_contract] = 0
        target_weights[xb_contract] = 0

        context.currently_long_the_spread = False
        context.currently_short_the_spread = False

    elif context.currently_long_the_spread and zscore > 0.0:
        # Update target weights to exit position
        target_weights[cl_contract] = 0
        target_weights[xb_contract] = 0

        context.currently_long_the_spread = False
        context.currently_short_the_spread = False

    elif zscore < -1.0 and (not context.currently_long_the_spread):
        # Update target weights to long the spread
        target_weights[cl_contract] = 0.5
        target_weights[xb_contract] = -0.5
        
        context.currently_long_the_spread = True
        context.currently_short_the_spread = False

    elif zscore > 1.0 and (not context.currently_short_the_spread):
        # Update target weights to short the spread
        target_weights[cl_contract] = -0.5
        target_weights[xb_contract] = 0.5

        context.currently_long_the_spread = False
        context.currently_short_the_spread = True

    return target_weights

def record_price(context, data):

    # Get current price of primary crude oil and gasoline contracts.
    crude_oil_price = data.current(context.crude_oil, 'price')
    gasoline_price = data.current(context.gasoline, 'price')
      
    # Adjust price of gasoline (42x) so that both futures have same scale.
    record(Crude_Oil=crude_oil_price, Gasoline=gasoline_price*42)
Congratulations on completing the Futures Tutorial on Quantopian! Now that you are familiar with the Futures API, try coming up with your own strategy and implement it yourself. If you need ideas, check out the Lecture Series to learn more about advanced quantitative finance, or look at ideas that people have shared in the community.
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 services by Quantopian.

In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian 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.