Overview

HFT EDGE LDX Feed publishes real-time market data over three transports. Choose the one that matches your latency budget and integration style — all three carry identical instrumentation from LD4 and NY4.

ProtocolTransportEncodingLatencyBest for
WebSocketWSS / TLSJSON< 1.5msApps, dashboards, fast integration
FIX 4.4TCP / TLSTag-value< 0.8msEMS / OMS, institutional order flow
ITCHUDP multicast + TCP recoveryBinary< 100µsUltra-low-latency HFT desks

Install the SDK

The official LDX Feed SDK (ldxfeed) wraps the WebSocket and FIX transports with reconnect, heartbeat, and sequence handling out of the box. ITCH is consumed directly as a binary stream.

terminal
pip install ldxfeed
terminal
dotnet add package LdxFeed.Sdk
pom.xml
<dependency>
  <groupId>com.ldxfeed</groupId>
  <artifactId>ldxfeed-sdk</artifactId>
  <version>1.0.0</version>
</dependency>
terminal
npm install @ldxfeed/sdk
terminal
go get github.com/ldxfeed/sdk-go

Authentication

Authentication depends on the transport you use:

  • WebSocket & ITCH — pass the API key issued from your dashboard as a query parameter (?api_key=...) or in the opening hello frame.
  • FIX — authenticate with a Logon message (35=A) carrying your SenderCompID, TargetCompID, and password. The SDK performs this automatically when you call logon().

Never expose your API key in client-side code. All connections must be made over TLS.

Quickstart

Subscribe to the live EUR/USD order book in under ten lines using the SDK.

quickstart.py
from ldxfeed import Feed

feed = Feed(api_key="YOUR_API_KEY", region="ld4")

@feed.on("book")
def on_book(msg):
    print(msg["symbol"], msg["bids"])

feed.connect()
feed.subscribe(channel="book", symbol="EUR/USD", depth=10)
feed.run()
Program.cs
using LdxFeed;

var feed = new Feed("YOUR_API_KEY", region: Region.Ld4);

feed.OnBook += (s, msg) =>
    Console.WriteLine($"{msg.Symbol} {msg.Bids[0]}");

await feed.ConnectAsync();
await feed.SubscribeAsync(channel: "book", symbol: "EUR/USD", depth: 10);
await feed.RunAsync();
Quickstart.java
import com.ldxfeed.Feed;
import com.ldxfeed.Book;

Feed feed = Feed.connect("YOUR_API_KEY", Feed.Region.LD4);

feed.onBook((Book msg) -> {
    System.out.println(msg.symbol + " " + msg.bids[0]);
    return null;
});

feed.subscribe("book", "EUR/USD", 10);
feed.run();
quickstart.mjs
import { Feed } from "@ldxfeed/sdk";

const feed = new Feed({ apiKey: "YOUR_API_KEY", region: "ld4" });

feed.on("book", (msg) => console.log(msg.symbol, msg.bids[0]));

await feed.connect();
await feed.subscribe({ channel: "book", symbol: "EUR/USD", depth: 10 });
await feed.run();
quickstart.go
package main

import "github.com/ldxfeed/sdk-go"

func main() {
  feed := ldxfeed.New("YOUR_API_KEY", ldxfeed.RegionLD4)

  feed.OnBook(func(m ldxfeed.Book) {
    println(m.Symbol, m.Bids[0])
  })

  feed.Connect()
  feed.Subscribe("book", "EUR/USD", 10)
  feed.Run()
}

WebSocket API

Prefer raw sockets? Connect directly to the WebSocket endpoint without the SDK. The server pushes JSON frames for every subscribed channel.

Channel Messages
opsubscribe typesnapshot typeupdate typetrade typeheartbeat
ws_stream.py
import asyncio, json, websockets

URL = "wss://ws.ld4.hftedge.com/v1?api_key=YOUR_API_KEY"

async def stream():
    async with websockets.connect(URL) as ws:
        await ws.send(json.dumps({
            "op": "subscribe", "channel": "book",
            "symbol": "EUR/USD", "depth": 10
        }))
        async for raw in ws:
            msg = json.loads(raw)
            print(msg["symbol"], msg.get("bids"))

asyncio.run(stream())
WsStream.cs
using System.Net.WebSockets;
using System.Text;

using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri(
    "wss://ws.ld4.hftedge.com/v1?api_key=YOUR_API_KEY"), default);

string sub = "{\"op\":\"subscribe\",\"channel\":\"book\",\"symbol\":\"EUR/USD\",\"depth\":10}";
await ws.SendAsync(Encoding.UTF8.GetBytes(sub),
    WebSocketMessageType.Text, true, default);

var buf = new byte[8192];
while (ws.State == WebSocketState.Open)
{
    var r = await ws.ReceiveAsync(buf, default);
    Console.WriteLine(Encoding.UTF8.GetString(buf, 0, r.Count));
}
WsStream.java
import java.net.URI;
import java.net.http.*;

