
This is the second part of the Set Up Trading API Template In Python. We’re going to focus on implementing the rest of the functions in our Interactive Broker class.
If you enjoy reading this and my other articles, feel free to join Medium membership program to read more about Quantitative Trading Strategy.
Previous researches
- 【Momentum Trading】A Defense Trading Strategy That Works - CPPI (Constant Proportion Portfolio Insurance)
- 【How 2】Set Up Trading API Template In Python - Connecting My Trading Strategies To Interactive Brokers
Recap and what’s the next
In the previous post, we get to know how our trading script sends API calls to the IBKR API service via the IB gateway. Also, we have learned how to configure the IB gateway application. Lastly, we also showcase the code snippet to get the available cash balance and the total investment market value under your account from the server. Now, we’ll look at the rest of the functions in our InteractiveBrokerTradeAPI
class.
- Get a much more detailed status report with get_account_detail
- Fetch the market calendar
- How to create a valid order for Interactive Broker
API document reference
Get a deep dive status report with get_account_detail
The get_account_detail()
in our earlier example has successfully extracted the TotalCashBalance
and StockMarketValue
from the ib.accountValues()
response. Yet, if we would like to know more about our portfolio status, we can include two more calls to obtain more information about our portfolio: 1. positions in our portfolio, 2. the orders that we placed in the past 24 hours.
For 1., we use ib.portfolio()
to acquire information on the stocks we hold. We will extract the position size and the market value of each symbol, thus gaining a bigger picture of how our portfolio looks like.1
2[PortfolioItem(contract=Stock(conId=42454579, symbol='SHV', right='0', primaryExchange='NASDAQ', currency='USD', localSymbol='SHV', tradingClass='NMS'), position=427.0, marketPrice=109.9701004, marketValue=46957.23, averageCost=109.98463535, unrealizedPNL=-6.21, realizedPNL=0.0, account='DU4399668'),
PortfolioItem(contract=Stock(conId=39622943, symbol='SSO', right='0', primaryExchange='ARCA', currency='USD', localSymbol='SSO', tradingClass='SSO'), position=1060.0, marketPrice=47.5340004, marketValue=50386.04, averageCost=48.7388397, unrealizedPNL=-1277.13, realizedPNL=-62.91, account='DU4399668')]
Response from ib.portfolio() call
As for 2., we use ib.trades()
to obtain the trades we made in the past 24 hours. Remember, the Interactive broker holds this information for only 24 hours or so, and you won’t be able to retrieve this piece once the server drops this information. Therefore, we will find a way to address this in another post to persist the order-related information. In the below Trade objects, we extract the information we need such as order id, average price, order status, commission cost, and so on for each symbol.1
[Trade(contract=Stock(conId=42454579, symbol='SHV', right='?', exchange='SMART', currency='USD', localSymbol='SHV', tradingClass='NMS'), order=Order(permId=423185966, action='SELL', orderType='MKT', lmtPrice=0.0, auxPrice=0.0, tif='DAY', ocaType=3, displaySize=2147483647, rule80A='0', openClose='', volatilityType=0, deltaNeutralOrderType='None', referencePriceType=0, account='DU4399668', clearingIntent='IB', cashQty=0.0, dontUseAutoPriceForHedge=True, filledQuantity=1.0, refFuturesConId=2147483647, shareholder='Not an insider or substantial shareholder'), orderStatus=OrderStatus(orderId=0, status='Filled', filled=0.0, remaining=0.0, avgFillPrice=0.0, permId=0, parentId=0, lastFillPrice=0.0, clientId=0, whyHeld='', mktCapPrice=0.0), fills=[Fill(contract=Stock(conId=42454579, symbol='SHV', right='?', exchange='SMART', currency='USD', localSymbol='SHV', tradingClass='NMS'), execution=Execution(execId='00025b49.63971bea.01.01', time=datetime.datetime(2022, 12, 12, 17, 2, 38, tzinfo=datetime.timezone.utc), acctNumber='DU4399668', exchange='EDGEA', side='SLD', shares=1.0, price=109.97, permId=423185966, clientId=0, orderId=0, liquidation=0, cumQty=1.0, avgPrice=109.97, orderRef='', evRule='', evMultiplier=0.0, modelCode='', lastLiquidity=2), commissionReport=CommissionReport(execId='00025b49.63971bea.01.01', commission=1.002648, currency='USD', realizedPNL=-1.017284, yield_=0.0, yieldRedemptionDate=0), time=datetime.datetime(2022, 12, 12, 17, 2, 38, tzinfo=datetime.timezone.utc))], log=[TradeLogEntry(time=datetime.datetime(2022, 12, 12, 17, 2, 38, tzinfo=datetime.timezone.utc), status='Filled', message='Fill 1.0@109.97', errorCode=0)], advancedError='')]
Response from ib.trades() call
Combining everything we talked about above, we can construct our get_account_detail()
function as below:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46def get_account_detail(self):
self.accounts = self.client.managedAccounts()
acc_data = []
for account in self.accounts:
acc = {}
acc['account'] = account
data = self.client.accountValues(account)
acc['cash'] = 0
acc['total_assets'] = 0
for row in data:
if row.tag in ['TotalCashBalance'] and row.currency == self.currency:
acc['cash'] = row.value
acc['total_assets'] += float(row.value)
elif row.tag in ['StockMarketValue'] and row.currency == self.currency:
acc['total_assets'] += float(row.value)
acc_data.append(acc)
pos_data = []
data = self.client.portfolio()
for position in data:
pos = {}
pos['code'] = position.contract.symbol
pos['qty'] = position.position
pos['cost_price'] = position.averageCost
pos['market_val'] = position.marketValue
pos['pl_val'] = position.unrealizedPNL
if pos['cost_price'] * pos['qty'] == 0:
pos['pl_ratio'] = 0
else:
pos['pl_ratio'] = pos['pl_val'] / (pos['cost_price'] * pos['qty'])
pos_data.append(pos)
orders_data = []
data = self.client.trades()
for order in data:
o = {}
o['order_id'] = order.order.orderId
o['order_status'] = order.orderStatus.status
o['create_time'] = order.log[-1].time
o['trd_side'] = order.order.action
o['order_type'] = order.order.action
o['code'] = order.contract.symbol
orders_data.append(o)
return acc_data, pos_data, orders_data
Full code of get_account_detail() function
Fetch the market calendar
The trading hours information in ib_insync
package is quite discreet. After reading the API document very carefully, I finally found it in the response of ib.reqContractDetails()
call and look like this:1
2
3
4
5
6
7
8
9
10
11
12ContractDetails(contract=Contract(secType='STK', conId=756733, symbol='SPY', exchange='SMART', primaryExchange='ARCA', currency='USD', localSymbol='SPY',
tradingClass='SPY'), marketName='SPY', minTick=0.01, orderTypes='ACTIVETIM,AD,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,BENCHPX,CASHQTY,COND,CONDORDER,DARKONLY,
DARKPOLL,DAY,DEACT,DEACTDIS,DEACTEOD,DIS,DUR,GAT,GTC,GTD,GTT,HID,IBKRATS,ICE,IOC,LIT,LMT,LOC,MIDPX,MIT,MKT,MOC,MTL,NGCOMB,NODARK,NONALGO,OCA,OPG,OPGREROUT,PEGBENCH,
PEGMID,POSTATS,POSTONLY,PREOPGRTH,PRICECHK,REL,REL2MID,RELPCTOFS,RTH,SCALE,SCALEODD,SCALERST,SIZECHK,SMARTSTG,SNAPMID,SNAPMKT,SNAPREL,STP,STPLMT,SWEEP,TRAIL,TRAILLIT,
TRAILLMT,TRAILMIT,WHATIF', validExchanges='SMART,AMEX,NYSE,CBOE,PHLX,ISE,CHX,ARCA,ISLAND,DRCTEDGE,BEX,BATS,EDGEA,CSFBALGO,JEFFALGO,BYX,IEX,EDGX,FOXRIVER,PEARL,NYSENAT,
LTSE,MEMX,IBEOS,PSX', priceMagnifier=1, underConId=0, longName='SPDR S&P 500 ETF TRUST', contractMonth='', industry='', category='', subcategory='', timeZoneId='US/Eastern',
tradingHours='20221212:0400-20221212:2000;20221213:0400-20221213:2000;20221214:0400-20221214:2000;20221215:0400-20221215:2000;20221216:0400-20221216:2000',
liquidHours='20221212:0930-20221212:1600;20221213:0930-20221213:1600;20221214:0930-20221214:1600;20221215:0930-20221215:1600;20221216:0930-20221216:1600', evRule='',
evMultiplier=0, mdSizeMultiplier=1, aggGroup=1, underSymbol='', underSecType='', marketRuleIds='26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26',
secIdList=[TagValue(tag='ISIN', value='US78462F1030')], realExpirationDate='', lastTradeTime='', stockType='ETF', minSize=0.0001, sizeIncrement=0.0001,
suggestedSizeIncrement=100.0, cusip='', ratings='', descAppend='', bondType='', couponType='', callable=False, putable=False, coupon=0, convertible=False,
maturity='', issueDate='', nextOptionDate='', nextOptionType='', nextOptionPartial=False, notes='')
Response of ib.reqContractDetails() functions
The trading calendar resides in this response, and we can extract them by parsing them like this:
1 | def is_market_open(self, offset_days=0): |
Full code of is_market_open() and is_market_open_now() functions
How to create a valid order for Interactive Broker
In order to create a valid order that Interactive Broker could recognize, there are a few steps to follow:
- Specify the symbol and the currency used. Use
contract = ib_insync.Stock(symbol, 'SMART', self.currency)
to create acontract
object for later use. Stock symbol would be the first parameter, the name of the stock exchange be the second, and the currency symbol (here we useUSD
) would be the third. - Make a query to the broker to filter and find the related stock information.
ib.qualifyContracts(contract)
would activate thecontract
object and infuse live data from the stock exchange.Contract object before and after using ib.qualifyContracts() to infuse correct data
- We need the latest quote price in order to calculate how many shares we would like to purchase. First of all, you need to specify the
reqMarketDataType
to tell the server which type of data you’re requesting. There are four market data types:- 1 - Live market data: (top of the book)
- 2 - Frozen data (at the close)
- 3 - Delayed data (can be used if there are no live subscriptions)
- 4 - Frozen Delayed data (outside of regular trading hours)
Once we have specified the market data type, we’re all set to request the quote price from the server using thecontract
in your first parameter andsnapshot
to True.One thing worth mentioning is that, do you remember the reason why I’m using the1
2
3
4
5
6
7
8ib.reqMarketDataType(3)
quote = ib.reqMktData(
contract,
genericTickList="",
snapshot=True,
regulatorySnapshot=False,
mktDataOptions=None
)ib_insync
package instead of the native IBKR API? Instead of saying fetching a quote price from the API server, subscribing to the periodical price change would be a better way to put it. We first subscribe to the price bar to get 5-minute, 10-minute, or one-day price data, and another thread would be created to stream the price data. Therefore, to extract the quote price from the returned object, you first must ensure the quote price has been successfully returned/received.Notes: Before requesting a market quote, you need to subscribe to the market data on the IBKR platform. You can find the management page in the TWS or IB gateway tab “Account” -> “Manage Account” -> “Subscribe Market Data/Research”
- Lastly, other than using
sleep()
function call to ensure that we have received the price data from the server, we can also assign the callback function to monitor the status of a specific order status change. Here we use a global configuration under theib
instance to specify this callback function by usingib.orderStatusEvent += [callback function]
1 |
|
Let’s wrap it up
We have all the pieces ready except the get_transaction()
, which we will talk about it in another post as we need to take Database management into account. Let’s now add some test code so that you can also place the order.
1 | # test.py |
And here’s the output.
1 | # Output |
Voila! Now as long as we schedule the time for each function to run, we will have our automated trading script ready to run! It’s time for you to put on your creative hat and start improvising, adding your own magic to your trading script. See you next time.