realtime

package
v0.0.0-...-6729b5a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: GPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package realtime connects to the ProjectX Gateway SignalR hubs: the user hub (accounts, orders, positions, trades) and the market hub (quotes, market trades, market depth).

It implements the ASP.NET Core SignalR JSON hub protocol v1 over WebSockets directly, as described in section 11 of spec/SDK_BEHAVIOR.md: it skips negotiation, sends the token as the access_token query parameter and as a bearer header, pings after 15 seconds of client silence, treats 30 seconds of server silence as a lost connection, reconnects with delays of 0, 1, 2, 5 and 10 seconds and then every 30 seconds (fetching a fresh token for every attempt), and replays successful subscriptions after each reconnect. When the server rejects a token with HTTP 401 during the upgrade, the hub reports it through WithTokenRejected (wired to (*topstepx.Client).InvalidateToken by the ForClient constructors), so the next attempt logs in again instead of reusing the rejected token.

Usage

client, _ := topstepx.NewClient(topstepx.WithCredentials(user, apiKey))
hub := realtime.NewUserHubForClient(client)
if err := hub.Connect(ctx); err != nil {
	return err
}
defer hub.Close()
if err := hub.SubscribeOrders(ctx, accountID); err != nil {
	return err
}
for ev := range hub.Events() {
	switch ev := ev.(type) {
	case *realtime.OrderEvent:
		fmt.Println(ev.Order.ID, ev.Order.Status)
	case *realtime.StateEvent:
		fmt.Println("state:", ev.State, ev.Err)
	}
}

Events are delivered on one buffered channel together with connection state events. Delivery never blocks the connection: a consumer that falls behind loses events (see WithOverflowPolicy), which Dropped counts and the logger reports. Payloads are decoded leniently; one that cannot be decoded arrives as an *UnknownEvent and the connection is unaffected.

Payload models follow the official realtime guide and are not assumed to match the REST models; every non-identifier field is optional because updates may be partial.

Index

Examples

Constants

View Source
const (
	EventGatewayQuote = "GatewayQuote"
	EventGatewayTrade = "GatewayTrade"
	EventGatewayDepth = "GatewayDepth"
)

Market hub server events.

View Source
const (
	// DefaultKeepAliveInterval is how long the client may stay silent before
	// it sends a ping.
	DefaultKeepAliveInterval = 15 * time.Second
	// DefaultServerTimeout is how long the server may stay silent before the
	// connection counts as lost.
	DefaultServerTimeout = 30 * time.Second
	// DefaultHandshakeTimeout bounds the WebSocket dial and SignalR handshake.
	DefaultHandshakeTimeout = 15 * time.Second
	// DefaultInvocationTimeout bounds each hub method invocation.
	DefaultInvocationTimeout = 30 * time.Second
	// DefaultEventBuffer is the capacity of the Events channel.
	DefaultEventBuffer = 1024
	// DefaultReadLimit is the largest accepted WebSocket message, in bytes.
	DefaultReadLimit = 16 << 20
	// UnlimitedAttempts is the ReconnectPolicy.MaxAttempts value for
	// unlimited reconnect attempts.
	UnlimitedAttempts = -1
)

Defaults from the cross-SDK contract (section 11).

View Source
const (
	EventGatewayUserAccount  = "GatewayUserAccount"
	EventGatewayUserOrder    = "GatewayUserOrder"
	EventGatewayUserPosition = "GatewayUserPosition"
	EventGatewayUserTrade    = "GatewayUserTrade"
)

User hub server events.

Variables

View Source
var (
	// ErrNotConnected is returned by invocations while the hub is not
	// connected, including while it reconnects. They fail fast rather than
	// wait.
	ErrNotConnected = errors.New("realtime: not connected")

	// ErrConnectionLost is returned by invocations pending when the
	// connection dropped, and wraps the cause reported in StateReconnecting
	// events.
	ErrConnectionLost = errors.New("realtime: connection lost")

	// ErrClosed is returned after Close, and by invocations that Close
	// interrupted.
	ErrClosed = errors.New("realtime: hub closed")

	// ErrAlreadyConnected is returned by Connect on a hub that is connected
	// or connecting.
	ErrAlreadyConnected = errors.New("realtime: already connected")

	// ErrInvocationTimeout is returned when no completion arrives within the
	// invocation timeout (30 s by default).
	ErrInvocationTimeout = errors.New("realtime: invocation timed out")

	// ErrServerTimeout is the connection-loss cause when nothing arrives from
	// the server within the server timeout (30 s by default).
	ErrServerTimeout = errors.New("realtime: server timeout: no message received")

	// ErrUnauthorized is matched by a *DialError whose WebSocket upgrade was
	// rejected with HTTP 401. It is the same value as topstepx.ErrUnauthorized,
	// so either name matches.
	ErrUnauthorized = topstepx.ErrUnauthorized
)

