erdewit / ib_insync

Python sync/async framework for Interactive Brokers API
BSD 2-Clause "Simplified" License
2.85k stars 774 forks source link

Duplicate order ID #308

Closed romanrdgz closed 4 years ago

romanrdgz commented 4 years ago

I have run into this problem (with latest 0.9.63, but also if running with 0.9.62 from a poetry environment i still hold without updating to latest version).

The strange thing is that this is happening in a piece of software I did not touch in the last days (and worked perfectly fine on previous days), and started to show this problem yesterday, each time I create a new order from there. Rebooting the TWS and my piece of software didn't help. Moreover, other users are successfully running this software without this error, both of us with latest TWS. I also reviewed TWS API settings, but nothing seemed out of place.

The error:

Canceled order: Trade(contract=Contract(secType='BAG', symbol='NVDA', exchange='SMART', currency='USD', comboLegs=[ComboLeg(conId=405841033, ratio=1, action='SELL', exchange='', openClose=0, shortSaleSlot=0, designatedLocation='', exemptCode=-1), ComboLeg(conId=446712248, ratio=1, action='BUY', exchange='', openClose=0, shortSaleSlot=0, designatedLocation='', exemptCode=-1)]), order=LimitOrder(orderId=322, clientId=20, action='BUY', totalQuantity=10, lmtPrice=0.05, transmit=False), orderStatus=OrderStatus(orderId=322, status='Cancelled', filled=0, remaining=0, avgFillPrice=0.0, permId=0, parentId=0, lastFillPrice=0.0, clientId=0, whyHeld='', mktCapPrice=0.0), fills=[], log=[TradeLogEntry(time=datetime.datetime(2020, 10, 29, 13, 17, 40, 708901, tzinfo=datetime.timezone.utc), status='PendingSubmit', message=''), TradeLogEntry(time=datetime.datetime(2020, 10, 29, 13, 18, 1, 982503, tzinfo=datetime.timezone.utc), status='Cancelled', message='Error 103, reqId 322: Duplicate order id')])
Error 103, reqId 325: Duplicate order id

I am not specifying reqIds: I am letting ib_insync take care of this.

I coded a minimal example which (at least in my case) reproduces the problem:

from ib_insync import IB, ComboLeg, Contract, LimitOrder

ib = IB()
ib.client.setConnectOptions('+PACEAPI')
ib.client.MaxRequests = 0 # Disables throttling
ib.connect('127.0.0.1', 7496, clientId=20)

combo = Contract(symbol='NVDA', secType='BAG', exchange='SMART', currency='USD', comboLegs=[
    ComboLeg(conId=448268928, ratio=1, action='SELL'),
    ComboLeg(conId=441119400, ratio=1, action='BUY')
])

# Create the order and launch a Trade referred to the option combo contract
#limit_order = LimitOrder('BUY', 2, 0.0, transmit=False, displaySize=1)
limit_order = LimitOrder('BUY', 1, 0.0, transmit=False)
ib.placeOrder(combo, limit_order)

ib.sleep(5)
ib.disconnect()
print('Done!')

Of course this happens also with any other combo, just using the same conId in the example for simplicity. How can I further investigate this problem?

erdewit commented 4 years ago

The script runs fine for me. To further investigate the first step would be to see if there really is another order with the same orderId.

Note that this line of code: https://github.com/erdewit/ib_insync/blob/08b40b6e5e5e5d6b4117385a22b984241663971d/ib_insync/wrapper.py#L368 should guard against duplicate order IDs being issued.

amitante commented 4 years ago

I ran into this problem lately too, I think that the problem happens in this scenario:

  1. connecting to IB after a session with many trades
  2. while ibinsync is downloading the previous trades -> create and send a new order.
  3. DUPLCIATE IDEA appear. (It can explain why it's hard to reproduce)
romanrdgz commented 4 years ago

Not my case.

It seems that TWS got updated and somehow my settings got back to default. I reviewed the API settings and left makrd only "Enable ActiveX and Socket Clients", "Use negative numbers to bind automatic orders" and "Expose entire trading schedule to API".

Hence, not an ib_insync problem.

erdewit commented 4 years ago
  • while ibinsync is downloading the previous trades -> create and send a new order.

This can only happen if an order is sent when ib.connect() is not finished yet, which is not advisable. After ib.connect() has finished, all completed and open orders have been synchronized.

mattsta commented 3 years ago

Ran into this recently and it looks like there's a simple fix!

After initial connect, the max ID is being set here instead of using the proper API's max value:

https://github.com/erdewit/ib_insync/blob/2c669c49759428760b85c5d4f6629cb31a13fbcc/ib_insync/wrapper.py#L373

Example of the error:

After my fix below, on startup the max request id is set properly to 566 and increments from there. (It appears the IBKR backend is only incrementing the minimum reqId value for trade requests and not market data requests too?)

So the fix is to make _reqIdSeq initialization wait on a flag for the server-side max ID instead of assuming the highest current order ID is the maximum ID in the system (since it can't see canceled orders, but those still consumed IDs which can't repeat):

@ client.py:343 @ class Client:
                     # snoop for nextValidId and managedAccounts response,
                     # when both are in then the client is ready
                     msgId = int(fields[0])
+                    setId = False
                     if msgId == 9:
                         _, _, validId = fields
                         self._reqIdSeq = int(validId)
+                        setId = True
                     elif msgId == 15:
                         _, _, accts = fields
                         self._accounts = [a for a in accts.split(',') if a]
-                    if self._reqIdSeq and self._accounts:
+                    if setId and self._accounts:
                         self._apiReady = True
                         self.apiStart.emit()

(optionally, the setId could also be an instance variable on Client itself if there's a potential race condition between receiving both the self.accounts update and the reqId out of order)