Live Algorithmic Trading with Python


How To Make Your Trading Strategies Work On Live Markets


© Ran Aroussi
@aroussi | aroussi.com | github.com/ranaroussi



August, 2017

Agenda

  • Working with the 3 types of market data:
    • EOD market data
    • Timed market data snapshopts
    • Live streaming data
  • How to execute and monitor trades in live markets
  • Live examples using Interactive Brokers and Oanda FX

Working with Market Data

Working with Market Data

  • EOD market data (for daily strategies)
  • Timed market data snapshopts (usually for intraday strategies)
  • Live (or paper) streaming data (for anything, really 😁)

EOD Market Data Sources

(live example to follow)

  • Historical market data providers
  • Live market data feed providers
  • Your broker

EOD Market Data Sources

  • Historical market data providers
    • Quandl
    • Yahoo! Finance
    • EODData.com
    • Tick data providers (QuantQuote.com, TickData.com, ...)
  • Live market data feed providers
    • IQFeed
    • Rithmic
    • CQG
    • ...
  • Your broker

Downloading EOD Market Data

Quandl Example: EOD Stocks

In [3]:
import quandl
# quandl.ApiConfig.api_key = "YOUR_KEY_HERE"

df = quandl.get('WIKI/AAPL')
df.tail()
Out[3]:
Open High Low Close Volume Ex-Dividend Split Ratio Adj. Open Adj. High Adj. Low Adj. Close Adj. Volume
Date
2017-08-17 160.52 160.71 157.8400 157.87 26925694.0 0.0 1.0 160.52 160.71 157.8400 157.87 26925694.0
2017-08-18 157.86 159.50 156.7200 157.50 27012525.0 0.0 1.0 157.86 159.50 156.7200 157.50 27012525.0
2017-08-21 157.50 157.89 155.1101 157.21 26145653.0 0.0 1.0 157.50 157.89 155.1101 157.21 26145653.0
2017-08-22 158.23 160.00 158.0200 159.78 21297812.0 0.0 1.0 158.23 160.00 158.0200 159.78 21297812.0
2017-08-23 159.07 160.47 158.8800 159.98 19198189.0 0.0 1.0 159.07 160.47 158.8800 159.98 19198189.0

Quandl Example: Continuous Futures Contract

In [4]:
df = quandl.get('CHRIS/CME_CL1')
df.tail()

# see more python methods @ quandl.com/tools/python

# Useful databases:
# - https://www.quandl.com/data/WIKI-Wiki-EOD-Stock-Prices
# - https://www.quandl.com/data/CHRIS-Wiki-Continuous-Futures
Out[4]:
Open High Low Last Change Settle Volume Previous Day Open Interest
Date
2017-08-17 46.80 47.19 46.46 46.93 0.31 47.09 596093.0 161590.0
2017-08-18 46.93 48.74 46.78 48.73 1.42 48.51 247019.0 96943.0
2017-08-21 48.72 48.75 47.03 47.40 1.14 47.37 124355.0 62446.0
2017-08-22 47.45 48.03 47.20 47.65 0.27 47.64 28373.0 25636.0
2017-08-23 47.64 48.50 47.53 48.37 0.58 48.41 700754.0 522164.0

Yahoo! Finance Example

(Sadly, this service became very unreliable in terms of availability)

In [5]:
from pandas_datareader import data as pdr

df = pdr.get_data_yahoo("AAPL", start="2010-01-01")
df.tail()
Out[5]:
Open High Low Close Adj Close Volume
Date
2017-08-18 157.860001 159.500000 156.720001 157.500000 157.500000 27428100
2017-08-21 157.500000 157.889999 155.110001 157.210007 157.210007 26368500
2017-08-22 158.229996 160.000000 158.020004 159.779999 159.779999 21604600
2017-08-23 159.070007 160.470001 158.880005 159.979996 159.979996 19284400
2017-08-24 160.429993 160.740005 158.550003 159.039993 159.039993 9437854

