Monday, 19 May 2014

Getting accounting data out of interactive brokers API into python with swigibpy

This is the final post. Not the final post of the blog; which may be good news or bad. But the final post in my short series on using swigibpy to interact with the C++ API for interactive brokers (IB).

Interactive brokers now have a native python API. An updated version of this post which uses that API is here: http://qoppac.blogspot.co.uk/2017/03/placing-orders-in-native-python-ib-api.html

Having got some prices and submitted some orders we want to know whether we made any money or not. Although the code is rather trivial, interpreting the results requires some understanding.

http://qoppac.blogspot.co.uk/2017/03/getting-position-and-accounting-data.html

So once again you need to get the code from the following repo.

The example code to run is in test5_IB.py and wrapper_v5.py. This will give your results with live or simulated Gateway / TWS session running (it probably won't produce anything of interest with a demo account but you can still follow the code). Note that IB have recently updated their Gateway software so you might need to re-download.


The output from test5_IB.py should be something like what is in green below.

Expiry is 20140908




Portfolio

([('3KTB', '20140617', 12, 106.0, 1272000000.0, 105842090.65714166, 1894912.11, 0.0, 'DU99', 'KRW'), ('BTP', '20140606', 2, 123.2049942, 246409.99, 121025.33335, 4359.32, 0.0, 'DU99', 'EUR'), ('EMG', '', 1100000, 0.91125, 1002374.99, 0.85376155, 63237.28, 0.0, 'DU99', 'GBP')]

account info

[('AccountCode', 'DU99', '', 'DU99'), ('AccountReady', 'true', '', 'DU99'), ('AccountType', 'INDIVIDUAL', '', 'DU99'), 

<SNIP>

, ('WarrantValue', '0', 'USD', 'DU99')]

 

 

Contract details - again?


Note we reuse the contract details code in http://qoppac.blogspot.co.uk/2014/05/the-placing-of-orders-to-interactive.html

The reason we do this is as before to correctly identify our positions we need the yyyymmdd expiry associated with the contract; although the matching up process isn't done here.

 

Positions


We're going to use a single client function to get both accounting and position data. However both of these are going to be looking at streamed accounting updates. This is returned by two different kinds of wrapper function.
 

Since writing this blog IB have introduced an API call to get positions directly - so there are less clunky ways of doing this than I show here. 

 From wrapper_v5.py, class IBclient: 


    def get_IB_account_data(self):

        self.cb.init_portfolio_data()
        self.cb.init_error()
       
        ## Turn on the streaming of accounting information
        self.tws.reqAccountUpdates(True, self.accountid)
       
        start_time=time.time()
        finished=False
        iserror=False

        while not finished and not iserror:
            finished=self.cb.flag_finished_contractdetails
            iserror=self.cb.flag_iserror

            if (time.time() - start_time) > MAX_WAIT_SECONDS:
                finished=True
                print "Didn't get an end for account update, might be missing stuff"
            pass

        ## Turn off the streaming
        ## Note portfolio_structure will also be updated
        self.tws.reqAccountUpdates(False, self.accountid)

        portfolio_data=self.cb.data_portfoliodata
        account_value=self.cb.data_accountvalue

       
       
        if iserror:
            print self.cb.error_msg
            print "Problem getting details"
            return None

        return (account_value, portfolio_data)


From wrapper_v5.py, class IBWrapper:

    def init_portfolio_data(self):
        if "data_portfoliodata" not in dir(self):
            setattr(self, "data_portfoliodata", [])
        if "data_accountvalue" not in dir(self):
            setattr(self, "data_accountvalue", [])
          
      
        setattr(self, "flag_finished_portfolio", False)
      

    def updatePortfolio(self, contract, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName):
        """
        Add a row to the portfolio structure
        """

        portfolio_structure=self.data_portfoliodata
              
        portfolio_structure.append((contract.symbol, contract.expiry, position, marketPrice, marketValue, averageCost,
                                    unrealizedPNL, realizedPNL, accountName, contract.currency))

    ## account value
  
    def updateAccountValue(self, key, value, currency, accountName):
        """
        Populates account value dictionary
        """
        account_value=self.data_accountvalue
      
        account_value.append((key, value, currency, accountName))
      

    def accountDownloadEnd(self, accountName):
        """
        Finished can look at portfolio_structure and account_value
        """
        setattr(self, "flag_finished_portfolio", True)

So once we begin streaming both the portfolio_structure and account_value global variables begin updating. Note that with portfolio_structure we have the same 'gotcha' as before, namely that we can't just drop the contract object into the global variable, as all we will get is a pointer that is meaningless outside of the wrapper method. Instead we need to save specific things of interest like contract.symbol.

Portfolio data

The output portfolio_data will be a list of tuples (contract.symbol, contract.expiry, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName, contract.currency). Note that the value, cost, PNL will be in local currency. You can extract exchange rates from the account_value information - which I discuss below.

Lets take an example:

('BTP', '20140606', 2, 123.2049942, 246409.99, 121025.33335, 4359.32, 0.0, 'DU99', 'EUR')

This is the June 2014 BTP (Italian 10 year bond) which I am long 2 in account DU99 (not my real account number) which is trading at 123.205 Euros per contract. I should use the contract information from above to match this to the yyyymm contracts my system knows about. My two contracts are worth Euro 246,410 because each contract price point is worth 1000 Euros (I can get this information from the contract details call).

I actually bought each of these things for 121,025 Euros each (so 121.025 in price points). This ill conceived lending of a quarter of a million Euros to the most unstable government in the Euro zone has so far earned me Euros 4359.32 (246,410 minus 2 * 121,025; or 2 * [123.205 - 121.025] * 1000).

Note my realized PnL is zero since I obviously haven't sold any of these futures yet (no quick profit taking for account DU99).

Here is the corresponding exchange rate from the account_value output.


[... ("ExchangeRate", 0.815491, "EUR", "DU99"),....
 ("ExchangeRate", 1.00000, "GBP", "DU99"),... ]

From this we can make out that my base currency is GBP, and 1 Euro is worth 0.815491 GBP. So I can convert my ill gotten gains in monopoly money ECB issued Euros 4359.32 * 0.815491 = £3,555 in real money.

For more information see the online documentation
C++ > Class EWrapper Functions > Account and Portfolio 

Note be careful about using comparisions between these portfolio snapshots from different times to calculate your profits. They won't take trades into account very well, and if you have sold something completely or hadn't bought it for the first snapshot the comparision wont' work.

Finally also be very careful about using these snapshots to work out what positions you have. Firstly it is common to get the position coming back repeated times. So you will need to filter out duplicates. Secondly positions may sometimes not appear at all.

I use my own record of trades to find out what my current position should be. I compare this to the IB snapshot throughout the day. If there are any breaks in the reconciliation I temporarily halt trading. Occasionally this is because IB has sent me a fill I haven't picked up, or because the IB position snapshot is missing some contracts.


Accounting information


The account_value output will be a very long list of tuples. Each one consists of (keyname, value, currency, account number). Currency BASE indicates it is the accounts base currency (GBP for me). Here are some of the more interesting account value entries.

  • ExchangeRate: This is what we use in the get positions example above. 
  •  FuturesPNL: This is how much we have made trading futures today, by currency. BASE is the total in base currency.
  • StockMarketValue: Self explanatory
  • NetLiquidation: This is what is my account worth if I close all my positions out (ignoring commissions and bid-ask). Its what I use to ultimately determine the total profit and loss used for rescaling my risk capital.
  • CashBalance: Quite self explanatory. Negative means you have borrowed money. BASE is the net of your balances converted into base currency.

When you initially do a futures trade in a non BASE country you will have to borrow money for initial margin. Only if you move into profit beyond this will you have no net borrowing. IB charges you for borrowing money! This is done at LIBOR+spread so is expensive for currencies with higher interest rates (this spread is also why I use IMM's to get direct currency exposure). You can flatten out your exposure by doing a spot FX trade. Personally I try and keep a small positive balance in all currencies, although not excessive as this means I am taking on currency risk. Note you can't trade all possible pairs eg if you find you can't buy AUDGBP then try selling GBPAUD instead. The exception is Korea where you can't hold any speculative currency exposure i.e. not arising from margin requirements in other kinds of trading. All you are allowed to do is flatten your Korean currency position back to zero.

There are many more keywords than shown above. The best advice I have for trying to understand what they all mean is to start with a clean simulated account (a demo account is no good since you are exposed to other peoples random trading, and the account will often be 'empty'). Take a dump of the account value output, then do a trade. Take another dump, then wait for prices to move. By comparing each dump you should be able to see how account value, margin, cash and so on interact. This is quite time consuming but definitely worth it for an insight into how the IB accounts operate for the kind of instruments you want to trade.

Anything missing?


I haven't covered the full gamut of what you can get from the IB API. There are some newer methods put in since I began using it to get positions and account summary information. There is a much richer variety of price and option data that is available. I haven't looked at fundamental data, option data, scanners and managed accounts because I don't use them. However I hope there is enough meat in this series of examples to get you started using IB with swigibpy, and to avoid wasting time trying to understand some of the weirder behaviour.

This is the final of a series of five posts on constructing a simple interface in python to the IB API using swigiby. The first four posts are:
http://qoppac.blogspot.co.uk/2014/03/using-swigibpy-so-that-python-will-play.html

http://qoppac.blogspot.co.uk/2014/04/getting-prices-out-of-ib-api-with.html 

http://qoppac.blogspot.co.uk/2014/04/streaming-prices-from-ib-api-swigibpy.html

http://qoppac.blogspot.co.uk/2014/05/the-placing-of-orders-to-interactive.html


If you've found this series useful then your next steps might be to learn how to design a systematic trading strategy, and to understand the nuts and bolts of creating a fully automated system.

8 comments:

  1. Hi all, here every person is sharing these kinds of know-how, so it’s nice to read this website, and I used to go to see this blog daily.

    Historical Options Data

    ReplyDelete
  2. Very nice series of posts. Thanks for taking the time to write them down and share.

    ReplyDelete
  3. Great series of posts, I found them useful. Thanks for taking the time to share.

    ReplyDelete
  4. Hi Rob,

    Thank you so much for doing this. This is epic. I've followed all of your posts, understand everything, and the only thing I haven't been able to get to work is the get_IB_account_data method. Everything else works perfectly. I have changed my account ID to the appropriate one, and left your code unchanged. If you'd be willing to take a look at my setup please let me know. Thanks again!

    ReplyDelete
    Replies
    1. You're welcome. Can you email me at rob @ qoppac.com with a screen dump of what happens?

      Delete
    2. Sent from Fishman.jk (at) gmail (dot) com. Let me know if there's anything else you need to diagnose what's going on. Thanks again!

      Delete
  5. Hi Rob,

    apparently Im having the same problem as Fishman1049.

    Thank you for writing this blog!

    Regards
    Martin

    ReplyDelete
    Replies
    1. Just try updating all your code, because there was a patch that was put in that you might not be picking up (that was fishman's problem). If that doesn't work then can you please send the full python trace to rob@qoppac.com; plus wrapper_v5.py so I can check it's the right version.

      Delete