Connections: order & account updates
Order & account updates
A trading provider reports everything — order lifecycle, fills, position and account changes, connectivity — as a single typed event stream you both emit and consume.
One stream, many event types
All state changes travel through one channel: the EventEmitted event on
ITradingProvider, carrying a
TradingEvent. TradingEvent is an abstract record with a small family of
sealed subtypes, so consumers pattern-match on the concrete type rather than switching on an enum tag.
Each event carries the full post-change snapshot of whatever it describes — the whole
Order, the whole Position — so a UI can rebind directly without looking
anything up or merging deltas.
consuming the streamprovider.EventEmitted += evt =>
{
switch (evt)
{
case OrderUpdateEvent o: // order accepted / working / filled / rejected ...
OnOrder(o.Order); break;
case FillEvent f: // one execution
OnFill(f.AccountId, f.Fill); break;
case PositionUpdateEvent p: // net qty / PnL changed
OnPosition(p.Position); break;
case AccountSnapshotEvent a: // cash / equity / margin changed
OnAccount(a.Account); break;
case ConnectionStateEvent c: // link up / down
OnConnectivity(c.Connected, c.Reason); break;
}
};
The event types
Every event derives from TradingEvent(DateTime TimestampUtc) and carries a monotonic
Sequence number stamped by the service as it fans out (a consumer detects a dropped or
duplicated event by watching for a step other than +1). The timestamp is when the provider
observed the change, not when the event reached the UI.
| Event | Payload | Meaning |
|---|---|---|
OrderUpdateEvent |
Order |
Order lifecycle change (accepted, working, modified, partial-fill summary, terminal). Carries the full post-change order. |
FillEvent |
AccountId, Fill
|
A raw execution. An OrderUpdateEvent fires alongside with the rolled-up order state. |
PositionUpdateEvent |
Position |
Net quantity or unrealised PnL changed. A position going flat emits one final event, then drops from the snapshot list. |
AccountSnapshotEvent |
Account |
Account financials changed (cash, equity, margin, buying power). |
BalanceSnapshotEvent |
AccountId, IReadOnlyList<AccountBalance>
|
Per-asset wallet balances changed — the crypto-spot counterpart of the single cash figure. Only emitted when the provider declares AssetBalances. |
AccountRemovedEvent |
AccountId |
An account closed/was deleted. Consumers cascade: drop the account row and all its orders and positions. |
ConnectionStateEvent |
ProviderKey, bool Connected, string? Reason
|
Provider connectivity changed. On reconnect the provider re-emits a snapshot per account and order so consumers reconcile without re-fetching. |
Emitting events (provider side)
If you are implementing a provider, you raise these as your session learns about changes. Leave
Sequence at its default of 0 ("not yet stamped") — the service overwrites
it during fan-out. Emit an OrderUpdateEvent for every lifecycle transition, a
FillEvent alongside each execution (with the matching order update for the rolled-up
state), a PositionUpdateEvent whenever net quantity or mark-to-market moves, and an
AccountSnapshotEvent when financials change. Always send full snapshots, never deltas.
OnOrderUpdate,
OnFill and OnPositionUpdate hooks are this very stream, delivered to a
single strategy for its own account. So the trading-provider event model and the strategy callbacks
are two ends of the same pipe — see Strategies.
ConnectionStateEvent(Connected: false, Reason);
on recovery, emit Connected: true followed by a fresh AccountSnapshotEvent per
account and OrderUpdateEvent per surviving order, so consumers reconcile state without
having to re-request it.