Sentinel errors, for use with errors.Is.

Functions

This section is empty.

Types

type AccountEvent

type AccountEvent struct {
	// Action is the envelope's action code when the payload was wrapped as
	// {"action": n, "data": {...}}. Its meaning is undocumented.
	Action *int32
	// Account is the decoded payload.
	Account GatewayUserAccount
	// Raw is the payload JSON, including undocumented members.
	Raw json.RawMessage
}

AccountEvent is a GatewayUserAccount update.

type ConnectionState

type ConnectionState int

ConnectionState is the kind of a StateEvent.

const (
	// StateConnected follows a successful Connect.
	StateConnected ConnectionState = iota + 1
	// StateReconnecting precedes each reconnect attempt.
	StateReconnecting
	// StateReconnected follows a successful reconnect and the replay of the
	// subscription registry.
	StateReconnected
	// StateClosed is the last event before the channel closes.
	StateClosed
)

ConnectionState values.

func (ConnectionState) String

func (s ConnectionState) String() string

String returns the state's name.

type DepthEvent

type DepthEvent struct {
	// ContractID is the contract the entries belong to.
	ContractID string
	// Entries are the depth entries, normalized to a list whether the server
	// sent one object or an array.
	Entries []GatewayDepth
	// Raw is the payload JSON.
	Raw json.RawMessage
}

DepthEvent carries the market-depth entries of one GatewayDepth message.

type DialError

type DialError struct {
	// StatusCode is the HTTP status of a rejected upgrade request, or 0.
	StatusCode int
	// contains filtered or unexported fields
}

DialError is a failed WebSocket connection attempt. Its message never contains the access token. An upgrade rejected with HTTP 401 matches ErrUnauthorized; the rejected token has then been reported through WithTokenRejected.

func (*DialError) Error

func (e *DialError) Error() string

Error describes the failure.

func (*DialError) Is

func (e *DialError) Is(target error) bool

Is reports whether target is ErrUnauthorized and the upgrade was rejected with HTTP 401.

func (*DialError) Unwrap

func (e *DialError) Unwrap() error

Unwrap returns context.Canceled or context.DeadlineExceeded when the dial ended because of its context, and nil otherwise.

type DomType

type DomType int32

DomType is the type of a market-depth entry. Unrecognized values are kept.

const (
	DomTypeUnknown    DomType = 0
	DomTypeAsk        DomType = 1
	DomTypeBid        DomType = 2
	DomTypeBestAsk    DomType = 3
	DomTypeBestBid    DomType = 4
	DomTypeTrade      DomType = 5
	DomTypeReset      DomType = 6
	DomTypeLow        DomType = 7
	DomTypeHigh       DomType = 8
	DomTypeNewBestBid DomType = 9
	DomTypeNewBestAsk DomType = 10
	DomTypeFill       DomType = 11
)

DomType values.

func (DomType) String

func (v DomType) String() string

String returns the value's name, or "Unrecognized(<n>)".

type Event

type Event interface {
	// contains filtered or unexported methods
}

Event is delivered on a hub's Events channel. Use a type switch on the concrete types: *StateEvent, *AccountEvent, *OrderEvent, *PositionEvent, *UserTradeEvent, *QuoteEvent, *MarketTradesEvent, *DepthEvent, *SubscriptionErrorEvent and *UnknownEvent.

type GatewayDepth

type GatewayDepth struct {
	// Timestamp is the entry timestamp.
	Timestamp *time.Time `json:"timestamp,omitempty"`
	// Type is the depth entry type.
	Type *DomType `json:"type,omitempty"`
	// Price is the price level.
	Price *decimal.Decimal `json:"price,omitempty"`
	// Volume is the volume at the level, decoded leniently.
	Volume *int64 `json:"volume,omitempty" jsonx:"lenient"`
	// CurrentVolume is the current volume at the level, decoded leniently.
	CurrentVolume *int64 `json:"currentVolume,omitempty" jsonx:"lenient"`
}

