Hello World
This example hand-rolls the raw WebSocket wire protocol. That's the right path only when you're porting a client to a language the SDK doesn't ship. To write a strategy, use the tektii Python SDK and the Writing a Strategy guide instead — it handles the connection and event stream for you, so a strategy is plain Python.
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_KEYset. If your Gateway enforces auth, send theAuthorization: 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 acknowledgmentevents_processed: Informational only — the Gateway does not match its entries to specific events, so an empty array is fine. There is no top-level eventid; if you want to record what you handled, echo the nestedorder.id/position.id/trade.idvalues (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
- Placing Orders - Learn how to submit and manage orders
- Event Handling - Advanced patterns for processing events
- WebSocket Messages - Full message reference