Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
ec8f145
feat: added version 2 of IbkrWsClient (still WIP) - refactoring the t…
Voyz Apr 29, 2026
ed19456
chore: updated to requests>=2.33 and added pydantic>=2.13
Voyz Apr 29, 2026
d263c0f
refactor(queue_controller): generic T is bound to Hashable instead of…
Voyz Apr 29, 2026
3d8fbe8
refactor(ws_v2): added binding_key (to Subscriptions and resolution) …
Voyz Apr 29, 2026
6c42477
fix(ws_v2): small fixes
Voyz Apr 29, 2026
772d985
fix(ws_v2): small fixes
Voyz Apr 29, 2026
7d25093
feat(ws_v2): added subscription handlers
Voyz Apr 30, 2026
9ecf495
Merge remote-tracking branch 'origin/feat/ws_v2' into feat/ws_v2
Voyz Apr 30, 2026
f481122
feat(ws_v2): added health checks handling and resets
Voyz May 1, 2026
40e8ea5
chore: updated wait_until's time usage from time.time to time.monotonic
Voyz May 1, 2026
87dbcff
feat(ws_v2): added WsDegraded event, removed WsReconnect and unified …
Voyz May 1, 2026
d693b10
feat(ws_v2): added automated handling of MarketHistory unsubscriptions
Voyz May 2, 2026
8aeb3e7
feat(ws_v2): added AsyncQueue and subscription expires
Voyz May 3, 2026
db5caa6
refactor(logs): project_logger now accepts both filepath and a normal…
Voyz May 3, 2026
68db65e
fix(ws_v2): small fixes
Voyz May 3, 2026
93c93fa
feat(ws_v2): added skip_utf8_validation, added wait_for(handles), ren…
Voyz May 3, 2026
da690bf
feat(ws_v2): deprecated IbkrWsKey - WsEvent types are used instead
Voyz May 3, 2026
90b9b22
chore(ws_v2): updated API with ws_v2
Voyz May 3, 2026
a68135d
chore(ws_v2): small reformatting
Voyz May 3, 2026
8749c2b
requirements: updated websocket-client to >=1.9 (from >=1.7)
Voyz May 3, 2026
77931ff
fix(logs): fixed project_logger incorrectly testing filepath
Voyz May 3, 2026
d33ddc0
fix(logs): fixed project_logger incorrectly testing filepath 2
Voyz May 3, 2026
ee6a601
chore: small reformats
Voyz May 3, 2026
68ecb8f
refactor(ws_v2): normalize event imports through `ibind.events`, re-e…
Voyz May 10, 2026
bffdbe5
fix(ws_transport): fixed return type of fetch_cookie for Python <=3.10
Voyz May 10, 2026
4f88a35
refactor(ws_v2): ws_v2.events was renamed to ws_v2._ws_events to indi…
Voyz May 10, 2026
d864d68
refactor(ws_v2): ws_v2.subscriptions was renamed to ws_v2.ws_subscrip…
Voyz May 10, 2026
295f253
docs: add TESTING.md
Voyz May 10, 2026
e612076
test: add test_ws_events.py
Voyz May 10, 2026
a32e7da
docs: added docstrings to ws_subscriptions.py
Voyz May 10, 2026
530438a
test: fixed ruff checks
Voyz May 10, 2026
fcedb06
test: add test_ws_subscriptions_u.py
Voyz May 10, 2026
6517698
refactor(ws_transport): small restructure to WsTransport class
Voyz May 10, 2026
5de6a7d
test: add test_ws_transport_u.py
Voyz May 10, 2026
61997e6
test: add test_ws_runtime_u.py
Voyz May 16, 2026
5dfa3f3
fix(ws_v2): fixed wait_until error messages not being fed through app…
Voyz May 16, 2026
b13ee44
fix(ws_v2): fixed ws_v2 import paths to include `ibind.`
Voyz May 16, 2026
0993c1b
fix(ws_runtime): fixed WsState enums and added is_ready()
Voyz May 16, 2026
409161b
feat(ibkr_ws_client_v2): added is_ready(), is_subscription_active() a…
Voyz May 16, 2026
688b949
chore(ibkr_subscriptions): ruff reformat
Voyz May 16, 2026
ee936e3
requirements(oauth): updated urllib3 from 2.3 to 2.7
Voyz May 16, 2026
bb4e2c7
chore(pyproject): updated dependencies: requests from 2.31 to 2.33, w…
Voyz May 16, 2026
b492a66
chore: added WsState, BindingStatus and make_binding_key to __init__ …
Voyz May 16, 2026
5ff61a8
chore: added __init__.py files
Voyz May 16, 2026
97ac169
test(ws_v2): fixed ruff checks
Voyz May 16, 2026
4f56e91
feat(ws_v2): added SubscriptionUpdated event, made CallbackSink.on id…
Voyz May 18, 2026
e27ae16
fix(ibkr_router): fixed IbkrError, Notification, Bulletin events and …
Voyz May 18, 2026
80d9131
chore(TransportEvent): improved `__str__` output
Voyz May 18, 2026
fed36bb
feat(ws_v2): aded sorting WsRuntime._transport_queue and AsynSink._qu…
Voyz May 18, 2026
424e15d
fix(ws_v2): fixed CallbackSink and QueueSink fields - previously inco…
Voyz May 18, 2026
e93fe7e
fix(ws_v2): synchronize websocket reset lifecycle using a threading.C…
Voyz May 22, 2026
dff46f7
fix(ws_v2): reset failed bindings on repeated intents
Voyz May 22, 2026
b3b7e41
fix(ws_v2): add exception handling to runtime thread main cycle
Voyz May 22, 2026
b7bf33e
fix(ws_v2): removed ready_state - now AUTHENTICATED is 'ready'
Voyz May 22, 2026
0e36ef5
docs: added docs/websocket
Voyz May 24, 2026
0e8be70
refactor(ws_runtime): broke out WsRuntime into separate modules under…
Voyz May 24, 2026
6a534e6
fix(mock_module_time): added time.monotonic mocking
Voyz May 24, 2026
4af5e6e
fix(MarketHistorySubscription): fixed get_server_id when there are no…
Voyz May 24, 2026
5d159cf
chore(support): small fixes
Voyz May 24, 2026
8297bd2
test: added ws_v2/ibkr_ws_v2 tests for ibkr_router, ibkr_subscription…
Voyz May 24, 2026
ada0fa5
test: added integration tests for ws_runtime
Voyz May 24, 2026
44fc3e5
docs: updated websocket docs
Voyz May 24, 2026
c3109c8
fix(ws_v2): small ruff fix to ws_lifecycle
Voyz May 24, 2026
cde350c
test(ws_v2): ruff check fixes
Voyz May 24, 2026
3c9bcc7
chore(ws_v2): removed recently commented out code
Voyz May 24, 2026
a7ef128
fix(ws_runtime): fixed Async sink initialisation by registering inter…
Voyz May 27, 2026
36ec579
docs(ws): added events, subscriptions and order-update-caveats
Voyz Jun 8, 2026
3192443
docs(ws): typo fixes in runtime-and-lifecycle and overview
Voyz Jun 8, 2026
a7c8108
fix(IbkrWsClientV2): fixed get_binding_status()
Voyz Jun 8, 2026
979febd
docs(ws_v2): added docstrings to ibkr_events
Voyz Jun 8, 2026
719b417
fix(ws_subscriptions): small fixes
Voyz Jun 8, 2026
2eba1a5
test: fixed unit tests to recent changes
Voyz Jun 8, 2026
6c490dc
test: added test.conftest with configure_logs to ensure all tests pro…
Voyz Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Testing Guide