var client = HttpClient.newHttpClient();
WebSocket ws = client.newWebSocketBuilder()
    .buildAsync(URI.create("wss://ws.ld4.hftedge.com/v1?api_key=YOUR_KEY"),
        new WebSocket.Listener() {
            public java.util.concurrent.CompletionStage<?> onText(
                    WebSocket w, CharSequence d, boolean last) {
                System.out.println(d);
                return null;
            }
        }).join();

ws.sendText("{\"op\":\"subscribe\",\"channel\":\"book\","
          + "\"symbol\":\"EUR/USD\",\"depth\":10}", true);
ws-stream.mjs
import WebSocket from "ws";

const ws = new WebSocket("wss://ws.ld4.hftedge.com/v1?api_key=YOUR_API_KEY");

ws.on("open", () => {
  ws.send(JSON.stringify({
    op: "subscribe", channel: "book", symbol: "EUR/USD", depth: 10
  }));
});

ws.on("message", (raw) => {
  const msg = JSON.parse(raw.toString());
  console.log(msg.symbol, msg.bids);
});
ws_stream.go
package main

import (
  "encoding/json"
  "fmt"
  "github.com/gorilla/websocket"
)

func main() {
  c, _, _ := websocket.DefaultDialer.Dial(
    "wss://ws.ld4.hftedge.com/v1?api_key=YOUR_API_KEY", nil)
  defer c.Close()

  sub, _ := json.Marshal(map[string]any{
    "op": "subscribe", "channel": "book",
    "symbol": "EUR/USD", "depth": 10,
  })
  c.WriteMessage(websocket.TextMessage, sub)

  for {
    _, msg, err := c.ReadMessage()
    if err != nil { break }
    fmt.Println(string(msg))
  }
}

FIX Protocol

Start a FIX 4.4 session and request a market-data stream. The SDK handles logon, heartbeats, and sequence numbers; you send a MarketDataRequest and iterate the inbound messages.

Core Message Types
35=ALogon 35=VMarketDataRequest 35=WSnapshot 35=XIncremental Refresh 35=1TestRequest
fix_client.py
from ldxfeed.fix import FixSession, Message

session = FixSession(
    host="fix.ld4.hftedge.com", port=5201,
    sender="YOUR_COMP_ID", target="HFT_EDGE",
    password="YOUR_PASSWORD")
session.logon()

req = Message("V")            # MarketDataRequest
req[262] = "MDREQ001"         # MDReqID
req[263] = "1"                # Snapshot + Updates
req[264] = "10"               # MarketDepth
req[269] = ["0", "1"]         # Bid + Offer
req[55]  = "EUR/USD"          # Symbol
session.send(req)

for msg in session:           # inbound stream
    print(msg[55], msg.get(270))   # symbol, price
FixClient.cs
using LdxFeed.Fix;

var session = new FixSession("fix.ld4.hftedge.com", 5201,
    sender: "YOUR_COMP_ID", target: "HFT_EDGE",
    password: "YOUR_PASSWORD");
session.Logon();

var req = new Message("V");
req[262] = "MDREQ001";
req[263] = "1";
req[264] = "10";
req[269] = new[] { "0", "1" };
req[55]  = "EUR/USD";
session.Send(req);

foreach (var msg in session)          // inbound stream
    Console.WriteLine($"{msg[55]} {msg[270]}");
FixClient.java
import com.ldxfeed.fix.*;

FixSession session = FixSession.builder()
    .host("fix.ld4.hftedge.com").port(5201)
    .sender("YOUR_COMP_ID").target("HFT_EDGE")
    .password("YOUR_PASSWORD").connect();

session.logon();

Message req = new Message("V");
req.set(262, "MDREQ001");
req.set(263, "1");
req.set(264, "10");
req.set(269, "0"); req.set(269, "1");
req.set(55, "EUR/USD");
session.send(req);

for (Message msg : session) {
    System.out.println(msg.get(55) + " " + msg.get(270));
}
fix-client.mjs
import { FixSession, Message } from "@ldxfeed/sdk/fix";

const session = new FixSession({
  host: "fix.ld4.hftedge.com", port: 5201,
  sender: "YOUR_COMP_ID", target: "HFT_EDGE",
  password: "YOUR_PASSWORD"
});
await session.logon();

const req = new Message("V");
req.set(262, "MDREQ001");
req.set(263, "1");
req.set(264, "10");
req.set(269, ["0", "1"]);
req.set(55, "EUR/USD");
await session.send(req);

for await (const msg of session) {
  console.log(msg.get(55), msg.get(270));
}
fix_client.go
package main

import "github.com/ldxfeed/sdk-go/fix"

func main() {
  s := fix.NewSession("fix.ld4.hftedge.com", 5201,
    "YOUR_COMP_ID", "HFT_EDGE", "YOUR_PASSWORD")
  s.Logon()

  req := fix.NewMessage("V")
  req.Set(262, "MDREQ001")
  req.Set(263, "1")
  req.Set(264, "10")
  req.Set(269, "0"); req.Set(269, "1")
  req.Set(55, "EUR/USD")
  s.Send(req)

  for msg := range s.Messages() {
    println(msg.Get(55), msg.Get(270))
  }
}

