Tektii just went live. We're shipping fixes daily — spot a bug? Let us know

Hello World

This example demonstrates the minimal code needed to connect to the Trading API and receive market events. You'll learn how to:

  • Establish a WebSocket connection to the Trading API
  • Receive and parse market events (candles, quotes)
  • Acknowledge events with event_ack
  • Handle ping/pong heartbeat messages

Connection URL

Connect to the Trading API WebSocket at:

ws://localhost:8080/v1/ws

This is the Gateway's single user-facing endpoint. Simulation vs paper vs live is selected on the Gateway side via GATEWAY_PROVIDER and GATEWAY_MODE — strategy code is identical across modes.

Auth: the examples below assume a local Gateway without GATEWAY_API_KEY set. If your Gateway enforces auth, send the Authorization: Bearer <key> header on the WebSocket upgrade request — see Connection → Authentication.

Python

Python has a first-class SDK, so you don't hand-roll the protocol. Install the tektii Python SDK and connect with AsyncTradingGateway — it manages the WebSocket connection, event decoding, acknowledgments, and heartbeats for you, so a hello-world is just an event loop:

pip install tektii
import asyncio
from tektii import AsyncTradingGateway, CandleEvent, ConnectionEvent

async def main():
    async with AsyncTradingGateway() as gw:
        print("Connected to the Trading Gateway")

        async with gw.stream() as events:
            async for event in events:
                match event:
                    case CandleEvent():
                        bar = event.bar
                        print(f"  {bar.symbol} - Close: {bar.close}")
                    case ConnectionEvent(event=ev, broker=broker):
                        print(f"  connection {ev} broker={broker}")

if __name__ == "__main__":
    asyncio.run(main())

AsyncTradingGateway reads TRADING_GATEWAY_URL (default http://localhost:8080) from the environment, so no connection URL is hard-coded. For the full strategy shape — order submission, position sizing, and history warm-up — see Writing a Strategy.

The examples below show the raw wire protocol for languages the SDK doesn't ship yet — reach for them only when you're porting a client to one of those languages.

JavaScript

const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');

const ws = new WebSocket('ws://localhost:8080/v1/ws');

ws.onopen = () => {
  console.log('Connected to Trading API');
};

ws.onmessage = (msg) => {
  const event = JSON.parse(msg.data);

  // Handle ping/pong heartbeat
  if (event.type === 'ping') {
    ws.send(JSON.stringify({ type: 'pong' }));
    return;
  }

  // Process market events
  console.log(`Received event: ${event.type}`);

  if (event.type === 'candle') {
    const bar = event.bar || {};
    console.log(`  ${bar.symbol} - Close: ${bar.close}`);
  } else if (event.type === 'quote') {
    const quote = event.quote || {};
    console.log(`  ${quote.symbol} - Bid: ${quote.bid} Ask: ${quote.ask}`);
  }

  // Acknowledge the event (one ack drains all outstanding events)
  const ack = {
    type: 'event_ack',
    correlation_id: uuidv4(),
    events_processed: [],
    timestamp: Date.now()
  };
  ws.send(JSON.stringify(ack));
};

ws.onerror = (err) => {
  console.error('WebSocket error:', err.message);
};

ws.onclose = () => {
  console.log('Disconnected from Trading API');
};

Java

import javax.websocket.*;
import java.net.URI;
import java.util.UUID;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

@ClientEndpoint
public class HelloWorld {
    private static final Gson gson = new Gson();

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("Connected to Trading API");
    }

    @OnMessage
    public void onMessage(String message, Session session) throws Exception {
        JsonObject event = gson.fromJson(message, JsonObject.class);
        String type = event.get("type").getAsString();

        // Handle ping/pong heartbeat
        if ("ping".equals(type)) {
            session.getBasicRemote().sendText("{\"type\":\"pong\"}");
            return;
        }

        // Process market events
        System.out.println("Received event: " + type);

        if ("candle".equals(type)) {
            JsonObject bar = event.getAsJsonObject("bar");
            System.out.printf("  %s - Close: %s%n",
                bar.get("symbol").getAsString(),
                bar.get("close").getAsString());
        } else if ("quote".equals(type)) {
            JsonObject quote = event.getAsJsonObject("quote");
            System.out.printf("  %s - Bid: %s Ask: %s%n",
                quote.get("symbol").getAsString(),
                quote.get("bid").getAsString(),
                quote.get("ask").getAsString());
        }

        // Acknowledge the event (one ack drains all outstanding events)
        JsonObject ack = new JsonObject();
        ack.addProperty("type", "event_ack");
        ack.addProperty("correlation_id", UUID.randomUUID().toString());
        ack.add("events_processed", gson.toJsonTree(new String[0]));
        ack.addProperty("timestamp", System.currentTimeMillis());
        session.getBasicRemote().sendText(gson.toJson(ack));
    }

    @OnClose
    public void onClose() {
        System.out.println("Disconnected from Trading API");
    }

    public static void main(String[] args) throws Exception {
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        URI uri = new URI("ws://localhost:8080/v1/ws");
        container.connectToServer(HelloWorld.class, uri);

        // Keep the connection open
        Thread.sleep(Long.MAX_VALUE);
    }
}