GatewayDepth is one market-depth entry of GatewayDepth events.

func (GatewayDepth) MarshalJSON

func (m GatewayDepth) MarshalJSON() ([]byte, error)

MarshalJSON encodes the entry with decimals as JSON numbers.

func (*GatewayDepth) UnmarshalJSON

func (m *GatewayDepth) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes the entry, reading timestamps and volumes leniently.

type GatewayQuote

type GatewayQuote struct {
	// Symbol is the symbol identifier.
	Symbol string `json:"symbol"`
	// SymbolName is the symbol's display name.
	SymbolName *string `json:"symbolName,omitempty"`
	// LastPrice is the last traded price.
	LastPrice *decimal.Decimal `json:"lastPrice,omitempty"`
	// BestBid is the best bid price.
	BestBid *decimal.Decimal `json:"bestBid,omitempty"`
	// BestAsk is the best ask price.
	BestAsk *decimal.Decimal `json:"bestAsk,omitempty"`
	// Change is the price change of the session.
	Change *decimal.Decimal `json:"change,omitempty"`
	// ChangePercent is the percentage price change of the session.
	ChangePercent *decimal.Decimal `json:"changePercent,omitempty"`
	// Open is the session's opening price.
	Open *decimal.Decimal `json:"open,omitempty"`
	// High is the session's highest price.
	High *decimal.Decimal `json:"high,omitempty"`
	// Low is the session's lowest price.
	Low *decimal.Decimal `json:"low,omitempty"`
	// Volume is the session volume, decoded leniently.
	Volume *int64 `json:"volume,omitempty" jsonx:"lenient"`
	// LastUpdated is when the quote last changed.
	LastUpdated *time.Time `json:"lastUpdated,omitempty"`
	// Timestamp is the quote timestamp.
	Timestamp *time.Time `json:"timestamp,omitempty"`
}

GatewayQuote is the payload of GatewayQuote events. Updates may carry only the fields that changed.

func (GatewayQuote) MarshalJSON

func (m GatewayQuote) MarshalJSON() ([]byte, error)

MarshalJSON encodes the payload with decimals as JSON numbers.

func (*GatewayQuote) UnmarshalJSON

func (m *GatewayQuote) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes the payload, reading timestamps and volumes leniently.

type GatewayTrade

type GatewayTrade struct {
	// SymbolID is the symbol identifier.
	SymbolID string `json:"symbolId"`
	// Price is the trade price.
	Price *decimal.Decimal `json:"price,omitempty"`
	// Timestamp is the trade timestamp.
	Timestamp *time.Time `json:"timestamp,omitempty"`
	// Type is the aggressor side.
	Type *TradeLogType `json:"type,omitempty"`
	// Volume is the traded volume, decoded leniently.
	Volume *int64 `json:"volume,omitempty" jsonx:"lenient"`
}

GatewayTrade is one market trade of GatewayTrade events.

func (GatewayTrade) MarshalJSON

func (m GatewayTrade) MarshalJSON() ([]byte, error)

MarshalJSON encodes the trade with decimals as JSON numbers.

func (*GatewayTrade) UnmarshalJSON

func (m *GatewayTrade) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes the trade, reading timestamps and volumes leniently.

type GatewayUserAccount

type GatewayUserAccount struct {
	// ID is the account identifier.
	ID int32 `json:"id"`
	// Name is the account name.
	Name *string `json:"name,omitempty"`
	// Balance is the account balance.
	Balance *decimal.Decimal `json:"balance,omitempty"`
	// CanTrade reports whether the account may trade.
	CanTrade *bool `json:"canTrade,omitempty"`
	// IsVisible reports whether the account is visible.
	IsVisible *bool `json:"isVisible,omitempty"`
	// Simulated reports whether the account is simulated.
	Simulated *bool `json:"simulated,omitempty"`
}

GatewayUserAccount is the payload of GatewayUserAccount events.

func (GatewayUserAccount) MarshalJSON

func (m GatewayUserAccount) MarshalJSON() ([]byte, error)

