Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Evidence

What was measured, and on what. Status is assigned from a named artifact — a test, a script, or a recorded server response — not from reading the code.

StatusDefinition
✅ SupportedImplemented; exercised against IBKR production servers; the answer is delivered to the caller
✅ DocumentedA property of the venue rather than a call: established against IBKR production servers, with nothing for this package to implement
✅ OfflineImplemented, and held by the offline suite on every run; not yet exercised against IBKR production servers
— Not yetNot in this repository yet

The suites

SuiteCountNeeds a session
tests/python3182 of them. The other 315 run offline, and 1 a Python version condition excludes
tests/ib_async_upstreamib_async’s own suite, 3 tests at 2.1.0 (ab629f34c1), written to run on the engine; not run against the venue at this revisionYes
scripts/3 checks against a paper account; not run against the venue at this revisionYes

CI builds the engine from source with its test hooks, at the engine commit this package is tested against (c1852350), and runs tests/python on every push to main and every pull request, on Python 3.11, 3.13 and free-threaded 3.14t, against this package installed from the wheel and from the source distribution it builds, never from the checkout. The same runs fail when a test count the book or the README publishes differs from what the suite collects. It also installs the wheel beside the engine built as a release builds it, without its test hooks, and imports it. The workflow passes no credentials, so there the two live tests are skipped; they run against the venue locally, with IB_USERNAME and IB_PASSWORD set.

A release is the Python package and the Rust client together. Its workflow refuses a tag until the Rust client is in the repository and nothing on this page is still “Not yet”, then runs the suite again against the tag’s wheel and source distribution before attaching them to the release.

Surfaces

SurfaceStatusVerification
ib_async_dx is ib_async’s public API✅ Offline__all__ equal to ib_async’s, and every name ib_async’s own object except IB and IBC, __version__ among them; ib_async’s 13 submodules under both import forms: tests/python/test_the_package_is_ib_async.py
ib_async’s IB on the engine, through attach✅ OfflineIts events, *Async calls and types, with no gateway. All 67 transport calls their IB makes are carried, read out of their source on every run by test_every_call_their_library_makes_is_carried. Written for the venue and not run against it at this revision: test_an_unmodified_program_runs_on_this_engine and test_an_order_lives_its_whole_life_through_their_api, in tests/python/test_ib_async_transport.py
Their client’s send and sendMsg✅ OfflineEach of the 80 requests their client writes as a message, written by their own code through send, reads back into the message it was and reaches the engine exactly as the same request made by name; a message that does not read is refused with 320, and one naming no request is logged and unanswered: tests/python/test_a_raw_message_is_the_request_it_names.py
ib_async_dx.IB on the engine✅ OfflineThe same client, installed by its connect, with the login, the positions fix and the competing-session warning: tests/python/test_ib_runs_on_the_engine.py. Written for the venue and not yet run there: their own suite through tests/ib_async_upstream/conftest.py; scripts/sdk_sweep.py (every read, printing what came back; places nothing), scripts/sdk_lifecycle.py and scripts/order_round_trip.py (an order placed, changed and withdrawn on paper); the eight notebooks
The session’s life: on the loop, a pass a batch; a login cancelled, overtaken or refused; a session ended as it opens or once open✅ Offlinetests/python/test_a_session_opens_and_closes_on_the_loop.py
The four ib_async bug fixes✅ Offlinetests/python/test_ib_runs_on_the_engine.py and tests/python/test_a_session_opens_and_closes_on_the_loop.py; see Beyond ib_async
The engine’s calls beyond the documented API, on IB✅ Offlinetests/python/test_ib_runs_on_the_engine.py; see Beyond ib_async
reqCorporateActions, reqSpreadScan, positionsElsewhere, accountValuesElsewhere✅ Offlinetests/python/test_ib_runs_on_the_engine.py; see Beyond ib_async
TickerExtras.statedRows✅ Offlinetests/python/test_ib_runs_on_the_engine.py; see Beyond ib_async
IBC with ib_async’s own Watchdog✅ OfflineA reconnect loop, connecting with its IBC’s login and again when the session ends, each Watchdog with its own: tests/python/test_ib_runs_on_the_engine.py
Rust client— Not yetComing, on the engine’s public API

ib_async’s own suite. Its three tests at 2.1.0, from their commit ab629f34c1, have not been run against the venue at this revision, so none of them is a result here. test_request_error_raised asserts a RequestError carrying code 321 from a refused what-if, which their own IB never raises: it counts 321 as a warning, and a warning never ends the request it belongs to. ib_async_dx.IB ends it with the refusal, which test_a_what_if_refused_with_321_ends_with_the_refusal holds offline. test_contract_format_data_pd builds its own ib_async.IB(), which the runner’s conftest makes this package’s before their tests are collected, so it connects to the engine.

