ib-api-reloaded / ib_async

Python sync/async framework for Interactive Brokers API (replaces ib_insync)
BSD 2-Clause "Simplified" License
463 stars 73 forks source link

Fixing whatIfOrder request drop off without orderState being updated #42

Closed praditik closed 4 months ago

praditik commented 4 months ago

This if statement is changed as UNSET_DOUBLE and orderState.initMarginChange were not equal to start with. Hence the request would get dropped even without the orderState being populated. I have been using this library with this modification for more than a month now and this fix works to solve the issue for whatIfOrder feature

mattsta commented 4 months ago

oh wow this actually explains a lot.

So the library assumes UNSET_DOUBLE is the universal float_max value of 1.7976931348623157e+308 but sometimes we get an un-parsed IBKR value directly of 1.7976931348623157E308 instead, so the wrong "unset" value is causing early exit of some requests by mistake.

I've always wondered why some order previews work (limit orders) and others just refuse to show any details (relative orders, midprice orders, etc).

Looking through my logs, it appears the library is parsing IBKR's unset value to 1.7976931348623157e+308 when missing account or instrument data:

icli-id=4-2023-12-13 00:29:17.244095-05:00-ibkr.log
12:2023-12-12 21:29:17,405 updatePortfolio: PortfolioItem(marketPrice=1.7976931348623157e+308, marketValue=1.7976931348623157e+308, averageCost=1.7976931348623157e+308, unrealizedPNL=0.0, realizedPNL=0.0, account='XX')
13:2023-12-12 21:29:17,405 updatePortfolio: PortfolioItem(marketPrice=1.7976931348623157e+308, marketValue=1.7976931348623157e+308, averageCost=1.7976931348623157e+308, unrealizedPNL=0.0, realizedPNL=0.0, account='XX')

But IBKR gives us 1.7976931348623157E308 when requesting order details and we weren't parsing it correct for the "wait until all data has arrived" check:

icli-id=0-2024-03-04 22:23:27.759024-05:00-icli-color.log
2946:    initMarginBefore='1.7976931348623157E308',
2947:    maintMarginBefore='1.7976931348623157E308',
2948:    equityWithLoanBefore='1.7976931348623157E308',
2949:    initMarginChange='1.7976931348623157E308',
2950:    maintMarginChange='1.7976931348623157E308',
2951:    equityWithLoanChange='1.7976931348623157E308',
2952:    initMarginAfter='1.7976931348623157E308',
2953:    maintMarginAfter='1.7976931348623157E308',
2954:    equityWithLoanAfter='1.7976931348623157E308',

So, an even simpler fix is probably just parsing the string float back into a standard float we expect as:

if float(orderState.initMarginChange) != UNSET_DOUBLE:

because python knows enough to convert the float E syntax into the other number we expect:

In [1]: import sys

In [2]: sys.float_info.max
Out[2]: 1.7976931348623157e+308

In [3]: float("1.7976931348623157E308")
Out[3]: 1.7976931348623157e+308

In [4]: float("1.7976931348623157E308") == sys.float_info.max
Out[4]: True

I added this change to my own dev tools version and it's working correctly for me now (non-market hours though), but I'll also run this fix for a day or two to confirm then add it to a new release.

Thanks for tracking this down! I had always just assumed the order preview system was broken for non limit orders.

praditik commented 4 months ago

Thanks for getting back. You are welcome and I would be happy to help maintain the library/write examples as I have been using ib_insync for more than 2 years extensively.

Thanks for the simpler fix using float conversion.

mattsta commented 4 months ago

Looking back further, the previous incorrect fix was from https://github.com/erdewit/ib_insync/issues/403 which was also trying to fix https://github.com/erdewit/ib_insync/issues/380 so this has been around for years and hopefully is finally fixed properly now.

Their primary confusion was the IBKR value being compared against wasn't parsed to the native python type yet, so it would never match the check as expected because the problem wasn't only "comparing number against string." Essentially str(UNSET_DOUBLE) is never correct to use (because the value compared against should be parsed into a proper native type before the comparison). I double checked all other source files and str(UNSET_DOUBLE) isn't being used anywhere else now.