MarshalJSON encodes the payload with decimals as JSON numbers.

func (*GatewayUserAccount) UnmarshalJSON

func (m *GatewayUserAccount) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes the payload.

type GatewayUserOrder

type GatewayUserOrder struct {
	// ID is the order identifier.
	ID int64 `json:"id"`
	// AccountID is the owning account.
	AccountID int32 `json:"accountId"`
	// ContractID is the contract identifier.
	ContractID string `json:"contractId"`
	// SymbolID is the symbol identifier.
	SymbolID string `json:"symbolId"`
	// CreationTimestamp is when the order was created.
	CreationTimestamp *time.Time `json:"creationTimestamp,omitempty"`
	// UpdateTimestamp is when the order last changed.
	UpdateTimestamp *time.Time `json:"updateTimestamp,omitempty"`
	// Status is the order status.
	Status *topstepx.OrderStatus `json:"status,omitempty"`
	// Type is the order type.
	Type *topstepx.OrderType `json:"type,omitempty"`
	// Side is the order side.
	Side *topstepx.OrderSide `json:"side,omitempty"`
	// Size is the order quantity.
	Size *int32 `json:"size,omitempty"`
	// LimitPrice is the limit price.
	LimitPrice *decimal.Decimal `json:"limitPrice,omitempty"`
	// StopPrice is the stop price.
	StopPrice *decimal.Decimal `json:"stopPrice,omitempty"`
	// FillVolume is the filled quantity, decoded leniently.
	FillVolume *int32 `json:"fillVolume,omitempty" jsonx:"lenient"`
	// FilledPrice is the fill price.
	FilledPrice *decimal.Decimal `json:"filledPrice,omitempty"`
	// CustomTag is the caller-supplied tag.
	CustomTag *string `json:"customTag,omitempty"`
}

GatewayUserOrder is the payload of GatewayUserOrder events.

func (GatewayUserOrder) MarshalJSON

func (m GatewayUserOrder) MarshalJSON() ([]byte, error)

MarshalJSON encodes the payload with decimals as JSON numbers.

func (*GatewayUserOrder) UnmarshalJSON

func (m *GatewayUserOrder) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes the payload, reading timestamps and volumes leniently.

type GatewayUserPosition

type GatewayUserPosition struct {
	// ID is the position identifier.
	ID int32 `json:"id"`
	// AccountID is the owning account.
	AccountID int32 `json:"accountId"`
	// ContractID is the contract identifier.
	ContractID string `json:"contractId"`
	// CreationTimestamp is when the position was opened.
	CreationTimestamp *time.Time `json:"creationTimestamp,omitempty"`
	// Type is the position direction.
	Type *topstepx.PositionType `json:"type,omitempty"`
	// Size is the position quantity.
	Size *int32 `json:"size,omitempty"`
	// AveragePrice is the average entry price.
	AveragePrice *decimal.Decimal `json:"averagePrice,omitempty"`
}

GatewayUserPosition is the payload of GatewayUserPosition events.

func (GatewayUserPosition) MarshalJSON

func (m GatewayUserPosition) MarshalJSON() ([]byte, error)

MarshalJSON encodes the payload with decimals as JSON numbers.

func (*GatewayUserPosition) UnmarshalJSON

func (m *GatewayUserPosition) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes the payload, reading timestamps leniently.

type GatewayUserTrade

type GatewayUserTrade struct {
	// ID is the trade identifier.
	ID int64 `json:"id"`
	// AccountID is the owning account.
	AccountID int32 `json:"accountId"`
	// ContractID is the contract identifier.
	ContractID string `json:"contractId"`
	// CreationTimestamp is when the trade happened.
	CreationTimestamp *time.Time `json:"creationTimestamp,omitempty"`
	// Price is the execution price.
	Price *decimal.Decimal `json:"price,omitempty"`
	// ProfitAndLoss is the realized profit and loss; nil for a half-turn.
	ProfitAndLoss *decimal.Decimal `json:"profitAndLoss,omitempty"`
	// Fees are the trade fees.
	Fees *decimal.Decimal `json:"fees,omitempty"`
	// Side is the trade side.
	Side *topstepx.OrderSide `json:"side,omitempty"`
	// Size is the traded quantity.
	Size *int32 `json:"size,omitempty"`
	// Voided reports whether the trade was voided.
	Voided *bool `json:"voided,omitempty"`
	// OrderID is the order that produced the trade.
	OrderID int64 `json:"orderId"`
}