The package is ib_async

All offline, in tests/python/test_the_package_is_ib_async.py.

What holdsTest
__all__ is ib_async’s 103 names, and only IB and IBC are other objectstest_the_names_are_theirs_and_so_are_the_objects
__version__ and __version_info__ are ib_async’s, and this package’s version is __ib_async_dx_version__, outside __all__test_the_version_is_ib_asyncs_and_this_packages_is_apart
Every submodule of ib_async’s resolves here, as an attribute and as an importtest_every_submodule_of_theirs_resolves_here, test_from_a_submodule_import_works
ib_async_dx.ib is ib_async’s ib module with IB replaced, and a star import of it binds the names ib_async’s doestest_the_ib_module_is_theirs_with_ib_replaced
ib_async_dx.ibcontroller is ib_async’s ibcontroller module with IB and IBC replaced, its Watchdog ib_async’s owntest_the_ibcontroller_module_is_theirs_with_IB_and_IBC_replaced
IB is a subclass of ib_async’s, with ib_async’s own __init__, and attach is outside __all__test_ib_is_their_ib
connect and connectAsync are ib_async’s parameters, defaults and kinds, then four keyword-only onestest_connect_is_theirs_plus_the_login

IB, connected

All offline, on the engine’s test session, in tests/python/test_ib_runs_on_the_engine.py.

