Quantopian Overview

Quantopian is a crowd-sourced hedge fund that provides the world's first browser-based algorithmic trading platform. Our goal is to handle all the infrastructure, data, and setup tasks so that you can focus on backtesting and live trading your investment ideas. Top-performing live trading algorithms will be considered for inclusion in our crowd-sourced hedge fund.

Important Concepts

Writing an algorithm in Quantopian uses two parts of our application: the IDE (interactive development environment) and backtester (testing).

Algorithms are developed in Python in our IDE. With our built-in syntax checking, validation, and smoke tests, the IDE tells you about issues before they crop up in backtesting.

Our backtester runs an event loop once per historical minute. Any calculations or orders your algorithm needs are handled on each loop. At the end of a backtest, all the relevant performance, positions, transactions, and risk data are available to you.

Data sources

We have minute bar historical data for US equities since 2002 up to the most recently completed trading day (data uploaded nightly). SPY, an ETF tracking the S&P 500, is the benchmark used for algorithms simulated in the backtest. It represents the total returns and reinvests the dividends to model the market performance. You can also set your own benchmark in your algorithm.

A minute bar is a summary of the trading activity for a security for a one-minute period, and gives you the opening price, closing price, high price, low price, and trading volume during that minute. Our US equity set is point-in-time, which is important for backtest accuracy. Since our event-based system sends trading events to you serially, your algorithm receives accurate historical data without any bias towards the present.

Our database includes all stocks and ETFs that traded since 2002, even ones that are no longer traded. This is very important because it helps you avoid survivorship bias in your algorithm. Databases that omit securities that are no longer traded ignore bankruptcies and other important events, and lead to false optimism about an algorithm. For example, LEH (Lehman Brothers) is a security in which your algorithm can trade in 2008, even though the company no longer exists in 2014; Lehman's bankruptcy was a major event that affected many algorithms at the time.

Our data uses adjusted close prices. That means that the effect of all stock splits and merger activity are applied to price and volume data. As an example: A stock is trading at $100, and has a 2:1 split. The new price is $50, and all past price data is retroactively updated 2:1 (volume data is correspondingly updated 1:2). In effect, that means you can ignore stock splits unless you are storing prices in a live trading algorithm. In a live trading algorithm, the stored variable needs to have the split applied to the price and volume. In a backtest, the split is already applied retroactively to all data.

In addition to pricing data, Quantopian provides access to fundamental data, free of charge. The data, from Morningstar, consists of over 600 metrics measuring the financial performance of companies and is derived from their public filings. This data can be used in a variety of manners. One primary use is filter down and select a set of securities for use in an algorithm. An additional, more open ended use, is to use the fundamentals metrics within the trading logic of the algorithm.

There are many other financial data sources we'd like to incorporate, such as options and futures. What kind of data sources would you like us to have? Let us know.

Paper Trading

Paper trading is also sometimes known as walk-forward testing. In paper trading, your algorithm gets live market data (actually, 15-minute delay data) and 'trades' against the live data with a simulated portfolio. This is a good test for any algorithm. If you inadvertently did overfitting during your algorithm development process, paper trading will often reveal the problem. You can simulate trades for free, with no risks, against the current market on Quantopian.

Quantopian paper trading uses the same order-fulfillment logic as a regular backtest, including the slippage model.

Before you can paper trade your algorithm, you must run a minute backtest. Go to your algorithm, choose the minute backtest option, and click the 'Full Backtest' button. Once that backtest is complete, you will see a 'Live Trade Algorithm' button. Also, on the My Algorithms list you will see a Start Trading button for any backtested algorithm. When you start paper trading, you will be prompted to specify the amount of money used in the strategy. Note that this cash amount is not enforced - it is used solely to calculate your algorithm's returns.

Live Trading

When you click the Start Trading button, you will have the option to choose 'Broker'.

You will need to authenticate to your Interactive Brokers* account. (Note: Quantopian does not store your brokerage password.) For more information about live trading and how to create an IB account, see our live trading FAQ.

We strongly recommend that you run your algorithm against IB's paper trading mode before running it against a real money account. IB paper trading is a good test to help find lingering bugs. Once you're satisfied with the paper trading performance, you should stop the live algorithm and re-launch it against your real money IB account.

While your algorithm is live trading, you can continue to make changes in the IDE to experiment with the code and run additional backtests. This will not interfere with the live algorithm. These changes will not apply to your live algo unless you stop the algorithm, run a full backtest with the new code, and redeploy.

If you manually stop a live trading algorithm, this will shutdown the algorithm and prevent it from placing additional orders. This will not liquidate your portfolio and any open order may still be filled.

By default, orders placed through Quantopian use IB's Smart Router algorithm for order execution. You can also specify to route your order to IEX.

Quantopian's live trading program is still in open beta, and the live trading platform has a number of limitations. These limitations will be reduced and removed as we keep improving the live trading platform.

  • The live trading platform does not have the capability to do a mid-day recovery from an outage. If your algo goes down mid-day (whether it's because of our bug, or your bug, or a third party issue) the algorithm won't automatically restart mid-day. If the bug was temporary, the algorithm will start the next day at market open. On restart, the data windows (batch_transform and simple transforms) will be filled with the "missing" data that passed during the previous day's outage.
  • There is no automatic liquidation on algorithm exit. If you need to liquidate your holdings, you need to do that programatically in your algorithm or in the Interactive Brokers interface.
  • If your IB login is terminated, you will need to log back in before trading continues. This could happen unexpectedly because of a server error, or it could be a planned code upgrade on our part. If a login is necessary we will contact you.
  • All open orders are cancelled at end-of-day.
  • Orders made in the last minute of the trading day will be automatically cancelled.
  • If your algorithm uses fetch_csv(), it is important that all historical data in your fetched data be accessible and unchanged. We do not keep a copy of fetched data; it is reloaded at the start of every trading day. If historical data in the fetched file is altered or removed, the algorithm will not run properly. Providing additional, new data in the file for dates in the future is fine.
  • The use of random() isn't supported because of technical limitations with our start-of-day initialization.
  • IB only allows one login at a time per account. If you try to log into IB during market hours using the same account as Quantopian, Quantopian will automatically try to force you out so that we can keep trading on your behalf. In general, we recommend that you maintain two logins, one for Quantopian and one for your use. If you need to log in to IB using the Quantopian login during market hours, you should first stop the algorithm in Quantopian.
  • The open beta is limited to IB accounts with a portfolio value of less than $1,000,000.
  • The open beta is limited to accounts based in US dollars. The account can be funded by other currencies, but the base must be US dollars.

We are working very hard at testing and improving our software; however, we certainly have software bugs. As members of the open beta, in particular, you need to be alert to any bugs that might affect your trading. Bug reports are greatly appreciated and we will try to fix them as quickly as possible.

We shared an example of a live trading strategy that automatically invests in equal positions across 9 sector ETFs. It rebalances once every 21 days to maintain the target weights. This example is similar to the algorithm running on our own real-money IB account.

* Interactive Brokers LLC is not affiliated with and does not endorse or recommend Quantopian, Inc. Interactive Brokers provides execution and clearing services to customers who integrate their Interactive Brokers account with their Quantopian account. For more information regarding Interactive Brokers LLC, please visit www.interactivebrokers.com.

Privacy and Security

We take privacy and security extremely seriously. All trading algorithms and backtest results you generate inside Quantopian are owned by you. Unless you choose to share your content, your content will remain private. We will never access your data unless we are helping you troubleshoot issues.

Our databases live in Amazon's cloud infrastructure.

Specifically, our security plans include:

  • Never storing your password in plaintext in our database
  • SSL encryption for the Quantopian application
  • Secure websocket communication for the backtest data
  • Encrypting all trading algorithms before writing them to our database

We want to be very transparent about our security measures. Don't hesitate to email us with any questions or concerns.

Two-Factor Authentication

Two-factor authentication (2FA) is a security mechanism to protect your account. When 2FA is enabled, your account requires both a password and an authentication code you generate on your smart phone.

With 2FA enabled, the only way someone can sign into your account is if they know both your password and have access to your phone. We strongly urge you to turn on 2FA to protect the safety of your account, especially for live trading.

How does it work?

Once you enable 2FA, you will still enter your password as you normally do when logging into Quantopian. Then you will be asked to enter an authentication code. This code can be either generated by the Google Authenticator app on your smartphone, or sent as a text message (SMS).

If you lose your phone, you will need to use a backup code to log into your account. It's very important that you get your backup code as soon as you enable 2FA. If you don't have your phone, and you don't have your backup code, you won't be able to log in. Make sure to save your backup code in a safe place!

You can setup 2FA via SMS or Google Authenticator, or both. These methods require a smartphone and you can switch between them at any time. To use the SMS option you need a US-based phone number, beginning with the country code +1.

To configure 2FA via SMS:

  1. Go to https://www.quantopian.com/account?page=twofactor' and click 'Configure via SMS'.
  2. Enter your phone number. Note: Only US-based phone numbers are allowed for the SMS option. Standard messaging rates apply.
  3. You'll receive a 5 or 6 digit security token on your mobile. Enter this code in the pop-on screen to verify your device. Once verified, you'll need to provide the security token sent via SMS every time you login to Quantopian.
  4. Download your backup login code in case you lose your mobile device. Without your phone and backup code, you will not be able to access your Quantopian account.

To configure 2FA via Google Authenticator:

  1. Go to https://www.quantopian.com/account?page=twofactor' and click 'Configure Google Authenticator'.
  2. If you don’t have Google Authenticator, go to your App Store and download the free app. Once installed, open the app, click 'Add an Account', then 'Scan a barcode'. You might have to install a barcode scanning app.
  3. Hold the app up to your computer screen and scan the QR code. Enter the Quantopian security token from the Google Authenticator app.
  4. Download your backup login code in case you lose your mobile device. Without your phone and backup code, you will not be able to access your Quantopian account.

Backup login code

If you lose your phone and can’t login via SMS or Google Authenticator, your last resort is to login using your backup login code. Without your phone and this code, you will not be able to login to your Quantopian account.

To save your backup code:

  1. Go to https://www.quantopian.com/account?page=twofactor
  2. Click 'View backup login code'.
  3. Enter your security token received via SMS or on your Google Authenticator app.
  4. Copy your backup code to a safe location.

Developing in the IDE

Quantopian's Python IDE is where you develop your trading ideas. The standard features (autosave, fullscreen, font size, color theme) help make your experience as smooth as possible.

Your work is automatically saved every 10 seconds, and you can click Save to manually save at any time. We'll also warn you if you navigate away from the IDE page while there's unsaved content.

Collaboration

You can collaborate in real-time with other Quantopian members on your algorithm. In the IDE, press the “Collaborate” button and enter your friend’s email address. Your collaborators will receive an email letting them know they have been invited. They will also see your algorithm listed in their Algorithms Library with a collaboration icon. If your collaborator isn't yet a member of Quantopian, they will have to register before they can see your algorithm.

Button

The collab experience is fully coordinated:

  • The code is changed on all screens in real time.
  • When one collaborator "Builds" a backtest, all of the collaborators see the backtest results, logging, and/or errors.
  • There is a chat tab for you to use while you collaborate.
Collab_ide

Technical Details

  • Only the owner can invite other collaborators, deploy the algorithm for live trading, or delete the algorithm.
  • There isn't a technical limit on number of collaborators, but there is a practical limit. The more collaborators you have, the more likely that you'll notice a performance problem. We'll improve that in the future.
  • To connect with a member, search their profile in the forums and send them a private message. If they choose to share their email address with you, then you can invite them to collaborate.

API Overview

We have a simple but powerful API for you to use:

initialize(context) is the setup method for initializing state or other bookkeeping. This method is called only once at the beginning of your algorithm. context is an augmented Python dictionary used for holding state between methods. Properties can be accessed using dot notation (context.some_property).

handle_data(context, data) is called on every trading event. data is a dictionary containing your universe at that time, keyed by security ids. For example, data[symbol('GOOG')] gets you the latest data for Google. Our backend will find all referenced securities in the algorithm and only send their trading events to this method.

Your algorithm can have other Python methods. For example, you might have handle_data call a utility method to do some calculations.

For more information, jump to the API Documentation section below.

Referencing securities

There are four ways to add securities to your algorithm. Each method accepts up to 200 securities.

  1. Manual lookup using symbol or sid
  2. Screen fundamental data to create a bundle of securities.
  3. Use dollar-volume price data to create a basket of securities
  4. Import your own list via Fetcher

Manual Lookup

If you want to manually select a stock, you can use the symbol function to look up a security by its ticker or company name. Using the symbol method brings up a search box that shows you the top results for your query.

Help-symbol-lookup

To reference multiple securities in your algorithm, call the symbols function to create a list of stocks. Note the pluralization! The function can accept a list of up to 200 securities and its parameters are not case sensitive.

Sometimes, tickers are reused over time as companies delist and new ones begin trading. For example, G used to refer to Gillette but now refers to Genpact. If a ticker was reused by multiple companies, use set_symbol_lookup_date('YYYY-MM-DD') to specify what date to use when resolving conflicts. This date needs to be set before any calls to symbol or symbols.

Help-symbol

Another option to manually enter stocks is to use the sid function. All securities have a unique security id (SID) in our system. Since symbols may be reused among exchanges, this prevents any confusion and verifies you are calling the desired security. You can use our sid method to look up a security by its id, symbol, or name.

Help-sid

In other words, sid(24) is equivalent to symbol('AAPL') or Apple.

Quantopian's backtester is intelligent and automatically detects the date range a stock traded. It will adjust the start or end dates of the backtest during the period the security traded. For example, if you're trying to run a backtest with Tesla in 2004, the backtest will suggest you begin on June 28, 2010 the first day the stock traded. This ability is significantly decreased when using symbol instead of sid.

Fundamental Data

Quantopian provides fundamental data from Morningstar, available for backtesting in your algorithm. The data covers over 8,000 companies traded in the US with over 670 metrics. Broadly, the get_fundamentals method (described below) queries across all of the stocks in the fundamentals database. Using a SQL-like syntax to choose columns, filter, sort, and limit results, you can generate a basket of securities for use in your Quantopian algorithm, based on the financial metrics of the company. Further, you can use these metrics as part of your trading logic in your algorithm.

A full listing of the available fields can be found at the Fundamentals Reference page.

Fundamentals allow you to do two activities inside Quantopian backtests:

  1. Create a universe of securities to use within your algorithm based on fundamental data
  2. Provide data within your algorithm (inside handle_data) for making investment decisions

There are three methods related to fundamental data: before_trading_start, get_fundamentals, and update_universe.

before_trading_start is called once per day prior to market open. It can be used to create a universe of securities using fundamental data by using get_fundamentals and passing in a query object. The query object is built using the syntax of SQLAlchemy, a python ORM. See the SQLAlchemy documentation for more details on how to generate a query.

In the example below, the algorithm gets two pieces of information per security: the P/E ratio and the sector code as determined by Morningstar. The get_fundamentals method automatically includes a column for the security id (SID). The returned data is then filtered down via filter, using P/E ratio and sector codes. Finally, the data is sorted using order_by and truncated using limit.

"""
    Called once before the start of each trading day,
    ahead of any handle_data calls.
    Updates our universe with the securities and values
    found from fetch_fundamentals
"""

def before_trading_start(context): 
    # Query for securities based on PE ratio and their economic sector
    fundamental_df = get_fundamentals(
        # Retrieve data based on PE ratio and economic sector
        query(
            fundamentals.valuation_ratios.pe_ratio,
            fundamentals.asset_classification.morningstar_sector_code,
        )

        # Filter where the Sector code matches our technology sector code
        .filter(fundamentals.asset_classification.morningstar_sector_code == 311)

        # Filter where PE ratio is greater than 20 
        .filter(fundamentals.valuation_ratios.pe_ratio > 20)

        # Filter where PE ratio is less than 50
        .filter(fundamentals.valuation_ratios.pe_ratio < 50)

        # Order by highest PE ratio and limit to 4 results 
        .order_by(fundamentals.valuation_ratios.pe_ratio.desc()).limit(4)
    )

A full listing of the available fields can be found at the Fundamentals Reference page. The fundamentals database is updated on a daily basis. A sample algorithm is available showing how to access fundamental data.

The get_fundamentals method returns a pandas dataframe, which can be used in two interesting ways:

  1. Added to context for later use in handle_data. Your algorithm can then make use of the data in this dataframe.

    context.fundamental_df = fundamental_df
    
  2. Passed to the update_universe method, which adds the securities to your universe. No orders of any kind can be executed from within before_trading_start.

    update_universe(context.fundamental_df.columns.values)
    

"as of" Dates

Each fundamental data field has a corresponding as_of field, e.g. basic_eps also has basic_eps_as_of. The as_of field contains the relevant time period of the metric, as a Python date object. Some of the data in Morningstar is quarterly (revenue, earnings, etc.), while other data is daily/weekly (market cap, P/E ratio, etc.). Each metric's as_of date field is set to the end date of the period to which the metric applies.

For example, if you use a quarterly earnings metric like basic_eps, the accompanying date field basic_eps_as_of will be set to the end date of the relevant quarter (for example, June 30, 2014). The as_of date indicates the date upon which the measured period ends.

Quantopian currently exposes the most recent period's data. If you need previous periods' metrics, you must manually save those metrics in your algorithm as your backtest progresses.

Point in Time

When running a backtest, Quantopian doesn't use the as_of date for exposing the data to your algorithm. Rather, a new value is exposed at the "point in time" in the past when the metric would be known to you. This day is called the file date.

Companies don't typically announce their earnings the day after the period is complete. If a company files earnings for the period ending June 30th (the as_of date), the file date (the date upon which this information is known to the public) is about 45 days later.

Quantopian takes care of this logic for you in the backtester. For data updates since Quantopian began subscribing to Morningstar's data, Quantopian tracks the file date based on when the information changes in Morningstar. For historic changes, Morningstar also provides a file date to reconstruct how the data looked at specific points in time. In circumstances where the file date is not known to Quantopian, the file date is defaulted to be 45 days after the as_of date.

Restrictions

Some restrictions regarding the use of fundamental data in Quantopian:

  • You cannot execute an order from before_trading_start
  • fundamental data cannot yet be used with the history function
  • Fundamentals (the get_fundamentals function) cannot yet be used with live trading

Dollar-Volume Universe

If your algorithm manually picks a few specific stocks to study, you have some bias risk in your strategy. To avoid this bias, we provide a tool called set_universe that selects a universe of securities based on their traded dollar-volume. This helps you avoid look-ahead bias and survivorship bias, because the criteria will be applied to the companies as they existed at the point-in-time of your simulation, rather than current values.

A universe is a set of securities defined by certain criteria. Periodically, we re-apply the criteria to update the set of qualifying securities. As a result, some securities might be added to the universe while others might be removed.

To set the universe, use the set_universe(YourUniverse) method in your algorithm's initialize method.

We currently provide a single universe implementation, DollarVolumeUniverse, which uses a percentile range for dollar volume of shares traded for securities. More explicitly, we multiply each security's trading volume by its price to calculate the dollar volume. We then rank each security by dollar volume, from #1 to (roughly) #8000 and convert that ranking into a percentile

To use the DollarVolumeUniverse, set the universe in the initialize method. You can set a universe interval up to 2% for backtests in minute mode or up to 10% in daily mode. Each percentile in the range will result in ~80 securities, so the following example consists of the ~160 securities with the highest dollar-volume volume, with changes coming on the quarter end:

set_universe(universe.DollarVolumeUniverse(floor_percentile=98.0,ceiling_percentile=100.0))

The DollarVolumeUniverse updates quarterly on the last trading day of March, June, September, and December. On these days, we use the dollar volume rank of the previous quarter to re-rank the securities. The quarterly boundaries have some specific behaviors that should be noted:

  • If a security is not present at the beginning of a quarter (due to an IPO for instance), it is excluded from the universe for that quarter. A security that stops trading mid-quarter (bankruptcy, delisting, etc.) is kept in the universe with a forward-fill using the last recorded price.
  • At the end of a quarter if your algorithm has a position in a security, that security is held in the universe even if it would have otherwise fallen out.
  • When a new security appears in a universe with a batch transform, the historical data for that stock is loaded. For example, if you have a batch transform with a refresh window of 5 days, and a new stock is added at the quarterly refresh on March 31, then the batch transform loads the stock data back to March 26 and fills the batch transform window.
  • When a new security appears in a universe and mavg(), vwap(), returns(), and stddev() are called, the data is not backfilled. For example, if you have a 5-day moving average and a new security is added at the quarterly refresh on March 31, then the moving average only has one day of data on April 1.

If you also refer to specific securities using the symbol function, those stocks will always be included in the universe.

All securities are accessible via data in handle_data. For example:

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(floor_percentile=98.0, ceiling_percentile=100.0))
    context.other_stocks = symbols('CSCO', 'INTC')  # add some specific securities

def handle_data(context, data):
    for stock in data:
        log.info(str(stock)) # this will print out all the securities in the universe as well as CSCO and INTC

Fetcher - Load any CSV file

Quantopian provides historical data since 2002 for US equities in minute and daily bars. The US market data provides a backbone for financial analysis, but some of the most promising areas of research are finding signals in non-market data. Fetcher provides your algorithm with access to external time series data. Any time series that can be retrieved as a CSV file via http or https can be incorporated into a Quantopian algorithm.

Fetcher lets you load CSV files over http. To use it, invoke fetch_csv(url) in your initialize method. fetch_csv will retrieve the text of the CSV file using the requests module. The CSV is parsed into a pandas dataframe using pandas.io.parsers.read_csv. You may then specify your own methods to modify the entire dataframe prior to the start of the simulation. During simulation, the rows of the CSV/dataframe are streamed to your algorithm's handle_data method as additional properties of the data parameter.

Best of all, your Fetcher data will play nicely with Quantopian's other data features:

  • Use record to plot a time series of your fetcher data.
  • Create a trailing window of data using history to make statistical models using your fetcher data.
  • Your data will be streamed to your algorithm without look-ahead bias. That means if your backtest is currently at 10/01/2013 but your Fetcher data begins on 10/02/2013, your algorithm will not have access to the Fetcher data until 10/02/2013. You can account for this by checking the existence of your Fetcher field, see common errors for more information.

Fetcher supports two kinds of time series:

  • Security Information: data that is about individual securities, such as short interest for a stock
  • Signals: data that stands alone, such as the Consumer Price Index, or the spot price of palladium

For Security Info, your CSV file must have a column with header of 'symbol' which represents the symbol of that security on the date of that row. Internally, Fetcher maps the symbol to the Quantopian security id (sid). You can have many securities in a single CSV file. However, any security that is not also initialized using symbol, sid(), or set_universe() is discarded. To access your CSV data in handle_data:

## This algo imports sample short interest data from a CSV file for one security, 
## NFLX, and plots the short interest:

def initialize(context):  
    # fetch data from a CSV file somewhere on the web.
    # Note that one of the columns must be named 'symbol' for 
    # the data to be matched to the stock symbol
    fetch_csv('https://dl.dropboxusercontent.com/u/169032081/fetcher_sample_file.csv', 
               date_column = 'Settlement Date',
               date_format = '%m/%d/%y')  
    context.stock = symbol('NFLX')
    
def handle_data(context, data):    
    if 'Days To Cover' in data[context.stock]:        
        record(Short_Interest = data[context.stock]['Days To Cover'])

Here is the sample CSV file. Note that for the Security Info type of import, one of the columns must be 'symbol'.

Settlement Date,symbol,Days To Cover
9/30/13,NFLX,2.64484
9/13/13,NFLX,2.550829
8/30/13,NFLX,2.502331
8/15/13,NFLX,2.811858
7/31/13,NFLX,1.690317

For Signals, your CSV file does not need a symbol column. Instead, you provide it via the symbol parameter:

def initialize(context):
    fetch_csv('http://yourserver.com/cpi.csv', symbol='cpi')
    context.stock = symbol('AAPL')

def handle_data(context, data):
    # get the cpi for this date
    current_cpi = data['cpi']['value']

    # plot it
    record(cpi=current_cpi)

Importing Files from Dropbox

Many users find Dropbox to be a convenient way to access CSV files. To use Dropbox, you need to use the 'Public URL' provided by Dropbox. A common mistake is to use a URL of format https://www.dropbox.com/s/abcdefg/filename.csv, which is a URL about the file, not the file itself. Instead, you should use the Public URL which has a format similar to https://dl.dropboxusercontent.com/u/1234567/filename.csv.

Importing Files from Google Drive

You can also import a CSV file directly from your Google Drive

  1. Create a new folder in Drive and share it as "Public on the web".
  2. Upload your CSV file to this folder.
  3. Open the file and click Open in the bottom-right corner.
  4. Click the Previewbutton in the toolbar.
  5. Share the URL that looks like "www.googledrive.com/host/..." from the preview window.

Data Manipulation with Fetcher

If you produce the CSV, it is relatively easy to put the data into a good format for Fetcher. First decide if your file should be a signal or security info source, then build your columns accordingly.

However, you may not always have control over the CSV data file. It may be maintained by someone else, or you may be using a service that dynamically generates the CSV. Quandl, for example, provides a REST API to access many curated datasets as CSV. While you could download the CSV files and modify them before using them in Fetcher, you would lose the benefit of the nightly data updates. In most cases it's better to request fresh files directly from the source.

Fetcher provides two ways to alter the CSV file:

  • pre_func specifies the method you want to run on the pandas dataframe containing the CSV immediately after it was fetched from the remote server. Your method can rename columns, reformat dates, slice or select data - it just has to return a dataframe.
  • post_func is called after Fetcher has sorted the data based on your given date column. This method is intended for time series calculations you want to do on the entire dataset, such as timeshifting, calculating rolling statistics, or adding derived columns to your dataframe. Again, your method should take a dataframe and return a dataframe.

Live Trading and Fetcher

When Fetcher is used in Live Trading or Paper Trading, the fetch_csv() command is invoked once per trading day, when the algorithm warms up, before market open. It's important that the fetched data with dates in the past be maintained so that warm up can be performed properly; Quantopian does not keep a copy of your fetched data, and algorithm warmup will not work properly if past data is changed or removed. Data for 'today' and dates going forward can be added and updated.

Working With Multiple Data Frequencies

Without Fetcher data, all Quantopian simulations operate on datasources with identical frequencies. If you choose a minutely simulation, your handle_data function is called exactly once per (simulation) minute. If you choose daily, your function is similarly called once per day.

When pulling in external data, you need to be careful about the data frequency to prevent look-ahead bias. If you are fetching daily data, but are running a minutely-based algorithm, the daily row will be fetched at the beginning of the day, instead of the end of day. To guard against the bias, you need to use the post_func function. For more information see post_func API documentation or take a look at this example algorithm.

With Fetcher, you will need to have some guard code in your handle_data methods to avoid KeyError problems accessing data properties. In general, you need to check that a security is in data before you access it, and that a property is available on a security before you access it.:

def initialize(context):
    # a signal Fetcher
    fetch_csv('http://priceoftea.com/', symbol='tea')
    # a security info Fetcher
    fetch_csv('http://insiderselling.com')

def handle_data(context, data):
    # guard against being called before the first trade of a security
    if symbol('XYZ') in data:
        # guard against trades happening before the first insider selling event
        if 'insider' in data[symbol('XYZ')]:
            if data[symbol('XYZ')]['insider'] > 10.0:
                order(symbol('XYZ'), -100)

    # signal data will pass a blank place holder if the first event has not been sent yet.
    # So, you can just guard against missing properties
    if 'price' in data['tea']:
        record(price_of_tea=data['tea']['price'])

For more information about Fetcher, go to the API documentation or look at the sample algorithms.

Using Fetcher to create a custom universe

Your algorithm's universe can be defined from your Fetcher data file by using the optional universe_func parameter in Fetcher. universe_func allows you to create a universe of up to 200 securities (SIDs) without manually entering each security in the initialize function.

This is useful if you have a large external fixed stock universe (such as a static list of index constituents) or if you have a signal file with changing values used for ranking and screening a new list of securities to trade every day.

To screen securities, use the universe_func to filter the securities down to a smaller set. This allows you to manipulate stock sets and associated data attributes of any size (e.g. Russell 3000) while only passing the stocks you want to trade on to handle_data where they will be accessible for minute by minute trading along with our market data.

Because fetch_csv is smart about aligning external time series data with Quantopian's market data, your universe will update on a day-to-day basis based on the dates in your file. The backtester will pull in only the data that is available on the current bar, based on the 'date_column' specified in fetch_csv to prevent look-ahead bias. This column will be overwritten with the algorithm datetime and be fed into the backtest once the data becomes available each day.

Here's an example file with ranked stocks:

symbol, start date, stock_score
AA,     2/13/12,     11.7
WFM,    2/13/12,     15.8
FDX,    2/14/12,     12.1
M,      2/16/12,     14.3

You can backtest the code below during the dates 2/13/2012 - 2/18/2012 in minute mode. When you use this sample file and algorithm, this will be your custom universe on each day:

  • 2/13/2012: AA, WFM
  • 2/14/2012: FDX
  • 2/15/2012: FDX (forward filled because no new data became available)
  • 2/16/2012: M
import pandas as pd

def initialize(context):
    # import the custom CSV data file
    fetch_csv("https://dl.dropboxusercontent.com/u/6865289/custom%20universe%20example.csv",
              pre_func=preview,
              post_func=preview,
              date_column='start date',
              universe_func=my_universe)

# my_universe returns a set of securities that define your universe.
def my_universe(context, fetcher_data):
    # fetcher_data is the data resulting from the CSV file from fetcher.

    # set my_stocks to be every security in the fetcher_data
    my_stocks = set(fetcher_data['sid'])

    # log the size of the universe for debugging
    context.count = len(my_stocks)
    print 'total universe size: {c}'.format(c=context.count)

    # return the securities we identified earlier
    return my_stocks

# see a snapshot of your CSV for debugging
def preview(df):
    log.info(' %s ' % df.head())
    return df

def handle_data(context,data):
    # Convert to EST timezone
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
  
    # loop over the stocks in universe
    for stock in data:
      # At 10:30AM on the entry dates in the file, order target value of 500 shares of
      # all stocks in universe with scores above 14
      if data[stock]['dt'].date() == exchange_time.date() and exchange_time.hour == 10 and exchange_time.minute == 30:
         log.info('Entry date for : ' + str(stock.symbol))

         if data[stock]['stock_score'] >= 14:
             order_target_value(stock, 500)
             log.info('ordered stock {s}'.format(s=stock.symbol))

Note that fetcher_data is bundled by day and passed into my_universe() when there is a new date in your Fetcher file. If your Fetcher file does not contain data on a given trading day the last known universe is used.

In the example above, the universe is empty until 2/13/12, so there are no stocks found by the algorithm. On 2/13/12, the first date populated with fetched_data, two stocks enter the universe. On 2/14/12, the universe consists of only one stock; the previous two-stock universe is replaced. On 2/15 there are no new rows in the file, so the two-stock universe is carried forward. On 2/16/12 there is a new one stock universe, and the previous day's stock is discarded.

In addition to the universe defined by your Fetcher data, any security that is explicitly referenced in your algorithm or held in your portfolio will be included in the universe. In the example above, an order is placed for WFM which keeps it in the universe until the order is filled, while AA is dropped entirely.

This feature cannot be used in combination with set_universe(). If set_universe is also called, Fetcher's universe_func() is ignored.

Note that when using this feature in live trading, it is important that all historical data in your fetched data be accessible and unchanged. We do not keep a copy of fetched data; it is reloaded at the start of every trading day. If historical data in the fetched file is altered or removed, the algorithm will not run properly. New data should always be appended to (not overwritten over) your existing Fetcher .csv source file.

Setting a custom benchmark

The default benchmark in your algorithm is SPY, an ETF that tracks the S&P 500. You can change it in the initialize function by using set_benchmark and passing in another security. Only one security can be used for the benchmark, and only one benchmark can be set per algorithm. If you set multiple benchmarks, the last one will be used.

Here's an example:

def initialize(context):
  set_benchmark(symbol('TSLA'))

def handle_data(context, data):
  ...

Checking that trade data exists for a security

Our backtester protects you from running an algorithm against securities that don't exist yet. For instance, if you try to backtest using Facebook, the backtest will fast-forward to the point where Facebook is traded. If you try to backtest both Lehman and Facebook you will get an error since Facebook wasn't traded until May 2012, many years after Lehman stopped trading.

Stocks do not necessarily trade every minute or even every day. This is especially true for thinly-traded securities, so your algorithm needs to be robust to cover these edge cases.

To check if trade data exists for a security during a given trading period, just check if it exists in the data structure that is passed to handle_data. For example, if sid in data: ... Your algorithm will check to see if the security has data in the bar. Otherwise, it will skip the security and continue with your algorithm calculations without receiving an error.

If your algorithm has multiple securities, it's possible that in a trade bar, one security has trade data while another does not. By default, your algorithm is set to set_nodata_policy(NoDataPolicy.LOG_ONLY), which logs the error, skips over the security with no data, and exits the handle_data call. In backtesting and live trading, if you try to access trade information for a security that had no data, the algorithm will log the information and continue running.

If you prefer to stop the algorithm entirely when you try to access missing trade data, you can use set_nodata_policy(NoDataPolicy.EXCEPTION) in initialize. Trying to access missing trade data will result in a NoTradeDataAvailableException, ending the backtest or live algorithm.

Validation

Our IDE has extensive syntax and validation checks. It makes sure your algorithm is valid Python, fulfills our API, and has no obvious runtime exceptions (such as dividing by zero). You can run the validation checks by clicking on the Build button (or pressing control-B), and we'll run them automatically right before starting a new backtest.

Errors and warnings are shown in the window on the right side of the IDE. Here's an example where the log line is missing an end quote.

Help-validation

When all errors and warnings are resolved, the Build button kicks off a quick backtest. The daily data is less detailed than the minute bar data, but it runs much faster. The quick backtest is a way to make sure that the algorithm roughly does what you want it to, without any errors.

Once the algorithm is running roughly the way you'd like, click the 'Full Backtest' button to kick off a full backtest with minute-bar data.

Ordering

Call order(security, amount) to place a simple market order. security is the security you wish to trade, and the amount is the number of shares you want to buy. Use a negative amount in order to sell. The method returns an order id that can be used to track the order's status. The FAQ has more detailed information about how orders are handled and filled by the backtester.

Quantopian supports four different order types:

  • market order: order(security, amount) will place a simple market order.
  • limit order: Use order(security, amount, style=LimitOrder(price)) to place a limit order. A limit order executes at the specified price or better, if the price is reached.
  • Note: order(security, amount, limit_price=price) is old syntax and will be deprecated in the future.

  • stop order: Call order(security, amount, style=StopOrder(price)) to place a stop order (also known as a stop-loss order). When the specified price is hit, the order converts into a market order.
  • Note: order(security, amount, stop_price=price) is old syntax and will be deprecated in the future.

  • stop-limit order: Call order(security, amount, style=StopLimitOrder(limit_price, stop_price)) to place a stop-limit order. When the specified stop price is hit, the order converts into a limit order.
  • Note: order(security, amount, limit_price=price1, stop_price=price2) is old syntax and will be deprecated in the future.

All orders are good-til-cancelled, except for algorithms that are connected to a brokerage. Orders placed with a brokerage (both paper trading and real-money trading) are cancelled at end-of-day. In the future we will most likely change backtest order behavior to also cancel on end-of-day, to make the backtester more accurate.

You can see the status of a specific order by calling get_order(order). Here is a code example that places an order, stores the order_id, and uses the id on subsequent calls to log the order amount and the amount filled:

# place a single order at market open.  
if exchange_time.hour ==9: and exchange_time.minute >=30:  
    context.order_id = order_target(context.aapl, 1000000)  
 
# retrieve the order placed in the first bar  
aapl_order = get_order(context.order_id)  
if aapl_order:  
    # log the order amount and the amount that is filled  
    message = 'Order for {amount} has {filled} shares filled.'  
    message = message.format(amount=aapl_order.amount, filled=aapl_order.filled)  
    log.info(message)

If your algorithm is using a stop order, you can check the stop status in the stop_reached attribute of the order ID:

# Monitor open orders and check is stop order triggered  
for stock in context.secs:    
      
      #check if we have any open orders queried by order id    
      ID = context.secs[stock]    
      order_info = get_order(ID)    

      # If we have orders, then check if stop price is reached    
      if order_info:      
        CheckStopPrice = order_info.stop_reached      
          if CheckStopPrice: log.info(('Stop price triggered for stock %s') % (stock.symbol)

You can see a list of all open orders by calling get_open_orders(). This example logs all the open orders across all securities:

# retrieve all the open orders and log the total open amount  
    # for each order  
    open_orders = get_open_orders()  
    # open_orders is a dictionary keyed by sid, with values that are lists of orders.  
    if open_orders:  
        # iterate over the dictionary  
        for security, orders in open_orders.iteritems():  
            # iterate over the orders  
            for oo in orders:  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=security)  
                log.info(message)

If you want to see the open orders for a specific stock, you can specify the security like: get_open_orders(sid=sid). Here is an example that iterates over open orders in one stock:

# retrieve all the open orders and log the total open amount  
    # for each order  
    open_aapl_orders = get_open_orders(context.aapl)  
    # open_aapl_orders is a list of order objects.  
    # iterate over the orders in aapl  
    for oo in open_aapl_orders:  
        message = 'Open order for {amount} shares in {stock}'  
        message = message.format(amount=oo.amount, stock=security)  
        log.info(message)

Orders can be cancelled by calling cancel_order(order). Orders are cancelled asynchronously.

Exchange Routing

In live trading with Interactive Brokers (IB), you can specify if you want your order routed to exchanges via IB’s Smart Routing or to be routed to IEX. By default, orders will use IB’s Smart Routing method.

To route your order to IEX, you need to first import IBExchange from brokers.ib. Then, in your order function, you can pass exchange=IBExchange.IEX into the order style parameter. For example, order(symbol('AAPL'), 100, style=MarketOrder(exchange=IBExchange.IEX)) will send a market order to IEX for 100 shares of Apple.

Order styles supported for routing to IEX:

  • MarketOrder
  • LimitOrder
  • StopOrder
  • StopLimitOrder

Below is a full code example:

from brokers.ib import IBExchange

def initialize(context):
    pass

def handle_data(context, data):
    # Explicitly setting exchange to IEX 
    order(symbol('AAPL'), 100, style=MarketOrder(exchange=IBExchange.IEX))

    # Explicitly setting exchange to SMART.  This is the default behavior if no exchange is passed.
    order(symbol('IBM'), 100, style=LimitOrder(10, exchange=IBExchange.SMART))

Logging

Your algorithm can easily generate log output by using the log.error (and warn, info, debug) methods. Log output appears in the right-hand panel of the IDE or in the backtest result page.

Logging is rate-limited (throttled) for performance reasons. The basic limit is two log messages per call of initialize and handle_data. Each backtest has an additional buffer of 20 extra log messages. Once the limit is exceeded, messages are discarded until the buffer has been emptied. A message explaining that some messages were discarded is shown.

Two examples:

  • Suppose in initialize you log 22 lines. Two lines are permitted, plus the 20 extra log messages, so this works. However, a 23rd log line would be discarded.
  • Suppose in handle_data, you log three lines. Each time handle_data is called, two lines are permitted, plus one of the extra 20 is consumed. Thus, 20 calls of handle_data are logged entirely. On the 21st call two lines are logged, and the last line is discarded. Subsequent log lines are also discarded until the buffer is emptied.

Additionally, there is a per-member overall log limit. When a backtest causes the overall limit to be reached, the logs for the oldest backtest are discarded.

Event Properties

The data object in handle_data holds data for all the securities your algorithm references. Each security has several trading event properties:

Help-properties

datetime is the Python datetime object representing the timestamp of last trade of this security. Trade data is provided in UTC.

price is the price of this security at the end of this event. Most code within Quantopian operates on this property; it is the property used by default to describe the price of a security.

volume is the volume of shares traded during this event.

open_price is the price of the security at start of this event.

close_price is the price of the security at end of this event. identical to price.

high is the highest price traded during this event.

low is the lowest price traded during this event.

(mavg, returns, and vwap are transforms, which are explained in the next section.)

Simple Transforms

In addition to event properties, we provide a number of transforms so that you don't have to calculate them yourself. They're listed in the dropdown in the event properties screenshot above, and additional contextual information is shown when you use them:

Help-transforms

mavg(days) calculates the moving average of the security's price over the given number of trading days, including today. A mavg(3) will return the moving average of the last 2 trading days plus today's trailing data.

returns() calculates the returns since the end of the previous trading day for the specified security.

stddev(days) calculates the standard deviation of the security's price over the given number of trading days, including today. A stddev(5) will return the standard deviation of the last 4 trading days plus today's trailing data. Standard deviation is calculated using Bessel's Correction. Note that mathematically there is no standard deviation for a single data point, so this function returns 'None' until the second data point.

vwap(days) calculates the volume-weighted average price over the given number of trading days, including today. A vwap(10) will return the volume-weighted average price of the last 9 trading days plus today's trailing data.

In daily mode backtests, the algorithm calculates the simple transform based on the daily close price. In minute mode backtests, the calculation is done using the minutely close prices. For example, a mavg(5) in daily mode will use the close price for previous 4 days plus today's data. In minute mode, this calculation would use 4 days * 390 minutes plus today's trailing prices. In essence, minute mode backtests use more data points for the calculation.

There are many other transforms we could add - which ones would you like? Let us know.

Recording and plotting variables

When backtesting, you can create time series charts by using the record method and passing series names and corresponding values using keyword arguments. Up to five series can be recorded and charted. Recording is done at day-level granularity. Recorded time series are then displayed in a chart below the performance chart.

A simple, contrived example:

def initialize(context):
    pass

def handle_data(context, data):
    # track the 20-day moving average for MSFT and AAPL
    record(msft_mavg=data[symbol('MSFT')].mavg(20), aapl_mavg=data[symbol('AAPL')].mavg(20))

In the result for this backtest, you'll get something like this:

Help-record

You can also pass variables as the series name using positional arguments. The value of the variable can change throughout the backtest, dynamically updating in the custom chart. The record function can accept a string or a variable that has a string value.

def initialize(context):
    context.stocks = context.stocks = symbols('AA', 'AAPL', 'ALK') 

def handle_data(context, data):
    # You can pass a string variable into record().
    # Here we record the price of all the stocks in our universe.
    for stock in data:
      price = data[stock].price
      record(stock, price)

    # You can also pass in a variable with a string value.
    # This records the high and low values for Apple.
    fields = ['high', 'low']
    for field in fields:
      record(field, data[symbol('AAPL')][field])

The code produces this custom chart. Click on a variable name to remove it from the chart, allowing you to zoom in on the other time series. Click the variable name again to restore it on the chart.

Help-record_kwargs

Some notes:

  • For each series, the value at the end of each trading day becomes the recorded value for that day. This is important to remember when backtesting with minute-level data.
  • Until a series starts being recorded, it will have no value. If record(new_series=10) is done on day 10, new_series will have no value for days 1-9.
  • Each series will retain its last recorded value until a new value is used. If record(my_series=5) is done on day 5, every subsequent day will have the same value (5) for my_series until a new value is given.

To see more, check out the example record algorithm at the end of the help documentation.

Dividends

The Quantopian database holds over 150,000 dividend events dating from January 2002. While other corporate events, such as splits and mergers, are handled by adjusting historical prices and volumes, dividends are more complex. Dividends are treated as events and streamed through the performance tracking system that monitors your algorithm during a backtest. Dividend events modify the security price and the portfolio's cash balance.

Dividends specify four dates:

  • declared date is the date on which the company announced the dividend.
  • record date is the date on which a shareholder must be recorded as an owner to receive a dividend payment. Because settlement can take 3 days, a second date is used to calculate ownership on the record date.
  • ex date is 3 trading days prior to the record date. If a holder sells the security before this date, they are not paid the dividend. The ex date is when the price of the security is typically most affected.
  • pay date is the date on which a shareholder receives the cash for a dividend.

Security prices are marked down by the dividend amount on the open following the ex_date. The portfolio's cash position is increased by the amount of the dividend on the pay date. Quantopian chose this method so that cash positions are correctly maintained, which is particularly important when an algorithm is used for live trading. The downside to this method is that this can create a noticeable discontinuity in price history and a short portfolio value.

In order for your algorithm to receive dividend cash payments, you must have a long position (positive amount) in the security as of the close of market on the trading day prior to the ex_date AND you must run the simulation through the pay date, which is typically about 60 calendar days later.

If you are short the security at market close on the trading day prior to the ex_date, your algorithm will be required to pay the dividends due. As with long positions, the cash balance will be debited by the dividend payments on the pay date. This is to reflect the short seller's obligation to pay dividends to the entity that loaned the security.

Special dividends (where more than 25% of the value of the company is involved) are not yet tracked in Quantopian.  There are several hundred of these over the last 11 years.  We will add these dividends to our data in the future.

Dividends are not relayed to algorithms as events that can be accessed by the API; we will add that feature in the future.

History

In many strategies, it is useful to compare the most recent bar data to previous bars. The Quantopian platform provides utilities to easily access and perform calculations on recent history.

This code queries the last 20 days of price history for a universe. Specifically, this returns the closing daily price for the last 20 days, including the current price for the current day:

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(90.0, 90.1))

def handle_data(context, data):
    price_history = history(bar_count=20, frequency='1d', field='price')

bar_count

The bar_count field specifies the number of days or minutes to include in the DataFrame returned by the history function. This parameter accepts only integer values.

frequency

The frequency field specifies how often the data is sampled: daily or minutely. Acceptable inputs are ‘1d’ or ‘1m’. Other frequencies such as ‘30m’ or are not yet supported.

Below are examples of code along with explanations of the data returned.

Day Units
  • "d" suffix to the frequency parameter.
  • The dataframe returned is always in daily bars. The bars never span more than one trading day.
  • Examples:
    • history(1, "1d", "price") returns the current price.
    • history(1, "1d", "volume") returns the volume since the current day's open, even if it is partial.
    • history(2, "1d", "price") returns yesterday's close price and the current price.
    • history(6, "1d", "price") returns the prices for the previous 5 days and the current price.
  • Partial trading days are treated as a single day unit. Scheduled half day sessions, unplanned early closures for unusual circumstances, and other truncated sessions are each treated as a single trading day.
Minute Units
  • "m" suffix to the frequency parameter.
  • examples:
    • history(1, "1m", "price") returns the current price.
    • history(2, "1m", "price") returns the previous minute's close price and the current price.
    • history(60, "1m", "volume") returns the volume for the previous 60 minutes.

Field

The history functions return a pandas DataFrame populated with the values for the field specified in the third parameter.

For example, history(bar_count=2, frequency='1d', field='price') returns a DataFrame populated with the price information for each security in the universe:

The available options for the field parameter are:

  • open_price
  • high
  • low
  • close_price
  • price
  • volume

Returned Data

The returned data for daily history, for each day, is:

  • open_price: the open of the first minute bar of the given day.
  • high: the maximum of the minute highs for the given day.
  • low: the minimum of the minute bar lows for the given day.
  • close_price: the close of the last minute bar in the specified period. This value is not forward-filled. If there is no price in the bar, the returned price is NaN.
  • price: the close of the last minute bar as of the specified period. This value is forward-filled. If there is no price in the bar, the returned price is the previous price, until there is new trade data available.
  • volume: the sum of all minute volumes. For the current day, the sum of volume thus far.

If the data source had minute-bar closing prices for a stock XYZ as such:

                        XYZ
2013-09-05 20:59:00     17.0
2013-09-05 21:00:00     19.0
2013-09-06 14:31:00     20.0
2013-09-06 14:32:00     18.0

The following call to history would output the following results:

history(bar_count=2, frequency='1d', field='price')

At 2013-09-06 14:31 UTC:

                       XYZ
2013-09-05 21:00:00    19.0
2013-09-06 14:31:00    20.0

One minute later, at 2013-09-06 14:32 UTC, notice that the timestamp and data for the previous day's data stays the same, but the current day's value updates to the current algo time and value for price.

                       XYZ
2013-09-05 21:00:00    19.0
2013-09-06 14:32:00    18.0

History and Backtest Start

The full history data panel is available on the first day of the backtest, so there is no need to 'warm up' the history DataFrame. The data is backfilled so that calculations can be done starting with the first call to handle_data.

Obviously, we can't load history where the requested data extends farther into the past than our database. The database limit is Jan 2, 2002 for backtesting and Mar 1, 2012 for paper trading and live trading.

Illiquidity and Forward Filling

By default, history methods will forward fill missing bars. If there is trade data missing for a security one day, the function will use the previous known price until there is new trade data.

However, it can be useful to have visibility into gaps in the trading history. We provide an option for the history methods, which disables forward filling. e.g., history(bar_count=15, frequency='1d', field='price', ffill=False) will return a DataFrame that has nan value for price for days on which a given security did not trade.

Common Usage: Current and Previous Bar

A common use case is to compare yesterday's close price with the current price.

This example compares yesterday's close (labeled prev_bar) with the current price (labeled curr_bar) and places an order for 20 shares if the current price is above yesterday's closing price.

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(90.0, 90.1))

def handle_data(context, data):
    price_history = history(bar_count=2, frequency='1d', field='price')
    for s in data:
        prev_bar = price_history[s][-2]
        curr_bar = price_history[s][-1]
        if curr_bar > prev_bar:
            order(s, 20)

Common Usage: Looking Back X Bars

It can also be useful to look further back into history for a comparison. Computing the percent change over given historical time frame requires the starting and ending price values only, and ignores intervening prices.

The following example operates over all stocks available in data, in pandas Series format to arrive at the percent change over the past 10 trading days.

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(90.0, 90.1))

def handle_data(context, data):
    prices = history(bar_count=10, frequency='1d', field='price')
    pct_change = (prices.ix[-1] - prices.ix[0]) / prices.ix[0]
    log.info(pct_change)

Alternatively, leveraging the following iloc pandas DataFrame function, which returns the first and last values as a pair:

price_history.iloc[[0, -1]]

The percent change example can be re-written as:

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(90.0, 90.1))

def handle_data(context, data):
    price_history = history(bar_count=10, frequency="1d", field='price')
    pct_change = price_history.iloc[[0, -1]].pct_change()
    log.info(pct_change)

The difference code example in the Current and Previous Bar section can also be written as:

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(90.0, 90.1))

def handle_data(context, data):
    price_history = history(bar_count=2, frequency="1d", field='price')
    diff = price_history.iloc[[0, -1]].diff()
    log.info(diff)

Common Usage: Rolling Transforms

Rolling transform calculations such as mavg, stddev, etc. can be calculated via methods provided by pandas.

Common Usage: Standard Deviation

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(90.0, 90.1))

def handle_data(context, data):
    price_history = history(bar_count=5, frequency='1d', field='price')
    log.info(price_history.std())

Common Usage: Moving Average

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(90.0, 90.1))

def handle_data(context, data):
    price_history = history(bar_count=5, frequency='1d', field='price')
    log.info(price_history.mean())

Common Usage: VWAP

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(90.0, 90.1))

def vwap(prices, volumes):
    return (prices * volumes).sum() / volumes.sum()

def handle_data(context, data):
    prices_a = history(15, '1d', field='price')
    volumes_a = history(15, '1d', field='volume')

    prices_b = history(30, '1d', field='price')
    volumes_b = history(30, '1d', field='volume')

    vwap_a = vwap(prices_a, volumes_a)
    vwap_b = vwap(prices_b, volumes_b)

    for s in data:
        if vwap_a[s] > vwap_b[s]:
            order(s, 50)

Common Usage: Using An External Library

Since history returns a pandas DataFrame, the values can then be passed libraries that operate on numpy and pandas data structures.

An example OLS strategy:

import statsmodels.api as sm

def ols_transform(prices, sec1, sec2):
    """
    Computes regression coefficient (slope and intercept)
    via Ordinary Least Squares between two securities.
    """
    p0 = prices[sec1]
    p1 = sm.add_constant(prices[sec2], prepend=True)
    return sm.OLS(p0, p1).fit().params

def initialize(context):
    context.sec1 = symbol('KO')
    context.sec2 = symbol('PEP')

def handle_data(context, data):
    price_history = history(bar_count=30, frequency='1d', field='price')
    intercept, slope = ols_transform(price_history, context.sec1, context.sec2)

Common Usage: Using TA-Lib

Since history returns a pandas DataFrame, a Series can be extracted and then passed to TA-Lib.

An example EMA calculation:

# Python TA-Lib wrapper
# https://github.com/mrjbq7/ta-lib
import talib

def initialize(context):
    context.my_stock = symbol('AAPL')

def handle_data(context, data):
    price_history = history(bar_count=30, frequency='1d', field='price')
    my_stock_series = price_history[context.my_stock]
    ema_result = talib.EMA(my_stock_series, timeperiod=12)
    record(ema=ema_result[-1])

Batch Transforms

Note: This function is deprecated in favor of history and will stop working at some point in the future. Please start using history instead.

Often, you will want to operate on a trailing window of minutely data. The work involved in creating a trailing window of data is so common, Quantopian provides a helper facility called batch_transform.

batch_transform is available in the coding environment as a python function decorator. To use it, you define a function that accepts a datapanel as its first argument, mark it with the batch_transform decorator, and then invoke the function from within handle_data using data as the first parameter. The batch_transform decorator transforms the data parameter sent to your function: data are accumulated into a pandas datapanel.

Here is a simple example that calculates the 10-day average price for all securities, running in a daily bar simulation:

# Here we create a universe of securities based on the top 0.1% most liquid by
# dollar volume traded. Approximately 7 individual securities will be used
# by the algorithm.
def initialize(context):
  set_universe(universe.DollarVolumeUniverse(99.9, 100.0))

# This method expects to receive a datapanel containing dataframes for price,
# volume, open_price, close_price, high, low, and volume. Each dataframe will
# have ten rows (1 per trading day), and about 7 columns (one for each
# stock). window_length is always in trading days.
@batch_transform(window_length=10)
def get_averages(datapanel):
  # get the dataframe of prices 
  prices = datapanel['price']
  # return a dataframe with one row showing the averages for each stock.
  return prices.mean()

def handle_data(context, data):
  # here is the magic part. We invoke the get_averages method using 
  # just the single data (a dictionary of daily bars indexed by security).
  # the decorator will convert this to a datapanel spanning the 10 day history
  # that we specified in with the window_length parameter in the decorator 
  # above.
  averages = get_averages(data)
  # add a newline to the beginning of the log line so that the column header of the 
  # is properly indented.
  log.info('\n%s' % averages) 

Minute Data in batch_transform, Rolling Updates, and refresh_period

The same code above could also be run in minutely mode. In that case, the window of data would be 10 trading days, but because updates are at minute granularity, the first and last day would be partial. For example a 10 day window would stretch from 10:32am on 3/1/2013 to 10:31am on 3/14/2013. This is in contrast to daily mode, where the trading days are always complete.

As you can see from this example, batch_transform is by default rolling - each new event passed to the function will update the window of data. We chose rolling as the default behavior because it is the most commonly needed, but sometimes it doesn't make sense to update the window on every bar: a batch transform may depend on comparing whole trading days, or complete weeks and months.

For these cases, the batch_transform provides an optional parameter refresh_period. For example, if you wanted your minutely simulation to only advance the window in whole day increments, so that the end of the window would always be through yesterday's close, you could specify the batch_transform like this:

@batch_transform(window_length=10, refresh_period=1)
def get_average(datapanel):
  ...

You may set refresh_period to any integer. Say for example, you are running a daily bar simulation and you want to update the trailing window each week:

@batch_transform(window_length=10, refresh_period=5)
def get_average(datapanel):
  ...

By default refresh_period is 0, which means 'rolling.'

Missing Bars for Batch Transforms

Stocks do not necessarily trade every minute or even every day. There are cases where the trailing window may include days that are pre-IPO or post-acquisition for a company. Stocks may be held from trading, or there simply may not be any trading activity in a day or minute. For these missing periods, the batch_transform helper will fill in missing values with the last prior value. As a result, it is still possible for the first period to have a missing value (there is no prior) or for an entire column to be missing when a stock doesn't trade at all in the entire trailing window. Missing values are represented by the pandas NA.

Pandas has excellent default handling in most aggregate functions so that NAs will not produce spurious results. Optionally, you can have the batch_transform leave the NAs instead of filling them. batch_transform will not fill the NAs if you set clean_nans to False (defaults to True). If NAs are not filled, your code needs to defensively handle them, or runtime errors will often result.

@batch_transfrom(window_length=10, clean_nans=False)
def get_averages(datapanel):
  prices = datapanel['price']
  return prices.mean()

Accumulating Custom Fields for Batch Transforms

batch_transform creates a dataframe for each property in the union of all properties for all events in data. You can extend the fields accumulated in the trailing window simply by adding those fields before sending data to the decorated function. Suppose you want to track an event, such as the stock trading above its 30 day vwap. You can set that new property on the events in data and then operate on the trailing history of the value in the batch_transform:

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(99.9, 100.0))

def handle_data(context, data):
    for stock in data:
        if data[stock].price > data[stock].vwap(30):
            data[stock]['over_vwap'] = 1
        else:
            data[stock]['over_vwap'] = 0

    print_signals(data)

@batch_transform(window_length=5)
def print_signals(datapanel):
    signal = datapanel['over_vwap']
    log.info('\n%s' % signal)

Supplemental Parameters for Batch Transforms

Sometimes your batch transform needs additional parameters, outside the datapanel. For example, you might want to pass in the current portfolio or a scalar value for the calculation.

The batch_transform decorator only modifies the first parameter by accumulating data into the datapanel. Any other parameters are simply forwarded unmodified. Here is how you would pass the current portfolio to a decorated method.

def initialize(context):
    set_universe(universe.DollarVolumeUniverse(99.9, 100.0))

def handle_data(context, data):
    # here is an example of passing an extra parameter to the function.
    averages = get_averages(data, context.portfolio)
    log.info('\n%s' % averages)

# Note that the decorator does not receive the supplemental parameters, 
# the function itself does.
@batch_transform(window_length=10)
def get_averages(datapanel, portfolio):
    # get the dataframe of prices 
    prices = datapanel['price']
    # return a dataframe with one row showing the averages for each stock.
    return prices.mean()

Repeated Invocations in Batch Transforms

batch_transform is idempotent, and will cache results where possible. However, for optimal speed, you should try to use vector operations in the batch_transform - operate on the complete dataframe and return dataframes. A rule of thumb is to strive to invoke a batch transform just once per handle_data invocation.

Partially Full Trailing Windows in Batch Transforms

By default, batch_transform will return None if the trailing window is not full. For example, if you set window_length = 10 it will return None for the first 9 trading days and begin returning trailing data on the 10th trading day. This ensures that each window is equal length, and that comparisons between results are valid. However, it is sometimes useful to perform a calculation before the trailing window is completely full. This is supported by the optional parameter compute_only_full, which defaults to True. If set to False, the batch_transform will trigger the function to recalculate values with whatever data is available. In the future, batch_transform will be deprecated in support of the more robust history function, which does not require this warm-up period

Slippage Models

Slippage is where our backtester calculates the realistic impact of your orders on the execution price you receive. When you place an order for a trade, your order affects the market. Your buy order drives prices up, and your sell order drives prices down; this is generally referred to as the 'price impact' of your trade. The size of the price impact is driven by how large your order is compared to the current trading volume. The slippage method also evaluates if your order is simply too big: you can't trade more than market's volume, and generally you can't expect to trade more than a quarter of the volume. All of these concepts are wrapped into the slippage method.

Slippage must be defined in the initialize method. It has no effect if defined in handle_data(). If you do not specify a slippage method, slippage defaults to VolumeShareSlippage(volume_limit=0.25, price_impact=0.1).

To set slippage, use the set_slippage method and pass in FixedSlippage, VolumeShareSlippage, or a custom slippage model that you define.

By default, backtests use the VolumeShareSlippage model.

Slippage-main

Fixed Slippage

When using the FixedSlippage model, the size of your order does not affect the price of your trade execution. You specify a 'spread' that you think is a typical bid/ask spread to use. When you place a buy order, half of the spread is added to the price; when you place a sell order, half of the spread is subtracted from the price.

Fixed-slippage

Volume Share Slippage

In the VolumeShareSlippage model, the price you get is a function of your order size relative to the security's actual traded volume. You provide a volume_limit cap (default 0.25), which limits the proportion of volume that your order can take up per bar. For example: if the backtest is running in one-minute bars, and you place an order for 60 shares; then 100 shares trade in each of the next several minute; and the volume_limit is .25; then your trade order will be split into three orders (25 shares, 25 shares, and 10 shares). Setting the volume_limit to 1.00 will permit the backtester to use up to 100% of the bar towards filling your order. Using the same example, this will fill 60 shares in the next minute bar.

The price impact constant (default 0.1) defines how large of an impact your order will have on the backtester's price calculation. The slippage is calculated by multiplying the price impact constant by the square of the ratio of the order to the total volume. In our previous example, for the 25-share orders, the price impact is .1 * (25/100) * (25/100), or 0.625%. For the 10-share order, the price impact is .1 * (10/100) * (10/100), or .1%.

Volumesharesslippage

Custom Slippage

You can build a custom slippage model that uses your own logic to convert a stream of orders into a stream of transactions. In the initialize() function you must specify the slippage model to be used and any special parameters that the slippage model will use. Example:

def initialize(context):
    set_slippage(MyCustomSlippage(slippage-param))

Your custom model must be a class that inherits from slippage.SlippageModel and implements process_order(self, trade_bar, order).

Each order that your algorithm places, along with the current price information for that security (the trade bar), is passed to your custom model's process_order. The logic inside that method decides whether to generate a transaction, and if so, at what amount and price. To create a transaction, use the slippage.create_transaction method

The order object has the following properties: amount (float), direction (1 for buy, -1 for sell), sid (int), stop and limit (float), and stop_reached and limit_reached (boolean). The trade_bar object is the same as data[sid] in handle_data and has open_price, close_price, high, low, volume, and sid.

The slippage.create_transaction method takes the given trade_bar, the given order, the price and amount calculated by your slippage model, and returns the newly built transaction.

Many slippage models' behavior depends on how much of the total volume traded is being captured by the algorithm. You can use self.volume_for_bar to see how many shares of the current security have been traded so far during this bar. If your algorithm has many different orders for the same stock in the same bar, this is useful for making sure you don't take an unrealistically large fraction of the traded volume.

If your slippage model doesn't place a transaction for the full amount of the order, the order stays open with an updated amount value, and will be passed to process_order on the next bar. Orders that have limits that have not been reached will not be passed to process_order. Finally, if your transaction has 0 shares or more shares than the original order amount, an exception will be thrown.

Please see the sample custom slippage model.

Commission Models

To set the cost of your trades, use the set_commission method and pass in PerShare or PerTrade. Like the slippage model, set_commission must be used in the initialize method and has no effect if used in handle_data. If you don't specify a commission, your backtest defaults to $0.03 per share.

You can define your trading cost in either dollars per share or dollars per trade.

Commission

Scheduling Functions

To have a function run on specific dates or at specific times, use schedule_function. schedule_function must be called from within initialize and accepts the name of the function, a date rule, a time rule (or both), and a flag specifying whether it should run on days when the market closes early.

By default, the scheduled function will run every trading day, one minute before market close, including days the market closes early.

def initialize(context):
  schedule_function(
    func=myfunc,
    date_rule=date_rules.every_day(),
    time_rule=time_rules.market_close(minutes=1),
    half_days=True
  )

Monthly modes (month_start and month_end) accept a days_offset parameter to offset the function execution by a specific number of trading days from the beginning and end of the month, respectively. In monthly mode, all day calculations are done using trading days. If the offset exceeds the number of trading days in a month, the function isn't run during that month.

Weekly modes (week_start and week_end) also accept a days_offset parameter. If the function execution is scheduled for a market holiday and there is at least one more trading day in the week, the function will run on the next trading day. If there are no more trading days in the week, the function is not run during that week.

To schedule a function more frequently, you can use multiple schedule_function calls. For instance, you can schedule a function to run every 30 minutes, every three days, or at the beginning of the day and the middle of the day.

Below are the options for date rules and time rules for a function. For more details, go to the API overview.

Help-schedulefunction-date Help-schedulefunction-timerules

Date Rules

Every Day

Runs the function once per trading day. The example below calls myfunc every morning, 15 minutes after the market opens.

def initialize(context):
  # Algorithm will call myfunc every day 15 minutes after the market opens
  schedule_function(
    myfunc,
    date_rules.every_day(),
    time_rules.market_open(minutes=15)
  )

def myfunc(context,data):
  pass

Week Start

Runs the function once per calendar week. By default, the function runs on the first trading day of the week. You can add an optional offset from the start of the week to choose another day. The example below calls myfunc once per week, on the second day of the week, 3 hours and 10 minutes after the market opens.

def initialize(context):
  # Algorithm will call myfunc once per week, on the second day of the week,
  # 3 hours and 10 minutes after the market opens
  schedule_function(
    myfunc,
    date_rules.week_start(days_offset=1),
    time_rules.market_open(hours=3, minutes=10)
  )

def myfunc(context,data):
  pass

Week End

Runs the function once per calendar week. By default, the function runs on the last trading day of the week. You can add an optional offset, from the end of the week, to choose the trading day of the week. The example below calls myfunc once per week, on the second-to-last day of the week, 1 hour after the market opens.

def initialize(context):
  # Algorithm will call myfunc once per week, on the last day of the week,
  # 1 hour after the market opens
  schedule_function(
    myfunc,
    date_rules.week_end(days_offset=1),
    time_rules.market_open(hours=1)
  )

def myfunc(context,data):
  pass

Month Start

Runs the function once per calendar month. By default, the function runs on the first trading day of each month. You can add an optional offset from the start of the month, counted in trading days (not calendar days).

The example below calls myfunc once per month, on the second trading day of the month, 30 minutes after the market opens.

def initialize(context):
  # Algorithm will call myfunc once per month, on the second day of the month,
  # 30 minutes after the market opens
  schedule_function(
    myfunc,
    date_rules.month_start(days_offset=1),
    time_rules.market_open(minutes=30)
  )

def myfunc(context,data):
  pass

Month End

Runs the function once per calendar month. By default, the function runs on the last trading day of each month. You can add an optional offset from the end of the month, counted in trading days. The example below calls myfunc once per month, on the third-to-last trading day of the month, 15 minutes after the market opens.

def initialize(context):
  # Algorithm will call myfunc once per month, on the last day of the month,
  # 15 minutes after the market opens
  schedule_function(
    myfunc,
    date_rules.month_end(days_offset=2),
    time_rules.market_open(minutes=15)
  )

def myfunc(context,data):
  pass

Time Rules

Market Open

Runs the function at a specific time relative to the market open. Without a specified offset, the function is run one minute after the market opens. The example below calls myfunc every day, 1 hour and 20 minutes after the market opens.

def initialize(context):
  # Algorithm will call myfunc every day, 1 hour and 20 minutes after the market opens
  schedule_function(
    myfunc,
    date_rules.every_day(),
    time_rules.market_open(hours=1, minutes=20)
  )

def myfunc(context,data):
  pass

Market Close

Runs the function at a specific time relative to the market close. Without a specified offset, the function is run one minute before the market close. The example below calls myfunc every day, 2 minutes before the market close.

def initialize(context):
  # Algorithm will call myfunc every day, 2 minutes before the market closes
  schedule_function(
    myfunc,
    date_rules.every_day(),
    time_rules.market_close(minutes=2)
  )

def myfunc(context,data):
  pass

To use time rules, the backtest must be run in minute mode. If a daily backtest has a scheduled function for sometime during the trading day, the function will be run at the end of the day.

Scheduling Multiple Functions

More complicated function scheduling can be done by combining schedule_function calls.

Using two calls to schedule_function, a portfolio can be rebalanced at the beginning and middle of each month:

def initialize(context):
  # execute on the second trading day of the month
  schedule_function(
    myfunc,
    date_rules.month_start(days_offset=1)
  )

  # execute on the 10th trading day of the month
  schedule_function(
    myfunc,
    date_rules.month_start(days_offset=9)
  )

def myfunc(context,data):
  pass

To call a function every 30 minutes, use a loop with schedule_function:

def initialize(context):
  # For every minute available (max is 6 hours and 30 minutes)
  total_minutes = 6*60 + 30

  for i in range(total_minutes):
    # Every 30 minutes run schedule
    if i % 30 == 0:
      # This will start at 9:31AM and will run every 30 minutes
      schedule_function(
      myfunc,
        date_rules.every_day(),
        time_rules.market_open(minutes=i),
        True
      )

def myfunc(context,data):
  pass

def handle_data(context,data):
  pass

Viewing Portfolio State

Your current portfolio state is accessible from the context object in handle_data:

Help-portfolio

To view an individual position's information, use the context.portfolio.positions dictionary:

Help-positions

Details on the portfolio and position properties can be found in the API documentation below.

Module Import

Only specific, whitelisted Python modules can be imported. If you need a module that isn't on this list, please let us know.

  • bisect
  • cmath
  • collections
  • datetime
  • functools
  • heapq
  • itertools
  • math
  • numpy
  • pandas
  • pytz
  • Queue
  • random
  • re
  • scipy
  • statsmodels
  • sklearn
  • talib
  • time
  • zipline

Running Backtests

You can set the start date, end date, and starting capital used by the backtest in the IDE.

Help-ide2

To create a new backtest, click the 'Run Full Backtest' button from the IDE. That button appears once your algorithm successfully validates. Press the 'Build' button to start the validation if the 'Run Full Backtest' button is not visible in the upper-right of the IDE.

We also provide a Backtests Page that has a summary of all backtests run against an algorithm. To go to the Backtests page, either click the Backtest button at the top right of the IDE, or, from the My Algorithms page click the number of backtests that have been run. The backtests page lists all the backtests that have been run for this algorithm, including any that are in progress. You can view an existing or in-progress backtest by clicking on it. Closing the browser will not stop the backtest from running. Quantopian runs in the cloud and it will continue to execute your backtest until it finishes running. If you want to stop the backtest, press the Cancel button.

Debugger

The debugger gives you a powerful way to inspect the details of a running backtest. By setting breakpoints, you can pause execution and examine variables, order state, positions, and anything else your backtest is doing.

Using the Debugger

In the IDE, click on a line number in the gutter to set a breakpoint. A breakpoint can be set on any line except comments and method definitions. A blue marker appears once the breakpoint is set.

Help-debugger-breakpoint

To set a conditional breakpoint, right-click on a breakpoint's blue marker and click 'Edit Breakpoint'. Put in some Python code and the breakpoint will hit when this condition evaluates to true. In the example below, this breakpoint hits when the price of Apple is above $100. Conditional breakpoints are shown in yellow in the left-hand gutter.

Help-conditional-breakpoint

You can set an unlimited number of breakpoints. Once the backtest has started, it will stop when execution gets to a line that has a breakpoint, at which point the backtest is paused and the debug window is shown. In the debugger, you can then query your variables, orders and portfolio state, data, and anything else used in your backtest. While the debugger window is active, you can set and remove other breakpoints.

Help-debugger-example

To inspect an object, enter it in the debug window and press enter. Most objects will be pretty-printed into a tree format to enable easy inspection and exploration.

The following commands can be used in the debugger window:

Keyboard Shortcut Action
.c or .continue Resume backtest until it triggers the next breakpoint or an exception is raised.
.n or .next Executes the next line of code. This steps over the next expression.
.s or .step Executes the next line of code. This steps into the next expression, following function calls.
.r or .return Execute until you are about to return from the current function call.
.f or .finish Disables all breakpoints and finishes the backtest.
.clear Clears the data in the debugger window.
.h or .help Shows the command shortcuts for the debugger.
Technical Details
  • The debugger is available in the IDE. It is not available on the Full Backtest screen.
  • You can edit your code during a debugging session, but those edits aren't used in the debugger until a new backtest is started.
  • After 10 minutes of inactivity in the IDE, any breakpoints will be suspended and the backtest will automatically finish running.
  • After 50 seconds, the breakpoint commands will timeout. This is the same amount of time given for 1 handle_data call.

Backtest results

Once a full backtest starts, we load all the trading events for the securities that your algorithm specified, and feed them to your algorithm in time order. Results will start streaming in momentarily after the backtest starts.

Here is a snapshot of a backtest results page. Mouse over each section to learn more.

Help-backtest-results
Overall results
Cumulative performance and benchmark overlay
Daily and weekly P/L
Transactions chart
Result details
Backtest settings and status

Backtest settings and status: Shows the initial settings for the backtest, the progress bar when the backtest is in progress, and the final state once the test is done. If the backtest is cancelled, exceeds its max daily loss, or have runtime errors, that information will be displayed here.

Result details: Here's where you dive into the details of your backtest results. You can examine every transaction that occurred during the backtest, see how your positions evolved over time, and look at detailed risk metrics. For the risk metrics, we show you 1, 3, 6, and 12-month windows to provide more granular breakdowns

Overall results: This is the overall performance and risk measures of your backtest. These numbers will update during the course of the backtest as new data comes in.

Cumulative performance and benchmark overlay: Shows your algorithm's performance over time (in blue) overlaid with the benchmark (in red).

Daily and weekly P/L: Shows your P/L per day or week, depending on the date range selected.

Transactions chart: Shows all the cumulative dollar value of all the buys and sells your algorithm placed, per day or week. Buys are shown as positive blue, and sells as negative reds.

Live Trade Your Algorithm with your Broker

You can live trade your algorithm with Interactive Brokers using your real money and paper broker account.

To deploy a real money algorithm, follow these steps:

  1. Enter your broker credentials in your Quantopian trading profile.
  2. Run a full backtest in minute mode.
  3. Click the 'Live Trade Algorithm' button in the top-right corner.
  4. Choose the Broker option to trade real money or paper trade with Interactive Brokers. The Quantopian option is free paper trading using 15-minute delayed data on the platform.
  5. Authenticate your Interactive Brokers account and the live trading dashboard is loaded.

For a detailed walk-through, here is a step-by-step overview video of deploying a real money algorithm.

Broker Account Fields

In your algorithm code, you can access the account information passed from the broker. This is useful to incorporate your margin, settled cash, liquidity, among other fields into the trading logic.

In backtesting, it will default to values from your simulated portfolio. In live trading, the fields will populate with data passed from the broker.

If the broker has not yet reported a value, the field will report None until new data is present. Below is an example showing how to check if the account field has been reported. The account information will be accessed once per minute, at every call of handle_data. Once a value has been reported, it will be forward filled until the next value is reported by the broker.

def handle_data(context,data):
    if context.account.buying_power is None or context.account.buying_power < 10000:
      return

    else:
      order_target(symbol('NFLX'), context.account.settled_cash)

See the API information section for the available broker fields.

Trading Guards

There are several trading guards you can place in your algorithm to prevent unexpected behavior. All the guards are enforced when orders are placed. These guards are set in the initialize function.

Exclude Specific Securities

You can prevent the algorithm from trading specific securities by using set_do_not_order_list. If the algorithm attempts to order any security in the list, it will stop trading and throw an exception. You can use the built-in leveraged ETF list provided by Quantopian or create a customized list of securities.

We've built a list for you that lists all the leveraged ETFs. To use this list, use set_do_not_order_list(security_lists.leveraged_etf_list). For a trader trying to track their own leverage levels, these ETFs are a challenge. The Quantopian Open prohibits trading in these ETFs for that reason.

As shown in the example below, within handle_datayou can test for a security's membership in a pre-built list. Quantopian-provided lists will be updated from time to time as tradeable ETFs are offered and withdrawn on the market.

def initialize(context):
  # create the trading guard to avoid over-leveraged ETFs 
  set_do_not_order_list(security_lists.leveraged_etf_list)

def handle_data(context,data):
  # the point in time lists allow for in/not in checks and iteration
  # the list is point-in-time and allows for checks and iterations
  if symbol('AAPL') not in security_lists.leveraged_etf_list:
    order_target(symbol('AAPL'), 100)

  # view the contents of the list
  for etf in security_lists.leveraged_etf_list:
    print '{s} is a leveraged etf'.format(s=etf)

Long Only

Specify long_only to prevent the algorithm from taking short positions. It does not apply to existing open orders or positions in your portfolio.

def initialize(context):
  # Algorithm will raise an exception if it attempts to place an 
  # order which would cause us to hold negative shares of any security.
  set_long_only()

Maximum Order Count

Sets a limit on the number of orders that can be placed by this algorithm in a single day. In the initialize function you can enter the set_max_order_count, it will have no effect if placed in handle_data.

def initialize(context):
  # Algorithm will raise an exception if more than 50 orders are placed in a day
  set_max_order_count(50)

Maximum Order Size

Sets a limit on the size of any single order placed by this algorithm. This limit can be set in terms of number of shares, dollar value, or both. The limit can optionally be set for a given security; if the security is not specified, it applies to all securities. This must be run in the initialize function.

def initialize(context):
  # Algorithm will raise an exception if we attempt to order more than 
  # 10 shares or 1000 dollars worth of AAPL in a single order.
  set_max_order_size(symbol('AAPL'), max_shares=10, max_notional=1000.0)

Maximum Position Size

Sets a limit on the absolute magnitude of any position held by the algorithm for a given security. This limit can be set in terms of number of shares, dollar value, or both. A position can grow beyond this limit because of market movement; the limit is only imposed at the time the order is placed. The limit can optionally be set for a given security; if the security is not specified, it applies to all securities. This must be run in the initialize function.

def initialize(context): 
  # Algorithm will raise an exception if we attempt to hold more than 
  # 30 shares or 2000 dollars worth of AAPL.
  set_max_position_size(symbol('AAPL'), max_shares=30, max_notional=2000.0)

Zipline

Zipline is our open-sourced engine that powers the backtester in the IDE. You can see the code repository in Github and contribute pull requests to the project. There is a Google group available for seeking help and facilitating discussions. For other questions, please contact [email protected]. You can use Zipline to develop your strategy offline and then port to Quantopian for paper trading and live trading using the get_environment method.

API Documentation

Methods to implement

Your algorithm is required to implement two methods: initialize and handle_data. A third method, 'before_trading_start' is optional.

initialize(context)

Called once at the very beginning of a backtest. Your algorithm can use this method to set up any bookkeeping that you'd like.

The context object will be passed to all the other methods in your algorithm.

Parameters

context: An initialized and empty Python dictionary. The dictionary has been augmented so that properties can be accessed using dot notation as well as the traditional bracket notation.

Returns

None

Example
def initialize(context):
    context.notional_limit = 100000

handle_data(context, data)

Called whenever a market event occurs for any of your algorithm's specified securities.

Parameters

data: A dictionary containing your universe at that time, keyed by security id. It represents a snapshot of your algorithm's universe as of when this method was called. Market information about each security and transforms are all available in this object. Read more below.

context: Same context object in initialize, stores any state you've defined, and stores portfolio object.

Returns

None

Example
def handle_data(context, data):
    # all your algorithm logic here
    # ...
    order(symbol('AAPL'), 100)
    # ...

There is also an optional method which runs once a day, prior to the open of the market called before_trading_start(context). Similar to initialize, the context dictionary can be updated for passing state between methods. The primary purpose of before_trading_start is to query Quantopian's fundamentals database for financial performance data on individual companies. Using this company fundamental data, you can set a universe of companies for use in your algorithm based on 600+ measurable criteria. The data dictionary is updated in this process.

before_trading_start(context)

Optional. Called daily prior to the open of market. Orders cannot be placed inside this method.

Parameters

context: Same context object as in initialize, which stores any state you've defined, and stores the portfolio object.

Returns

None

Example
def before_trading_start(context):
    # logic to access fundamental data
    # and build the SQLAlchemy query
    fundamental_df = get_fundamentals(your_query)

    # Add the fundamental data to context object to store the state
    context.fundamental_df = fundamental_df

    # Update the universe of securities
    update_universe(context.fundamental_df.columns.values)

Order Methods

Within your algorithm, there are some order methods you can use:

order(security, amount, style=OrderType)

Places an order for the specified security of the specified number of shares. Order type is inferred from the parameters used. If only security and amount are used as parameters, the order is placed as a market order.

Parameters

security: A security object.

amount: The integer amount of shares. Positive means buy, negative means sell.

OrderType: (optional) Specifies the order style and the default is a market order. The available order styles are:

  • style=MarketOrder(exchange)
  • style=StopOrder(stop_price, exchange)
  • style=LimitOrder(limit_price, exchange)
  • style=StopLimitOrder(limit_price=price1, stop_price=price2, exchange)

Click here to view an example for exchange routing.

Returns

An order id.

order_value(security, amount, style=OrderType)

Place an order by desired value rather than desired number of shares. Placing a negative order value will result in selling the given value. Orders are always truncated to whole shares.

Example

Order AAPL worth up to $1000: order_value(symbol('AAPL'), 1000). If price of AAPL is $105 a share, this would buy 9 shares, since the partial share would be truncated (discarding slippage and transaction cost).

Parameters

security: A security object.

amount: The integer amount of shares. Positive means buy, negative means sell.

OrderType: (optional) Specifies the order style and the default is a market order. The available order styles are:

  • style=MarketOrder(exchange)
  • style=StopOrder(stop_price, exchange)
  • style=LimitOrder(limit_price, exchange)
  • style=StopLimitOrder(limit_price=price1, stop_price=price2, exchange)

Click here to view an example for exchange routing.

Returns

An order id.

order_percent(security, amount, style=OrderType)

Places an order in the specified security corresponding to the given percent of the current portfolio value, which is the sum of the positions value and ending cash balance. Placing a negative percent order will result in selling the given percent of the current portfolio value. Orders are always truncated to whole shares. Percent must be expressed as a decimal (0.50 means 50%).

Example

order_percent(symbol('AAPL'), .5) will order AAPL shares worth 50% of current portfolio value. If AAPL is $100/share and the portfolio value is $2000, this buys 10 shares (discarding slippage and transaction cost).

Parameters

security: A security object.

amount: The integer amount of shares. Positive means buy, negative means sell.

OrderType: (optional) Specifies the order style and the default is a market order. The available order styles are:

  • style=MarketOrder(exchange)
  • style=StopOrder(stop_price, exchange)
  • style=LimitOrder(limit_price, exchange)
  • style=StopLimitOrder(limit_price=price1, stop_price=price2, exchange)

Click here to view an example for exchange routing.

Returns

An order id.

order_target(security, amount, style=OrderType)

Places an order to adjust a position to a target number of shares. If there is no existing position in the security, an order is placed for the full target number. If there is a position in the security, an order is placed for the difference between the target number of shares and the current number of shares. Placing a negative target order will result in a short position equal to the negative number specified.

Example

If the current portfolio has 5 shares of AAPL and the target is 20 shares, order_target(symbol('AAPL'), 20) orders 15 more shares of AAPL.

Parameters

security: A security object.

amount: The integer amount of shares. Positive means buy, negative means sell.

OrderType: (optional) Specifies the order style and the default is a market order. The available order styles are:

  • style=MarketOrder(exchange)
  • style=StopOrder(stop_price, exchange)
  • style=LimitOrder(limit_price, exchange)
  • style=StopLimitOrder(limit_price=price1, stop_price=price2, exchange)

Click here to view an example for exchange routing.

Returns

An order id.

order_target_value(security, amount, style=OrderType)

Places an order to adjust a position to a target value. If there is no existing position in the security, an order is placed for the full target value. If there is a position in the security, an order is placed for the difference between the target value and the current position value. Placing a negative target order will result in a short position equal to the negative target value. Orders are always truncated to whole shares

Example

If the current portolio holds $500 worth of AAPL and the target is $2000, order_target_value(symbol('AAPL'), 2000) orders $1500 worth of AAPL (rounded down to the nearest share).

Parameters

security: A security object.

amount: The integer amount of shares. Positive means buy, negative means sell.

OrderType: (optional) Specifies the order style and the default is a market order. The available order styles are:

  • style=MarketOrder(exchange)
  • style=StopOrder(stop_price, exchange)
  • style=LimitOrder(limit_price, exchange)
  • style=StopLimitOrder(limit_price=price1, stop_price=price2, exchange)

Click here to view an example for exchange routing.

Returns

An order id.

order_target_percent(security, percent, style=type)

Place an order to adjust a position to a target percent of the current portfolio value. If there is no existing position in the security, an order is placed for the full target percentage. If there is a position in the security, an order is placed for the difference between the target percent and the current percent. Placing a negative target percent order will result in a short position equal to the negative target percent. Portfolio value is calculated as the sum of the positions value and ending cash balance. Orders are always truncated to whole shares, and percentage must be expressed as a decimal (0.50 means 50%).

Example

If the current portfolio value is 5% worth of AAPL and the target is to allocate 10% of the portfolio value to AAPL, order_target_percent(symbol('AAPL'), 0.1) will place an order for the difference, in this case ordering 5% portfolio value worth of AAPL.

Parameters

security: A security object.

percent: The portfolio percentage allocated to the security. Positive means buy, negative means sell.

type: (optional) Specifies the order style and the default is a market order. The available order styles are:

  • style=MarketOrder(exchange)
  • style=StopOrder(stop_price, exchange)
  • style=LimitOrder(limit_price, exchange)
  • style=StopLimitOrder(limit_price=price1, stop_price=price2, exchange)

Click here to view an example for exchange routing.

Returns

An order id.

cancel_order(order)

Attempts to cancel the specified order. Cancel is attempted asynchronously.

Parameters

order: Can be the order_id as a string or the order object.

Returns

None

get_open_orders(sid=sid)

If sid is None or not specified, returns all open orders. If sid is specified, returns open orders for that sid

Parameters

sid: (optional) A security object. Can be also be None.

Returns

If sid is unspecified or None, returns a dictionary keyed by security id. The dictionary contains a list of orders for each sid, oldest first. If a sid is specified, returns a list of open orders for that sid, oldest first.

get_order(order)

Returns the specified order. The order object is discarded at the end of handle_data.

Parameters

order: Can be the order_id as a string or the order object.

Returns

returns an order object that is read/writeable but is discarded at the end of handle_data.

get_fundamentals(query, filter_ordered_nulls)

Loads Morningstar fundamental data based on the query parameter.

Parameters

query: A query built using SQLAlchemy's syntax for accessing fundamental data. SQLAlchemy is an ORM. Below is a simple example of the SQLAlchemy syntax:

# Retrieve fundamental data based on the PE ratio and economic sector
# Then, filter results to a specific sector, based on PE value.
# Finally, return the first 4 results from a asending list
query(
    fundamentals.valuation_ratios.pe_ratio,
    fundamentals.asset_classification.morningstar_sector_code,

    .filter(fundamentals.asset_classification.morningstar_sector_code == 311)
    .filter(fundamentals.valuation_ratios.pe_ratio > 20)
    .filter(fundamentals.valuation_ratios.pe_ratio < 50)
    order_by(fundamentals.valuation_ratios.pe_ratio).limit(4)
)

Within Quantopian, specific SQLAlchemy methods are whitelisted for your use within the Quantopian IDE. These methods are filter, filter_by, first, offset, order_by, limit, exists, group_by, having.


Some SQLAlchemy column operators have been whitelisted use within the Quantopian IDE: asc, desc, nullsfirst, nullslast, label, in_, like, is_, contains (and corresponding negating operators)


filter_ordered_nulls: Optional. Defaults to True. When True, if you are sorting the query results via an `order_by` method, any row with a NULL value in the sorted column will be filtered out. Setting to False overrides this behavior and provides you with rows with a NULL value for the sorted column.

Returns

A Pandas dataframe for use in `context` and for updating your universe with `update_universe`.

Order Execution in IB

Within your algorithm, there are special execution styles for live trading:

Relative Order

order(security, amount, style=RelativeOrder(offset, pct_offset, limit_price, exchange))

RelativeOrder is an order type with Interactive Brokers (IB) that allows you to seek a more aggressive price than the National Best Bid and Offer in live trading. By placing more aggressive bids and offers than the current best bids and offers, you can increase your odds of filling the order. This can be placed as a fixed off-set amount (e.g. two cents higher on a buy, two cents lower on a sell) and/or as a percentage offset. You can combine a fixed amount-offset with a percentage offset. The quotes are automatically adjusted as the markets move, to remain aggressive.

To use this order type, you need to have a subscription to IB's market data for Relative Orders. Otherwise, IB will provide the free 15-minute delayed data for these values.

In a buy order, if the National Best Bid (NBB) increases before your order executes, your order automatically adjusts upwards to maintain its fixed offset from the NBB. If the NBB moves down, there will be no adjustment because your bid will become even more aggressive and execute.

For sell orders, your offer is pegged to the National Best Offer (NBO) by a more aggressive offset, and if the NBO moves down, your offer will also adjust downward. If the NBO moves up, there will be no change because your offer will become more aggressive and execute.

If you specify both a fixed off-set amount and percent offset, IB will use the more aggressive of the two possible prices (i.e. higher on a buy, lower on a sell). In this order type, you can also specify a limit price that guarantees your order will never adjust to a value greater than the specified price on a buy and will never adjust lower than the specified price on a sell.

When backtesting this order type, or forward-testing a Quantopian-backed paper trading algorithm, the order is modeled as a simple market order. It is only executed as a true RelativeOrder when run with an IB-backed paper or real-money algorithm. In the future, the backtester will be updated to include this order type. Also, you may choose to build a custom slippage model that models this type of order in backtesting.

Technical Details:

  • Only IB real money accounts can execute algorithms with RelativeOrder.
  • IB doesn't support RelativeOrder for paper accounts. The order will appear as 'Cancelled' in the live algorithm dashboard.
  • In backtesting and Quantopian paper trading, the order is modeled as a simple market order.
  • You need a subscription to IB's market data for Relative Orders, otherwise IB provides 15 minute delayed prices.

To use this order style, you will need to import RelativeOrder from the library brokers.ib.


Parameters

security: A security object.

amount: The integer amount of shares. Positive means buy, negative means sell.

offset: Floating point value measured in dollars. Specifies a fixed offset from the current NBB or NBO.

pct_offset: Floating point value between 0.0 and 100.0 inclusive. Specifies a fixed % offset from the NBB or NBO.

limit_price: Price specifying the maximum (on a buy) or minimum (on a sell) price at which order executes.

exchange: Not used. This order routes to SMART in IB Live Trading and is ignored in non-live trading. Routing to IEX is not allowed for this order type.

Example
from brokers.ib import RelativeOrder

def initialize(context):
  pass

def handle_data(context, data):
  # Order to buy at a price two cents higher than the National Best Bid.
  order(symbol('AAPL'), 100, style=RelativeOrder(offset=.02))
  
  # Order to sell at a price 0.5% lower than the National Best Bid
  order(symbol('BAC'), -100, style=RelativeOrder(pct_offset=0.5))
  # Order to buy at a price two cents higher than 0.5% higher than
  # the current National Best Bid.
  # But at a price no greater than $15.00 per share.
  order(symbol('JPM'), 100, style=RelativeOrder(limit_price=15, offset=0.02, pct_offset=0.5))

VWAPBestEffort

order(security, amount, style=VWAPBestEffort(limit_price=price1, start_date=date1,

end_date=date2, max_pct_vol=percent,

avoid_liquidity=False, exchange=Exchange))

VWAPBestEffort is an Interactive Brokers (IB) execution algorithm that breaks up your order into small portions and distributes them over a specified period in attempt to get you the Volume Weighted Average Price (VWAP). It seeks to achieve the VWAP calculated from the time you submit the order to the market close. This order type is exclusive to IB and only available for live trading. IB supports BestEfforts VWAP for market orders and limit orders. It cannot be applied to stop orders or stop-limit orders. This is compatible with any ordering function including order, order_target, order_value, and order_target_percent. Once your market or limit order is submitted to IB, the broker will attempt to fill it according to their BestEfforts algorithm.

Using this method, your order is partially filled throughout the timperiod and is completely filled by the end date. We strongly encourage you to check for open orders in your algorithm to drive your ordering logic.

You can specify a fixed window for your VWAP calculation or you can maintain a moving window throughout the day, as specified by the start_date and end_date parameters. To place a VWAP order you will need to import the VWAPBestEffort class from the brokers.ib module.

Parameters

security: A security object.

amount: The integer amount of shares. Positive means buy, negative means sell.

limit_price: (optional) Floating point value specifying the limit price for orders. If left None, orders are placed as market orders.

start_date: (optional) Start of period to obtain VWAP. The default is the current algorithm time + 1 minute. The BestEffort order will be submitted to IB in the next minute bar.

end_date: (optional) End of period to obtain VWAP. The default is at market close.

max_pct_volume: (optional) The maximum percentage allowed to trade of the daily volume. The available range is 0.01 – 0.5, and if left unspecified will default to 0.25.

avoid_liquidity: (optional) Check to ensure the order will not hit the bid or lift the offer if possible. This may help to avoid liquidity-taker fees, and could result in liquidity-adding rebates. However, it may also result in greater deviations from the benchmark. It is a boolean variable that defaults to False.

exchange: (optional) Exchange where to route orders. Defaults to SMART in IB Live Trading, and is ignored in non-live trading. Routing to IEX is not allowed for this order type.

Returns

An order id.

Example
from brokers.ib import VWAPBestEffort, IBExchange
from datetime import timedelta
import pandas as pd

def initialize(context):
  pass

def handle_data(context, data):
  now = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
  # Do nothing if there are open orders:
  if has_orders(context,data):
    print('has open orders - doing nothing!')
    return

  # For IB Paper and Live Trading, executes a VWAP Best Effort for in a moving 30 minute window
  # order using the specified parameters, with MKT as the underlying
  # order type.
  # Equivalent to order_target(symbol('AAPL'), 100) during zipline backtesting.
    order_target(symbol('AAPL'), 100, style=VWAPBestEffort(start_date=now + timedelta(minutes=1), end_date=now + timedelta(minutes=30),
                  max_pct_vol=.05,avoid_liquidity=False,exchange=IBExchange.SMART))
  
  # For IB Paper and Live Trading, executes a VWAP Best Effort during the entire day
  # order using the specified parameters, with LMT as the underlying
  # order type.
  # Throws a validation warning and has an effect equivalent to
  # order_target(symbol('MSFT'), 100, style=LimitOrder(10)) during zipline backtesting.
  order_target(symbol('MSFT'), 100, style=VWAPBestEffort(limit_price=10, start_date=now + timedelta(minutes=5), end_date=now + timedelta(minutes=90), 
                max_pct_vol=.05, avoid_liquidity=False, exchange=IBExchange.SMART))
  
def has_orders(context,data):
  # Return true if there are pending orders.
  has_orders = False
  for stock in data:
    orders = get_open_orders(stock)
    if orders:
      for oo in orders:
        message = 'Open order for {amount} shares in {stock}'  
        message = message.format(amount=oo.amount, stock=stock)
        log.info(message)
      has_orders = True
    return has_orders

Other Methods

Within your algorithm, there are some other methods you can use:

fetch_csv(url, pre_func=None, post_func=None, universe_func, date_column='date',
           date_format='%m/%d/%y', timezone='UTC', symbol=None, mask=True, **kwargs)

Loads the given CSV file (specified by url) to be used in a backtest.

Parameters

url: A well-formed http or https url pointing to a CSV file that has a header, a date column, and a symbol column (symbol column required to match data to securities).

pre_func: (optional) A function that takes a pandas dataframe parameter (the result of pandas.io.parsers.read_csv) and returns another pandas dataframe.

post_func: (optional) A function that takes a pandas dataframe parameter and returns a dataframe.

universe_func: (optional) A function that accepts two parameters (context and fetcher_data) and returns a set of Security objects

date_column: (optional) A string identifying the column in the CSV file's header row that holds the parseable dates. Data is only imported when the date is reached in the backtest to avoid look-ahead bias.

date_format: (optional) A string defining the format of the date/time information held in the date_column.

timezone: (optional) Either a pytz timezone object or a string conforming to the pytz timezone database.

symbol: (optional) If specified, the fetcher data will be treated as a signal source, and all of the data in each row will be added to the data parameter of the handle_data method. You can access all the CSV data from this source as data['symbol'].

mask: (optional) This is a boolean whose default is True. By default it will import information only for sids initialized in your algo. If set to False, it will import information for all securities in the CSV file.

**kwargs: (optional) Additional keyword arguments that are passed to the requests.get and pandas read_csv calls. Click here to see the valid arguments.

get_datetime(timezone)

Returns the current algorithm time. By default this is set to UTC, and you can pass an optional parameter to change the timezone.

Parameters

timezone: (Optional) Timezone string that specifies in which timezone to output the result. For example, to get the current algorithm time in US Eastern time, set this parameter to 'US/Eastern'.

Returns

Returns a Python datetime object with the current time in the algorithm. For daily data, the hours, minutes, and seconds are all 0. For minute data, it's the end of the minute bar.

get_environment(field='platform')

Returns information about the environment in which the backtest or live algorithm is running.

If no parameter is passed, the platform value is returned. Pass * to get all the values returned in a dictionary.

To use this method when running Zipline standalone, import get_environment from the zipline.api library.

Parameters

arena: Returns IB, live (paper trading), or backtest.

data_frequency: Returns minute or daily.

start: Returns the UTC datetime for start of backtest. In IB and live arenas, this is when the live algorithm was deployed.

end: Returns the UTC datetime for end of backtest. In IB and live arenas, this is the trading day's close datetime.

capital_base: Returns the float of the original capital in USD.

platform: Returns the platform running the algorithm: quantopian or zipline.


Below is an example showing the parameter syntax.

from zipline.api import get_environment

def handle_data(context, data):
  # execute this code when algorithm is running with an
  # Interactive Brokers account
  if get_environment('arena') == 'IB':
    pass

  # execute this code when paper trading in Quantopian
  if get_environment('arena') == 'live':
    pass  

  # execute this code if the backtest is in minute mode
  if get_environment('data_frequency') == 'minute':
    pass

  # retrieve the start date of the backtest or live algorithm
  print get_environment('start')  

  # code that will only execute in Zipline environment.
  # this is equivalent to get_environment() == 'zipline'
  if get_environment('platform') == 'zipline':
    pass

  # Code that will only execute in Quantopian IDE
  # this is equivalent to get_environment() == 'quantopian'
  if get_environment('platform') == 'quantopian': 
   pass

  # show the starting cash in the algorithm
  print get_environment('capital_base')

  # get all the current parameters of the environment    
  print get_environment('*')              

history(bar_count, frequency, field, ffill=True)

Function to get a trailing window of daily data. For more complete documentation, see the history overview section.

Parameters

bar_count: The int number of bars returned by the history call. This includes the current bar.

frequency: The size of the bars returned by history. Available frequencies are '1d' and '1m'.

field: The data field selected from history. Currently, only the OHLCV data is supported. Options are: 'open_price', 'close_price', 'price', 'high', 'low', 'volume'.

ffill: Whether or not to forward fill the history data. If ffill is False, the return frame will have np.nan for pricing data, and 0 for volume. The default is True.

Returns

Returns a pandas DataFrame which contains bar_count rows of data for the specified field. The columns of the DataFrame are the sids in the current universe.

log.error(message), log.info(message), log.warn(message), log.debug(message)

Logs a message with the desired log level. Log messages are displayed in the backtest output screen, and we only persist the last 512 of them.

Parameters

message: The message to log.

Returns

None

record(series1_name=value1, series2_name=value2, ...)

Records the given series and values. Generates a chart for all recorded series.

Parameters

values: Keyword arguments (up to 5) specifying series and their values.

Returns

None

record('series1_name', value1, 'series2_name', value2, ...)

Records the given series and values. Generates a chart for all recorded series.

Parameters

values: Variables or securities (up to 5) specifying series and their values.

Returns

None

schedule_function(func=myfunc, date_rule=date_rule, time_rule=time_rule, half_days=True)

Automatically run a function on a predetermined schedule. Can only be called from inside initialize.

Parameters

func: The name of the function to run. This function must accept context and data as parameters, which will be the same as those passed to handle_data.

date_rule: Specifies the date portion of the schedule. This can be every day, week, or month and has an offset parameter to indicate days from the first or last of the month. The default is daily, and the default offset is 0 days. In other words, if no date rule is specified, the function will run every day.

The valid values for date_rule are:

  • date_rules.every_day()
  • date_rules.week_start(days_offset=0)
  • date_rules.week_end(days_offset=0)
  • date_rules.month_start(days_offset=0)
  • date_rules.month_end(days_offset=0)

time_rule: Specifies the time portion of the schedule. This can be set as market_open or market_close and has an offset parameter to indicate how many hours or minutes from the market open or close. The default is market open, and 1 minute before close.

The valid values for date_rule are:

  • time_rules.market_open(hours=0, minutes=1)
  • time_rules.market_close(hours=0, minutes=1)

half_days: Boolean value specifying whether half-days should be included in the schedule. If false, the function will not be called on days with an early market close. The default is True. If your function execution lands on a half day and this value is false, your function will not get run during that week or month cycle.

Returns

None

set_symbol_lookup_date('YYYY-MM-DD')

Globally sets the date to use when performing a symbol lookup (either by using symbol or symbols). This helps disambiguate cases where a symbol historically referred to different securities. If you only want symbols that are active today, set this to a recent date (e.g. '2014-10-21'). Needs to be set in initialize before calling any symbol or symbols functions.

Parameters

date: The YYYY-MM-DD string format of a date.

Returns

None

sid(int)

Convenience method to look up a security by its id. Within the IDE, an inline search box appears showing you matches on security id, symbol, and name.

Parameters

int: The id of a security.

Returns

A security object.

symbol('symbol1')

Convenience method to look up a security by its symbol. Within the IDE, an inline search box appears showing you matches on security id, symbol, and name.

Parameters

'symbol1': The string symbol of a security.

Returns

A security object.

symbols('symbol1', 'symbol2', ...)

Convenience method to initialize several securities by their symbol.

Parameters

'symbol1', 'symbol2', ...: Several string symbols.

Returns

A list of security objects.

update_universe(sids)

Updates your universe of securities and is called within before_trading_start.

Parameters

sids: A list of securities typically generated by the get_fundamentals() method

None

A list of security objects.

Event properties and transforms

Quantopian's backend parses all the referenced securities in your algorithm and sends you the trading events for those securities. Each time any of the securities has a trading event (at most once per trading minute), handle_data is called and the data object contains all the market data for your securities.

For example, to access the market event data for AAPL, use data[symbol('AAPL')]. The following properties are supported:

datetime

DateTime: The UTC timestamp of the market event.

price

Float: The closing price of the security for the given bar.

open_price

Float: The opening price of the security for the given bar.

close_price

Float: The closing price of the security for the given bar. Identical to price.

high

Float: The highest price of the security within the given bar.

low

Float: The lowest price of the security within the given bar.

volume

Integer: The whole number of shares traded in the most recent market event for this security.

We also provide some transforms:

mavg(days)

Moving average price for the given security for the given number of trailing days.

returns()

The returns of this security since the end of the previous trading day.

stddev(days)

Standard deviation of the price of the given security for the given number of trailing days, calculated using Bessel's Correction.

vwap(days)

Volume-weighted average price for the given security for the given number of trailing days.

Order object

If you have a reference to an order object, there are several properties that might be useful:

created

Datetime: The date and time the order was created, in UTC timezone.

stop

Float: Optional stop price.

limit

Float: Optional limit price.

amount

Integer: Total shares ordered.

sid

Security object: The security being ordered.

filled

Integer: Total shares bought or sold for this order so far.

stop_reached

Boolean: Variable if stop price has been reached.

limit_reached

Boolean: Variable if limit price has been reached.

Commission

Integer: Commission for transaction.

Portfolio object

The portfolio object is accessed using context.portfolio and has the following properties:

capital_used

Float: The net capital consumed (positive means spent) by buying and selling securities up to this point.

cash

Float: The current amount of cash in your portfolio.

pnl

Float: Dollar value profit and loss, for both realized and unrealized gains.

positions

Dictionary: A dictionary of all the open positions, keyed by security ID. More information about each position object can be found in the next section.

portfolio_value

Float: Sum value of all open positions and ending cash balance.

positions_value

Float: Sum value of all open positions.

returns

Float: Cumulative percentage returns for the entire portfolio up to this point. Calculated as a fraction of the starting value of the portfolio. The returns calculation includes cash and portfolio value. The number is not formatted as a percentage, so a 10% return is formatted as 0.1.

starting_cash

Float: Initial capital base for this backtest or live execution.

start_date

DateTime: UTC datetime of the beginning of this backtest's period. For live trading, this marks the UTC datetime that this algorithm started executing.

Position object

The position object represents a current open position, and is contained inside the positions dictionary. For example, if you had an open AAPL position, you'd access it using context.portfolio.positions[symbol('AAPL')]. The position object has the following properties:

amount

Integer: Whole number of shares in this position.

cost_basis

Float: The volume-weighted average price paid (price and commission) per share in this position.

last_sale_price

Float: Price at last sale of this security. This is identical to close_price and price.

sid

Integer: The ID of the security.

Security object

If you have a reference to a security object, there are several properties that might be useful:

sid

Integer: The id of this security.

symbol

String: The ticker symbol of this security.

security_name

String: The full name of this security.

security_start_date

Datetime: The date when this security first started trading.

security_end_date

Datetime: The date when this security stopped trading (= today for securities that are trading normally).

Account object

Below is a table of account fields available to reference in a live trading algorithm with Interactive Brokers. In backtesting, the field will take a default value from the simulated portfolio.

TA-Lib methods

TA-Lib is an open-source library to process financial data. The methods are available to use in the Quantopian API and you can see here for the full list of available functions.

When using TA-Lib methods, import the library at the beginning of your algorithm.

Help-talib

You can look at the examples of commonly-used TA-Lib functions and at our sample TA-Lib algorithm.

Daily vs minute data

The talib library respects the data frequency in your backtest or live algorithm. If you're passing daily data into the backtest, then all the time periods are calculated in days. If you're backtesting or live trading with minute data, all the time periods are in minutes.

Moving average types

Some of the TA-Lib methods have an integer matype parameter. Here's the list of moving average types:
0: SMA (simple)
1: EMA (exponential)
2: WMA (weighted)
3: DEMA (double exponential)
4: TEMA (triple exponential)
5: TRIMA (triangular) 
6: KAMA (Kaufman adaptive)
7: MAMA (Mesa adaptive)
8: T3 (triple exponential T3)

Examples of Commonly Used Talib Functions


ATR

This algorithm uses ATR as a momentum strategy to identify price breakouts.

Clone Algorithm
# This strategy was taken from http://www.investopedia.com/articles/trading/08/atr.asp

# The idea is to use ATR to identify breakouts, if the price goes higher than
# the previous close + ATR, a price breakout has occurred. The position is closed when
# the price goes 1 ATR below the previous close. 

# This algorithm uses ATR as a momentum strategy, but the same signal can be used for 
# a reversion strategy, since ATR doesn't indicate the price direction.

# Because this algorithm uses the history function, it will only run in minute mode. 
# entry and exit points for trading the SPY.


import talib
import numpy as np
import pandas as pd

# Setup our variables
def initialize(context):
    context.stock = symbol('SPY')
    
    # Create a variable to track the date change
    context.date = None
    
    # Algorithm will only take long positions.
    # It will stop if encounters a short position. 
    set_long_only()

   
def handle_data(context, data):
    
    # We will constrain the trading to once per day at market open in this example.
    todays_date = get_datetime().date()
    
    # Do nothing unless the date has changed and it's a new day.
    if todays_date == context.date:
        return
    
    # Set the new date
    context.date = todays_date
    
    # Track our position
    current_position = context.portfolio.positions[context.stock].amount
    record(position_size=current_position)
    
    
    # Load historical data for the stocks
    high =  history(30, '1d', 'high')
    low =   history(30, '1d', 'low')
    close = history(30, '1d', 'close_price')
    
    # Calculate the ATR for the stock
    atr = talib.ATR(high[context.stock],
                    low[context.stock],
                    close[context.stock],
                    timeperiod=14)[-1]
    
    price=data[context.stock].price
    
    # Use the close price from 2 days ago because we trade at market open
    prev_close = close.iloc[-3][context.stock]
    
    # An upside breakout occurs when the price goes 1 ATR above the previous close
    upside_signal = price - (prev_close + atr)
    
    # A downside breakout occurs when the previous close is 1 ATR above the price
    downside_signal = prev_close - (price + atr)
    
    # Enter position when an upside breakout occurs. Invest our entire portfolio to go long.
    if upside_signal > 0 and current_position <= 0:
        order_target_percent(context.stock, 1.0)
    
    # Exit position if a downside breakout occurs
    elif downside_signal > 0 and current_position >= 0:
        order_target_percent(context.stock, 0.0)
        
    
    record(upside_signal=upside_signal,
           downside_signal=downside_signal,
           ATR=atr)
    
Bollinger Bands

This example uses the talib Bollinger Bands function to determine entry points for long and short positions. When the the price breaks out of the upper Bollinger band, a short position is opened. A long position is created when the price dips below the lower band.

Clone Algorithm
# This algorithm uses the talib Bollinger Bands function to determine entry entry 
# points for long and short positions.

# When the price breaks out of the upper Bollinger band, a short position
# is opened. A long position is opened when the price dips below the lower band.

# Because this algorithm uses the history function, it will only run in minute mode. 
# We will constrain the trading to once per day at market open in this example.

import talib
import numpy as np
import pandas as pd

# Setup our variables
def initialize(context):
    context.stock = symbol('SPY')
    
    # Create a variable to track the date change
    context.date = None

def handle_data(context, data):
    todays_date = get_datetime().date()
    
    # Do nothing unless the date has changed
    if todays_date == context.date:
        return
    # Set the new date
    context.date = todays_date

    current_position = context.portfolio.positions[context.stock].amount
    price=data[context.stock].price
    
    # Load historical data for the stocks
    prices = history(15, '1d', 'price')
    
    upper, middle, lower = talib.BBANDS(
        prices[context.stock], 
        timeperiod=10,
        # number of non-biased standard deviations from the mean
        nbdevup=2,
        nbdevdn=2,
        # Moving average type: simple moving average here
        matype=0)
    
    # If price is below the recent lower band and we have
    # no long positions then invest the entire
    # portfolio value into SPY
    if price <= lower[-1] and current_position <= 0:
        order_target_percent(context.stock, 1.0)
    
    # If price is above the recent upper band and we have
    # no short positions then invest the entire
    # portfolio value to short SPY
    elif price >= upper[-1] and current_position >= 0:
        order_target_percent(context.stock, -1.0)
        
    record(upper=upper[-1],
           lower=lower[-1],
           mean=middle[-1],
           price=price,
           position_size=current_position)
   
    

MACD

In this example, when the MACD signal less than 0, the stock price is trending down and it's time to sell the security. When the MACD signal greater than 0, the stock price is trending up it's time to buy.

