MFX Hub
SDK reference

Stream and live account

Generated from mfx-sdk-python 0.9.11·rebuilt on every release

One WebSocket, many channels, resumable by sequence number; and a view of an account kept current from it.

Types

AliasValues
TimeframeLiteral['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.

async send(message: str) -> None
ParameterTypeDefaultDescription
messagestrrequired

Returns None

async recv() -> str

The next frame, or raise StreamClosed when the connection has ended.

Returns str

async close() -> None

Returns None

StreamMessage

One message on one channel.

FieldTypeDefaultMeaning
channelstr
seqint | None
typestr | None

The event type on events and results (command.completed, feed.degraded …). None on a tick or a candle, which are the channel's only payload.

dataMapping[str, Any]
baseint | NoneNone

account:{terminal_id} only: the seq of the snapshot this message applies to, a snapshot's being its own (account.md ). None on every other channel, and on an account message from a platform that does not stamp it yet. LiveAccount is what this is for — it is the whole of, and applying a mark without checking it silently merges a delta into a position set it was never measured against.

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.

FieldTypeDefaultMeaning
channelstr

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.

FieldTypeDefaultMeaning
channelstr
codestr

The platform's own code - not_found, forbidden, not_subscribed.

messagestr
detailsMapping[str, Any]default_factory=dict

StreamStats

What the stream missed and how often it had to come back (TC-S1-W4-040).

FieldTypeDefaultMeaning
reconnectsint0

Connections opened after the first one.

droppedint0

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 resume could not reach.

resyncsint0

Channels told to resync because the replay window had passed.

slow_consumer_dropsint0

Times the server closed us for falling behind (4008).

lifetime_reconnectsint0

Times the 24 h connection lifetime was reached (4009). Expected, not a fault.

refusedint0

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.

md(server_id: str | UUID, symbol: str) -> str

Ticks for one symbol on one server, in the broker's own name for it.

ParameterTypeDefaultDescription
server_idstr | UUIDrequired

The server's id from the catalogue.

symbolstrrequired

The broker's own symbol name, exactly as the catalogue returns it. MFX Hub never renames an instrument.

Returns str

candles(server_id: str | UUID, symbol: str, timeframe: Timeframe) -> str

Closed candles. Only closed ones are published — a forming candle is not a fact yet.

ParameterTypeDefaultDescription
server_idstr | UUIDrequired

The server's id from the catalogue.

symbolstrrequired

The broker's own symbol name, exactly as the catalogue returns it. MFX Hub never renames an instrument.

timeframeTimeframerequired

One of M1, M5, M15, M30, H1, H4, D1, W1, MN1.

Returns str

results(terminal_id: str | UUID) -> str

Execution results for one terminal: where a 202 command_id is finally answered.

ParameterTypeDefaultDescription
terminal_idstr | UUIDrequired

The terminal to act on. Another customer's id answers 404, never 403.

Returns str

account(terminal_id: str | UUID) -> str

One terminal's live account: a snapshot, then marks (account.md , ).

ParameterTypeDefaultDescription
terminal_idstr | UUIDrequired

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.

ea(terminal_id: str | UUID) -> str

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.

ParameterTypeDefaultDescription
terminal_idstr | UUIDrequired

The terminal to act on. Another customer's id answers 404, never 403.

Returns str

book(server_id: str | UUID, symbol: str) -> 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).

ParameterTypeDefaultDescription
server_idstr | UUIDrequired

The server's id from the catalogue.

symbolstrrequired

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.

stats -> StreamStats property

What has been missed so far. A snapshot; it never mutates under the caller.

channels -> tuple[str, ...] property

The channels currently subscribed, in the order they were asked for.

async subscribe(channel: str) -> None

Subscribe to one channel. The platform acknowledges it on the stream.

ParameterTypeDefaultDescription
channelstrrequired

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.

async resume(channel: str) -> None

Ask the platform to bring one channel back up to date, from the last seq seen.

ParameterTypeDefaultDescription
channelstrrequired

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.

async unsubscribe(channel: str) -> None

Stop watching a channel, and stop resubscribing to it after a reconnect.

ParameterTypeDefaultDescription
channelstrrequired

A channel from stream.channels: account, results, md or candles.

Returns None

async aclose() -> 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.

FieldTypeDefaultMeaning
terminal_idstr
as_ofstr | None

The as_of of the last message applied, not of the snapshot: the figures are that fresh.

seqint | None

The seq of the last message applied. A jump between this and the next message's is what calls a gap.

baseint | None

The seq of the snapshot this view is drawn from, which every marks message names.

figuresdict[str, Any]default_factory=dict
positionsdict[int, dict[str, Any]]default_factory=dict
pendingtuple[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.

view -> AccountView | None property

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.

behind -> bool property

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.

apply(event: StreamEvent) -> AccountView | None

Take one stream event. Returns the new view, or None if nothing was applied.

ParameterTypeDefaultDescription
eventStreamEventrequired

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.