What holdsTest
The login comes from the environment when connect names none, and the session file is named for the accounttest_the_login_comes_from_the_environment_when_connect_names_none
A login named on connect is the one used, in ib_async’s positional form; the client id reaches the login; sessionFile=False keeps nothingtest_a_login_named_on_connect_is_the_one_used
A session is paper unless the program says livetest_a_session_is_paper_unless_the_program_says_live
An IB connects again after it disconnected, with ib_async’s own placeOrdertest_an_ib_connects_again_after_it_disconnected
A second connect closes the session the first openedtest_a_second_connect_closes_the_first_session
An IBC’s login is the one a connect naming none logs in with, paper unless its trading mode is live, and terminating it ends that sessiontest_an_ibc_carries_its_login_to_the_connect_and_terminates_its_session
Terminating an IBC from another context ends every session its login opened, and no later connect logs in with ittest_a_terminated_ibc_ends_every_session_of_its_login_and_lends_it_to_none
ib_async’s own Watchdog logs in with its IBC’s login, again after the session ends, and each Watchdog with its owntest_an_unmodified_watchdog_logs_in_with_its_ibcs_login
An order’s status names the client that placed it, so an order another client placed reaches its Tradetest_an_order_status_names_the_client_that_placed_the_order
A price reaches their ticker with the size that goes with it, and a size that changed alone as a size ticktest_a_price_carries_its_size_and_a_size_alone_reaches_tickSize
A refused new order reaches its Trade, and one refused with 321 is cancelledtest_a_refused_new_order_reaches_its_trade
An order placed once the engine has given the session up is refused after placeOrder has made its Trade, and the trade is cancelled before the closetest_an_order_placed_as_the_engine_gives_the_session_up_reaches_its_trade
A what-if refused with 321 ends with the refusal, as RequestError where RaiseRequestErrors is settest_a_what_if_refused_with_321_ends_with_the_refusal
321 on an order already working stays a warning, and the order is kepttest_321_on_an_order_already_working_stays_a_warning
The engine checks an option list as a gateway checks one: 10337 for another key, none taken on the two option computations, 10338 for another value, in the gateway’s words, under the request’s number, nothing senttest_an_option_list_is_checked_as_a_gateway_checks_one
An order stating an attribute the venue no longer takes is carried to the engine, which gives the notice a gateway givestest_an_order_stating_a_retired_attribute_reaches_the_engine_with_it
A handler asking again on every refusal is answered a refusal a pass, and does not hold the looptest_a_handler_that_asks_again_on_every_refusal_does_not_hold_the_loop
A wrapper that raises is logged, and the session carries ontest_a_wrapper_that_raises_is_logged_and_the_session_carries_on
An order id the venue names after the connect is not handed outtest_an_order_id_the_venue_names_after_the_connect_is_not_handed_out
On an account whose order ids have outgrown a request, a new order, a bracket and a what-if take the engine’s next order id and are taken, and requests go on from their own countertest_an_order_is_numbered_past_an_order_id_no_request_can_carry
Prices stated as the session ends reach the ticker before the closetest_prices_stated_as_the_session_ends_reach_the_ticker
A refusal held as the program disconnects does not reach the next sessiontest_a_refusal_held_as_the_program_disconnects_does_not_reach_the_next_session
Orders and requests are numbered from one counter, so an order’s refusal reaches the order and not a request still waitingtest_orders_and_requests_are_numbered_from_one_counter
updateEvent fires on what arrives, and timeoutEvent when nothing doestest_updateEvent_and_timeoutEvent_follow_what_arrives
fetchFields without POSITIONS asks for no positions at connect; ib_async 2.1 asks anywaytest_fetchFields_without_positions_asks_for_no_positions
reqUserInfo answers the White Branding ID; ib_async 2.1 answers []test_reqUserInfo_answers_the_white_branding_id
reqMktDataEx asks with the market data type named, with its option list for the engine to check, and None keeps the session’stest_reqMktDataEx_asks_with_the_market_data_type_named
reqCurrentTimeInMillis answers an int of milliseconds, within 2 s of the local clock on a session with no venuetest_reqCurrentTimeInMillis_is_the_venues_clock
The option model is read by the ticker’s request, and an unstated figure is Nonetest_the_option_model_is_read_by_the_tickers_request
What the engine states arrives in TickerExtras, OrderPreset and CompetingSessiontest_what_the_engine_states_is_carried_in_this_packages_types
The account’s grants are read from the enginetest_the_accounts_grants_are_read_from_the_engine
A ping reaches the enginetest_a_ping_reaches_the_engine
An extra called while not connected, before connect or after disconnect, raises ConnectionErrortest_an_extra_while_not_connected_raises
A record ib_async builds whole, a routing component, is answered from the engine and the session carries ontest_the_routing_components_arrive_as_their_records
connectionStats() counts the messages each way, carries the engine’s byte counts, and raises while not connectedtest_connectionStats_counts_the_messages_each_way
The competing-session warning is logged on ib_async_dx.ibtest_the_competing_session_warning_is_this_packages_own
A connect that completed is not failed by the warning after it: not by a stamp that does not parse, nor by a handler that disconnectedtest_a_completed_connect_is_not_failed_by_what_follows_it
priceBasedVol is a bool, False when the venue did not state ittest_priceBasedVol_is_a_bool_when_unstated
The size kept for a quote goes with its subscription, cancelled or a snapshot answeredtest_a_subscription_over_leaves_no_size_behind
ib_async’s own Watchdog, with this package’s IBC, connects and connects again when the session endstest_an_unmodified_watchdog_keeps_the_session_up
reqCorporateActions asks by the contract’s id, takes the answer, withdraws a query given up, and refuses a contract with no idtest_reqCorporateActions_asks_by_the_contracts_id_and_takes_the_answer, test_reqCorporateActions_given_up_withdraws_the_query, test_reqCorporateActions_needs_the_contracts_id
reqSpreadScan takes the first answer and cancels, its quotes go to the underlying’s ticker, unanswered in time it found nothing, and refused it cancels nothingtest_reqSpreadScan_takes_the_first_answer_and_cancels, test_reqSpreadScan_unanswered_in_time_found_nothing, test_reqSpreadScan_refused_cancels_nothing
A reqCorporateActions or reqSpreadScan the program disconnects under raises ConnectionError and withdraws nothingtest_a_request_the_program_disconnects_under_ends_with_the_session (2)
Holdings elsewhere and their figures are read apart from the account’s own, and a set is one of threetest_holdings_elsewhere_are_kept_apart, test_accountValuesElsewhere_names_one_of_three_sets

The session’s life

All offline, in tests/python/test_a_session_opens_and_closes_on_the_loop.py. Where a login has to be held open, the test engine keeps the engine’s own rule: a disconnect counted during a login drops the session it opens.

