Connection Guide

This guide explains how to establish a connection between your strategy and the Tektii Trading Gateway, configure market data subscriptions, and handle the event stream.

Connection Endpoints

The gateway exposes both WebSocket and REST over a single HTTP port. Your strategy uses the WebSocket channel for the event stream and the REST API for requests such as placing orders or reading account state.

ProtocolPortURL
WebSocket8080ws://localhost:8080/v1/ws
REST8080http://localhost:8080/v1/…

Every REST endpoint is prefixed with /v1/. The only exceptions are the unversioned health and metrics endpoints (/health, /livez, /readyz, /metrics). In production, these URLs are supplied to your strategy via environment variables or configuration.

Host and Port Configuration

Point your strategy at a remote gateway via the GATEWAY_URL environment variable:

GATEWAY_URL=http://gateway.example.com:8080

Or use the localhost default for local development.

Authentication

When the gateway is started with the GATEWAY_API_KEY environment variable set (minimum 32 characters), every REST and WebSocket request must carry the key in one of two headers. If both are present, Authorization wins precedence.

HeaderValue
AuthorizationBearer <key>
X-API-Key<key>

The same headers are sent on the WebSocket upgrade request — there is no separate WebSocket auth handshake or first-message auth.

When GATEWAY_API_KEY is not set, the gateway runs without authentication and logs a warning at startup. Anyone with network access to the port can place, modify, and cancel orders. Do not deploy this way outside a trusted local environment.