This document defines how tests are chosen, written, and evaluated.

---

## Testing philosophy

- Use commentary with `## Arrange` `## Act` and `## Assert` sections for test structure.
- Tests exist to lock behaviour, not to chase coverage.
- Avoid tests that duplicate what the language/runtime already guarantees.
- Use fixtures for setup/teardown of test state.
- Mock internal dependencies for unit tests. Mock external dependencies for integration tests.
- Prefer integration tests for verifying component boundaries and data flow.
- Capture and assert on logs for error and warning conditions.

## Guidelines

- When `time.time` is used, use `mock_module_time` from `test.test_utils` rather than waiting for the time to pass.


## Test Type Structure

Tests are organized into three main categories:

```
test/
├── unit/ # Fast, isolated tests for core logic
├── integration/ # Multi-component tests
└── manual/ # Manual and performance tests
```


## Test types and boundaries

Use the lightest test type that still provides confidence.

### Unit tests
Use when:
- logic is isolated and deterministic
- behaviour can be validated without wiring other components

Guidelines:
- No network, filesystem, threads, or time dependence.
- Mock only at clear boundaries; do not mock internals of the unit under test.
- Data should be small, synthetic, and explicit.
- Prefer clarity over clever parametrisation.

### Integration tests
Use when:
- correctness depends on interaction between components
- data flow or ordering matters

