Connections: order & account updates

Connections & data

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.

Strategies see the same events, reshaped. The strategy API's 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.
Reconnect cleanly. On a dropped link, emit 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.