What holdsTest
A connect cancelled during its login tells the engine, and leaves no sessiontest_a_connect_cancelled_during_its_login_leaves_no_session
A disconnect() during the login ends the connect with ConnectionError, leaves no session, and returns at once while the login holds the enginetest_a_disconnect_during_the_login_ends_it
Of two connects on one IB, the later one’s session is left open and the earlier ends at once, on ib_async_dx.IB and on an attached ib_async.IBtest_a_later_connect_retires_an_earlier_one_still_logging_in (2)
Only the connect’s own startup request for positions is skippedtest_only_the_connects_own_startup_request_for_positions_is_skipped
A connect overtaken once logged in, still waiting on the venue, fails and leaves the later session open, on ib_async_dx.IB and on an attached ib_async.IBtest_a_connect_overtaken_after_its_login_leaves_the_later_session_open (2)
A connect overtaken in ib_async’s startup sync fails, and leaves the later session open with connectedEvent said once, whatever raiseSyncErrors saystest_a_connect_overtaken_in_its_startup_sync_fails_and_leaves_the_later_open (2)
Of three connects made at once, the last is left opentest_of_three_connects_at_once_the_last_is_left_open
A disconnect() from a handler the connect runs ends the connecttest_a_disconnect_from_a_handler_during_the_connect_ends_it
A login given up on does not hold the program opentest_a_login_given_up_on_does_not_hold_the_program_open
Under util.startLoop() a handler can wait on the session: a blocking request made in an errorEvent handler is answered, and a connect made in a disconnectedEvent handler of an attached ib_async.IB logs in, whether the engine ended the session or its IBC was terminated. Skipped on Python 3.14, where nest_asyncio cannot run asyncio’s timeoutstest_a_handler_can_wait_on_the_session_under_startLoop
A login given up on reaches nothing and keeps nothing, when the engine installs its session after the disconnect, and its engine’s close does not end the later sessiontest_a_login_given_up_on_reaches_nothing_and_keeps_nothing (2)
An interrupted connect() opens nothing latertest_an_interrupted_connect_opens_nothing_later
A handler that ends the session ends the pass: no held price and no held refusal reaches the cleared wrappertest_a_handler_that_ends_the_session_mid_pass_ends_the_pass, test_a_handler_that_ends_the_session_leaves_the_refusals_behind_it
Their wrapper is called on the loop’s thread only, what the engine announces from inside the login among it, and hears connectAck, managedAccounts and nextValidId theretest_their_wrapper_is_called_on_the_loops_thread_only
A program away from its loop queues no passes, and passes go on while the loop runstest_a_program_that_leaves_the_loop_queues_no_passes
A loop closed without disconnect() leaves no pass runningtest_a_loop_closed_without_disconnect_leaves_nothing_running
A session that ends as it opens fails the connect, says why on apiError, and nothing more is done for ittest_a_session_that_ends_as_it_opens_fails_the_connect_and_stops
A refused login raises ConnectionError, says why on apiError, and leaves the client disconnectedtest_a_login_refused_raises_ConnectionError_and_says_so
timeout does not bound the login; were the engine to wait for the next id after it, timeout would bound that (a guard: the engine answers at once)test_timeout_bounds_the_wait_after_the_login_not_the_login
Connecting an attached IB that is connected ends that session and opens a new onetest_connecting_an_attached_ib_that_is_connected_opens_a_new_session
serverVersion() is 0 until connectedtest_serverVersion_is_nought_until_connected
attach to a connected IB ends its session first, and unties the old clienttest_attach_to_a_connected_ib_ends_its_session_first
attach names no client idtest_attach_names_no_client_id
connect, run, reset, MaxRequests, RequestsInterval and events on the client mean what they mean on theirstest_the_client_level_names_mean_what_they_mean_in_ib_async
What leaves for the engine is paced: at most MaxRequests per RequestsInterval, the overflow deferred on the loop’s clock, throttleStart and throttleEnd said around the wait, 0 letting the whole burst leave at once, and a session ended mid-throttle leaving nothing queued for the nexttest_requests_past_the_rate_leave_an_interval_later
A request reached by name on the client takes keywordstest_a_request_reached_by_name_takes_keywords
A pass that raises ends the session oncetest_a_pass_that_raises_ends_the_session_once
What one pass delivers is one batch, on the looptest_what_one_pass_delivers_is_one_batch

What crosses to the engine

All offline, in tests/python.