Reading CSV Time-Series Files

In [6]:
# read csv from eoddata.com
df = pd.read_csv('./AAPL_20170102_20170823.csv')
df.tail()
Out[6]:
Symbol Date Open High Low Close Volume
163 AAPL 17-Aug-2017 160.52 160.71 157.84 157.86 27940500
164 AAPL 18-Aug-2017 157.86 159.50 156.72 157.50 27428000
165 AAPL 21-Aug-2017 157.50 157.89 155.11 157.21 26368500
166 AAPL 22-Aug-2017 158.23 160.00 158.02 159.78 21604500
167 AAPL 23-Aug-2017 159.07 160.47 158.88 159.98 19399000

Reading CSV Time-Series Files

In [7]:
# parse date column and set as index while reading
df = pd.read_csv('./AAPL_20170102_20170823.csv',
                 parse_dates=['Date'], index_col=['Date'])
df.tail()
Out[7]:
Symbol Open High Low Close Volume
Date
2017-08-17 AAPL 160.52 160.71 157.84 157.86 27940500
2017-08-18 AAPL 157.86 159.50 156.72 157.50 27428000
2017-08-21 AAPL 157.50 157.89 155.11 157.21 26368500
2017-08-22 AAPL 158.23 160.00 158.02 159.78 21604500
2017-08-23 AAPL 159.07 160.47 158.88 159.98 19399000

Downloading Data from Oanda

Oanda Example (using REST API)

In [8]:
api_token, account_id = open('./oanda.conf').read().split(',')
In [9]:
import oandapy
oanda = oandapy.API(environment="practice", access_token=api_token)
In [10]:
data = oanda.get_history(instrument="EUR_USD",
                         granularity='D',
                         start='2017-01-01')

# convert candles to dataframe
df = pd.DataFrame(data['candles'])

df.set_index('time', inplace=True)
df.index = pd.to_datetime(df.index)

df.tail()
Out[10]:
closeAsk closeBid complete highAsk highBid lowAsk lowBid openAsk openBid volume
time
2017-08-17 21:00:00 1.17633 1.17581 True 1.17758 1.17741 1.17095 1.17079 1.17235 1.17203 58908
2017-08-20 21:00:00 1.18157 1.18135 True 1.18286 1.18273 1.17325 1.17313 1.17600 1.17500 32658
2017-08-21 21:00:00 1.17627 1.17603 True 1.18251 1.18237 1.17461 1.17447 1.18175 1.18139 36071
2017-08-22 21:00:00 1.18082 1.18060 True 1.18239 1.18226 1.17412 1.17393 1.17627 1.17599 39235
2017-08-23 21:00:00 1.18063 1.18050 False 1.18190 1.18175 1.17849 1.17836 1.18105 1.18072 27493

Getting Snapshot Data

  • Get price of EUR/USD every 5 seconds
  • Stop after 5 ticks (for demo porposes)
In [13]:
reqs = 0
while True:
    price = oanda.get_prices(instruments="EUR_USD")
    print(price) # price available via price['prices'][0]

    reqs += 1
    if (reqs == 5):
        break
    time.sleep(1)
{'prices': [{'instrument': 'EUR_USD', 'time': '2017-08-24T15:46:16.172298Z', 'bid': 1.18053, 'ask': 1.18065}]}
{'prices': [{'instrument': 'EUR_USD', 'time': '2017-08-24T15:46:16.172298Z', 'bid': 1.18053, 'ask': 1.18065}]}
{'prices': [{'instrument': 'EUR_USD', 'time': '2017-08-24T15:46:16.172298Z', 'bid': 1.18053, 'ask': 1.18065}]}
{'prices': [{'instrument': 'EUR_USD', 'time': '2017-08-24T15:46:16.172298Z', 'bid': 1.18053, 'ask': 1.18065}]}
{'prices': [{'instrument': 'EUR_USD', 'time': '2017-08-24T15:46:24.734534Z', 'bid': 1.18049, 'ask': 1.18061}]}

