The test script that demonstrates this with IQFeed with the long IB symbols. If I'm doing something incorrectly here, I'd appreciate what is wrong.
#!/usr/bin/env python
"""
IQFeed live data
IB Brokerage
"""
import pandas as pd
import argparse
import datetime
import os
import sys
from pytz import timezone
from pandas_market_calendars import get_calendar
from pandas import Series, DataFrame, bdate_range
import backtrader as bt
class Strategy(bt.Strategy):
"""Trading strategy class."""
params = (
('stocks', ('AAPL-STK-SMART-USD','TWTR-STK-SMART-USD','CAT-STK-SMART-USD',)),
('timezone', timezone('US/Eastern')),
('rsize', 1000),
('stdperiod', 30),
('debug', True),
('info', True),
)
def __init__(self):
bt.Strategy.__init__(self)
# create IB contract objects
ibstore = bt.stores.ibstore.IBStore() # to be able to call makecontract API
self.contracts = [ibstore.makecontract(*stock.split('-')) for stock in self.p.stocks]
for i, stock in enumerate(self.p.stocks):
self.datas[i].tradecontract = self.contracts[i]
# initialize parameters
self.position_sizes = { 'AAPL-STK-SMART-USD': 1200,
'TWTR-STK-SMART-USD': 0,
'CAT-STK-SMART-USD': 300}
self.stop_orders = {}
self.df_min = {}
self.daily = {}
self.orders = []
self.entry_price = {}
# set early close days
calendar = get_calendar('NYSE')
now = datetime.datetime.now()
schedule = calendar.schedule(start_date=now - datetime.timedelta(weeks=1),
end_date=now + datetime.timedelta(weeks=200))
self.early_closes = calendar.early_closes(schedule).index
self.p.debug = True
def debug(self, msg):
if self.p.debug:
dtime = self.datas[0].datetime
print('debug: %s %s, %s' % (dtime.date(0, tz=self.p.timezone),
dtime.time(0, tz=self.p.timezone), msg))
def info(self, msg):
if self.p.info:
dtime = self.datas[0].datetime
print('info: %s %s, %s' % (dtime.date(0, tz=self.p.timezone),
dtime.time(0, tz=self.p.timezone), msg))
def notify_order(self, order):
if order.status == order.Completed:
# calculate cost basis
stock = order.p.data._name
value = order.executed.value + order.executed.comm
entry_price = abs(value / order.executed.size)
self.info('%s: %s order executed: price: %.2f, value: %.2f, commm %.2f, '
'cost basis: %.2f, stock price: %s' %
(stock, order.ref, order.executed.price, order.executed.value,
order.executed.comm, entry_price, order.p.data[0]))
self.orders.remove(order)
def notify_trade(self, trade):
if trade.isclosed:
self.info('Trade value: %.2f, P/L %.2f, P/L comm: %.2f' %
(trade.value, trade.pnl, trade.pnlcomm ))
def eod(self):
"""
Calculate time of eod actions.
"""
if self.data0.datetime.date() in self.early_closes:
return datetime.time(12, 30)
else:
return datetime.time(15, 54)
def next(self):
"""Main processing is happening here. This method is called for every bar."""
# check to make sure all data has been accumulated before calling next
ctime = self.data0.datetime.time(0, tz=self.p.timezone)
cdate = self.data0.datetime.date(0, tz=self.p.timezone)
cdt = self.data0.datetime.datetime(0, tz=self.p.timezone)
tz = self.p.timezone
# skip bars from history
if datetime.datetime.now(tz=tz) - tz.localize(self.data0.datetime.datetime()) >= datetime.timedelta(minutes=1):
return
# check to make sure all data has been accumulated before calling next
if len(set(data.datetime.datetime() for data in self.datas )) > 1:
# skip processing if all minute bars not arrived yet
return
# don't process bars after 16:00 or before 9:30
for data in self.datas:
if data.datetime.time(0, tz=self.p.timezone) < datetime.time(9,30) or self.data.datetime.time(0, tz=self.p.timezone) > datetime.time(16,0,0):
return
for i, stock in enumerate(self.p.stocks):
# get position data - first method using getdatabyname(stock)
data_min = self.datas[i]
position = self.getposition(data_min)
# if there is no position and it's not set to zero, create a short order for it
if not position.size and self.position_sizes[stock] != 0:
order = self.sell(data_min, size=self.position_sizes[stock])
self.orders.append(order)
self.stop_orders[stock] = ''
# second position method
data_min2 = self.getdatabyname(stock.split("-")[0])
position2 = self.getpositionbyname(stock.split("-")[0])
position_size2 = position2.size
entry_price2 = position2.price
self.info(" \n getpositionbyname method %s position size: %s entry price: %s" %(stock, position_size2, entry_price2))
# demonstrate stop orders not being set
if position.size != 0 and position.price:
self.entry_price[stock] = position.price
self.info("%s position size: %s entry price: %s current close: %s" %(stock, position.size, position.price, data_min.close[0]))
if not self.stop_orders.get(stock) and self.entry_price[stock]:
stop_price = self.entry_price[stock] + .3
stop_order = self.close(data_min, price=stop_price, exectype=bt.Order.Stop)
self.stop_orders[stock] = stop_order
self.orders.append(stop_order)
self.info(" %s: stop order sent at %.2f" %(stock,stop_price))
def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Live strategy run on Interactive Brokers')
parser.add_argument('--host', default='127.0.0.1',
required=False, action='store',
help='Host for the Interactive Brokers TWS Connection')
parser.add_argument('--qcheck', default=0.5, type=float,
required=False, action='store',
help=('Timeout for periodic '
'notification/resampling/replaying check'))
parser.add_argument('--port', default=7497, type=int,
required=False, action='store',
help='Port for the Interactive Brokers TWS Connection')
parser.add_argument('--clientId', default=None, type=int,
required=False, action='store',
help='Client Id to connect to TWS (default: random)')
parser.add_argument('--reconnect', default=3, type=int,
required=False, action='store',
help='Number of recconnection attempts to TWS')
parser.add_argument('--timeout', default=180, type=float,
required=False, action='store',
help='Timeout between reconnection attempts to TWS')
parser.add_argument('--debug',
required=False, action='store_true',
help='Display all info received form IB')
return parser.parse_args()
def main(argv):
"""Entry point."""
args = parse_args()
# Create a cerebro entity
cerebro = bt.Cerebro(maxcpus=1, oldsync=False)
# Set broker
broker = bt.brokers.IBBroker(host=args.host, port=args.port,
clientId=args.clientId, reconnect=args.reconnect,
timeout=args.timeout, _debug=args.debug)
cerebro.setbroker(broker)
# Create store
store = bt.stores.IQFeedStore(reconnect=args.reconnect,
timeout=args.timeout,
debug=args.debug)
# Add data feeds
for stock in Strategy.params.stocks:
ticker = stock.split('-')[0]
hist_start_date = bdate_range(end=datetime.datetime.now(), periods=Strategy.params.stdperiod + 2)[0].to_pydatetime()
data = bt.feeds.IQFeed(dataname=ticker, name=ticker,
tz=Strategy.params.timezone,
timeframe=bt.TimeFrame.Minutes,
sessionstart=datetime.time(hour=9, minute=30),
sessionend=datetime.time(hour=16, minute=00, second=59),
fromdate=hist_start_date)
cerebro.adddata(data)
# Print out the starting conditions
print 'Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()
# Add a strategy
cerebro.addstrategy(Strategy)
# Run over everything
cerebro.run()
if __name__ == '__main__':
sys.exit(main(sys.argv))