GatewayUserTrade is the payload of GatewayUserTrade events.

func (GatewayUserTrade) MarshalJSON

func (m GatewayUserTrade) MarshalJSON() ([]byte, error)

MarshalJSON encodes the payload with decimals as JSON numbers.

func (*GatewayUserTrade) UnmarshalJSON

func (m *GatewayUserTrade) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes the payload, reading timestamps leniently.

type HandshakeError

type HandshakeError struct {
	// Message is the server's error text or the reason the handshake failed.
	Message string
}

HandshakeError is a SignalR handshake that the server rejected or that did not complete.

func (*HandshakeError) Error

func (e *HandshakeError) Error() string

Error describes the failure.

type HubError

type HubError struct {
	// Method is the invoked hub method.
	Method string
	// Message is the server's error text.
	Message string
}

HubError is an invocation the server completed with an error.

func (*HubError) Error

func (e *HubError) Error() string

Error describes the failure.

type MarketHub

type MarketHub struct {
	// contains filtered or unexported fields
}

MarketHub is a connection to the SignalR market hub, which streams quotes, market trades and market depth per contract. It is safe for concurrent use.

Create it with NewMarketHub or NewMarketHubForClient, read Events, call Connect, then subscribe. Subscriptions are replayed after every reconnect.

func NewMarketHub

func NewMarketHub(tokens TokenProvider, opts ...Option) *MarketHub

NewMarketHub returns an unconnected market hub. tokens is called before every connection attempt; pass (*topstepx.Client).Token. The URL defaults to topstepx.DefaultMarketHubURL.

Example
package main

import (
	"context"
	"log"

	topstepx "github.com/CurtisTechSolutions/TopstepX/go"
	"github.com/CurtisTechSolutions/TopstepX/go/realtime"
)

func main() {
	client, _ := topstepx.NewClient(topstepx.WithCredentials("user", "api-key"))
	hub := realtime.NewMarketHub(client.Token,
		realtime.WithURL(client.MarketHubURL()),
		realtime.WithEventBuffer(4096),
		realtime.WithOverflowPolicy(realtime.DropOldest),
	)
	if err := hub.Connect(context.Background()); err != nil {
		log.Fatal(err)
	}
	defer hub.Close()
	_ = hub.SubscribeContractQuotes(context.Background(), "CON.F.US.EP.U25")
}

func NewMarketHubForClient

func NewMarketHubForClient(c *topstepx.Client, opts ...Option) *MarketHub

NewMarketHubForClient returns an unconnected market hub that takes its token, URL and logger from c and reports tokens rejected with HTTP 401 to c.InvalidateToken, so the next attempt logs in again. Later options override these settings.

func (*MarketHub) Close

func (h *MarketHub) Close() error

Close stops reconnecting, sends a SignalR close message, closes the socket, fails pending invocations with ErrClosed and closes the event channel. It waits for the hub's goroutines to exit and is idempotent.

func (*MarketHub) Connect

func (h *MarketHub) Connect(ctx context.Context) error

Connect opens the connection and performs the SignalR handshake. A failed first connect is not retried automatically; call Connect again.

func (*MarketHub) Dropped

func (h *MarketHub) Dropped() uint64

Dropped returns how many events were discarded because the consumer fell behind.

func (*MarketHub) Events

func (h *MarketHub) Events() <-chan Event

Events returns the event channel. It is buffered, never blocks the connection and is closed after the final StateClosed event.

func (*MarketHub) Invoke

func (h *MarketHub) Invoke(ctx context.Context, method string, args ...any) (json.RawMessage, error)

Invoke calls any hub method and returns its raw result. It is not recorded for replay.

func (*MarketHub) SubscribeContractMarketDepth

func (h *MarketHub) SubscribeContractMarketDepth(ctx context.Context, contractID string) error

SubscribeContractMarketDepth subscribes to GatewayDepth events of the contract.

func (*MarketHub) SubscribeContractQuotes

func (h *MarketHub) SubscribeContractQuotes(ctx context.Context, contractID string) error

SubscribeContractQuotes subscribes to GatewayQuote events of the contract.

func (*MarketHub) SubscribeContractTrades