Downloading Data from Interactive Brokers

Downloading Historical Data from Interactive Brokers

Before anything...

  • Requires IB's TWS or IB Gateway running on your machine with API permissions enabled
  • For order execution, you also need to check "Bypass Order Precautions for API Orders" under "Precautions"
  • All IB examples will be using the ezIBpy library


Interactive Brokers Example

  1. Connect to TWS
  2. Create a callback method
  3. Create a "contract"
  4. Request historical data
  5. Access historical data
In [14]:
import ezibpy
import time

# initialize ezIBpy
ibConn = ezibpy.ezIBpy()
In [15]:
# create callback method

completed = False

def ibCallback(caller, msg, **kwargs):
    global completed
    if caller == "handleHistoricalData":
        completed = kwargs['completed']
In [28]:
# connect to TWS
ibConn.connect(port=4001)
ibConn.ibCallback = ibCallback

# create a contract
contract = ibConn.createStockContract("AAPL")

# request historical data
ibConn.requestHistoricalData(resolution="1 day", lookback="1 Y")

# wait until data is downloaded
while not completed:
    time.sleep(1)

# if we got here, download is completed
print("download completed")

# cancel request and disconnect from TWS
ibConn.cancelHistoricalData(contract)
ibConn.disconnect()
Server Version: 76
TWS Time at connection:20170824 18:48:42 IST
download completed
In [18]:
# access historica data
df = ibConn.historicalData['AAPL']
df.tail()
Out[18]:
C H L O OI V WAP
datetime
2017-08-20 154.89 162.13 152.69 158.73 152116 180035 157.1500
2017-08-21 156.09 161.01 151.88 154.80 152570 181123 155.0460
2017-08-22 157.77 162.28 153.72 156.13 152129 180130 158.0560
2017-08-23 156.50 163.15 153.84 157.73 152233 180199 157.1290
2017-08-24 156.99 161.44 154.08 156.58 74676 88752 156.8995

Working with Live, Streaming Data

Oanda Streaming Data

Oanda Streamer

In [19]:
# create streamer
class OandaBasicStreamer(oandapy.Streamer):

    def __init__(self, *args, **kwargs):
        oandapy.Streamer.__init__(self, *args, **kwargs)
        self.ticks = 0

    def stream(self, account_id, symbols):
        oandapy.Streamer.start(self,
                               accountId=account_id,
                               instruments=symbols
                              )

    def on_error(self, data):
        self.disconnect()

    # logic goes here:
    def on_success(self, data):
        if 'tick' in data:
            self.ticks += 1
            print(data)
In [23]:
stream = OandaBasicStreamer(environment="practice", access_token=api_token)

try:
    stream.rates(account_id, instruments="EUR_USD")
except (KeyboardInterrupt, SystemExit):
    print('Stopped by user...')
{'tick': {'instrument': 'EUR_USD', 'time': '2017-08-24T15:47:54.821310Z', 'bid': 1.18048, 'ask': 1.1806}}
{'tick': {'instrument': 'EUR_USD', 'time': '2017-08-24T15:47:58.643527Z', 'bid': 1.18043, 'ask': 1.18056}}
{'tick': {'instrument': 'EUR_USD', 'time': '2017-08-24T15:48:01.889783Z', 'bid': 1.18039, 'ask': 1.18052}}
{'tick': {'instrument': 'EUR_USD', 'time': '2017-08-24T15:48:01.922014Z', 'bid': 1.18039, 'ask': 1.18051}}
{'tick': {'instrument': 'EUR_USD', 'time': '2017-08-24T15:48:03.111637Z', 'bid': 1.18042, 'ask': 1.18054}}
Stopped by user...

Plotting Oanda's Streaming Data

In [29]:
import plotly.plotly as ply
import plotly.graph_objs as pgo