Guidelines:
- Mock only outside the test boundary (eg. broker, network).
- Use realistic but minimal fixtures.
- Allow threads/timers only if they are part of the behaviour being tested.
- Failures should clearly indicate which interaction broke.

### Manual / performance tests
Use when:
- validating full-system flows
- measuring throughput, latency, or concurrency
- interacting with real or near-real external systems

Guidelines:
- Never run automatically in CI.
- Keep secrets out of test code.
- Prefer recorded or replayable inputs where possible.
- Treat results as diagnostic, not pass/fail gates.


## Choosing what to test

Test:
- decision logic
- state transitions
- boundary conditions
- error and warning paths
- behaviour that has broken before

Do not test:
- trivial getters/setters
- pure delegation
- obvious library behaviour
- formatting or logging text unless it signals correctness


## Running tests

- Prefer running the smallest relevant subset while iterating.
- Run broader suites when touching core or high-risk code.
125 changes: 125 additions & 0 deletions docs/websocket/core-concepts/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Events

Events in IBind WebSocket client are typed Pydantic dataclasses representing messages, errors, state changes and lifecycle updates, intended for consumption by the application code.

There are several types of events:

- Internal events - State changes, errors and subscription status updates.
- IBKR unsolicited events - Messages received from the IBKR server without a topic subscription.
- IBKR solicited events - Messages received from the IBKR server in response to a subscription request.
- IBKR auxiliary events - Meta-messages derived from the IBKR messages.


## Event Consumption

Events are automatically generated by the client and emitted using the Sink pattern. There are several built-in classes implementing the Sink protocol:

- CallbackSink - Sink that invokes registered callbacks for specific event types.
- QueueSink - Sink that stores events in separate queues per event type.
- CompositeSink - Sink that forwards events to multiple child sinks.
- LogSink - Sink that logs events using the project logger.

Example usage:

```python
from ibind import CallbackSink, events

def on_market_data(event: events.MarketData):
print(event)

sink = CallbackSink()
sink.on(events.MarketData, on_market_data)
```

See [Sinks documentation][sinks] to learn more about event consumption.

