├── .gitignore ├── README.md ├── config.py ├── main.py ├── model ├── __init__.py └── toy_model.py └── util ├── __init__.py ├── trade.py ├── util.py └── watcher.py /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_store 3 | .AppleDouble 4 | 5 | # Python 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | *.pyc 10 | 11 | # Data 12 | *.csv 13 | /log/ 14 | /data/ 15 | /tradeConfirmations/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-IB-Trader 2 | 3 | this is a minimal automated trader for American stock using **official** Interactive Brokers python API (ibapi) 4 | 5 | ## dependencies 6 | 7 | * Interactive Brokers' account! 8 | 9 | * ibapi v9.73.05 10 | * IB Gateway 964 or later 11 | * python 3.5 or later 12 | 13 | ## usage 14 | 15 | * login IB Gateway. 16 | * implement your model for trading under `model/`. 17 | * setting `config.py` 18 | * run `main.py` before market open everyday 19 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from datetime import datetime, timedelta 3 | 4 | 5 | root_dir = '/Users/haqishen/trader_ibapi/' 6 | 7 | symbols = ['QQQ'] # symbols to trade 8 | black_list = [] 9 | 10 | today = datetime.today() 11 | # summer time in Japan is 22:30:00 12 | # winter time in Japan is 23:30:00 13 | RTH_begin = datetime( 14 | today.year, 15 | today.month, 16 | today.day, 17 | 22, 30, 0 18 | ) 19 | RTH_end = RTH_begin + timedelta(hours=6, minutes=29) 20 | 21 | account = 'UXXXXXX' 22 | cash = 20000.0 23 | 24 | port = 4002 # IB Gateway 25 | IP = '127.0.0.1' # localhost 26 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | ''' 3 | main function 4 | ''' 5 | 6 | import time 7 | import config 8 | from util.watcher import Watcher 9 | from model.toy_model import Model 10 | from util.trade import IBClientApp 11 | from datetime import datetime 12 | 13 | 14 | def main(): 15 | 16 | # app for sending orders 17 | app = IBClientApp(config.IP, config.port, 1) 18 | app.getConnection() 19 | 20 | # app for watching market price 21 | watcher = Watcher(config.IP, config.port, 2, config.symbols) 22 | watcher.getConnection() 23 | watcher.begin() 24 | 25 | # load model 26 | model = Model( 27 | symbols=config.symbols, 28 | cash=config.cash, 29 | app=app 30 | ) 31 | 32 | # main iteration, run every 5 secs 33 | is_begin = False 34 | t = time.time() 35 | while True: 36 | 37 | # get current time 38 | cur_time = datetime.today() 39 | 40 | # close position and exit the program 41 | if cur_time > config.RTH_end: 42 | print('| Stop!') 43 | model.stop(watcher.data) 44 | info = app.getAccInfo() 45 | print(info) 46 | break 47 | 48 | # run the model 5 seconds a time if is in Regular Trading Hours. 49 | if cur_time > config.RTH_begin: 50 | if not is_begin: 51 | is_begin = True 52 | print('| Begin!') 53 | model.run(watcher.data, cur_time) 54 | 55 | while time.time() - t < 5: 56 | time.sleep(0.0002) 57 | t += 5 58 | 59 | 60 | if __name__ == '__main__': 61 | main() 62 | -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haqishen/python-IB-Trader/bf07d415748c4ceefb835448888a99f4800f544a/model/__init__.py -------------------------------------------------------------------------------- /model/toy_model.py: -------------------------------------------------------------------------------- 1 | ''' 2 | this is a toy model only for demonstrating the workflow of this repo. 3 | ''' 4 | 5 | 6 | class Model(): 7 | 8 | def __init__(self, symbols, cash, app): 9 | self.is_init = False 10 | self.symbols = symbols 11 | self.app = app 12 | self.cash = {} 13 | 14 | self.Open = {} 15 | self.Flag = {} 16 | self.Hold = {} 17 | for symbol in symbols: 18 | self.cash[symbol] = cash // len(symbols) 19 | self.Flag[symbol] = 0 20 | self.Hold[symbol] = 0 21 | 22 | def initiate(self, cur_price): 23 | for key in cur_price.keys(): 24 | self.Open[key] = cur_price[key] 25 | self.is_init = True 26 | 27 | def run(self, cur_price, cur_time): 28 | if not self.is_init: 29 | self.initiate(cur_price) 30 | else: 31 | for symbol in cur_price.keys(): 32 | if cur_price[symbol] > (self.Open[symbol] * 1.01) and self.Flag[symbol] != 1: 33 | quantity = self.cash[symbol] // cur_price[symbol] 34 | self.app.sendOrderToServer(symbol, quantity) 35 | self.Hold[symbol] = quantity 36 | self.Flag[symbol] = 1 37 | 38 | def stop(self, cur_price): 39 | for symbol in cur_price.keys(): 40 | if self.Flag[symbol] != 0: 41 | quantity = self.Hold[symbol] 42 | self.app.sendOrderToServer(symbol, -quantity) 43 | self.Hold[symbol] = 0 44 | self.Flag[symbol] = 0 45 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haqishen/python-IB-Trader/bf07d415748c4ceefb835448888a99f4800f544a/util/__init__.py -------------------------------------------------------------------------------- /util/trade.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import time 3 | import util 4 | import config 5 | from ibapi import comm # read message returned from API 6 | from ibapi.account_summary_tags import AccountSummaryTags 7 | from ibapi.utils import iswrapper 8 | 9 | 10 | class IBClientApp(util.IBWrapper, util.IBClient): 11 | 12 | def __init__(self, IP, port, clientId, logger=None): 13 | util.IBWrapper.__init__(self) 14 | util.IBClient.__init__(self, wrapper=self) 15 | self.IP = IP 16 | self.port = port 17 | self.clientId = clientId 18 | self.logger = logger 19 | self.info = {'positions': []} 20 | self.history_data = {} 21 | self.order_record = [] 22 | self.permId2ord = {} 23 | 24 | def getConnection(self): 25 | self.connect(self.IP, self.port, self.clientId) 26 | self.reqIds(-1) 27 | self.getMessage(1) 28 | 29 | # ValidID 30 | def getNextValidId(self): 31 | self.valid_id += 1 32 | return self.valid_id 33 | 34 | @iswrapper 35 | def nextValidId(self, orderId): 36 | super().nextValidId(orderId) 37 | self.valid_id = orderId 38 | 39 | @iswrapper 40 | def error(self, reqId, errorCode, errorString): 41 | super().error(reqId, errorCode, errorString) 42 | if int(errorCode) >= 2000: 43 | return 44 | print('| Server return an error! reqId: %s, errorCode:%s, msg:%s' % ( 45 | reqId, errorCode, errorString)) 46 | 47 | def getAccInfo(self): 48 | self.info = {'positions': []} 49 | self.reqAccountSummary(102, "All", AccountSummaryTags.AllTags) 50 | self.reqPositions() 51 | time.sleep(2) 52 | self.cancelAccountSummary(102) 53 | self.getMessage(1) 54 | return self.info 55 | 56 | @iswrapper 57 | def accountSummary(self, reqId: int, account: str, tag: str, value: str, 58 | currency: str): 59 | super().accountSummary(reqId, account, tag, value, currency) 60 | if (tag == 'TotalCashValue'): 61 | self.info['cash'] = value 62 | if (tag == 'NetLiquidation'): 63 | self.info['total'] = value 64 | 65 | @iswrapper 66 | def accountSummaryEnd(self, reqId: int): 67 | super().accountSummaryEnd(reqId) 68 | return 69 | 70 | @iswrapper 71 | def position(self, account, contract, position, avgCost): 72 | super().position(account, contract, position, avgCost) 73 | tmp = [contract.symbol, contract.secType, contract.currency, position, avgCost] 74 | self.info['positions'].append(tmp) 75 | 76 | @iswrapper 77 | def positionEnd(self): 78 | super().positionEnd() 79 | return 80 | 81 | def getHistoryData(self, reqId, symbol, queryTime, lastFor='10000 S', timeGap='5 secs'): 82 | contract = util.createContract(symbol, 'STK', 'SMART', 'SMART', 'USD') 83 | self.reqHistoricalData( 84 | reqId, contract, queryTime, 85 | lastFor, timeGap, 'TRADES', 1, 1, [] 86 | ) 87 | 88 | @iswrapper 89 | def historicalData(self, reqId, date, open, high, 90 | low, close, volume, barCount, 91 | WAP, hasGaps): 92 | super().historicalData(reqId, date, open, high, low, close, volume, 93 | barCount, WAP, hasGaps) 94 | 95 | if reqId not in self.history_data.keys(): 96 | self.history_data[reqId] = [] 97 | 98 | single_row = '%s,%s,%s,%s,%s,%s\n' % ( 99 | date, open, high, low, close, volume 100 | ) 101 | self.history_data[reqId].append(single_row) 102 | 103 | @iswrapper 104 | def historicalDataEnd(self, reqId: int, start: str, end: str): 105 | super().historicalDataEnd(reqId, start, end) 106 | 107 | def sendOrderToServer( 108 | self, 109 | symbol, 110 | quantity, 111 | sec_type='STK', 112 | primary_exch='SMART', 113 | price=None 114 | ): 115 | 116 | contract = util.createContract( 117 | symbol, 118 | sec_type, 119 | 'SMART', 120 | primary_exch, 121 | 'USD' 122 | ) 123 | action = "BUY" if quantity > 0 else "SELL" 124 | order = util.createOrder(action, abs(quantity), price) 125 | orderId = self.getNextValidId() 126 | 127 | print('|- Place order. ID is %d' % orderId) 128 | self.placeOrder(orderId, contract, order) 129 | self.order_record[orderId] = [symbol, action, False] 130 | 131 | @iswrapper 132 | def orderStatus(self, orderId, status, filled, 133 | remaining, avgFillPrice, permId, 134 | parentId, lastFillPrice, clientId, 135 | whyHeld): 136 | super().orderStatus(orderId, status, filled, remaining, 137 | avgFillPrice, permId, parentId, 138 | lastFillPrice, clientId, whyHeld) 139 | 140 | if status != 'Filled' or self.order_record[orderId][2]: 141 | return 142 | symbol = self.order_record[orderId][0] 143 | action = self.order_record[orderId][1] 144 | self.order_record[orderId][2] = True 145 | 146 | try: 147 | msg = '| %s Filled! %s quantity:%d avgPrice:%.2f Total:%.2f\n' % ( 148 | time.strftime('%Y%m%d %H:%M:%S'), 149 | action, filled, avgFillPrice, 150 | filled * avgFillPrice 151 | ) 152 | print(msg) 153 | self.logger.log(symbol, msg) 154 | except Exception: 155 | print('| Error in logger!') 156 | 157 | @iswrapper 158 | def openOrder(self, orderId, contract, order, orderState): 159 | super().openOrder(orderId, contract, order, orderState) 160 | # OpenOrder. ID: 2 UVXY STK @ SMART : BUY MKT 10.0 PreSubmitted 161 | print("OpenOrder. ID:", orderId, contract.symbol, contract.secType, 162 | "@", contract.exchange, ":", order.action, order.orderType, 163 | order.totalQuantity, orderState.status) 164 | order.contract = contract 165 | self.permId2ord[order.permId] = order 166 | 167 | @iswrapper 168 | def openOrderEnd(self): 169 | # ! [openorderend] 170 | super().openOrderEnd() 171 | print("OpenOrderEnd") 172 | # ! [openorderend] 173 | print("Received %d openOrders" % len(self.permId2ord)) 174 | 175 | def getMessage(self, wait=3): 176 | time.sleep(wait) 177 | while not self.msg_queue.empty(): 178 | text = self.msg_queue.get(block=True, timeout=0.2) 179 | fields = comm.read_fields(text) 180 | self.decoder.interpret(fields) 181 | 182 | 183 | # FOR DEBUG 184 | if __name__ == '__main__': 185 | app = IBClientApp(config.IP, config.port, clientId=230) 186 | app.getConnection() 187 | contract = util.createContract('UVXY', 'STK', 'SMART', 'SMART', 'USD') 188 | app.disconnect() 189 | -------------------------------------------------------------------------------- /util/util.py: -------------------------------------------------------------------------------- 1 | from ibapi.wrapper import EWrapper 2 | from ibapi.client import EClient 3 | from ibapi.contract import Contract 4 | from ibapi.order import Order 5 | 6 | 7 | def createContract( 8 | symbol, 9 | sec_type='STK', 10 | exch='SMART', 11 | prim_exch='ISLAND', 12 | currency='USD' 13 | ): 14 | contract = Contract() 15 | contract.symbol = symbol 16 | contract.secType = sec_type 17 | contract.exchange = exch 18 | contract.primaryExch = prim_exch 19 | contract.currency = currency 20 | return contract 21 | 22 | 23 | class IBClient(EClient): 24 | def __init__(self, wrapper): 25 | EClient.__init__(self, wrapper) 26 | 27 | 28 | class IBWrapper(EWrapper): 29 | def __init__(self): 30 | EWrapper.__init__(self) 31 | 32 | 33 | def createOrder(action, quantity, limit_price=None): 34 | 35 | def LimitOrder(action, quantity, limit_price): 36 | # ! [limitorder] 37 | order = Order() 38 | order.action = action 39 | order.orderType = "LMT" 40 | order.totalQuantity = quantity 41 | order.lmtPrice = limit_price 42 | # ! [limitorder] 43 | return order 44 | 45 | def MarketOrder(action, quantity): 46 | # ! [market] 47 | order = Order() 48 | order.action = action 49 | order.orderType = "MKT" 50 | order.totalQuantity = quantity 51 | # ! [market] 52 | return order 53 | 54 | if limit_price is None: 55 | return MarketOrder(action, quantity) 56 | else: 57 | return LimitOrder(action, quantity, limit_price) 58 | -------------------------------------------------------------------------------- /util/watcher.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import sys 3 | import time 4 | import util 5 | import config 6 | from threading import Thread 7 | from ibapi.utils import iswrapper 8 | 9 | 10 | class Watcher(util.IBWrapper, util.IBClient): 11 | 12 | def __init__(self, IP, port, clientId, symbols): 13 | util.IBWrapper.__init__(self) 14 | util.IBClient.__init__(self, wrapper=self) 15 | self.IP = IP 16 | self.port = port 17 | self.clientId = clientId 18 | self.symbols = symbols 19 | self.data = {} 20 | self.Open = {} 21 | self.High = {} 22 | self.Low = {} 23 | self.access = [] 24 | self.initialize() 25 | 26 | def getConnection(self): 27 | self.connect(self.IP, self.port, self.clientId) 28 | 29 | def initialize(self): 30 | for symbol in self.symbols: 31 | self.High[symbol] = -1.0 32 | self.Low[symbol] = 9999. 33 | 34 | @iswrapper 35 | def tickPrice(self, reqId, tickType, price, attrib): 36 | super().tickPrice(reqId, tickType, price, attrib) 37 | 38 | symbol = self.symbols[reqId] 39 | 40 | if symbol not in self.access and tickType in range(1, 5): 41 | print('| %s real-time data accessed!' % symbol) 42 | self.access.append(symbol) 43 | if tickType == 4: 44 | self.data[symbol] = price 45 | if price > self.High[symbol]: 46 | self.High[symbol] = price 47 | if price < self.Low[symbol]: 48 | self.Low[symbol] = price 49 | 50 | @iswrapper 51 | def tickSize(self, reqId, tickType, size): 52 | super().tickSize(reqId, tickType, size) 53 | 54 | def begin(self): 55 | for tickerId in range(len(self.symbols)): 56 | symbol = self.symbols[tickerId] 57 | 58 | contract = util.createContract( 59 | symbol, 60 | 'STK', 61 | 'SMART', 62 | 'SMART', 63 | 'USD' 64 | ) 65 | self.reqMktData(tickerId, contract, "", False, False, []) 66 | 67 | thread = Thread(target=self.run) 68 | thread.start() 69 | setattr(self, "_thread", thread) 70 | 71 | 72 | # FOR DEBUG 73 | if __name__ == '__main__': 74 | 75 | sys.path.append('../') 76 | watcher = Watcher(config.IP, config.port, 2351, config.symbols) 77 | watcher.getConnection() 78 | watcher.begin() 79 | 80 | for i in range(50): 81 | # watcher.getMessage() 82 | print() 83 | print('| '+time.strftime('%H:%M')) 84 | for symbol in watcher.data.keys(): 85 | print('| %s : %.2f' % (symbol, watcher.data[symbol])) 86 | 87 | time.sleep(10) 88 | 89 | watcher.disconnect() 90 | --------------------------------------------------------------------------------