import plotly.tools as tls
stream_ids = tls.get_credentials_file()['stream_ids']
In [34]:
def init_plotly_streaming_chart(token, maxpoints=100,
                                title="Untitled", auto_open=False):
    # create a new streaming chart
    stream = pgo.Stream(
        token=token,
        maxpoints=maxpoints
    )

    # initialize streaming chart
    tracer = pgo.Scatter(x=[], y=[], stream=stream)
    figure = pgo.Figure(data=pgo.Data([tracer]),
                       layout=pgo.Layout(title=title))

    return ply.plot(figure, auto_open=auto_open)
In [40]:
chart_url = init_plotly_streaming_chart(
    token=stream_ids[0],
    maxpoints=50,
    title="Oanda Streaming Demo")

print(chart_url)
https://plot.ly/~ranaroussi/132
In [36]:
# create streamer
class OandaPlotter(OandaBasicStreamer):

    # logic goes here:
    def on_success(self, data):
        if 'tick' in data:
            midprice = (data['tick']['bid'] + data['tick']['ask']) / 2
            streamer.write(dict(x=self.ticks, y=midprice))
            self.ticks += 1
In [41]:
tls.embed(chart_url)
Out[41]:
In [42]:
stream = OandaPlotter(environment="practice", access_token=api_token)

streamer = ply.Stream(stream_ids[0])
streamer.open()

try:
    stream.rates(account_id, instruments="EUR_USD")
except (KeyboardInterrupt, SystemExit):
    print('Stopped by user...')

streamer.close()
Stopped by user...

Interactive Brokers Streaming Data

In [43]:
import ezibpy
import time

# initialize ezIBpy
ibConn = ezibpy.ezIBpy()

# create callback method
def ibCallback(caller, msg, **kwargs):
    if "tick" in kwargs:
        tick = ibConn.marketData[msg.tickerId]
        tick['datetime'] = tick.index
        print(tick.to_dict(orient='records')[0])

# connect to TWS
ibConn.connect(port=4001)
ibConn.ibCallback = ibCallback
Server Version: 76
TWS Time at connection:20170824 18:50:23 IST
In [44]:
# create a contract
contract = ibConn.createFuturesContract("ES",
                exchange="GLOBEX", expiry="201709")

# request market data for the contract
ibConn.requestMarketData()

try:
    while True:
        time.sleep(1)

except (KeyboardInterrupt, SystemExit):
    print('Stopped by user...')

    # cancel request and disconnect from TWS
    ibConn.cancelMarketData(contract)
    ibConn.disconnect()
{'ask': 2424.75, 'asksize': 432, 'bid': 2424.5, 'bidsize': 410, 'last': 2424.75, 'lastsize': 2, 'volume': 805304, 'datetime': '2017-08-24 18:50:27.000000'}
{'ask': 48.25, 'asksize': 4, 'bid': 48.23, 'bidsize': 2, 'last': 48.23, 'lastsize': 5, 'volume': 236484, 'datetime': '2017-08-24 18:50:27.000000'}
{'ask': 2424.75, 'asksize': 432, 'bid': 2424.5, 'bidsize': 410, 'last': 2424.75, 'lastsize': 2, 'volume': 805304, 'datetime': '2017-08-24 18:50:27.000000'}
{'ask': 48.25, 'asksize': 124, 'bid': 48.23, 'bidsize': 92, 'last': 48.23, 'lastsize': 5, 'volume': 236484, 'datetime': '2017-08-24 18:50:27.000000'}
{'ask': 2424.75, 'asksize': 176, 'bid': 2424.5, 'bidsize': 176, 'last': 2424.75, 'lastsize': 88, 'volume': 805304, 'datetime': '2017-08-24 18:50:28.000000'}
{'ask': 48.25, 'asksize': 124, 'bid': 48.23, 'bidsize': 92, 'last': 48.24, 'lastsize': 1, 'volume': 236484, 'datetime': '2017-08-24 18:50:30.000000'}
Stopped by user...

Plotting IB's Streaming Data

