feat: WebSocket client v2 (ws_v2)#151
Draft
Conversation
…hreading/lifecycle model, making (un)subscribing actions idempotent and introducing Pydantic models at input and output
… expecting only str and Enum
…and changed subscription_controller._bindings key from Subscription to new the binding_key. - added SubscriptionResolver which allow SubscriptionController to automatically detect binding_keys that need confirmation on (un)subscriptions - finished implementing ibkr_subscriptions
# Conflicts: # ibind/ibkr_ws_v2/ibkr_events.py
…marking as degraded across WsRuntime
…amed 'channel' to 'topic', implemented QueueSink as replacement of QueueController/Accessor chore(ws_v2): cleaned up ws_runtime and ws_transport
fix(ws_v2): fixed TransportEvent attempts refactor(ws_v2): renamed ClientInternalEvents to LifecycleEvents
This was referenced May 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Full refactor of the WebSocket handling in IBind.
Work is still in progress, no changes are final.
Available as
0.2.1rc1.Overview:
The
IbkrWsClientV1 implementation - existing until now - had several unfavourable angles:Although usable, it required close familiarity with the documentation, presented slightly awkward usage, and its threading model lead to complex logic, difficult testability and race conditions.
The
IbkrWsClientV2 at its core introduces better threading and lifecycle management. It is implemented using three threads:As a result, most connectivity and authentication issues are handled gracefully without need of restarting threads or recreating the WebSocketApp instance.
This refactor was also taken as an opportunity to re-evaluate the API, resulting in a number of improvements to the subscription API, event consumption and connection health. Some responsibilities previously expected of the user are now handled by the client out of the box.
BREAKING CHANGE:
channelanddatain favour of new PydanticSubscriptionclasses which encapsulate necessary parameters for each topic, reducing ambiguity and necessity to depend on IBKR docs (as discussed inIbkrWsKeyshould haveconidfor market data subscriptions #18)EventSinkprotocol instead of depending on theQueueAccessorAPI. Currently implemented areCallbackSink(propagating events into user-specified callbacks),QueueSink(supporting the QueueAccessor API),CompositeSink(allowing to mix multiple sinks together) andLogSink(logging all events, useful for debugging). Custom sinks that implementEventSinkprotocal are accepted.WsEventclass events, replacing propagatingdictobjectsBindingobjects, used for managing the lifecycle of each subscription. As a result,IbkrWsClient.subscribe()andIbkrWsClient.unsubscribe()are no longer blocking and instead they return aSubscriptionHandlethat can be waited on using.wait()SubscriptionProcessorclass - insteadSubscriptionobjects define the subscribe and unsubscribe payload generationrestart_on_closeandrestart_on_criticalparameters - both are now fixed as true: the WebSocketApp will always reconnect on close, and the transport thread will always recreate a new WebSocketApp on critical.connected,readyandrunningproperties - now replaced byis_running()andget_state()IbkrWsKey- identifying subscriptions and events now uses concreteWsEventclass types instead of the enumQueueAccessor- currently still available throughQueueSink.new_queue_accessor()for backwards compatibility. InsteadQueueSinkserves as a 1-1 replacement forQueueAccessor, passing appropriate queue-identifying key to itsget()andempty()methods.Feature:
expiry_secondsallowing to periodically resubscribe (fixes Feature request: auto-refresh IBKR WS smd subscriptions on 15-min server-side expiry #145)LIFECYCLEkey, facilitating responding to lifecycle changes in user applicationsSubscriptionProcessor(as demonstrated in https://github.com/Voyz/ibind/blob/master/examples/ws_03_market_history.py)Refactor:
ibind.ibkr_ws_clientlogger.Chore:
channelwas replaced bytopicto align with IBKR nomenclature (as discussed inIbkrWsKeyshould haveconidfor market data subscriptions #18)Behind The Scenes
QueueControllerSubscriptionControllerIbkrWsClient(still present inIbkrClient)Implementation Details
Event Lifecycle
Most events received by the WebSocket go through the following flow:
WebSocketAppTransportEventand enqueued in runtime transport queueTransportEventTransportEventIbkrRouterintoWsEventinstances (multiple instances can be generated from a single message)WsEventAsyncSinkby default, or directly to user sink if disabled)WsEventTwo event classes
TransportEventandWsEvent- rather than just a single commonEvent- are used to create clear separation of concerns.TransportEventinstances are used only forWebSocket -> Clientinternal communication, whileWsEventinstances are used for both inner-client andClient -> User Applicationcommunication.Event Propagation
Events are now propagated using a dedicated thread. Once received by the client, they're parsed and enqueued into an outgoing queue. The new thread then consumes and propagates the events through sinks.
This ensures that slow user application code does not affect the functionality of the client.
This method can be disabled using a parameter, causing event propagation to be carried out by the runtime thread, which may be useful for debugging.
Example Usage
In V1:
In V2:
Sink types:
Roadmap:
I'm very much open to feedback - please feel free to share it here if it is related to the refactor, or as a new issue if you'd like to suggest other changes/enhancements.
You can install and test this version out as
0.2.1rc1.