Files
zeekayandHanzo Dev 05796eb725 fix: FIX Latest — ExecType said what OrdStatus already said
The gateway spoke 8=FIX.4.4 and reported a fill as ExecType 1/2 — the pair
FIX Latest retired precisely because it duplicated order state into the
event field. Both fields carried the same thing, so nothing on the wire
distinguished "a trade happened" from "the order is done", and a client
could read either.

Session layer is now FIXT.1.1; the application layer is FIX.5.0SP2 + EP307,
named on the Logon by DefaultApplVerID(1137) and ApplExtID(1156). Every
fill is ExecType=F (Trade) and OrdStatus alone moves Partial -> Filled.
Nothing depended on the old BeginString, so the wire moved with the
semantics instead of straddling both. This is the wire pkg/dexprotocol
already documents (150=F, 39=1|2).

A partially-filled IOC also never reached a terminal state: the last report
said Partial and nothing followed, leaving a client waiting for an end that
never came. It now closes with Canceled.

Vocabulary, one word per concept: execReport -> execution, gateway
OrderFill -> Fill, multisig ExecutionResult -> Result (it holds an error
string, never an execution).

Deletes the SDK's hand-rolled FIX encoder — it pinned a second protocol
version, omitted BodyLength entirely so the acceptor would have rejected
every frame, and sendFIXMessage returned nil without sending.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 09:09:37 -07:00

258 lines
5.2 KiB
Go

//go:build grpc
// Go SDK for LX Trading Platform.
//
// This file contains the gRPC-backed client and is built only when the
// `grpc` build tag is set. The default build provides a stub in
// client_nogrpc.go whose NewClient returns an error directing callers
// to rebuild with `-tags=grpc`.
package lxsdk
import (
"fmt"
"sync"
"sync/atomic"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
// Side represents order side
type Side int
const (
Buy Side = iota
Sell
)
// OrderType represents order type
type OrderType int
const (
Market OrderType = iota
Limit
Stop
StopLimit
)
// Order represents a trading order
type Order struct {
ID string
Symbol string
Price float64
Quantity float64
Side Side
Type OrderType
Timestamp time.Time
}
// Trade represents an executed trade
type Trade struct {
ID string
OrderID string
Symbol string
Price float64
Quantity float64
Side Side
Timestamp time.Time
}
// Client is the main trading client
type Client struct {
conn *grpc.ClientConn
address string
// Performance metrics
ordersSent atomic.Int64
tradesRecv atomic.Int64
latencyNanos atomic.Int64
// Callbacks
onTrade func(*Trade)
onOrder func(*Order)
onError func(error)
mu sync.RWMutex
}
// NewClient creates a new trading client
func NewClient(address string) (*Client, error) {
conn, err := grpc.Dial(address,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(100*1024*1024), // 100MB
grpc.MaxCallSendMsgSize(100*1024*1024),
),
)
if err != nil {
return nil, fmt.Errorf("failed to connect: %w", err)
}
return &Client{conn: conn, address: address}, nil
}
// SendOrder sends a new order
func (c *Client) SendOrder(symbol string, price, quantity float64, side Side) (*Order, error) {
start := time.Now()
defer func() {
c.latencyNanos.Store(time.Since(start).Nanoseconds())
}()
order := &Order{
ID: fmt.Sprintf("ORD%d", time.Now().UnixNano()),
Symbol: symbol,
Price: price,
Quantity: quantity,
Side: side,
Type: Limit,
Timestamp: time.Now(),
}
c.ordersSent.Add(1)
if c.onOrder != nil {
c.onOrder(order)
}
return order, nil
}
// SendBulkOrders sends multiple orders efficiently
func (c *Client) SendBulkOrders(orders []*Order) error {
// Batch processing for high throughput
batch := make([]*Order, 0, 1000)
for _, order := range orders {
batch = append(batch, order)
if len(batch) >= 1000 {
if err := c.processBatch(batch); err != nil {
return err
}
batch = batch[:0]
}
}
if len(batch) > 0 {
return c.processBatch(batch)
}
return nil
}
// CancelOrder cancels an existing order
func (c *Client) CancelOrder(orderID string) error {
// Implementation
return nil
}
// Subscribe subscribes to market data
func (c *Client) Subscribe(symbols []string, callback func(*Trade)) error {
c.onTrade = callback
// Implementation would set up streaming
return nil
}
// GetOrderBook gets current order book
func (c *Client) GetOrderBook(symbol string) (bids, asks []Order, err error) {
// Implementation
return nil, nil, nil
}
// Performance metrics methods
// GetMetrics returns performance metrics
func (c *Client) GetMetrics() map[string]interface{} {
return map[string]interface{}{
"orders_sent": c.ordersSent.Load(),
"trades_received": c.tradesRecv.Load(),
"latency_ns": c.latencyNanos.Load(),
"latency_ms": float64(c.latencyNanos.Load()) / 1e6,
}
}
// GetThroughput returns current throughput
func (c *Client) GetThroughput() float64 {
// Implementation would track time-based throughput
return float64(c.ordersSent.Load())
}
// Close closes the client connection
func (c *Client) Close() error {
if c.conn != nil {
return c.conn.Close()
}
return nil
}
// Private methods
func (c *Client) processBatch(orders []*Order) error {
// Batch processing implementation
for range orders {
c.ordersSent.Add(1)
}
return nil
}
// HighFrequencyClient is optimized for HFT
type HighFrequencyClient struct {
*Client
// Pre-allocated order pool
orderPool sync.Pool
// Lock-free queues
sendQueue chan *Order
recvQueue chan *Trade
}
// NewHighFrequencyClient creates HFT-optimized client
func NewHighFrequencyClient(address string) (*HighFrequencyClient, error) {
client, err := NewClient(address)
if err != nil {
return nil, err
}
hft := &HighFrequencyClient{
Client: client,
sendQueue: make(chan *Order, 100000),
recvQueue: make(chan *Trade, 100000),
}
hft.orderPool.New = func() interface{} {
return &Order{}
}
// Start worker goroutines
for i := 0; i < 10; i++ {
go hft.sendWorker()
}
return hft, nil
}
func (hft *HighFrequencyClient) sendWorker() {
for order := range hft.sendQueue {
hft.SendOrder(order.Symbol, order.Price, order.Quantity, order.Side)
hft.orderPool.Put(order)
}
}
// SendOrderAsync sends order without waiting
func (hft *HighFrequencyClient) SendOrderAsync(symbol string, price, quantity float64, side Side) {
order := hft.orderPool.Get().(*Order)
order.Symbol = symbol
order.Price = price
order.Quantity = quantity
order.Side = side
select {
case hft.sendQueue <- order:
default:
// Queue full, drop order
hft.orderPool.Put(order)
}
}