Public endpoints — always unauthenticated: /health, /livez, /readyz, /metrics. Every /v1/* path requires the key when GATEWAY_API_KEY is set.

import websockets

headers = {"Authorization": f"Bearer {api_key}"}
async with websockets.connect("ws://localhost:8080/v1/ws", extra_headers=headers) as ws:
    ...

Note: this is gateway auth. The Tektii CLI, which talks to the Tektii management API at api.tektii.com, uses a separate TEKTII_API_KEY — see CLI Authentication Setup.

Establishing a Connection

WebSocket Handshake

  1. Open a WebSocket connection to the gateway
  2. Wait for the CONNECTED connection event
  3. Begin receiving market data events
import websockets
import json

async def connect():
    async with websockets.connect("ws://localhost:8080/v1/ws") as ws:
        # Wait for connection confirmation
        msg = await ws.recv()
        event = json.loads(msg)

        if event["type"] == "connection" and event["event"] == "CONNECTED":
            print("Connected to server")

        # Start receiving events
        async for message in ws:
            handle_event(json.loads(message))

Connection Events

The server sends connection status events ("type": "connection", with the variant in the event field):

EventDescription
CONNECTEDSuccessfully connected to provider
DISCONNECTINGConnection closing gracefully
RECONNECTINGAttempting to reconnect after failure
BROKER_DISCONNECTEDUpstream broker link dropped
BROKER_RECONNECTEDUpstream broker link restored after an outage; gap_duration_ms reports the gap
BROKER_CONNECTION_FAILEDInitial broker connect failed, or a reconnect attempt failed terminally

The BROKER_* variants carry an optional broker field (the upstream provider, e.g. "alpaca-paper"); BROKER_RECONNECTED also carries gap_duration_ms. See Connection Events for the full field reference.

Event Subscription

Configuring Subscriptions

Subscriptions are configured on the gateway at startup via the SUBSCRIPTIONS environment variable, not via WebSocket messages. The value is a JSON array; each entry names a broker platform, an instrument, and the events to stream for it.

SUBSCRIPTIONS='[
  {"platform": "alpaca-paper", "instrument": "AAPL", "events": ["quote"]},
  {"platform": "alpaca-paper", "instrument": "MSFT", "events": ["quote", "candle_1m"]}
]'

Each entry has three required fields:

FieldTypeDescription
platformstringBroker identifier — see below
instrumentstringSymbol or instrument identifier used by the broker
eventsstring[]Event names to subscribe to — see below

Event Names

The following event names are valid in the events array:

Event NameDescription
quoteLevel-1 bid/ask/last price updates
tradePublic trade prints from the venue
candle_1m1-minute OHLCV bars
candle_5m5-minute OHLCV bars
candle_1h1-hour OHLCV bars
candle_*Wildcard — every timeframe the broker supports
order_updateChanges to your own orders
position_updateChanges to your own positions
account_updateAccount balance / equity updates
trade_updateFills on your own orders
option_greeksGreeks stream for option instruments

Platform Values

platform identifies the upstream broker + mode. Valid values include alpaca-paper, alpaca-live, binance-spot-testnet, binance-spot-live, binance-futures-testnet, binance-futures-live, binance-margin-testnet, binance-margin-live, binance-coin-futures-testnet, binance-coin-futures-live, oanda-practice, oanda-live, saxo-sim, saxo-live, and mock. See the gateway README's "Supported Brokers" section for the canonical list as providers are added.

instrument strings are passed through to the broker verbatim — use the ticker, contract, or pair the broker itself expects (for example AAPL on Alpaca, BTCUSDT on Binance). There is no Tektii-internal prefix scheme.

Handling Events

Message Format

All WebSocket messages use JSON with a "type" field for routing:

{"type": "candle", "bar": {...}, "timestamp": "2025-01-01T00:00:00Z"}
{"type": "quote", "quote": {...}, "timestamp": "2025-01-01T00:00:00Z"}
{"type": "order", "event": "ORDER_FILLED", "order": {...}}
{"type": "ping", "timestamp": "2025-01-01T00:00:00Z"}

Event Types

TypeDirectionDescription
candleServer → ClientOHLCV bar data
quoteServer → ClientBid/ask/last price
orderServer → ClientOrder state changes
positionServer → ClientPosition state changes
accountServer → ClientAccount state changes
tradeServer → ClientTrade execution
connectionServer → ClientConnection status
rate_limitServer → ClientRate limit warnings
data_stalenessServer → ClientPer-symbol market-data went STALE/FRESH
errorServer → ClientError notifications
pingServer → ClientHeartbeat ping
pongClient → ServerHeartbeat response
event_ackClient → ServerEvent acknowledgment

Ping/Pong Heartbeat

The server sends periodic ping messages. Respond with pong to maintain the connection:

// Server sends
{"type": "ping", "timestamp": "2025-01-01T00:00:00Z"}

// Client responds
{"type": "pong"}

Event Acknowledgment

Backtest Mode (Required)

In backtest mode, time simulation is controlled by your strategy. After processing a batch of events, send an acknowledgment to advance time:

{
  "type": "event_ack",
  "correlation_id": "550e8400-e29b-41d4-a716-446655440000",
  "events_processed": ["evt_123", "evt_124"],
  "timestamp": 1700000000000
}
FieldTypeDescription
correlation_idstringUnique ID for this acknowledgment
events_processedstring[]List of event IDs that were processed
timestampnumberUnix timestamp (ms) when events were processed

Without acknowledgments, the backtest engine will not advance time.

Trading Gateway Mode (Optional)

When using the Trading Gateway (paper or live mode), acknowledgments are informational only. The market continues regardless of whether you acknowledge events. However, sending them helps with debugging and monitoring.

Error Handling

Error Event Envelope

The server sends error events with a stable three-field envelope. code is the machine-readable discriminator, message is a human-readable description, and details carries per-code structured context (may be omitted for some codes).

{
  "type": "error",
  "code": "ORDER_REJECTED",
  "message": "Order rejected: insufficient margin",
  "details": { "reject_code": "INSUFFICIENT_MARGIN" },
  "timestamp": "2025-01-01T00:00:00Z"
}

The same envelope is returned as the body of REST error responses (minus the type / timestamp fields). See REST Endpoints — Error Codes and Retry Strategy for the full code list and per-endpoint error tables.

Common Error Codes

codeExample messageRetryable?
INVALID_REQUESTMissing required field symbolNo — terminal, fix the request
INVALID_VALUEInvalid value for quantity: must be positiveNo — terminal
VALIDATION_ERRORValidation failed: 2 errorsNo — terminal
UNAUTHORIZEDAPI key missing or invalidNo — terminal, fix credentials
ORDER_NOT_FOUNDOrder not found: ord_abc123No — terminal
POSITION_NOT_FOUNDPosition not found: pos_xyz789No — terminal
SYMBOL_NOT_FOUNDSymbol not found: ZZZZNo — terminal
ORDER_NOT_MODIFIABLEOrder already filledNo — terminal
ORDER_REJECTEDOrder rejected by broker (see details.reject_code)No for classification codes like INSUFFICIENT_FUNDS; only retry when message or details.reject_code contains self-trade / wash (the two transient rejection classes the Gateway recognises)
RATE_LIMITEDRate limit exceededYes — honour details.retry_after_seconds / details.reset_at
PROVIDER_UNAVAILABLEProvider unavailable: upstream timeoutYes — exponential backoff
PROVIDER_ERRORProvider error: 5xx from upstreamYes — exponential backoff
SHUTTING_DOWNGateway is shutting downNo during this instance; retry against the replacement once /readyz is green
INTERNAL_ERRORInternal errorNo — surface to operators
UNSUPPORTED_OPERATIONmodify_order not supported on alpaca-paperNo — terminal for that provider

A rate_limit event ("type": "rate_limit") may also fire as a warning before a hard RATE_LIMITED rejection — treat it as a signal to throttle.

Reconnection Strategy

Tektii clients should assume the WebSocket connection can drop at any time (network hiccup, upstream provider cycle, Gateway restart). Plan for reconnection from day one.

Server-driven signals:

  • DISCONNECTING — connection closing gracefully (Gateway shutdown, config reload). Stop sending new orders; wait for CONNECTED on the replacement.
  • RECONNECTING — the Gateway is re-establishing an upstream provider session. The WebSocket to your strategy stays open; market-data events may pause briefly.
  • BROKER_DISCONNECTED — the upstream broker feed dropped. Market data and order updates may stall until it returns; pause trading on affected symbols.
  • BROKER_RECONNECTED — the broker feed is back after an outage; gap_duration_ms reports how long it was gone. Reconcile any state that may have changed during the gap.
  • BROKER_CONNECTION_FAILED — the broker connection failed terminally (gave up after max retries). Surface to operators; the Gateway will not recover this session on its own.

Client-side reconnect loop — exponential backoff with full jitter:

SettingRecommended value
Initial delay1 second
Factor2× per failed attempt
Maximum delay30 seconds
Jitterfull (delay = random_between(0, current_delay))
Max attemptsunlimited (alert operators after N consecutive failures)

Reset the delay counter to the initial value after the next successful CONNECTED event — otherwise a brief flap will slow every subsequent reconnect.

import asyncio
import random
import websockets

INITIAL_DELAY = 1.0
MAX_DELAY = 30.0

async def run_with_reconnect(url, headers):
    delay = INITIAL_DELAY
    while True:
        try:
            async with websockets.connect(url, extra_headers=headers) as ws:
                delay = INITIAL_DELAY  # reset on successful connect
                async for message in ws:
                    handle_event(message)
        except (websockets.ConnectionClosed, OSError) as exc:
            wait = random.uniform(0, delay)  # full jitter
            print(f"Disconnected ({exc}); reconnecting in {wait:.1f}s")
            await asyncio.sleep(wait)
            delay = min(delay * 2, MAX_DELAY)


if __name__ == "__main__":
    headers = {"Authorization": f"Bearer {api_key}"}
    asyncio.run(run_with_reconnect("ws://localhost:8080/v1/ws", headers))

Non-retryable errors (everything marked "terminal" above) do not justify reconnection — fix the request or credentials and resume. If UNAUTHORIZED keeps firing on reconnect, the API key has been rotated out-of-band; stop the reconnect loop and surface the failure to operators rather than looping indefinitely.

Next Steps

  • Review the Concepts documentation for an overview of the unified interface
  • See REST Endpoints for the canonical error-code table and per-endpoint recovery guidance