func (h *MarketHub) SubscribeContractTrades(ctx context.Context, contractID string) error

SubscribeContractTrades subscribes to GatewayTrade events of the contract.

func (*MarketHub) UnsubscribeContractMarketDepth

func (h *MarketHub) UnsubscribeContractMarketDepth(ctx context.Context, contractID string) error

UnsubscribeContractMarketDepth cancels SubscribeContractMarketDepth for the contract.

func (*MarketHub) UnsubscribeContractQuotes

func (h *MarketHub) UnsubscribeContractQuotes(ctx context.Context, contractID string) error

UnsubscribeContractQuotes cancels SubscribeContractQuotes for the contract.

func (*MarketHub) UnsubscribeContractTrades

func (h *MarketHub) UnsubscribeContractTrades(ctx context.Context, contractID string) error

UnsubscribeContractTrades cancels SubscribeContractTrades for the contract.

type MarketTradesEvent

type MarketTradesEvent struct {
	// ContractID is the contract the trades belong to.
	ContractID string
	// Trades are the trades, normalized to a list whether the server sent
	// one object or an array.
	Trades []GatewayTrade
	// Raw is the payload JSON.
	Raw json.RawMessage
}

MarketTradesEvent carries the market trades of one GatewayTrade message.

type Option

type Option func(*config)

Option configures a hub.

func WithEventBuffer

func WithEventBuffer(n int) Option

WithEventBuffer sets the capacity of the Events channel (default DefaultEventBuffer, minimum 1).

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets the HTTP client used for the WebSocket upgrade.

func WithHandshakeTimeout

func WithHandshakeTimeout(d time.Duration) Option

WithHandshakeTimeout bounds the WebSocket dial and SignalR handshake (default DefaultHandshakeTimeout).

func WithInvocationTimeout

func WithInvocationTimeout(d time.Duration) Option

WithInvocationTimeout bounds each hub method invocation (default DefaultInvocationTimeout).

func WithKeepAliveInterval

func WithKeepAliveInterval(d time.Duration) Option

WithKeepAliveInterval sets how long the client may stay silent before it sends a ping (default DefaultKeepAliveInterval).

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the logger. The default discards everything. Tokens are never logged.

func WithOverflowPolicy

func WithOverflowPolicy(p OverflowPolicy) Option

WithOverflowPolicy sets which event is lost when the Events buffer is full (default DropOldest). Dropped events are counted by Dropped.

func WithReadLimit

func WithReadLimit(n int64) Option

WithReadLimit sets the largest accepted WebSocket message in bytes (default DefaultReadLimit).

func WithReconnectPolicy

func WithReconnectPolicy(p ReconnectPolicy) Option

WithReconnectPolicy sets the reconnect policy (default DefaultReconnectPolicy).

func WithServerTimeout

func WithServerTimeout(d time.Duration) Option

WithServerTimeout sets how long the server may stay silent before the connection counts as lost (default DefaultServerTimeout).

func WithTokenRejected

func WithTokenRejected(f func(token string)) Option

WithTokenRejected sets a function that receives the token of a connection attempt whose WebSocket upgrade the server rejected with HTTP 401. It is called before the next attempt, for the initial Connect and for every reconnect, so that the token source can discard the token instead of handing it out again until it expires. Pass (*topstepx.Client).InvalidateToken; NewUserHubForClient and NewMarketHubForClient do so automatically. The function must not block.

func WithURL

func WithURL(hubURL string) Option

WithURL sets the hub URL. An https or http URL is converted to wss or ws.

func WithoutReconnect

func WithoutReconnect() Option

WithoutReconnect disables automatic reconnection.

type OrderEvent

type OrderEvent struct {
	// Action is the envelope's action code, if the payload was wrapped.
	Action *int32
	// Order is the decoded payload.
	Order GatewayUserOrder
	// Raw is the payload JSON, including undocumented members.
	Raw json.RawMessage
}

OrderEvent is a GatewayUserOrder update.

type OverflowPolicy

type OverflowPolicy int

OverflowPolicy decides which event is lost when the consumer falls behind and the Events buffer is full. Delivery never blocks the connection.

const (
	// DropOldest evicts the oldest buffered event to make room (default).
	DropOldest OverflowPolicy = iota
	// DropNewest discards the incoming data event. State events are always
	// enqueued, evicting the oldest buffered event if needed.
	DropNewest
)