What holdsTest
attach replaces the transport and nothing else: their IB, their wrappertest_ib_async_transport.py::test_attach_replaces_only_the_transport
A combination keeps its legs on every request path — details, quotes, barstest_ib_async_bridge_carries_everything.py::test_a_combination_keeps_its_legs_on_every_request_path
A contract named by more than its symbol keeps the rest: an ISIN, includeExpiredtest_ib_async_bridge_carries_everything.py::test_a_contract_named_by_more_than_its_symbol_keeps_the_rest
A contract named by its id states nothing else beside ittest_ib_async_transport.py::test_a_contract_named_by_id_states_nothing_else
An algo’s parameters, a combination’s routing and a soft-dollar tier reach the ordertest_ib_async_transport.py::test_what_tunes_an_algo_reaches_the_order
A field set to something the engine cannot carry is refused, by nametest_ib_async_transport.py::test_a_field_that_cannot_be_carried_is_refused
A field at ib_async’s default is carried as ib_async sends it; what it sends empty is left to the enginetest_ib_async_transport.py::test_a_field_at_their_default_is_carried
An order condition joined by or is carriedtest_ib_async_transport.py::test_a_condition_joined_by_or_is_carried
A fill’s cost reaches them as their own CommissionReporttest_ib_async_bridge_carries_everything.py::test_a_fills_cost_arrives_as_the_record_their_wrapper_reads, test_ib_async_depth.py::test_a_commission_report_reaches_them_as_their_own_type
A histogram reaches them as their HistogramDatatest_a_histogram_crosses_the_bridge_in_their_type.py (2)
A historical tick reaches them as their own recordtest_ib_async_depth.py::test_a_historical_tick_is_handed_over_as_their_own_record
A record tuple is rebuilt field by field, and a record named as theirs and of another type is rebuilt as theirstest_ib_async_bridge_carries_everything.py::test_a_record_tuple_is_rebuilt_field_by_field, ::test_a_record_named_as_theirs_and_not_theirs_is_rebuilt_as_theirs
A book level reaches their ticker, on both sides and below the toptest_ib_async_depth.py::test_a_book_level_reaches_their_ticker, ::test_the_other_side_and_a_deeper_level
A size with no price beside it is a size ticktest_ib_async_bridge_carries_everything.py::test_a_size_with_no_price_beside_it_is_a_size_tick
A bar carries its average price, a condition how it joins the next, and a record ib_async builds whole arrives wholetest_ib_async_bridge_carries_everything.py::test_a_bar_carries_its_average_price, ::test_a_condition_arrives_saying_how_it_joins_the_next, ::test_a_record_theirs_builds_whole_arrives_whole
A callback that cannot be rebuilt is logged and passed overtest_ib_async_bridge_carries_everything.py::test_a_callback_that_cannot_be_rebuilt_is_logged_and_passed_over
A refusal reaches their error in the four-argument shape it declarestest_a_refusal_reaches_their_wrapper.py (3)
Every account the login holds crosses overtest_ib_async_bridge_carries_everything.py::test_every_account_the_login_holds_crosses_over
A wide order id placed elsewhere leaves requests numberable, and no id past the widest a request carries is handed outtest_an_order_id_wider_than_a_request_leaves_requests_numberable.py (2), test_ib_async_depth.py::test_a_seeded_order_id_is_the_next_one_their_client_issues
readonly reaches the sessiontest_ib_async_transport.py::test_readonly_reaches_the_session_through_the_adapter
An outage leaves the session connected; a session the engine ends fires disconnectedEvent once, stops delivery and refuses requeststest_ib_async_transport.py::test_an_outage_leaves_the_session_open_and_an_end_closes_it
Ending a session is not reported as a session that went awaytest_ib_async_transport.py::test_ending_a_session_is_not_a_session_that_went_away
The handshake their client can send, verifyRequest and the three after it, is taken and answered by nothing, as over a gatewaytest_ib_async_transport.py::test_the_handshake_goes_unanswered_as_over_a_gateway
Their suite’s own ib_async.IB() is this package’s in the runnertest_ib_async_transport.py::test_their_suite_builds_this_packages_IB_where_it_builds_its_own
Every request their client writes as a message reads back into the message it was, an order field for field, and reaches the engine as the request it names, an order whole whatever parts of the message it writes; an order carries what their client writes and nothing else; a message that does not read is refused with 320, and one naming no request is logged and unanswered; nothing is sent while not connectedtest_a_raw_message_is_the_request_it_names.py (171)
Each of ib_async’s eight notebook subjects has a notebook here, and each is Python that connects through ib_async_dx on a paper sessiontest_the_notebooks_are_ib_asyncs_subjects.py (2)

Measured on the engine

These were measured on the engine this package runs on, with the client this package uses, and are recorded with the engine in ib-dx’s evidence, as it stood when they were taken.

CapabilityStatusWhat was seen
Market depth through ib_async✅ SupportedInserts, updates and deletes delivered as the venue states them, ten levels a side, through ib_async’s own ticker. Which venues answer is the account’s entitlement
Real-time bars through ib_async✅ SupportedFive-second bars during regular hours, each with open, high, low, close and volume, alongside a book on the same session, through ib_async’s own reqRealTimeBars
A session survives losing its connection✅ SupportedA dropped connection is rebuilt on the session already open, with no second factor: five forced drops recovered in 2–8 s, and an eight-hour session rode through its losses unattended
A session does not survive its process✅ DocumentedEnded without logging out, the session was gone forty seconds later, and a later start was answered with a full login rather than a challenge. A restart is therefore a new login. What that costs an account with a second factor has not been measured; a paper session presents none
A long session✅ SupportedOne session held for 175 minutes across a market open: 106,053 quotes, 180,433 trades, 95,985 book rows and 4,148 bars, with no unrequested disconnect