In [45]:
chart_url = init_plotly_streaming_chart(
    token=stream_ids[1],
    maxpoints=50,
    title="IB Streaming Demo")

print(chart_url)# create streamer
https://plot.ly/~ranaroussi/134
In [46]:
ticks = 0
def ibCallback(caller, msg, **kwargs):
    global ticks
    if "tick" in kwargs:
        tick = ibConn.marketData[msg.tickerId]
        streamer.write(dict(x=ticks, y=tick['last'].values[0]))
        ticks += 1
In [47]:
tls.embed(chart_url)
Out[47]:
In [48]:
streamer = ply.Stream(stream_ids[1])
streamer.open()

ibConn.connect(port=4001)
ibConn.ibCallback = ibCallback

contract = ibConn.createFuturesContract("ES",
                exchange="GLOBEX", expiry="201709")

ibConn.requestMarketData()

try:
    while True:
        time.sleep(1)

except (KeyboardInterrupt, SystemExit):
    print('Stopped by user...')

    # cancel request and disconnect from TWS
    ibConn.cancelMarketData(contract)
    ibConn.disconnect()

    streamer.close()
Server Version: 76
TWS Time at connection:20170824 18:50:47 IST
Stopped by user...

Executing & Monitoring Orders

Executing Orders on Oanda

In [49]:
# market order
order = oanda.create_order(account_id,
                           instrument="EUR_USD",
                           units=1000,
                           side='buy',
                           type='market'
                          )

pretty_print(order)
{'instrument': 'EUR_USD',
 'price': 1.18048,
 'time': '2017-08-24T15:51:21.000000Z',
 'tradeOpened': {'id': 10810337186,
                 'side': 'buy',
                 'stopLoss': 0,
                 'takeProfit': 0,
                 'trailingStop': 0,
                 'units': 1000},
 'tradeReduced': {},
 'tradesClosed': []}
In [50]:
# limit order
trade_expire = datetime.datetime.now() + datetime.timedelta(hours=1)

order = oanda.create_order(account_id,
                           instrument="EUR_USD",
                           units=1000,
                           side='buy',
                           type='limit',
                           price=1.1,
                           expiry = trade_expire.isoformat("T")
                          )

pretty_print(order)
{'instrument': 'EUR_USD',
 'orderOpened': {'expiry': '2017-08-24T19:51:35.000000Z',
                 'id': 10810337470,
                 'lowerBound': 0,
                 'side': 'buy',
                 'stopLoss': 0,
                 'takeProfit': 0,
                 'trailingStop': 0,
                 'units': 1000,
                 'upperBound': 0},
 'price': 1.1,
 'time': '2017-08-24T15:51:35.000000Z'}
In [51]:
# get trade, positions, orders information
positions = oanda.get_positions(account_id)
trades = oanda.get_trades(account_id)
orders = oanda.get_orders(account_id)

positions, trades, orders
Out[51]:
({'positions': [{'avgPrice': 1.18048,
    'instrument': 'EUR_USD',
    'side': 'buy',
    'units': 1000}]},
 {'trades': [{'id': 10810337186,
    'instrument': 'EUR_USD',
    'price': 1.18048,
    'side': 'buy',
    'stopLoss': 0,
    'takeProfit': 0,
    'time': '2017-08-24T15:51:21.000000Z',
    'trailingAmount': 0,
    'trailingStop': 0,
    'units': 1000}]},
 {'orders': [{'expiry': '2017-08-24T19:51:35.000000Z',
    'id': 10810337470,
    'instrument': 'EUR_USD',
    'lowerBound': 0,
    'price': 1.1,
    'side': 'buy',
    'stopLoss': 0,
    'takeProfit': 0,
    'time': '2017-08-24T15:51:35.000000Z',
    'trailingStop': 0,
    'type': 'limit',
    'units': 1000,
    'upperBound': 0}]})
In [52]:
# close posiiton (market)
order = oanda.create_order(account_id,
                           instrument="EUR_USD",
                           units=1000,
                           type='market',
                           side='sell'
                          )