Rust

use tokio_tungstenite::{connect_async, tungstenite::Message};
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Serialize)]
struct EventAck {
    #[serde(rename = "type")]
    msg_type: String,
    correlation_id: String,
    events_processed: Vec<String>,
    timestamp: u64,
}

#[derive(Serialize)]
struct Pong {
    #[serde(rename = "type")]
    msg_type: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "ws://localhost:8080/v1/ws";
    let (ws_stream, _) = connect_async(url).await?;
    println!("Connected to Trading API");

    let (mut write, mut read) = ws_stream.split();

    while let Some(msg) = read.next().await {
        let msg = msg?;
        if let Message::Text(text) = msg {
            let event: Value = serde_json::from_str(&text)?;
            let event_type = event["type"].as_str().unwrap_or("unknown");

            // Handle ping/pong heartbeat
            if event_type == "ping" {
                let pong = Pong { msg_type: "pong".to_string() };
                write.send(Message::Text(serde_json::to_string(&pong)?)).await?;
                continue;
            }

            // Process market events
            println!("Received event: {}", event_type);

            match event_type {
                "candle" => {
                    if let Some(bar) = event.get("bar") {
                        println!("  {} - Close: {}",
                            bar["symbol"].as_str().unwrap_or(""),
                            bar["close"].as_str().unwrap_or(""));
                    }
                }
                "quote" => {
                    if let Some(quote) = event.get("quote") {
                        println!("  {} - Bid: {} Ask: {}",
                            quote["symbol"].as_str().unwrap_or(""),
                            quote["bid"].as_str().unwrap_or(""),
                            quote["ask"].as_str().unwrap_or(""));
                    }
                }
                _ => {}
            }

            // Acknowledge the event
            let timestamp = SystemTime::now()
                .duration_since(UNIX_EPOCH)?
                .as_millis() as u64;

            // One ack drains all outstanding events; events_processed is informational.
            let ack = EventAck {
                msg_type: "event_ack".to_string(),
                correlation_id: Uuid::new_v4().to_string(),
                events_processed: vec![],
                timestamp,
            };
            write.send(Message::Text(serde_json::to_string(&ack)?)).await?;
        }
    }

    Ok(())
}

Go

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/google/uuid"
	"github.com/gorilla/websocket"
)

type EventAck struct {
	Type            string   `json:"type"`
	CorrelationID   string   `json:"correlation_id"`
	EventsProcessed []string `json:"events_processed"`
	Timestamp       int64    `json:"timestamp"`
}

type Pong struct {
	Type string `json:"type"`
}

func main() {
	url := "ws://localhost:8080/v1/ws"
	conn, _, err := websocket.DefaultDialer.Dial(url, nil)
	if err != nil {
		log.Fatal("Connection failed:", err)
	}
	defer conn.Close()

	fmt.Println("Connected to Trading API")

	for {
		_, message, err := conn.ReadMessage()
		if err != nil {
			log.Println("Read error:", err)
			return
		}

		var event map[string]interface{}
		if err := json.Unmarshal(message, &event); err != nil {
			log.Println("Parse error:", err)
			continue
		}

		eventType, _ := event["type"].(string)

		// Handle ping/pong heartbeat
		if eventType == "ping" {
			pong := Pong{Type: "pong"}
			conn.WriteJSON(pong)
			continue
		}

		// Process market events
		fmt.Printf("Received event: %s\n", eventType)

		switch eventType {
		case "candle":
			if bar, ok := event["bar"].(map[string]interface{}); ok {
				fmt.Printf("  %s - Close: %s\n", bar["symbol"], bar["close"])
			}
		case "quote":
			if quote, ok := event["quote"].(map[string]interface{}); ok {
				fmt.Printf("  %s - Bid: %s Ask: %s\n",
					quote["symbol"], quote["bid"], quote["ask"])
			}
		}

		// Acknowledge the event (one ack drains all outstanding events)
		ack := EventAck{
			Type:            "event_ack",
			CorrelationID:   uuid.New().String(),
			EventsProcessed: []string{},
			Timestamp:       time.Now().UnixMilli(),
		}
		conn.WriteJSON(ack)
	}
}

Key Concepts

Event Acknowledgment

Acknowledgment is required in backtest mode — it controls time progression on the hosted Tektii platform so runs stay deterministic. In live and paper trading modes it is informational but still recommended for tracking purposes.

A single event_ack frame drains all of the Gateway's outstanding events at once, so you do not ack events individually. The event_ack message carries:

  • correlation_id: A unique ID for tracking this acknowledgment
  • events_processed: Informational only — the Gateway does not match its entries to specific events, so an empty array is fine. There is no top-level event id; if you want to record what you handled, echo the nested order.id / position.id / trade.id values (see Handling Events).
  • timestamp: Unix timestamp in milliseconds

Heartbeat

The server sends ping messages to check connection health. Your client must respond with pong to keep the connection alive. If no response is received, the connection may be closed.

Next Steps