## Solicited and Unsolicited Messages
_(from [IBKR Campus documentation](https://www.interactivebrokers.com/campus/ibkr-api-page/cpapi-v1/#ws-messages))_

> There are two types of messages sent to clients via the websocket:
>
> * **Solicited messages**: Messages delivered to the client in response to an explicit, client-initiated subscription to a topic.
> * **Unsolicited messages**: Messages delivered automatically to the client by the server. These messages are not associated with a topic subscription that can be canceled, and they typically contain session or other administrative information.

## Event Types

### Base type

`WsEvent` is the WebSocket event base class used by all events.

Each WsEvent contains a `received_at` field that records the date and time the event was received from the WebSocket.

### Internal

Events generated by the WebSocket client as a result of state changes, errors and subscription status updates.

| Event Class | Represents | Attributes |
|-----------------------|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `WsStarting` | Connection is starting | - |
| `WsStopping` | Connection is stopping | - |
| `WsStopped` | Connection has stopped | - |
| `WsOpen` | Connection has successfully opened | - |
| `WsAuthenticated` | Connection is authenticated | - |
| `WsDegraded` | Connection entered a degraded state | - |
| `WsReady` | Connection is ready for use | - |
| `WsClose` | Connection is closed | <ul><li>`close_status_code` (int \| None): WebSocket close status code.</li><li>`close_msg` (str \| None): Close message.</li></ul> |
| `WsError` | Error occurred | <ul><li>`error` (Exception): The exception that occurred.</li></ul> |
| `SubscriptionUpdated` | Subscription status changes | <ul><li>`subscription` (Subscription): The subscription that changed.</li><li>`binding_key` (str): The binding key of the subscription.</li><li>`status` (BindingStatus): The new status of the subscription.</li></ul> |


### IBKR Unsolicited

Messages delivered automatically to the client by the server. These messages are not associated with a topic subscription that can be canceled, and they typically contain session or other administrative information.

| Event Class | Represents | Attributes |
|------------------------|------------------------------------|-----------|
| `IbkrError` | An error message from IBKR | <ul><li>`data` (dict): The raw error payload from IBKR.</li></ul> |
| `WaitingForSession` | Authentication is pending | - |
| `Notification` | IBKR notification | <ul><li>`data` (dict): The notification payload.</li></ul> |
| `Bulletin` | IBKR bulletin | <ul><li>`data` (dict): The bulletin payload.</li></ul> |
| `AccountUpdate` | Details of the current account | <ul><li>`data` (dict): The account update payload.</li></ul> |
| `System` | Connection confirmations | <ul><li>`data` (dict): The system message payload.</li></ul> |
| `AuthenticationStatus` | Session authentication status change | <ul><li>`data` (dict): The raw status payload.</li><li>`authenticated` (bool \| None): Whether the session is authenticated.</li><li>`competing` (bool \| None): Whether another session is competing for the same account.</li></ul> |

### IBKR Solicited

Messages delivered to the client in response to an explicit, client-initiated subscription to a topic.

See [IBKR WebSocket documentation][ibkr-websocket] for exact payload format.

| Event Class | Represents | Attributes |
|------------------|----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `AccountSummary` | Account summary messages | <ul><li>`account_id` (str): The account the summary belongs to.</li><li>`data` (dict): The account summary payload.</li></ul> |
| `AccountLedger` | Account ledger messages | <ul><li>`account_id` (str): The account the ledger entry belongs to.</li><li>`data` (dict): The ledger payload.</li></ul> |
| `MarketData` | Update to market data | <ul><li>`conid` (str): The contract identifier the update applies to.</li><li>`data` (dict): The changed market data fields.</li></ul> |
| `MarketHistory` | Historical OHLC bar | <ul><li>`conid` (str): The contract identifier the bars apply to.</li><li>`data` (dict): The historical bar payload.</li></ul> |
| `Orders` | Live order updates | <ul><li>`data` (dict): The order update payload.</li></ul> |
| `PriceLadder` | BookTrader price ladder data | <ul><li>`account_id` (str): The account the ladder is requested for.</li><li>`conid` (str): The contract identifier the ladder applies to.</li><li>`exchange` (str): The exchange the ladder is sourced from.</li><li>`data` (dict): The price ladder payload.</li></ul> |
| `Pnl` | Live profit and loss information | <ul><li>`data` (dict): The profit and loss payload.</li></ul> |
| `Trades` | Trade took place | <ul><li>`data` (dict): The trade payload.</li></ul> |

### IBKR Auxiliary

Auxiliary messages from IBKR.

| Event Class | Represents | Attributes |
|--------------------|----------------------------------------|-----------|
| `GenericIbkrEvent` | Uncategorised IBKR message | <ul><li>`message` (dict \| None): The full raw message as received from IBKR.</li><li>`topic` (str \| None): The message topic, if one was present.</li><li>`data` (dict \| None): The message arguments, if any were extracted.</li></ul> |
| `ServerId` | Server ID extracted for Market History | <ul><li>`target_event_type` (type[IbkrTopicEvent]): The topic event the server ID belongs to.</li><li>`conid` (str): The contract identifier mapped to the server ID.</li><li>`server_id` (str): The server ID assigned by IBKR.</li></ul> |
| `Unsubscription` | Explicit unsubscription confirmation | <ul><li>`target_event_type` (type[IbkrTopicEvent]): The topic event that was unsubscribed.</li><li>`conid` (str \| None): The contract identifier when the subscription was contract-specific, otherwise None.</li></ul> |


## <a name="internal-event-systems"></a> Internal Event Systems

There are two distinct event systems in the WebSocket client:

- TransportEvents - private transport events, facilitating the `WebSocket -> transport -> runtime` communication. These are never propagated outside of the internal transport queue.
- WsEvents - main events that can be consumed by sinks, such as in reaction to data received from the server or indicating lifecycle changes.

Note that everywhere in this documentation the word "event" is used, it refers to `WsEvents`. The TransportEvents are private and should be opaque to the external code.


[sinks]: ./sinks.md
[subscriptions]: ./subscriptions.md

[ibkr-websocket]: https://www.interactivebrokers.com/campus/ibkr-api-page/cpapi-v1/#websockets
Loading
Loading