pretty_print(order)
{'instrument': 'EUR_USD',
 'price': 1.18031,
 'time': '2017-08-24T15:51:40.000000Z',
 'tradeOpened': {},
 'tradeReduced': {},
 'tradesClosed': [{'id': 10810337186, 'side': 'buy', 'units': 1000}]}
In [53]:
# get trade, positions, orders information
positions = oanda.get_positions(account_id)
trades = oanda.get_trades(account_id)
orders = oanda.get_orders(account_id)

positions, trades, orders
Out[53]:
({'positions': []},
 {'trades': []},
 {'orders': [{'expiry': '2017-08-24T19:51:35.000000Z',
    'id': 10810337470,
    'instrument': 'EUR_USD',
    'lowerBound': 0,
    'price': 1.1,
    'side': 'buy',
    'stopLoss': 0,
    'takeProfit': 0,
    'time': '2017-08-24T15:51:35.000000Z',
    'trailingStop': 0,
    'type': 'limit',
    'units': 1000,
    'upperBound': 0}]})
In [54]:
# close pending order
orderId = orders['orders'][0]['id']
oanda.close_order(account_id, orderId)

pretty_print(order)
{'instrument': 'EUR_USD',
 'price': 1.18031,
 'time': '2017-08-24T15:51:40.000000Z',
 'tradeOpened': {},
 'tradeReduced': {},
 'tradesClosed': [{'id': 10810337186, 'side': 'buy', 'units': 1000}]}
In [55]:
# get trade, positions, orders information
positions = oanda.get_positions(account_id)
trades = oanda.get_trades(account_id)
orders = oanda.get_orders(account_id)

positions, trades, orders
Out[55]:
({'positions': []}, {'trades': []}, {'orders': []})

Executing Orders on Interactive Brokers

In [56]:
import ezibpy

# initialize ezIBpy
ibConn = ezibpy.ezIBpy()

# create callback method
def ibCallback(caller, msg, **kwargs):
    if caller == "handleOrders":
        order = ibConn.orders[msg.orderId]
        print('Order > ', order)
In [57]:
# issue market order
ibConn.connect(port=4001)
ibConn.ibCallback = ibCallback

contract = ibConn.createFuturesContract("ES",
                exchange="GLOBEX", expiry="201709")

order = ibConn.createOrder(1)
orderId = ibConn.placeOrder(contract, order)
time.sleep(1)