OverflowPolicy values.

type PositionEvent

type PositionEvent struct {
	// Action is the envelope's action code, if the payload was wrapped.
	Action *int32
	// Position is the decoded payload.
	Position GatewayUserPosition
	// Raw is the payload JSON, including undocumented members.
	Raw json.RawMessage
}

PositionEvent is a GatewayUserPosition update.

type QuoteEvent

type QuoteEvent struct {
	// ContractID is the contract the quote belongs to.
	ContractID string
	// Quote is the decoded payload. Updates may carry only changed fields.
	Quote GatewayQuote
	// Raw is the payload JSON, including undocumented members.
	Raw json.RawMessage
}

QuoteEvent is a GatewayQuote update for a contract.

type ReconnectPolicy

type ReconnectPolicy struct {
	// Delays are the waits before successive attempts; the last one repeats
	// for later attempts. Empty means the default delays.
	Delays []time.Duration
	// MaxAttempts caps the attempts per outage. UnlimitedAttempts (any
	// negative value) removes the cap; zero disables reconnection.
	MaxAttempts int
}

ReconnectPolicy controls automatic reconnection after a lost connection or a server close that allows reconnecting.

func DefaultReconnectPolicy

func DefaultReconnectPolicy() ReconnectPolicy

DefaultReconnectPolicy returns the contract policy: delays of 0, 1, 2, 5 and 10 seconds, then every 30 seconds, with unlimited attempts.

type ServerCloseError

type ServerCloseError struct {
	// Message is the server's error text, empty when none was sent.
	Message string
	// AllowReconnect is the server's allowReconnect flag.
	AllowReconnect bool
}

ServerCloseError is the cause reported when the server sends a SignalR close message. The hub reconnects only when AllowReconnect is true.

func (*ServerCloseError) Error

func (e *ServerCloseError) Error() string

Error describes the close.

type StateEvent

type StateEvent struct {
	// State is the new state.
	State ConnectionState
	// Attempt is the 1-based reconnect attempt for StateReconnecting and
	// StateReconnected.
	Attempt int
	// Err is the cause: for StateReconnecting, why the connection was lost
	// or why the previous attempt failed; for StateClosed, why the
	// connection ended, or nil after Close.
	Err error
}

StateEvent reports a connection state change. A state event is always enqueued, even into a full buffer (the oldest buffered event is evicted); like any buffered event it can later be evicted under DropOldest when the consumer falls behind. The channel closing always follows StateClosed.

type SubscriptionErrorEvent

type SubscriptionErrorEvent struct {
	// Method is the subscribe method, such as "SubscribeOrders".
	Method string
	// Arguments are the method arguments.
	Arguments []any
	// Err is the failure.
	Err error
}

SubscriptionErrorEvent reports a subscription that failed to be replayed after a reconnect. The subscription stays registered and is replayed again after the next reconnect.

type TokenProvider

type TokenProvider func(ctx context.Context) (string, error)

TokenProvider returns the access token for a connection attempt. It is called before every attempt, so each reconnect uses a fresh token. Pass (*topstepx.Client).Token.

type TradeLogType

type TradeLogType int32

TradeLogType is the aggressor side of a market trade. Unrecognized values are kept.

const (
	TradeLogTypeBuy  TradeLogType = 0
	TradeLogTypeSell TradeLogType = 1
)

TradeLogType values.

func (TradeLogType) String

func (v TradeLogType) String() string

String returns the value's name, or "Unrecognized(<n>)".

type UnknownEvent

type UnknownEvent struct {
	// Target is the server event name.
	Target string
	// Arguments are the raw invocation arguments.
	Arguments []json.RawMessage
	// Err is the decode error, or nil for an unknown target.
	Err error
}

UnknownEvent is a server invocation with an unknown target, or one whose payload failed to decode. The connection is unaffected.

type UserHub

type UserHub struct {
	// contains filtered or unexported fields
}

UserHub is a connection to the SignalR user hub, which streams account, order, position and trade updates. It is safe for concurrent use.

Create it with NewUserHub or NewUserHubForClient, read Events, call Connect, then subscribe. Subscriptions are replayed after every reconnect.

func NewUserHub

func NewUserHub(tokens TokenProvider, opts ...Option) *UserHub

