Skip to main content
The exchange pushes state changes over Phoenix channels. STX.Sdk wraps each topic in a strongly-typed class you can register events on. Every channel takes care of:
  • JWT attach on the connection URL
  • 30-second heartbeat + auto-reconnect with exponential backoff
  • Automatic resubscribe after reconnect

Available channels

ChannelTopicWhat it pushes
STXPortfolioChannelportfolio:{user_id}Available balance, escrow, total P&L
STXActiveOrdersChannelactive_orders:{user_id}Order state transitions
STXActiveTradesChannelactive_trades:{user_id}Fills as they land
STXActiveSettlementsChannelWrapperactive_settlements:{user_id}Market settlements
STXPositionsChannelactive_positions:{user_id}Position changes
STXUserInfoChanneluser_info:{user_id}Profile/account updates
STXMarketChannelmarkets / market_info / market_updatesMarket state + price ticks

Connect a channel

var portfolio = serviceProvider.GetRequiredService<STXPortfolioChannel>();

portfolio.OnSummary += summary =>
    Console.WriteLine($"Available: {summary.AvailableBalance}c  Escrow: {summary.Escrow}c");

portfolio.OnUpdate += summary =>
    Console.WriteLine($"Balance tick: {summary.AvailableBalance}c");

await portfolio.ConnectAsync();
ConnectAsync opens the socket (if not open), joins the topic, and registers handlers. It’s idempotent — calling it again is a no-op.

Portfolio channel

The smallest useful example — keep a live balance on screen:
public class BalanceTracker
{
    private readonly STXPortfolioChannel _portfolio;
    public long AvailableBalance { get; private set; }

    public BalanceTracker(STXPortfolioChannel portfolio)
    {
        _portfolio = portfolio;
        _portfolio.OnSummary += s => AvailableBalance = s.AvailableBalance;
        _portfolio.OnUpdate  += s => AvailableBalance = s.AvailableBalance;
    }

    public Task StartAsync() => _portfolio.ConnectAsync();
}
The server pushes summary on join and update on every account-state change (order placed, order filled, deposit, withdrawal, settlement).

Active orders & trades

For a trading loop, you typically wire both:
var orders = sp.GetRequiredService<STXActiveOrdersChannel>();
var trades = sp.GetRequiredService<STXActiveTradesChannel>();

orders.OnOrderUpdate += o =>
    _logger.LogInformation("Order {Id} -> {Status}", o.Id, o.Status);

trades.OnTrade += t =>
    _logger.LogInformation("Fill {TradeId}: {MarketId} {Price}c × {Quantity}",
        t.Id, t.MarketId, t.Price, t.Quantity);

await orders.ConnectAsync();
await trades.ConnectAsync();
Requires an authenticated session — the underlying JWT is pulled from STXUserStorage automatically.

Market channel (price ticks)

var market = sp.GetRequiredService<STXMarketChannel>();

market.OnPriceUpdate += tick =>
{
    // tick carries: MarketId, BestBid, BestAsk, LastTrade, MarketStatus, ...
};

await market.ConnectAsync();
await market.SubscribeAsync("mkt_abc", "mkt_def");
Subscribe/unsubscribe at runtime as your market universe changes — the channel dedupes subscriptions and only resubscribes the delta.

Lifecycle

Channels are registered as singletons, so state (subscriptions, the underlying socket) is shared across your app. A typical startup sequence looks like:
public class STXWorker : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stop)
    {
        await _login.LoginAsync(email, password, keepSessionAlive: true);
        await _portfolio.ConnectAsync();
        await _orders.ConnectAsync();
        await _trades.ConnectAsync();
        await _market.ConnectAsync();

        stop.WaitHandle.WaitOne();   // block until shutdown

        await _market.DisconnectAsync();
        await _trades.DisconnectAsync();
        await _orders.DisconnectAsync();
        await _portfolio.DisconnectAsync();
    }
}

Reconnect behavior

If the underlying socket drops:
  1. The SDK tries to reconnect with exponential backoff (250ms → 5s cap).
  2. On reconnect, every previously-joined topic is rejoined.
  3. Event handlers stay bound — no re-registration needed.
If the session JWT has expired during the outage, the session background service refreshes it before the next connect attempt (when keepSessionAlive: true).

See also