ibConn.disconnect()
Server Version: 76
TWS Time at connection:20170824 18:51:52 IST
Order >  {'id': 196, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eb65d68>, 'status': 'OPENED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 51, 52)}
Order >  {'id': 196, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eb65d68>, 'status': 'SUBMITTED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 51, 52)}
Order >  {'id': 196, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eb65d68>, 'status': 'FILLED', 'reason': None, 'avgFillPrice': 2424.75, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 51, 53)}
In [58]:
# get trade, positions, orders information
ibConn.positions, ibConn.portfolio, ibConn.orders
Out[58]:
({'CLU2017_FUT': {'account': 'DU15012',
   'avgCost': 0.0,
   'position': 0,
   'symbol': 'CLU2017_FUT'},
  'ESU2017_FUT': {'account': 'DU15012',
   'avgCost': 121237.5,
   'position': 1,
   'symbol': 'ESU2017_FUT'}},
 {'CLU2017_FUT': {'account': 'DU15012',
   'averageCost': 0.0,
   'marketPrice': 48.2200012,
   'marketValue': 0.0,
   'position': 0,
   'realizedPNL': -540.0,
   'symbol': 'CLU2017_FUT',
   'totalPNL': -540.0,
   'unrealizedPNL': 0.0},
  'ESU2017_FUT': {'account': 'DU15012',
   'averageCost': 0.0,
   'marketPrice': 2424.625,
   'marketValue': 0.0,
   'position': 0,
   'realizedPNL': -2595.9,
   'symbol': 'ESU2017_FUT',
   'totalPNL': -2595.9,
   'unrealizedPNL': 0.0}},
 {196: {'avgFillPrice': 2424.75,
   'id': 196,
   'order': <ib.ext.Order.Order at 0x11eb65d68>,
   'parentId': 0,
   'reason': None,
   'status': 'FILLED',
   'symbol': 'ESU2017_FUT',
   'time': datetime.datetime(2017, 8, 24, 18, 51, 53)}})
In [59]:
# bracker limit order
ibConn.connect(port=4001)
ibConn.ibCallback = ibCallback

contract = ibConn.createFuturesContract("ES",
                exchange="GLOBEX", expiry="201709")

order = ibConn.createBracketOrder(
    contract, quantity=1, entry=2400, target=2500., stop=2300.)

time.sleep(1)
ibConn.disconnect()
Server Version: 76
TWS Time at connection:20170824 18:52:04 IST
Order >  {'id': 198, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eefecc0>, 'status': 'OPENED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 4)}
Order >  {'id': 198, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eefecc0>, 'status': 'PRESUBMITTED', 'reason': 'child,locate', 'avgFillPrice': 0.0, 'parentId': 197, 'time': datetime.datetime(2017, 8, 24, 18, 52, 4)}
Order >  {'id': 201, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11ef06c18>, 'status': 'OPENED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 5)}
Order >  {'id': 201, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11ef06c18>, 'status': 'PRESUBMITTED', 'reason': 'child,locate,trigger', 'avgFillPrice': 0.0, 'parentId': 197, 'time': datetime.datetime(2017, 8, 24, 18, 52, 5)}
Order >  {'id': 197, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eeee9e8>, 'status': 'OPENED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 5)}
Order >  {'id': 197, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eeee9e8>, 'status': 'SUBMITTED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 5)}
In [60]:
# get order information
pretty_print(ibConn.orders)
{196: {'avgFillPrice': 2424.75,
       'id': 196,
       'order': <ib.ext.Order.Order object at 0x11eb65d68>,
       'parentId': 0,
       'reason': None,
       'status': 'FILLED',
       'symbol': 'ESU2017_FUT',
       'time': datetime.datetime(2017, 8, 24, 18, 51, 53)},
 197: {'avgFillPrice': 0.0,
       'id': 197,
       'order': <ib.ext.Order.Order object at 0x11eeee9e8>,
       'parentId': 0,
       'reason': None,
       'status': 'SUBMITTED',
       'symbol': 'ESU2017_FUT',
       'time': datetime.datetime(2017, 8, 24, 18, 52, 5)},
 198: {'avgFillPrice': 0.0,
       'id': 198,
       'order': <ib.ext.Order.Order object at 0x11eefecc0>,
       'parentId': 197,
       'reason': 'child,locate',
       'status': 'PRESUBMITTED',
       'symbol': 'ESU2017_FUT',
       'time': datetime.datetime(2017, 8, 24, 18, 52, 4)},
 201: {'avgFillPrice': 0.0,
       'id': 201,
       'order': <ib.ext.Order.Order object at 0x11ef06c18>,
       'parentId': 197,
       'reason': 'child,locate,trigger',
       'status': 'PRESUBMITTED',
       'symbol': 'ESU2017_FUT',
       'time': datetime.datetime(2017, 8, 24, 18, 52, 5)}}

Live Trading Example

Live Trading "Template"

  • Subscribe to streaming data (resample data if necessary)
  • Run strategy logic
  • Execute & monitor orders
In [61]:
# build ohlc _bars
_bars = pd.DataFrame()
_ticks = pd.DataFrame()
_res = '1T'
_window = 100

def ibCallback(caller, msg, **kwargs):
    global _bars, _ticks, _res, _window

    if caller == "handleOrders":
        order = ibConn.orders[msg.orderId]
        print('Order > ', order)

    elif "tick" in kwargs:
        # read tick
        tick = ibConn.marketData[msg.tickerId]

        # add to _ticks df
        _ticks = pd.concat([_ticks, tick])
        _ticks.index = pd.to_datetime(_ticks.index)

        # on_new_bar(_ticks.copy())
        # return 

        # resample _ticks to 1min _bars
        ticks_as_bars = _ticks['last'].resample(_res).ohlc()
        ticks_as_bars['volume'] = _ticks['lastsize'].resample(_res).sum()

        # replace latest bar data with resampled _ticks
        _bars = _bars[_bars.index != ticks_as_bars.index[-1]]
        _bars = pd.concat([_bars, ticks_as_bars[-1:]])

        # remove old _ticks
        pre_trimmed_ticks = len(_ticks)
        _ticks = _ticks[_ticks.index >= ticks_as_bars.index[-1]]

        # run strategy logic
        if len(_ticks) != pre_trimmed_ticks:
            on_new_bar(_bars[-_window:].copy())
In [62]:
def on_new_bar(bars):

    position = 0
    if contractString in ibConn.positions:
        position = ibConn.positions[contractString]['position']

    # calculate sma
    bars['sma'] = bars['last'].rolling(2).mean()

    # get lastest bar as dict
    bar = bars.to_dict(orient='records')[-1]

    order_qty = 0

    if bar['last'] > bar['sma'] and position <= 0:
        order_qty = abs(position) + 1

    elif bar['last'] < bar['sma'] and position >= 0:
        order_qty = -abs(position) - order_qty

    # send order?
    if order_qty != 0:
        print('order qty ==>', order_qty)
        order = ibConn.createOrder(order_qty)
        ibConn.placeOrder(contract, order)
In [64]:
ibConn.connect(port=4001)
ibConn.ibCallback = ibCallback

contract = ibConn.createFuturesContract("ES",
                exchange="GLOBEX", expiry="201709")

contractString = ibConn.contractString(contract)

ibConn.requestMarketData()

try:
    while True:
        time.sleep(1)

except (KeyboardInterrupt, SystemExit):
    print('Stopped by user...')

    # cancel request and disconnect from TWS
    ibConn.cancelMarketData(contract)
    ibConn.disconnect()
Server Version: 76
TWS Time at connection:20170824 18:52:40 IST
order qty ==> -1
order qty ==> -1
Order >  {'id': 202, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11ef1f160>, 'status': 'OPENED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 40)}
Order >  {'id': 202, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11ef1f160>, 'status': 'FILLED', 'reason': None, 'avgFillPrice': 2424.75, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 40)}
Order >  {'id': 203, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eb388d0>, 'status': 'OPENED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 42)}
Order >  {'id': 203, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11eb388d0>, 'status': 'FILLED', 'reason': None, 'avgFillPrice': 2424.75, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 42)}
order qty ==> 2
Order >  {'id': 204, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11ee6ee10>, 'status': 'OPENED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 42)}
Order >  {'id': 204, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11ee6ee10>, 'status': 'FILLED', 'reason': None, 'avgFillPrice': 2425.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 42)}
order qty ==> -1
Order >  {'id': 205, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11ef06630>, 'status': 'OPENED', 'reason': None, 'avgFillPrice': 0.0, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 43)}
Order >  {'id': 205, 'symbol': 'ESU2017_FUT', 'order': <ib.ext.Order.Order object at 0x11ef06630>, 'status': 'FILLED', 'reason': None, 'avgFillPrice': 2424.75, 'parentId': 0, 'time': datetime.datetime(2017, 8, 24, 18, 52, 43)}
order qty ==> 1
Stopped by user...

To Be Continued...

This was the part 3 out of the 4-part webinar series

Up Next...

  • Prototyping Trading Strategies
  • Backtesting & Optimization
  • Live Trading
  • Using Machine Learning in Trading

Q&A Time

Live Algorithmic Trading with Python


Thank you for attending!


© Ran Aroussi
@aroussi | aroussi.com | github.com/ranaroussi



August, 2017