NewUserHub returns an unconnected user hub. tokens is called before every connection attempt; pass (*topstepx.Client).Token. The URL defaults to topstepx.DefaultUserHubURL.

func NewUserHubForClient

func NewUserHubForClient(c *topstepx.Client, opts ...Option) *UserHub

NewUserHubForClient returns an unconnected user hub that takes its token, URL and logger from c and reports tokens rejected with HTTP 401 to c.InvalidateToken, so the next attempt logs in again. Later options override these settings.

Example
package main

import (
	"context"
	"fmt"
	"log"

	topstepx "github.com/CurtisTechSolutions/TopstepX/go"
	"github.com/CurtisTechSolutions/TopstepX/go/realtime"
)

func main() {
	client, err := topstepx.NewClient(topstepx.WithCredentials("user", "api-key"))
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()
	hub := realtime.NewUserHubForClient(client)
	if err := hub.Connect(ctx); err != nil {
		log.Fatal(err)
	}
	defer hub.Close()
	if err := hub.SubscribeOrders(ctx, 123); err != nil {
		log.Fatal(err)
	}
	for ev := range hub.Events() {
		switch ev := ev.(type) {
		case *realtime.OrderEvent:
			fmt.Println("order", ev.Order.ID, ev.Order.Status)
		case *realtime.StateEvent:
			fmt.Println("state", ev.State, ev.Err)
		}
	}
}

func (*UserHub) Close

func (h *UserHub) Close() error

Close stops reconnecting, sends a SignalR close message, closes the socket, fails pending invocations with ErrClosed and closes the event channel. It waits for the hub's goroutines to exit and is idempotent.

func (*UserHub) Connect

func (h *UserHub) Connect(ctx context.Context) error

Connect opens the connection and performs the SignalR handshake. A failed first connect is not retried automatically; call Connect again.

func (*UserHub) Dropped

func (h *UserHub) Dropped() uint64

Dropped returns how many events were discarded because the consumer fell behind.

func (*UserHub) Events

func (h *UserHub) Events() <-chan Event

Events returns the event channel. It is buffered, never blocks the connection and is closed after the final StateClosed event.

func (*UserHub) Invoke

func (h *UserHub) Invoke(ctx context.Context, method string, args ...any) (json.RawMessage, error)

Invoke calls any hub method and returns its raw result. It is not recorded for replay.

func (*UserHub) SubscribeAccounts

func (h *UserHub) SubscribeAccounts(ctx context.Context) error

SubscribeAccounts subscribes to GatewayUserAccount events.

func (*UserHub) SubscribeOrders

func (h *UserHub) SubscribeOrders(ctx context.Context, accountID int32) error

SubscribeOrders subscribes to GatewayUserOrder events of the account.

func (*UserHub) SubscribePositions

func (h *UserHub) SubscribePositions(ctx context.Context, accountID int32) error

SubscribePositions subscribes to GatewayUserPosition events of the account.

func (*UserHub) SubscribeTrades

func (h *UserHub) SubscribeTrades(ctx context.Context, accountID int32) error

SubscribeTrades subscribes to GatewayUserTrade events of the account.

func (*UserHub) UnsubscribeAccounts

func (h *UserHub) UnsubscribeAccounts(ctx context.Context) error

UnsubscribeAccounts cancels SubscribeAccounts.

func (*UserHub) UnsubscribeOrders

func (h *UserHub) UnsubscribeOrders(ctx context.Context, accountID int32) error

UnsubscribeOrders cancels SubscribeOrders for the account.

func (*UserHub) UnsubscribePositions

func (h *UserHub) UnsubscribePositions(ctx context.Context, accountID int32) error

UnsubscribePositions cancels SubscribePositions for the account.

func (*UserHub) UnsubscribeTrades

func (h *UserHub) UnsubscribeTrades(ctx context.Context, accountID int32) error

UnsubscribeTrades cancels SubscribeTrades for the account.

type UserTradeEvent

type UserTradeEvent struct {
	// Action is the envelope's action code, if the payload was wrapped.
	Action *int32
	// Trade is the decoded payload.
	Trade GatewayUserTrade
	// Raw is the payload JSON, including undocumented members.
	Raw json.RawMessage
}

UserTradeEvent is a GatewayUserTrade update.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL