QTPyLib Algorithm Callbacks, Moving-Average Crosses, and Instrument Handling
Summary
This documentation explains the structure of QTPyLib trading algorithms. It describes optional callbacks for startup, quotes, ticks, bars, order-book updates, and fills, and shows how strategies can use these events to inspect instrument history and positions, submit orders, and record signals. A simple moving-average crossover example buys when a short average crosses above a long average and exits on a downward cross, while checking for sufficient history, pending orders, and current exposure.
Further examples cover coordinating positions across instruments, configuring parameters and bar resolutions, adding contracts after startup, and choosing runtime settings for live or backtest use. The document is an API and workflow guide rather than evidence for a profitable strategy: it gives no measured returns or validation results. It also warns that sample algorithms can lose money quickly and recommends demo trading. Instrument availability, data quality, broker connectivity, and execution behavior remain important practical constraints.
Key ideas
- QTPyLib strategies can respond to startup, quote, tick, bar, order-book, and fill events.
- A sample crossover strategy uses historical bars, rolling averages, position state, and pending-order checks to manage entries and exits.
- The documentation demonstrates access to and position management across multiple instruments.
- Resolution, preload, timezone, logging, and backtest settings can be configured in the strategy or at runtime.
- The examples are instructional and unvalidated, and the guide cautions that live use can lose money quickly.
Tags
Full text
# algo
Writing Your Algorithm
======================
When creating your algorithm, there are 4 functions that handle
incoming market data from the running Blotter. These are
``on_quote()`` which is invoked on every quote change,
``on_tick()`` which is invoked on every tick captured,
``on_bar()``, which is invoked on every bar created in the pre-specified resolution, and
``on_orderbook()``, which is invoked on every change to the Order Book.
An `Instrument Object <./api_instrument.html>`_ is being passed to each method when called.
If you need to run some logic when your strategy starts,
simply add an ``on_start()`` method to your strategy, and set
your parameters there.
If your strategy requires you to take action on every fill,
simply add an ``on_fill()`` method to your strategy, and
run write code logic there.
All methods are optional. You can run logic on start *and/or*
on every tick *and/or* on every bar event as needed. Unnecessary can
either use ``pass`` or be omitted from your strategy code.
.. warning::
You're going to lose a lot of money very quickly by
running the sample algorithms in this documentation!
**Please use the demo account when logging into
IB TWS / IB Gateway (user: edemo, password: demouser).**
Basic Algo Structure
--------------------
Here's a code for an algo that buys Apple Stock when flat
and sells when in position.
.. code:: python
# strategy.py
from qtpylib.algo import Algo
class DumbAlgo(Algo):
def on_start(self):
# optional method that gets called once upon start
pass
def on_fill(self, instrument, order):
# optional method that gets called on every order fill
pass
def on_orderbook(self, instrument):
# optional method that gets called on every orderbook change
pass
def on_quote(self, instrument):
# optional method that gets called on every quote change
pass
def on_tick(self, instrument):
# optional method that gets called on every tick received
pass
def on_bar(self, instrument):
# optional method that gets called on every bar received
# buy if position = 0, sell if in position > 0
if instrument.positions['position'] == 0:
instrument.buy(100)
else:
instrument.exit()
if __name__ == "__main__":
# initialize the algo
strategy = DumbAlgo(
instruments = [ "AAPL" ],
resolution = "1T" # 1Min bar resolution (Pandas "resample" resolutions)
)
# run the algo
strategy.run()
With your Blotter running in the background, run your algo from the command line:
.. code:: bash
$ python strategy.py
The algo will communicate with the Blotter running in the background and
generate orders based on the rules specified.
.. note::
A trade log will be saved in the database specified in the
currently running Blotter and will be available via the
:doc:`reports`.
-----
Simple MA Cross Over Strategy
-----------------------------
While the Blotter running in the background, write and execute your algorithm:
.. code:: python
# strategy.py
from qtpylib.algo import Algo
class CrossOver(Algo):
def on_bar(self, instrument):
# get instrument history
bars = instrument.get_bars(window=20)
# make sure we have at least 20 bars to work with
if len(bars) < 20:
return
# compute averages using internal rolling_mean
bars['short_ma'] = bars['close'].rolling_mean(window=10)
bars['long_ma'] = bars['close'].rolling_mean(window=20)
# get current position data
positions = instrument.get_positions()
# trading logic - entry signal
if bars['short_ma'].crossed_above(bars['long_ma'])[-1]:
if not instrument.pending_orders and positions["position"] == 0:
# send a buy signal
instrument.buy(1)
# record values for future analysis
self.record(ma_cross=1)
# trading logic - exit signal
elif bars['short_ma'].crossed_below(bars['long_ma'])[-1]:
if positions["position"] != 0:
# exit / flatten position
instrument.exit()
# record values for future analysis
self.record(ma_cross=-1)
if __name__ == "__main__":
strategy = CrossOver(
instruments = [ ("CL", "FUT", "NYMEX", "USD", 201609) ],
resolution = "1H"
)
strategy.run()
With your Blotter running in the background, run your algo from the command line:
.. code:: bash
$ python strategy.py --log ~/qtpylib/
By adding ``--log ~/qtpylib/`` we ask that the resulting trade journal be saved
in ``~/qtpylib/STRATEGY_YYYYMMDD.csv`` for later analysis **in addition** to
being saved in the database.
-----
Using Multiple Instruments
--------------------------
.. code:: python
# strategy.py
from qtpylib.algo import Algo
class BuyStockSellOil(Algo):
def on_bar(self, instrument):
# get instrument object
ES = self.get_instrument('ESU2016_FUT')
CL = self.get_instrument('CLU2016_FUT')
# rotate holding between ES and CL
# yes - this strategy makes no sense :)
es_pos = ES.get_positions()
cl_pos = CL.get_positions()
if es_pos["position"] == 0 and cl_pos["position"] > 0:
ES.buy(1)
CL.exit(1)
elif es_pos["position"] > 0 and cl_pos["position"] == 0:
ES.exit(1)
CL.buy(1)
if __name__ == "__main__":
strategy = BuyStockSellOil(
instruments = [
("ES", "FUT", "GLOBEX", "USD", 201609),
("CL", "FUT", "NYMEX", "USD", 201609)
],
resolution = "15T"
)
strategy.run()
-----
Initializing Parameters
-----------------------
Sometimes you'd want to set some parameters when you initialize
your Strategy. To do so, simply add an ``on_start()`` method
to your strategy, and set your parameters there. It will be
invoked once when you strategy starts.
.. code:: python
# strategy.py
from qtpylib.algo import Algo
class MyStrategy(Algo):
def on_start(self):
self.paramA = "a"
self.paramB = "b"
...
-----
Adding Contracts After Initialization
-------------------------------------
In some cases, you'd want to add instruments/contracts to your
Strategy after it has already been initialized.
This can be achieved using:
.. code:: python
# strategy.py
strategy = MyStrategy(
instruments = ["AAPL"]
resolution = "1T"
)
strategy.add_instruments("GOOG", "MSFT", "FUT.ES", ...)
# ^^ accepts strings, IB contracts and instrument Tuples
strategy.run()
-----
Available Arguments
-------------------
Below are all the parameters that can either be set via the ``Algo()``
or via CLI (**all are optional**).
Algo Parameters
~~~~~~~~~~~~~~~
- ``instruments`` List of stock symbols (for US Stocks) / IB Contract Tuples. Default is empty (no instruments)
- ``resolution`` Bar resolution (pandas resample resolution + ``K`` for tick bars and ``V`` for volume bars). Default is 1T (1 min)
- ``tick_window`` Length of tick lookback window to keep (defaults to ``1``)
- ``bar_window`` Length of bar lookback window to keep (defaults to ``100``)
- ``timezone`` Convert IB timestamps to this timezone, eg. "US/Central" (defaults to ``UTC``)
- ``preload`` Preload history upon start (eg. 1H, 2D, etc, or K for tick bars). (defaults to ``None``)
- ``continuous`` Tells preloader to construct continuous Futures contracts (default is ``True``)
- ``blotter`` Log trades to MySQL server used by this Blotter (default: ``auto-detect``).
- ``backtest`` Work in Backtest mode (default: ``False``)
- ``start`` Backtest start date (``YYYY-MM-DD [HH:MM:SS[.MS]``)
- ``end`` Backtest end date (``YYYY-MM-DD [HH:MM:SS[.MS]``)
- ``data`` Path to the directory with `QTPyLib-compatible CSV files <./workflow.html>`_ (back-testing mode only)
- ``output`` Path to save the recorded data (default: ``None``)
- ``sms`` List of numbers to text orders (default: ``None``)
- ``log`` Path to store trade data (default: ``None``)
- ``ibport`` IB TWS/GW Port to use (default: ``4001``)
- ``ibclient`` IB TWS/GW Client ID (default: ``998``)
- ``ibserver`` IB TWS/GW Server hostname (default: ``localhost``)
**Example:**
.. code:: python
# strategy.py
...
strategy = MyStrategy(
instruments = [ "AAPL" ],
resolution = "512K", # 512 tick bars
tick_window = 10, # keep last 10 ticks bars
bar_window = 500, # keep last 500 (tick) bars
preload = "4H", # pre-load the last 4 hours of tick bar data
timezone = "US/Central", # convert all tick/bar timestamps to "US/Central"
blotter = "MainBlotter" # use this blotter's database to store the trade log
)
strategy.run()
Runtime (CLI) Parameters
~~~~~~~~~~~~~~~~~~~~~~~~
You can override any of the above parameters using run-time using command line arguments:
- ``--ibport`` IB TWS/GW Port to use (default: ``4001``)
- ``--ibclient`` IB TWS/GW Client ID (default: ``998``)
- ``--ibserver`` IB TWS/GW Server hostname (default: ``localhost``)
- ``--sms`` List of numbers to text orders (default: ``None``)
- ``--log`` Path to store trade data (default: ``None``)
- ``--backtest`` Work in Backtest mode (flag, default: ``False``)
- ``--start`` Backtest start date (``YYYY-MM-DD [HH:MM:SS[.MS]``)
- ``--end`` Backtest end date (``YYYY-MM-DD [HH:MM:SS[.MS]``)
- ``--data`` Path to the directory with `QTPyLib-compatible CSV files <./workflow.html>`_ (back-testing mode only)
- ``--output`` Path to save the recorded data (default: ``None``)
- ``--blotter`` Log trades to MySQL server used by this Blotter (default: ``auto-detect``)
- ``--continuous`` Construct continuous Futures contracts (flag, default: ``True``)
- ``--threads`` Maximum number of threads to use (default is 1)
**Example:**
.. code:: bash
$ python strategy.py --ibport 4001 --log ~/qtpy/ --blotter MainBlotter --sms +15551230987 ...
.. note::
**It's recommended that you set the** ``threads`` **parameter based on your strategy's needs and your machine's capabilities!**
As a general rule of thumb, strategies that are trading a handful of symbols probably don't need to tweak this parameter.
----
Back-Testing Using QTPyLib
---------------------------
In addition to live/paper trading, QTPyLib can also be used for back-testing
**without changing a single line of code**, simply by adding the
following arguments when running your algo.
.. note::
In order to run back-tests, you **MUST** have the relevant
historical data either stored in your ``Blotter``'s database or
as `QTPyLib-compatible CSV files <./workflow.html>`_
(if using CSV files, you must specify the path using the ``--data`` parameter).
When backtesting Futures, the Blotter will default to streaming
adjusted, continuous contracts for the contracts requested, based
on previously captured market data stored in the Database.
- ``--backtest`` [flag] Work in Backtest mode (default: ``False``)
- ``--start`` Backtest start date (``YYYY-MM-DD [HH:MM:SS[.MS]``)
- ``--end`` Backtest end date (``YYYY-MM-DD [HH:MM:SS[.MS]``)
- ``--data`` Path to the directory with `QTPyLib-compatible CSV files <./workflow.html>`_
With your Blotter running in the background, run your algo from the command line:
.. code:: bash
$ python strategy.py --backtest --start 2015-01-01 --end 2015-12-31 --data ~/mycsvdata/ --output ~/portfolio.pkl
The resulting back-tested portfolio will be saved in ``~/portfolio.pkl`` for later analysis.
----
Recording Data
--------------
You can record data from within your algo and make this data available as a csv/pickle/h5 file.
You can record whatever you want by adding this to your algo code (bar data is recorded automatically):
.. code:: python
self.record(key=value, ...)
Then run your algo with the ``--output`` flag:
.. code:: bash
$ python strategy.py --output path/to/recorded-file.csv
The recorded data (and bar data) will be made available in ``./path/to/recorded-file.csv``,
which gets updated in real-time.
-----
The Instrument Object
---------------------
When writing your algo, an ``Instrument`` Object is passed to each of the algos
methods (``on_tick()``, ``on_bar()``, ``on_quote()`` and ``on_fill()``), which
has many useful `methods and properties <api.html#instrument-api>`_,
including methods to access to the tick/bar/quote data.
Whenever you call ``instrument.get_quotes(...)``, ``instrument.get_ticks(...)`` or ``instrument.get_bars(...)``,
you'll get a Pandas DataFrame (and optionally, a dict object) with the following columns/keys:
* ``asset_class`` (ie. STK, FUT, CASH, OPT, FOP, ...)
* ``symbol`` (ie. ESZ2016_FUT, AAPL, SPX20161024P02150000_OPT, ...)
* ``symbol_group`` (ie. ES_F, AAPL, SPX20161024P, ...)
**Quotes / Ticks will include:**
``bid``, ``bidsize``, ``ask``, ``asksize``, ``last``, ``lastsize``
**Bars will include:**
``open``, ``high``, ``low``, ``close``, ``volume``
**Options (Quotes/Ticks/Bars) will include:**
* ``opt_underlying`` Options' Underlying's Price
* ``opt_dividend`` Options' Underlying Dividend
* ``opt_iv`` Options' Implied Volatility
* ``opt_oi`` Options' Open Interest
* ``opt_price`` Options' Price
* ``opt_volume`` Options' Volume
* ``opt_delta`` Options' Delta
* ``opt_gamma`` Options' Gamma
* ``opt_theta`` Options' Theta
* ``opt_vega`` Options' Vega
.. note::
See a list of all of ``Instrument`` Object's methods and properties in the
`Instrument API Reference <api.html#instrument-api>`_.
-----
Instruments Tuples
------------------
When initializing your algo, you're required to pass a list of instruments
you want to trades. List items can be a Ticker Symbol ``String`` (for **US Stocks** only),
and either an IB Contract object or a ``Tuple`` in IB format for all other instruments.
**Example: US Stocks**
.. code:: python
instruments = [ "AAPL", "GOOG", "..." ]
For anything other than US Stocks, you must use IB Tuples in the
following data information:
``(symbol, sec_type, exchange, currency [, expiry [, strike, opt_type]])``
Where ``expiry`` must be provided for Futures (YYYYMM or YYYYMMDD) and Options (YYYYMMDD)
whereas ``strike`` and ``opt_type`` must be a provided for Options (PUT/CALL).
**Example: UK Stock**
.. code:: python
instruments = [ ("BARC", "STK", "LSE", "GBP"), (...) ]
**Example: S&P E-mini Futures**
.. code:: python
instruments = [ ("ES", "FUT", "GLOBEX", "USD", 201609), (...) ]
.. note::
If you're trading **Front-Month Futures issued by CME-Group**, you can use the
``FUT.SYMBOL`` shorthand to have the QTPyLib create the tuple for you (`see more Futures-specific methods here <./futures.html>`_).
.. code:: python
instruments = [ "FUT.ES", "FUT.CL", "..." ]
**Example: Netflix Option**
.. code:: python
instruments = [ ("NFLX", "OPT", "SMART", "USD", 20160819, 98.50, "PUT"), (...) ]
**Example: Forex (EUR/USD)**
.. code:: python
instruments = [ ("EUR", "CASH", "IDEALPRO", "USD"), (...) ]
-----
For best practice, its recommended that you use the full IB Tuple
structure for all types of instruments:
.. code:: python
instruments = [
("AAPL", "STK", "SMART", "USD", "", 0.0, ""),
("BARC", "STK", "LSE", "GBP", "", 0.0, ""),
("ES", "FUT", "GLOBEX", "USD", 201609, 0.0, ""),
("NFLX", "OPT", "SMART", "USD", 20160819, 98.50, "PUT"),
("EUR", "CASH", "IDEALPRO", "USD", "", 0.0, ""),
...
]Shown in full with attribution under the source's licence. Licence: Apache-2.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.