Clone Algorithm
# This example algorithm uses the Moving Average Crossover Divergence (MACD) indicator as a buy/sell signal.

# When the MACD signal less than 0, the stock price is trending down and it's time to sell.
# When the MACD signal greater than 0, the stock price is trending up it's time to buy.

# Because this algorithm uses the history function, it will only run in minute mode. 
# We will constrain the trading to once per day at market open in this example.

import talib
import numpy as np
import pandas as pd

# Setup our variables
def initialize(context):
    context.stocks = symbols('MMM', 'SPY', 'GOOG_L', 'PG', 'DIA')
    context.pct_per_stock = 1.0 / len(context.stocks)
    
    # Create a variable to track the date change
    context.date = None

def handle_data(context, data):
    todays_date = get_datetime().date()
    
    # Do nothing unless the date has changed and its a new day.
    if todays_date == context.date:
        return
    
    # Set the new date
    context.date = todays_date
    
    # Load historical data for the stocks
    prices = history(40, '1d', 'price')
    
    # Create the MACD signal and pass in the three parameters: fast period, slow period, and the signal.
    # This is a series that is indexed by sids.
    macd = prices.apply(MACD, fastperiod=12, slowperiod=26, signalperiod=9)
    
    # Iterate over the list of stocks
    for stock in context.stocks:
        current_position = context.portfolio.positions[stock].amount
        
        # Close position for the stock when the MACD signal is negative and we own shares.
        if macd[stock] < 0 and current_position > 0:
            order_target(stock, 0)
            
        # Enter the position for the stock when the MACD signal is positive and 
        # our portfolio shares are 0.
        elif macd[stock] > 0 and current_position == 0:
            order_target_percent(stock, context.pct_per_stock)
           
        
    record(goog=macd[symbol('GOOG_L')],
           spy=macd[symbol('SPY')],
           mmm=macd[symbol('MMM')])
   

# Define the MACD function   
def MACD(prices, fastperiod=12, slowperiod=26, signalperiod=9):
    '''
    Function to return the difference between the most recent 
    MACD value and MACD signal. Positive values are long
    position entry signals 

    optional args:
        fastperiod = 12
        slowperiod = 26
        signalperiod = 9

    Returns: macd - signal
    '''
    macd, signal, hist = talib.MACD(prices, 
                                    fastperiod=fastperiod, 
                                    slowperiod=slowperiod, 
                                    signalperiod=signalperiod)
    return macd[-1] - signal[-1]



RSI

Use the RSI signal to drive algorithm buying and selling decisions. When the RSI is over 70, a stock can be seen as overbought and it's time to sell. On the other hand, when the RSI is below 30, a stock can be seen as underbought and it's time to buy.

Clone Algorithm
# This example algorithm uses the Relative Strength Index indicator as a buy/sell signal.
# When the RSI is over 70, a stock can be seen as overbought and it's time to sell.
# When the RSI is below 30, a stock can be seen as oversold and it's time to buy.

# Because this algorithm uses the history function, it will only run in minute mode. 
# We will constrain the trading to once per day at market open in this example.

import talib


# Setup our variables
def initialize(context):
    context.stocks = symbols('MMM', 'SPY', 'GE')
    context.max_cash_per_stock = 100000.0 / len(context.stocks)
    context.LOW_RSI = 30
    context.HIGH_RSI = 70
    
    # Create a variable to track the date change
    context.date = None

def handle_data(context, data):
    todays_date = get_datetime().date()
    
    # Do nothing unless the date has changed
    if todays_date == context.date:
        return
    # Set the new date
    context.date = todays_date

    cash = context.portfolio.cash
    
    # Load historical data for the stocks
    prices = history(15, '1d', 'price')
    
    # Use pandas dataframe.apply to get the last RSI value
    # for for each stock in our basket
    rsi = prices.apply(talib.RSI, timeperiod=14).iloc[-1]
    
    # Loop through our list of stocks
    for stock in context.stocks:
        current_position = context.portfolio.positions[stock].amount
        
        # RSI is above 70 and we own shares, time to sell
        if rsi[stock] > context.HIGH_RSI and current_position > 0:
            order_target(stock, 0)
            log.info('{0}: RSI is at {1}, selling {2} shares'.format(
                stock.symbol, rsi[stock], current_position
            ))
   
        # RSI is below 30 and we don't have any shares, time to buy
        elif rsi[stock] < context.LOW_RSI and current_position == 0:
            # Use floor division to get a whole number of shares
            target_shares = cash // data[stock].price
            order_target(stock, target_shares)
            log.info('{0}: RSI is at {1}, buying {2} shares.'.format(
                stock.symbol, rsi[stock], target_shares
            ))

    # record the current RSI values of each stock
    record(ge_rsi=rsi[symbol('GE')],
           spy_rsi=rsi[symbol('SPY')],
           mmm_rsi=rsi[symbol('MMM')])
           



STOCH

Below is an algorithm that uses the talib STOCH function. When the stochastic oscillator dips below 10, the stock is determined to be oversold and a long position is opened. The position is exited when the indicator rises above 90 because the stock is considered to be overbought.

Clone Algorithm
# This algorithm uses talib's STOCH function to determine entry and exit points.

# When the stochastic oscillator dips below 10, the stock is determined to be oversold
# and a long position is opened. The position is exited when the indicator rises above 90
# because the stock is thought to be overbought.

# Because this algorithm uses the history function, it will only run in minute mode. 
# We will constrain the trading to once per day at market open in this example.

import talib
import numpy as np
import pandas as pd

# Setup our variables
def initialize(context):
    context.stocks = symbols('SPY', 'AAPL', 'GLD', 'AMZN')
    
    # Set the percent of the account to be invested per stock
    context.long_pct_per_stock = 1.0 / len(context.stocks)
    
    # Create a variable to track the date change
    context.date = None

def handle_data(context, data):
    todays_date = get_datetime().date()
    
    # Do nothing unless the date has changed
    if todays_date == context.date:
        return
    # Set the new date
    context.date = todays_date
    
    # Load historical data for the stocks
    high = history(30, '1d', 'high')
    low = history(30, '1d', 'low')
    close = history(30, '1d', 'close_price')
    
    # Iterate over our list of stocks
    for stock in context.stocks:
        current_position = context.portfolio.positions[stock].amount
        slowk, slowd = talib.STOCH(high[stock],
                                   low[stock],
                                   close[stock],
                                   fastk_period=5,
                                   slowk_period=3,
                                   slowk_matype=0,
                                   slowd_period=3,
                                   slowd_matype=0)

        # get the most recent value
        slowk = slowk[-1]
        slowd = slowd[-1]
        
        # If either the slowk or slowd are less than 10, the stock is 
        # 'oversold,' a long position is opened if there are no shares
        # in the portfolio.
        if slowk < 10 or slowd < 10 and current_position <= 0:
            order_target_percent(stock, context.long_pct_per_stock)
        
        # If either the slowk or slowd are larger than 90, the stock is 
        # 'overbought' and the position is closed. 
        elif slowk > 90 or slowd > 90 and current_position >= 0:
            order_target(stock, 0)

Common Error Messages

Below are examples and sample solutions to commonly-encountered errors while coding your strategy. If you need assistance, contact us and we can help you debug the algorithm.

ExecutionTimeout

Cause:
Request took too long to execute. Possible messages include HandleDataTimeoutException, InitializeTimoutException, TimeoutException, ExecutionTimeout.
Solution:
  • Optimize your code to use faster functions. For example, use history() instead of batch_transform().
  • Use a smaller universe of stocks. Every security you include in the algorithm increases memory and CPU requirements of the algorithm.
  • Backtest over a small time period.

KeyError

Causes:
  • Incorrect use of a dictionary. Mapping key is not found in the set of existing keys.
  • Security price data is missing for the given bar, since every stock doesn't trade each minute. This often happens in the case of illiquid securities.
  • Fetcher data is missing for the given bar, since Fetcher will populate your data object only when there is data for the given date.
Solutions:
  • For missing keys in a dictionary, check that the key exists within the dictionary object. For example:
  • if 'blank' in dict_obj:
        value = dict_obj['blank']
  • For illiquid securities, check for the existence of the security before accessing it's pricing data. For example:
  • # loop through the list of securities in the algorithm
    for security in data:
        # check there is trade data for the security
        if security in data and 'price' in data[security]:
            data[security].price
  • For missing Fetcher data, check for the existence of the target field before accessing. For example:
  • if 'Close' in data['vix']:
        log.info(data['vix'])

LinAlgError

Cause:
Illegal matrix operation. For example, trying to take the determinant of a singular matrix.
Solution:
Update matrix values to perform desired operation.

MemoryError

Cause:
Backtest ran out of memory on server.
Solution:
  • Avoid creating too many objects or unusually large objects in your algorithm. An object can be a variable, function, or data structure.
  • Use a smaller universe of stocks. Every security you include in the algorithm increases memory and CPU requirements of the algorithm.
  • Avoid accumulating an increasing amount of data in each bar of the backtest.
  • Backtest over a shorter time period.

Something went wrong on our end. Sorry for the inconvenience.

Cause:
Unknown error.
Solution:
Please contact feedback so we can help debug your code.

SyntaxError

Cause:
Invald Python or Quantopian syntax. May be due to missing colon, bracket, parenthesis, or quote.
Solution:
Insert or remove extra notation in function.

TypeError

Cause:
Raised when encountering an object that is not of the expected type.
Solution:
Insert or remove extra notation in function.

ValueError

Cause:
Raised when a function receives an argument that has the right type but an inappropriate value. For example, if you try to take the square root of a negative number.

Solution:
Perform legal operations on expected values.

IDE Tips and Shortcuts

You can customize the color and text size of the IDE for your algorithms. Simply click on the gear button in the top right corner and choose your settings.

Help-settings

Below are keyboard shortcuts you can use in the IDE.

Action Windows Keystroke Apple Keystroke
Build Algorithm Ctrl + B Cmd + B
Indent Ctrl + ] Cmd + ]
Outdent Ctrl + [ Cmd + [
Create/Remove Comment Ctrl + / Cmd + /
Search Code Ctrl + F Cmd + F
Undo Ctrl + Z Cmd + Z
Redo Ctrl + Y Cmd + Y

Need more space to see your code? Drag the bar all the way to the right to expand the code window.

Help-expandwindow

If you are building a custom graph you can record up to five variables. To view only certain variables, click on the variable name to remove it from the chart. Click it again to add it back to the graph. Want to zoom in on a certain timeperiod? Use the bar underneath the graph to select a specific time window.

Below is an example of a custom graph with all the variables selected. The following graph displays only the recorded cash value.

Help-customgraph Help-customgraph-cash

Sample Algorithms

Our first example is very basic. If you want to get started quickly, you can just copy this code into your algorithm and run a backtest. Other examples get into more advanced concepts.

Basic Algorithm

This example demonstrates all of the basic concepts you need to write a simple algorithm that tries to capitalize on a stock's momentum. This shows you the symbol function, logging, volume-weighted average price, price, and placing orders. You clone this algorithm below or from the community discussion of this post.

Clone Algorithm
# For this example, we're going to write a simple momentum script.  
# When the stock goes up quickly, we're going to buy; 
# when it goes down we're going to sell.  
# Hopefully we'll ride the waves.

# To run an algorithm in Quantopian, you need two functions: 
# initialize and handle_data.
def initialize(context):
    # The initialize function sets any data or variables that 
    # you'll use in your algorithm. 
    # For instance, you'll want to define the security 
    # (or securities) you want to backtest.  
    # You'll also want to define any parameters or values 
    # you're going to use later. 
    # It's only called once at the beginning of your algorithm.
    
    # In our example, we're looking at Apple.  
    # If you re-type this line you'll see 
    # the auto-complete that is available for security. 
    context.security = symbol('AAPL')

# The handle_data function is where the real work is done.  
# This function is run either every minute 
# (in live trading and minute backtesting mode) 
# or every day (in daily backtesting mode).
def handle_data(context, data):
    # We've built a handful of useful data transforms for you to use,
    # such as moving average. 
    
    # To make market decisions, we're calculating the stock's 
    # moving average for the last 5 days and its current price. 
    average_price = data[context.security].mavg(5)
    current_price = data[context.security].price
    
    # Another powerful built-in feature of the Quantopian backtester is the
    # portfolio object.  The portfolio object tracks your positions, cash,
    # cost basis of specific holdings, and more.  In this line, we calculate
    # the current amount of cash in our portfolio.   
    cash = context.portfolio.cash
    
    # Here is the meat of our algorithm.
    # If the current price is 1% above the 5-day average price 
    # AND we have enough cash, then we will order.
    # If the current price is below the average price, 
    # then we want to close our position to 0 shares.
    if current_price > 1.01*average_price and cash > current_price:
        
        # Need to calculate how many shares we can buy
        number_of_shares = int(cash/current_price)
        
        # Place the buy order (positive means buy, negative means sell)
        order(context.security, +number_of_shares)
        log.info("Buying %s" % (context.security.symbol))
        

    elif current_price < average_price:
        
        # Sell all of our shares by setting the target position to zero
        order_target(context.security, 0)
        log.info("Selling %s" % (context.security.symbol))
    
    # You can use the record() method to track any custom signal. 
    # The record graph tracks up to five different variables. 
    # Here we record the Apple stock price.
    record(stock_price=data[context.security].price)

Live Trading Algorithm

This example is a live trading algorithm that you can connect to Interactive Brokers. The strategy automatically invests in equal positions across 9 industry sectors. It rebalances every 21 days at 10:15AM to maintain its target weights. Clone the algorithm to get your own copy where you can change the securitites and rebalancing window.

Clone Algorithm
'''
    This algorithm defines a long-only equal weight portfolio and 
    rebalances it at a user-specified frequency.
    NOTE: This algo is intended to run in minute-mode simulation and is compatible with LIVE TRADING.

'''

# Import the libraries we will use here
import datetime
import pytz
import pandas as pd

def initialize(context):
    # This initialize function sets any data or variables 
    # that you'll use in your algorithm. 
    # You'll also want to define any parameters or values 
    # you're going to use.

    # In our example, we're looking at 9 sector ETFs.  
    context.secs = symbols('XLY',  # XLY Consumer Discrectionary SPDR Fund   
                           'XLF',  # XLF Financial SPDR Fund  
                           'XLK',  # XLK Technology SPDR Fund  
                           'XLE',  # XLE Energy SPDR Fund  
                           'XLV',  # XLV Health Care SPRD Fund  
                           'XLI',  # XLI Industrial SPDR Fund  
                           'XLP',  # XLP Consumer Staples SPDR Fund   
                           'XLB',  # XLB Materials SPDR Fund  
                           'XLU')  # XLU Utilities SPRD Fund

    # Change this variable if you want to rebalance less frequently
    context.Rebalance_Days = 21

    # These other variables are used in the algorithm for leverage, trade time, etc.
    # Rebalance at 10:15AM EST
    context.rebalance_date = None
    context.weights = 0.99/len(context.secs)
    context.rebalance_hour_start = 10
    context.rebalance_hour_end = 15

    # These are the default commission and slippage settings.  Change them to fit your
    # brokerage fees. These settings only matter for backtesting.  When you trade this 
    # algorithm, they are moot - the brokerage and real market takes over.
    set_commission(commission.PerTrade(cost=0.03))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):

    # Get the current exchange time, in the exchange timezone 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

    # If it's a rebalance day (defined in intialize()) then rebalance:
    if  context.rebalance_date == None or exchange_time > context.rebalance_date + datetime.timedelta(days=context.Rebalance_Days):

        # Do nothing if there are open orders:
        if has_orders(context):
            print('has open orders - doing nothing!')
            return

        rebalance(context, data, exchange_time)  

def rebalance(context, data, exchange_time):  
    # Only rebalance if we are in the user specified rebalance time-of-day window
    if exchange_time.hour < context.rebalance_hour_start or exchange_time.hour > context.rebalance_hour_end:
       return

    # Do the rebalance. Loop through each of the stocks and order to the target
    # percentage.  If already at the target, this command doesn't do anything.
    # A future improvement could be to set rebalance thresholds.
    for sec in context.secs:
        order_target_percent(sec, context.weights, limit_price=None, stop_price=None)

    context.rebalance_date = exchange_time
    log.info("Rebalanced to target portfolio weights at %s" % str(exchange_time))

def has_orders(context):
    # Return true if there are pending orders.
    has_orders = False
    for sec in context.secs:
        orders = get_open_orders(sec)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=sec)  
                log.info(message)

            has_orders = True
    return has_orders

Fundamental Data Algorithm

This example demonstrates how to use fundamental data in your algorithm. It constructs a SQLAlchemy query to pull securities based on their PE ratio and economic sector. The results are then filtered and sorted. The algorithm seeks equal-weights in its positions and rebalances once at the beginning of each month.

Clone Algorithm
"""
    Trading Strategy using Fundamental Data
    
    1. Filter the top 50 companies by market cap 
    2. Find the top two sectors that have the highest average PE ratio
    3. Every month exit all the positions before entering new ones at the month
    4. Log the positions that we need 
"""

import pandas as pd
import numpy as np

def initialize(context):
    # Dictionary of stocks and their respective weights
    context.stock_weights = {}
    # Count of days before rebalancing
    context.days = 0
    # Number of sectors to go long in
    context.sect_numb = 2
    
    # Sector mappings
    context.sector_mappings = {
       101.0: "Basic Materials",
       102.0: "Consumer Cyclical",
       103.0: "Financial Services",
       104.0: "Real Estate",
       205.0: "Consumer Defensive",
       206.0: "Healthcare",
       207.0: "Utilites",
       308.0: "Communication Services",
       309.0: "Energy",
       310.0: "Industrials",
       311.0: "Technology"
    }
    
    # Rebalance monthly on the first day of the month at market open
    schedule_function(rebalance,
                      date_rule=date_rules.month_start(),
                      time_rule=time_rules.market_open())
    
def rebalance(context, data):
    # Exit all positions before starting new ones
    for stock in context.portfolio.positions:
        if stock not in context.fundamental_df:
            order_target_percent(stock, 0)

    log.info("The two sectors we are ordering today are %r" % context.sectors)

    # Create weights for each stock
    weight = create_weights(context, context.stocks)

    # Rebalance all stocks to target weights
    for stock in context.fundamental_df:
        if weight != 0:
            log.info("Ordering %0.0f%% percent of %s in %s" 
                     % (weight * 100, 
                        stock.symbol, 
                        context.sector_mappings[context.fundamental_df[stock]['morningstar_sector_code']]))
            
        order_target_percent(stock, weight)
    
