Stream and live account
One WebSocket, many channels, resumable by sequence number; and a view of an account kept current from it.
Types
| Alias | Values |
|---|---|
| Timeframe | Literal['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1', 'W1', 'MN1'] |
StreamClosed (MfxError)
The connection ended. code is the WebSocket close code, reason the server's words.
StreamConnection (Protocol)
What this module needs of a WebSocket, and nothing more.
Narrow on purpose: it keeps the library choice out of every other module, and it lets the reconnect and gap logic — the part that is easy to get wrong — be tested against a socket that closes exactly when the test says to.
| Parameter | Type | Default | Description |
|---|---|---|---|
| message | str | required |
Returns None
The next frame, or raise StreamClosed when the connection has ended.
Returns str
Returns None
StreamMessage
One message on one channel.
| Field | Type | Default | Meaning |
|---|---|---|---|
| channel | str | ||
| seq | int | None | ||
| type | str | None | The event type on | |
| data | Mapping[str, Any] | ||
| base | int | None | None |
|
Resync
The requested position is older than the replay window: read current state over REST.
Delivered rather than raised, because it is not an error — it is the platform being honest that it cannot replay this far back, and the remedy is a read, not a retry.
| Field | Type | Default | Meaning |
|---|---|---|---|
| channel | str |
ChannelRefused
The platform refused one channel. Every other channel on this stream is unaffected.
Delivered rather than raised. A refusal is about one channel, and raising it out of async for message in stream ended the whole connection: a tenant following hundreds of terminals lost every one of them because a single terminal had been retired since it last subscribed. That is the common case, not an exotic one - not_found is exactly what the platform answers for a terminal that is gone, or was never this tenant's.
The subscription is dropped when this arrives, so a reconnect does not ask for it again and collect the same refusal for the life of the process. A caller that believes the channel should exist subscribes again and finds out again.
An error that names no channel is about the connection itself and is still raised.
| Field | Type | Default | Meaning |
|---|---|---|---|
| channel | str | ||
| code | str | The platform's own code - | |
| message | str | ||
| details | Mapping[str, Any] | default_factory=dict |
StreamStats
What the stream missed and how often it had to come back (TC-S1-W4-040).
| Field | Type | Default | Meaning |
|---|---|---|---|
| reconnects | int | 0 | Connections opened after the first one. |
| dropped | int | 0 | Messages the sequence numbers prove were not delivered, summed over every channel. On md and candles this is the server dropping oldest-first for a slow client; on events and results it is what |
| resyncs | int | 0 | Channels told to resync because the replay window had passed. |
| slow_consumer_drops | int | 0 | Times the server closed us for falling behind (4008). |
| lifetime_reconnects | int | 0 | Times the 24 h connection lifetime was reached (4009). Expected, not a fault. |
| refused | int | 0 | Channels the platform refused. Each one cost its own channel and nothing else. |
Channel
Builds the channel keys the platform accepts, from ids rather than names.
Ticks for one symbol on one server, in the broker's own name for it.
| Parameter | Type | Default | Description |
|---|---|---|---|
| server_id | str | UUID | required | The server's id from the catalogue. |
| symbol | str | required | The broker's own symbol name, exactly as the catalogue returns it. MFX Hub never renames an instrument. |
Returns str
Closed candles. Only closed ones are published — a forming candle is not a fact yet.
| Parameter | Type | Default | Description |
|---|---|---|---|
| server_id | str | UUID | required | The server's id from the catalogue. |
| symbol | str | required | The broker's own symbol name, exactly as the catalogue returns it. MFX Hub never renames an instrument. |
| timeframe | Timeframe | required | One of M1, M5, M15, M30, H1, H4, D1, W1, MN1. |
Returns str
Execution results for one terminal: where a 202 command_id is finally answered.
| Parameter | Type | Default | Description |
|---|---|---|---|
| terminal_id | str | UUID | required | The terminal to act on. Another customer's id answers 404, never 403. |
Returns str
One terminal's live account: a snapshot, then marks (account.md , ).
| Parameter | Type | Default | Description |
|---|---|---|---|
| terminal_id | str | UUID | required | The terminal to act on. Another customer's id answers 404, never 403. |
Returns str
Account state has its own channel per terminal rather than riding events, because state is latest-wins and events may not be lost: at thousands of terminals, snapshots competing with fills for one 15-minute replay window turns that window over in seconds. Use LiveAccount to merge what arrives; position.opened/closed still arrive on events, which is where notifications and history belong.
What the tenant's own expert on this terminal writes (ea-telemetry.md): batches of its records as expert_advisor.telemetry, and expert_advisor.telemetry_lost when the host had to discard some. Needs ea:read. Replayed up to 15 minutes on resume.
| Parameter | Type | Default | Description |
|---|---|---|---|
| terminal_id | str | UUID | required | The terminal to act on. Another customer's id answers 404, never 403. |
Returns str
The symbol's order book as one of the tenant's terminals reads it: an md.book message with the whole book each time it changes; read it with book_of. Granted as md is - md:read and a live subscription to the symbol - and it carries nothing until a terminal on that server reads the symbol's depth (depth_instruments, replace_instruments).
| Parameter | Type | Default | Description |
|---|---|---|---|
| server_id | str | UUID | required | The server's id from the catalogue. |
| symbol | str | required | The broker's own symbol name, exactly as the catalogue returns it. MFX Hub never renames an instrument. |
Returns str
MfxStream
The multiplexed stream. Opened with async with client.stream as stream.
What has been missed so far. A snapshot; it never mutates under the caller.
The channels currently subscribed, in the order they were asked for.
Subscribe to one channel. The platform acknowledges it on the stream.
| Parameter | Type | Default | Description |
|---|---|---|---|
| channel | str | required | A channel from stream.channels: account, results, md or candles. |
Returns None
Subscribing twice is not an error: the channel is already being watched, and a caller re-asserting its subscriptions after its own restart should not have to remember.
Ask the platform to bring one channel back up to date, from the last seq seen.
| Parameter | Type | Default | Description |
|---|---|---|---|
| channel | str | required | A channel from stream.channels: account, results, md or candles. |
Returns None
The SDK does this for you across a reconnect. This is for the other case: the caller has noticed, from the data itself, that its own view is behind — which only it can know. On account:{terminal_id} that is the documented remedy for a seq gap or an unapplicable marks message (account.md), and the answer there is a fresh snapshot rather than a replay.
On a channel that does not replay, and on one never subscribed, this subscribes instead of resuming. Both are the honest request: there is no position to resume from, so asking to carry on from one would be asking for something the platform cannot give, and a caller would be left with a silent channel rather than a current one.
Stop watching a channel, and stop resubscribing to it after a reconnect.
| Parameter | Type | Default | Description |
|---|---|---|---|
| channel | str | required | A channel from stream.channels: account, results, md or candles. |
Returns None
Returns None
AccountView
One terminal's account as the stream last said it was: a snapshot, with the marks applied.
positions is keyed by ticket because that is how a mark names the position it moves, and because a caller drawing a table wants to find a row rather than scan for it. The order is the snapshot's own - dicts keep insertion order - so rendering it twice reads the same way.
| Field | Type | Default | Meaning |
|---|---|---|---|
| terminal_id | str | ||
| as_of | str | None | The | |
| seq | int | None | The | |
| base | int | None | The | |
| figures | dict[str, Any] | default_factory=dict | |
| positions | dict[int, dict[str, Any]] | default_factory=dict | |
| pending | tuple[dict[str, Any], ...] | () |
LiveAccount
The rules, applied to the events you hand it.
Not a subscriber: it holds no socket and asks for nothing. The caller drives the stream, which is what lets one connection follow several terminals and lets a caller who already has an event loop keep it.
The account, or None until the first snapshot arrives.
None rather than an empty view: an account with no positions and a zero balance is a statement about a tenant's money, and "we have not been told yet" is not that statement.
True when a message could not be applied, so this view is not the account.
The remedy is resume, which answers with a snapshot. Waiting is also correct - a snapshot comes within 60 s by itself - but until one does, do not show these figures as live.
Take one stream event. Returns the new view, or None if nothing was applied.
| Parameter | Type | Default | Description |
|---|---|---|---|
| event | StreamEvent | required |
Returns AccountView | None
None deliberately covers several things, because a caller does the same for all of them - draw nothing new, then check behind: an event for another channel, terminal or kind; a marks message that does not apply; a resync; and a sequence gap.