ITCH Protocol

ITCH is a raw binary multicast feed with no framing library — join the UDP group and decode each message by its leading type byte. Below, each client decodes an Add Order ('A') message from the LD4 stream.

Message Types
'S'System Event 'R'Symbol Directory 'A'Add Order 'E'Order Executed 'X'Order Cancel 'D'Order Delete
itch_feed.py
import socket, struct

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("", 10000))
mreq = struct.pack("4sl", socket.inet_aton("233.1.2.3"), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

while True:
    pkt, _ = sock.recvfrom(2048)
    if pkt[0:1] != b"A":          # Add Order
        continue
    ts, ref, side, shares = struct.unpack_from("<QQcI", pkt, 1)
    symbol = pkt[22:30].rstrip(b"\x00").decode()
    price  = struct.unpack_from("<Q", pkt, 30)[0]
    print(symbol, "B" if side == ord("B") else "S", shares, price)
ItchFeed.cs
using System.Net.Sockets;
using System.Text;

using var udp = new UdpClient(10000);
udp.JoinMulticastGroup(System.Net.IPAddress.Parse("233.1.2.3"));

var ep = new IPEndPoint(0, 0);
while (true)
{
    byte[] pkt = udp.Receive(ref ep);
    if (pkt[0] != (byte)'A') continue;   // Add Order

    long ts    = BitConverter.ToInt64(pkt, 1);
    long refNo = BitConverter.ToInt64(pkt, 9);
    char side  = (char)pkt[17];
    int shares = BitConverter.ToInt32(pkt, 18);
    string sym = Encoding.ASCII.GetString(pkt, 22, 8).TrimEnd('\0');
    long price = BitConverter.ToInt64(pkt, 30);

    Console.WriteLine($"{sym} {side} {shares} {price}");
}
ItchFeed.java
import java.net.*;
import java.nio.*;

var group = InetAddress.getByName("233.1.2.3");
try (var sock = new MulticastSocket(10000)) {
    sock.joinGroup(group);

    var pkt = new DatagramPacket(new byte[2048], 2048);
    while (true) {
        sock.receive(pkt);
        var b = ByteBuffer.wrap(pkt.getData())
                          .order(ByteOrder.LITTLE_ENDIAN);
        if ((char) b.get() != 'A') continue;

        long ts    = b.getLong();
        long refNo = b.getLong();
        char side  = (char) b.get();
        int shares = b.getInt();
        byte[] sym = new byte[8]; b.get(sym);
        long price = b.getLong();

        System.out.println(new String(sym).trim()
            + " " + side + " " + shares + " " + price);
    }
}
itch-feed.mjs
import dgram from "node:dgram";

const sock = dgram.createSocket({ type: "udp4", reuseAddr: true });

sock.on("message", (pkt) => {
  if (pkt[0] !== 0x41) return;            // 'A' = Add Order
  const ts     = pkt.readBigUInt64LE(1);
  const refNo  = pkt.readBigUInt64LE(9);
  const side   = String.fromCharCode(pkt[17]);
  const shares = pkt.readUInt32LE(18);
  const symbol = pkt.subarray(22, 30).toString("ascii").replace(/\0/g, "");
  const price  = pkt.readBigUInt64LE(30);
  console.log(symbol, side, shares, price);
});

sock.bind(10000, () => sock.addMembership("233.1.2.3"));
itch_feed.go
package main

import (
  "encoding/binary"
  "fmt"
  "net"
)

func main() {
  addr, _ := net.ResolveUDPAddr("udp", "233.1.2.3:10000")
  sock, _ := net.ListenMulticastUDP("udp", nil, addr)
  buf := make([]byte, 2048)

  for {
    n, _, err := sock.ReadFromUDP(buf)
    if err != nil || n < 39 || buf[0] != 'A' {
      continue
    }
    refNo  := binary.LittleEndian.Uint64(buf[9:17])
    shares := binary.LittleEndian.Uint32(buf[18:22])
    symbol := string(buf[22:30])
    price  := binary.LittleEndian.Uint64(buf[30:38])
    fmt.Println(symbol, string(buf[17]), shares, price, refNo)
  }
}

Reconnection & Error Handling

Production consumers must stay healthy across network drops. The SDK handles all of the following automatically; raw consumers must implement them.

  • Heartbeats — FIX sessions exchange TestRequest/Heartbeat every 30s; the SDK tears down stale sessions.
  • Reconnect with backoff — exponential backoff (1s → 30s cap) on dropped WebSocket or FIX connections.
  • Sequence numbers — FIX stores last inbound/outbound seqnums and replays gaps on reconnect.
  • ITCH gap recovery — detect gaps in the multicast sequence and request retransmits from the TCP recovery feed.

Every connection exposes a status event you can subscribe to for logging and alerting.

Ready to trade on the edge? Generate your API key and ship your first feed today.

Get API Access