def before_trading_start(context): 
    """
      Called before the start of each trading day. 
      It updates our universe with the
      securities and values found from get_fundamentals.
    """
    
    num_stocks = 50
    
    # Setup SQLAlchemy query to screen stocks based on PE ratio
    # and industry sector. Then filter results based on 
    # market cap and shares outstanding.
    # We limit the number of results to num_stocks and return the data
    # in descending order.
    fundamental_df = get_fundamentals(
        query(
            # put your query in here by typing "fundamentals."
            fundamentals.valuation_ratios.pe_ratio,
            fundamentals.asset_classification.morningstar_sector_code
        )
        .filter(fundamentals.valuation.market_cap != None)
        .filter(fundamentals.valuation.shares_outstanding != None)
        .order_by(fundamentals.valuation.market_cap.desc())
        .limit(num_stocks)
    )

    # Find sectors with the highest average PE
    sector_pe_dict = {}
    for stock in fundamental_df:
        sector = fundamental_df[stock]['morningstar_sector_code']
        pe = fundamental_df[stock]['pe_ratio']
        
        # If it exists add our pe to the existing list. 
        # Otherwise don't add it.
        if sector in sector_pe_dict:
            sector_pe_dict[sector].append(pe)
        else:
            sector_pe_dict[sector] = []
    
    # Find average PE per sector
    sector_pe_dict = dict([(sectors, np.average(sector_pe_dict[sectors])) 
                               for sectors in sector_pe_dict if len(sector_pe_dict[sectors]) > 0])
    
    # Sort in ascending order
    sectors = sorted(sector_pe_dict, key=lambda x: sector_pe_dict[x], reverse=True)[:context.sect_numb]
    
    # Filter out only stocks with that particular sector
    context.stocks = [stock for stock in fundamental_df
                      if fundamental_df[stock]['morningstar_sector_code'] in sectors]
    
    # Initialize a context.sectors variable
    context.sectors = [context.sector_mappings[sect] for sect in sectors]

    # Update context.fundamental_df with the securities (and pe_ratio) that we need
    context.fundamental_df = fundamental_df[context.stocks]
    
    
    update_universe(context.fundamental_df.columns.values)   
    
    
def create_weights(context, stocks):
    """
        Takes in a list of securities and weights them all equally 
    """
    if len(stocks) == 0:
        return 0 
    else:
        weight = 1.0/len(stocks)
        return weight
        
def handle_data(context, data):
    """
      Code logic to run during the trading day.
      handle_data() gets called every bar.
    """

    # track how many positions we're holding
    record(num_positions = len(context.portfolio.positions))
        

Multiple Security Example

This example shows how to initialize and trade with multiple securities. It imports the datetime and pytz libraries and uses the log function to understand the behavior. You can clone this algorithm from the community discussion of this example.

Clone Algorithm
# This example runs the same momentum play as the first sample 
# (https://www.quantopian.com/help#sample-basic), but this time it uses more
# securities during the backtest.
    
# Important note: All securities in an algorithm must be traded for the 
# entire length of the backtest.  For instance, if you try to backtest both
# Google and Facebook against 2011 data you will get an error; Facebook
# wasn't traded until 2012.

# First step is importing any needed libraries.

import datetime
import pytz

def initialize(context):
    # Here we initialize each stock.
    # By calling symbols('AAPL', 'IBM', 'CSCO') we're storing the Security objects.
    context.stocks = symbols('AAPL', 'IBM', 'CSCO')
    context.vwap = {}
    context.price = {}
 
    # Setting our maximum position size, like previous example
    context.max_notional = 1000000.1
    context.min_notional = -1000000.0

    # Initializing the time variables we use for logging
    # Convert timezone to US EST to avoid confusion
    est = pytz.timezone('EST')
    context.d=datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=est)
   

def handle_data(context, data):
    # Initializing the position as zero at the start of each frame
    notional=0
    
    # This runs through each stock.  It computes
    # our position at the start of each frame.
    for stock in context.stocks:
        price = data[stock].price 
        notional = notional + context.portfolio.positions[stock].amount * price
        tradeday = data[stock].datetime
        
    # This runs through each stock again.  It finds the price and calculates
    # the volume-weighted average price.  If the price is moving quickly, and
    # we have not exceeded our position limits, it executes the order and
    # updates our position.
    for stock in context.stocks:   
        vwap = data[stock].vwap(3)
        price = data[stock].price  

        if price < vwap * 0.995 and notional > context.min_notional:
            order(stock,-100)
            notional = notional - price*100
        elif price > vwap * 1.005 and notional < context.max_notional:
            order(stock,+100)
            notional = notional + price*100

    # If this is the first trade of the day, it logs the notional.
    if (context.d + datetime.timedelta(days=1)) < tradeday:
        log.debug(str(notional) + ' - notional start ' + tradeday.strftime('%m/%d/%y'))
        context.d = tradeday


Set Universe Example

This example shows how to use the set_universe command. Here the stocks are chosen by selecting a segment of the dollar volume universe.

Clone Algorithm
# This is the standard Quantopian function that initializes your data and 
# variables. In it, we define how large of a 'bet' we're making (in dollars) and what 
# stock we're working with.
def initialize(context):
    # we want to try this on a range of highly liquid stocks
    set_universe(universe.DollarVolumeUniverse(98, 99))
    context.bet_amount = 1000
    context.count = 20

# This is the standard Quantopian event handling function. This function is 
# run once for each bar of data.  In this example, it the min and max 
# prices for the trailing window.  If the price exceeds the recent high, it 
# goes short; if the price dips below the recent low, it goes long. The algo
# is a contrarian/mean reversion bet.
def handle_data(context, data):
    # Until our batch transform's datapanel is full, it will return None.  Once
    # the datapanel is full, then we have a max and min to work with.
    
    prices = history(bar_count=10, frequency='1d', field='price')
    
    ranking = sort_returns(prices)

    if ranking is not None:
        column_name = ranking.columns[0]  
        # bottom quantile to go long
        bottom = ranking[-1*context.count:] 
        longs = bottom[bottom[column_name] < 0]
        
        # top quantile to go short
        top = ranking[:context.count]
        shorts = top[top[column_name] > 0]
        
        for stock in data.keys():
            if stock in longs.index:
                amount = calculate_order_amount(context, stock, 1, data[stock].price)
            elif stock in shorts.index:
                amount = calculate_order_amount(context, stock, -1, data[stock].price)
            else:
                amount = calculate_order_amount(context, stock, 0, data[stock].price)
                
            order(stock, amount)    
   
# This method is purely for order managment. It calculates and returns an 
# order amount to place binary bets.
# If signal_val is -1, get to a short position of -1 * context.bet_size
# If signal_val is 1, get to a long position of context.bet_size
def calculate_order_amount(context, stock, signal_val, cur_price):
    current_amount = context.portfolio.positions[stock].amount
    abs_order_amount = int(context.bet_amount / cur_price) 
    
    if signal_val == -1:
        return (-1 * abs_order_amount) - current_amount
    elif signal_val == 1:
        return abs_order_amount - current_amount
    elif signal_val == 0:
        return -1 * current_amount
    else:
        return 0        

def sort_returns(prices):
    shifted_prices = prices.shift(9)
    returns = (prices - shifted_prices) / shifted_prices
    # use a slice operator to get the most recent returns
    last_returns = returns[-1:]
    last_date = last_returns.index[-1]
    sorted_returns = last_returns.T.sort(columns=last_date, ascending=0)
    return sorted_returns    

Record Variables Example

This example compares Google's 20-day moving average with its 80-day moving average and trades when the two averages cross. It records both moving averages as well as Google's share price.

Clone Algorithm
# This initialize function sets any data or variables that you'll use in
# your algorithm.
def initialize(context):
    context.stock = symbol('BA')  # Boeing

    
# Now we get into the meat of the algorithm. 
def handle_data(context, data):
    # Create a variable for the price of the Google stock
    context.price = data[context.stock].price
    
    # Create variables to track the short and long moving averages. 
    # The short moving average tracks over 20 days and the long moving average
    # tracks over 80 days. 
    short = data[context.stock].mavg(20)
    long = data[context.stock].mavg(80)

    # If the short moving average is higher than the long moving average, then 
    # we want our portfolio to hold 500 stocks of Google
    if (short > long):
        order_target(context.stock, +500)
    
    # If the short moving average is lower than the long moving average, then
    # then we want to sell all of our Google stocks and own 0 shares
    # in the portfolio. 
    elif (short < long):
        order_target_value(context.stock, 0)

    # Record our variables to see the algo behavior. You can record up to 
    # 5 custom variables. To see only a certain variable, deselect the 
    # variable name in the custom graph in the backtest. 
    record(short_mavg = short,
        long_mavg = long,
        goog_price = context.price)

Fetcher Example

This example loads and formats two price series from Quandl. It displays the price movements and decides to buy and sell Tiffany stock based on the price of gold.

Clone Algorithm
import pandas

def rename_col(df):
    df = df.rename(columns={'New York 15:00': 'price'})
    df = df.rename(columns={'Value': 'price'})
    df = df.fillna(method='ffill')
    df = df[['price', 'sid']]
    # Correct look-ahead bias in mapping data to times   
    df = df.tshift(1, freq='b')
    log.info(' \n %s ' % df.head())
    return df
    
def initialize(context):
    # import the external data
    fetch_csv('http://www.quandl.com/api/v1/datasets/JOHNMATT/PALL.csv?trim_start=2012-01-01',
        date_column='Date',
        symbol='palladium',
        post_func=rename_col,
        date_format='%Y-%m-%d')

    fetch_csv('http://www.quandl.com/api/v1/datasets/BUNDESBANK/BBK01_WT5511.csv?trim_start=2012-01-01',
        date_column='Date',
        symbol='gold',
        post_func=rename_col,
        date_format='%Y-%m-%d')
    
    # Tiffany
    context.stock = symbol('TIF')

def handle_data(context, data):
    # Invest 10% of the portfolio in Tiffany stock when the price of gold is low.
    # Decrease the Tiffany position to 5% of portfolio when the price of gold is high.

    if (data['gold'].price < 1600):
       order_target_percent(context.stock, 0.10)
    if (data['gold'].price > 1750):
       order_target_percent(context.stock, 0.05)

    #record the variables   
    if 'price' in data['palladium']:
       record(palladium=data['palladium'].price, gold=data['gold'].price)

Custom Slippage Example

This example implements the fixed slippage model, but with the additional flexibility to specify a different spread for each stock.

Clone Algorithm
# Our custom slippage model
class PerStockSpreadSlippage(slippage.SlippageModel):

    # We specify the constructor so that we can pass state to this class, but this is optional.
    def __init__(self, spreads):
        # Store a dictionary of spreads, keyed by sid.
        self.spreads = spreads

    def process_order(self, trade_bar, order):
        spread = self.spreads[order.sid]
   
        # In this model, the slippage is going to be half of the spread for 
        # the particular stock
        slip_amount = spread / 2
        # Compute the price impact of the transaction. Size of price impact is 
        # proprotional to order size. 
        # A buy will increase the price, a sell will decrease it. 
        new_price = trade_bar.price + (slip_amount * order.direction)

        log.info('executing order ' + str(trade_bar.sid) + ' stock bar price: ' + \
                 str(trade_bar.price) + ' and trade executes at: ' + str(new_price))

        # Create the transaction using the new price we've calculated.
        return slippage.create_transaction(
            trade_bar,
            order,
            new_price,
            order.amount
        )

def initialize(context):
    # Provide the bid-ask spread for each of the securities in the universe.
    spreads = {
        sid(24): 0.05,
        sid(3766): 0.08
    }
   
    # Initialize slippage settings given the parameters of our model
    set_slippage(PerStockSpreadSlippage(spreads))


def handle_data(context, data):
    # We want to own 100 shares of each stock in our universe
    for stock in data:
            order_target(stock, 100)
            log.info('placing market order for ' + str(stock.symbol) + ' at price ' \
                     + str(data[stock].price))
            

TA-Lib Example

This example uses TA-Lib's RSI method. For more examples of commonly-used TA-Lib functions, click here.

Clone Algorithm
# This example algorithm uses the Relative Strength Index indicator as a buy/sell signal.
# When the RSI is over 70, a stock can be seen as overbought and it's time to sell.
# When the RSI is below 30, a stock can be seen as oversold and it's time to buy.

# Because this algorithm uses the history function, it will only run in minute mode. 
# We will constrain the trading to once per day at market open in this example.

import talib
import numpy as np
import math

# Setup our variables
def initialize(context):
    context.max_notional = 100000
    context.intc = symbol('INTC')   # Intel 
    context.LOW_RSI = 30
    context.HIGH_RSI = 70

def handle_data(context, data):
    
    #Get a trailing window of data
    prices = history(15, '1d', 'price')
    
    
    # Use pandas dataframe.apply to get the last RSI value
    # for for each stock in our basket
    rsi_data = prices.apply(talib.RSI, timeperiod=14).iloc[-1]

    intc_rsi = rsi_data[context.intc]

    # check how many shares of Intel we currently own
    current_intel_shares = context.portfolio.positions[context.intc].amount

    # until 14 time periods have gone by, the rsi value will be numpy.nan
    
    # RSI is above 70 and we own GOOG, time to close the position.
    if intc_rsi > context.HIGH_RSI and current_intel_shares > 0:
        order_target(context.intc, 0)
        log.info('RSI is at ' + str(intc_rsi) + ', selling ' + str(current_intel_shares) + ' shares')
    
    # RSI is below 30 and we don't have any Intel stock, time to buy.
    elif intc_rsi < context.LOW_RSI and current_intel_shares == 0:
        num_shares = math.floor(context.max_notional / data[context.intc].close_price)
        order(context.intc, num_shares)
        log.info('RSI is at ' + str(intc_rsi) + ', buying ' + str(num_shares)  + ' shares')

    # record the current RSI value and the current price of INTC.
    record(intcRSI=intc_rsi, intcPRICE=data[context.intc].close_price)

Portfolio Allocation Example

This example uses order_target_percent order method.

Clone Algorithm
def initialize(context):
    context.stocks = symbols('CERN', 'DLTR') 

def handle_data(context, data):
    # This will order as many shares as needed to
    # achieve the desired portfolio allocation.
    # In our case, we end up with 20% allocation for
    # one stock and 80% allocation for the other stock.
    order_target_percent(context.stocks[0], .2)
    order_target_percent(context.stocks[1], .8)

    # Plot portfolio allocations
    pv = float(context.portfolio.portfolio_value)
    portfolio_allocations = []
    for stock in context.stocks:
        pos = context.portfolio.positions[stock]
        portfolio_allocations.append(
            pos.last_sale_price * pos.amount / pv * 100
        )

    record(perc_stock_0=portfolio_allocations[0],
           perc_stock_1=portfolio_allocations[1])

History Example

This example uses the history function.

Clone Algorithm
# Standard Deviation Using History
# Use history() to calculate the standard deviation of the days' closing
# prices of the last 10 trading days, including price at the time of 
# calculation.
def initialize(context):
    # this example works on Apple's data
    context.aapl = symbol('AAPL')

def handle_data(context, data):
    # use history to pull the last 10 days of price
    price_history = history(bar_count=10, frequency='1d', field='price')
    # calculate the standard deviation using std()
    std = price_history.std()
    # record the standard deviation as a custom signal
    record(std=std[context.aapl])

Batch Transform Example

This example shows how to create and use a batch transform. If the price hits the high in the trailing window, it sells; if it hits the low in the trailing window, it buys.

Clone Algorithm
# This is the standard Quantopian function that initializes your data and 
# variables. In it, we define how large of a 'bet' we're making (in dollars) and what 
# stock we're working with.
def initialize(context):
    # we will be trading in AMZN and WMT shares
    context.stocks = symbols('AMZN', 'WMT')
    context.bet_amount = 100000
    context.long = 0

# This is the standard Quantopian event handling function. This function is 
# run once for each bar of data.  In this example, it the min and max 
# prices for the trailing window.  If the price exceeds the recent high, it 
# goes short; if the price dips below the recent low, it goes long. The algo
# is a contrarian/mean reversion bet.
def handle_data(context, data):
    # Until our batch transform's datapanel is full, it will return None.  Once
    # the datapanel is full, then we have a max and min to work with.
    rval = minmax(data)
    
    if rval is None:
        return
    
    maximums, minimums = rval
    
    for stock in context.stocks:
        cur_max = maximums[stock]
        cur_min = minimums[stock]
        cur_price = data[stock].price
        cur_position = context.portfolio.positions[stock]
           
        order_direction = calculate_direction(stock, cur_min, cur_max, cur_price, cur_position)
        order_amount = calculate_order_amount(context, stock, order_direction, cur_price)
        
        # Optional: uncomment the log line below if you're looking for more detail about what's 
        # going on.  It will log all the information that is a 'moving part' of this
        # algorithm. Note: if you're doing a full backtest it's a lot of log lines!
        logmsg = '\n{s}: max {m} min {i} price {p} position amount {l}\nordering {n} shares'
        log.info(logmsg.format(
            s=stock, 
            m=cur_max, 
            i=cur_min, 
            p=cur_price, 
            l=cur_position.amount,
            n=order_amount
        ))
        
        order(stock, order_amount)
        
# Here we do our test to see if we should buy or sell or do nothing.  This is
# the main part of the algorithm. Once we establish a position (long or short)
# we use the context.long variable to remember which we took.
def calculate_direction(stock, cur_min, cur_max, cur_price, cur_position):
    if cur_max is not None and cur_position.amount <= 0 and cur_price >= cur_max:
        return -1
    elif cur_min is not None and cur_position.amount >= 0 and cur_price <= cur_min:
        return 1
    
    return 0
   
# This method is purely for order management. It calculates and returns an 
# order amount to place binary bets.
# If signal_val is -1, get to a short position of -1 * context.bet_size
# If signal_val is 1, get to a long position of context.bet_size
def calculate_order_amount(context, stock, signal_val, cur_price):
    current_amount = context.portfolio.positions[stock].amount
    abs_order_amount = int(context.bet_amount / cur_price) 
    
    if signal_val == -1:
        return (-1 * abs_order_amount) - current_amount
    elif signal_val == 1:
        return abs_order_amount - current_amount
    else:
        return 0        
            
# This is our batch transform decorator/declaration. We set the 
# refresh_period and length of the window.  In this case, once per day we're loading 
# the last 10 trading days and evaluating them.
@batch_transform(refresh_period=1, window_length=10)
def minmax(datapanel):
    # We are looking for the min and the max price to return. Just because it's interesting
    # we also are logging the current price. 
    prices_df = datapanel['price']
    min_price = prices_df.min()
    max_price = prices_df.max()

    if min_price is not None and max_price is not None:
        return (max_price, min_price)
    else:
        return None