Files
dex/pkg/lx/orderbook_advanced.go

933 lines
23 KiB
Go

package lx
import (
"container/heap"
"errors"
"fmt"
"math"
"sync"
"sync/atomic"
"time"
)
// Advanced order types - defined in types_common.go
// TimeInForce - defined in types_common.go
// AdvancedOrderBook implements full-featured order book with all order types
type AdvancedOrderBook struct {
mu sync.RWMutex
// Core data structures
symbol string
bidLevels map[float64]*PriceLevel
askLevels map[float64]*PriceLevel
bidHeap *OrderHeap
askHeap *OrderHeap
// Order tracking
orders map[uint64]*AdvancedOrder
stopOrders map[uint64]*AdvancedOrder
icebergOrders map[uint64]*IcebergData
hiddenOrders map[uint64]*AdvancedOrder
conditionalOrders map[uint64]*ConditionalOrder
// Market data
lastPrice float64
lastTradeTime time.Time
volume24h float64
trades []Trade
marketDataFeeds []chan MarketUpdate
// Statistics
totalOrders uint64
totalTrades uint64
totalVolume float64
bestBid float64
bestAsk float64
// Configuration
enableSelfTrade bool
enableHidden bool
tickSize float64
lotSize float64
// Performance metrics
lastUpdateID uint64
sequenceNumber uint64
}
// AdvancedOrder extends basic order with all features
type AdvancedOrder struct {
// Basic fields
ID uint64
ClientID string
UserID string
Symbol string
Side Side
Type OrderType
Price float64
StopPrice float64
Size float64
ExecutedSize float64
RemainingSize float64
// Advanced fields
TimeInForce TimeInForce
ExpireTime time.Time
DisplaySize float64 // For iceberg
PegOffset float64 // For pegged orders
TrailAmount float64 // For trailing stop
MinExecuteSize float64 // Minimum execution size
AllOrNone bool
PostOnly bool
ReduceOnly bool
Hidden bool
// Status
Status OrderStatus
CreateTime time.Time
UpdateTime time.Time
LastFillTime time.Time
LastFillPrice float64
LastFillSize float64
// Fees
MakerFee float64
TakerFee float64
FeesPaid float64
}
// OrderStatus - defined in types_common.go
// IcebergData - defined in types_common.go (alias for IcebergState)
// ConditionalOrder - defined in types_common.go
// MarketUpdate - defined in types_common.go
type OrderHeap []*AdvancedOrder
func (h OrderHeap) Len() int { return len(h) }
func (h OrderHeap) Less(i, j int) bool {
// For bids: higher price = higher priority
// For asks: lower price = higher priority
if h[i].Side == Buy {
if h[i].Price != h[j].Price {
return h[i].Price > h[j].Price
}
} else {
if h[i].Price != h[j].Price {
return h[i].Price < h[j].Price
}
}
// Same price: earlier order has priority (FIFO)
return h[i].CreateTime.Before(h[j].CreateTime)
}
func (h OrderHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *OrderHeap) Push(x interface{}) {
*h = append(*h, x.(*AdvancedOrder))
}
func (h *OrderHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
// NewAdvancedOrderBook creates a fully-featured order book
func NewAdvancedOrderBook(symbol string) *AdvancedOrderBook {
bidHeap := &OrderHeap{}
askHeap := &OrderHeap{}
heap.Init(bidHeap)
heap.Init(askHeap)
return &AdvancedOrderBook{
symbol: symbol,
bidLevels: make(map[float64]*PriceLevel),
askLevels: make(map[float64]*PriceLevel),
bidHeap: bidHeap,
askHeap: askHeap,
orders: make(map[uint64]*AdvancedOrder),
stopOrders: make(map[uint64]*AdvancedOrder),
icebergOrders: make(map[uint64]*IcebergData),
hiddenOrders: make(map[uint64]*AdvancedOrder),
conditionalOrders: make(map[uint64]*ConditionalOrder),
trades: make([]Trade, 0),
marketDataFeeds: make([]chan MarketUpdate, 0),
tickSize: 0.01,
lotSize: 0.001,
}
}
// AddOrder processes any type of order
func (book *AdvancedOrderBook) AddOrder(order *AdvancedOrder) ([]Trade, error) {
book.mu.Lock()
defer book.mu.Unlock()
// Validate order
if err := book.validateOrder(order); err != nil {
order.Status = StatusRejected
return nil, err
}
// Set initial values
order.Status = StatusNew
order.CreateTime = time.Now()
order.UpdateTime = order.CreateTime
order.RemainingSize = order.Size
atomic.AddUint64(&book.totalOrders, 1)
// Route to appropriate handler
switch order.Type {
case Market:
return book.processMarketOrder(order)
case Limit:
return book.processLimitOrder(order)
case Stop, StopLimit:
return book.processStop(order)
case Iceberg:
return book.processIcebergOrder(order)
case Peg:
return book.processPeggedOrder(order)
case Bracket:
return book.processTrailingStop(order)
default:
return nil, fmt.Errorf("unsupported order type: %v", order.Type)
}
}
// processMarketOrder executes market order immediately
func (book *AdvancedOrderBook) processMarketOrder(order *AdvancedOrder) ([]Trade, error) {
trades := make([]Trade, 0)
var oppositeHeap *OrderHeap
if order.Side == Buy {
oppositeHeap = book.askHeap
} else {
oppositeHeap = book.bidHeap
}
// Match against opposite side
for oppositeHeap.Len() > 0 && order.RemainingSize > 0 {
bestOrder := (*oppositeHeap)[0]
// Skip hidden orders for market orders
if bestOrder.Hidden {
heap.Pop(oppositeHeap)
continue
}
// Execute trade
trade := book.executeTrade(order, bestOrder)
trades = append(trades, trade)
// Remove filled orders
if bestOrder.RemainingSize == 0 {
heap.Pop(oppositeHeap)
bestOrder.Status = StatusFilled
delete(book.orders, bestOrder.ID)
}
if order.RemainingSize == 0 {
order.Status = StatusFilled
break
}
}
// Cancel any remaining market order quantity
if order.RemainingSize > 0 {
order.Status = StatusCanceled
}
book.publishMarketData("trade", trades)
return trades, nil
}
// processLimitOrder adds limit order to book or matches immediately
func (book *AdvancedOrderBook) processLimitOrder(order *AdvancedOrder) ([]Trade, error) {
trades := make([]Trade, 0)
// Check if order crosses the spread
canMatch := false
if order.Side == Buy && book.bestAsk > 0 && order.Price >= book.bestAsk {
canMatch = true
} else if order.Side == Sell && book.bestBid > 0 && order.Price <= book.bestBid {
canMatch = true
}
// Post-only check
if order.PostOnly && canMatch {
order.Status = StatusRejected
return nil, errors.New("post-only order would cross spread")
}
// Match if possible
if canMatch {
trades = book.matchOrder(order)
}
// Add remaining to book
if order.RemainingSize > 0 {
if order.TimeInForce == TIF_IOC {
order.Status = StatusCanceled
} else if order.TimeInForce == TIF_FOK && order.RemainingSize < order.Size {
// FOK not fully filled, cancel entire order
order.Status = StatusCanceled
order.RemainingSize = order.Size
order.ExecutedSize = 0
return nil, errors.New("FOK order could not be fully filled")
} else {
book.addToBook(order)
}
}
book.updateBestPrices()
book.publishMarketData("orderbook", nil)
return trades, nil
}
// processStop handles stop and stop-limit orders
func (book *AdvancedOrderBook) processStop(order *AdvancedOrder) ([]Trade, error) {
order.Status = StatusPending
book.stopOrders[order.ID] = order
book.orders[order.ID] = order
// Check if stop should trigger immediately
if book.shouldTriggerStop(order) {
return book.triggerStop(order)
}
return nil, nil
}
// processIcebergOrder handles iceberg orders
func (book *AdvancedOrderBook) processIcebergOrder(order *AdvancedOrder) ([]Trade, error) {
// Store iceberg data
book.icebergOrders[order.ID] = &IcebergData{
TotalSize: order.Size,
DisplaySize: order.DisplaySize,
RemainingSize: order.Size,
RefillCount: 0,
}
// Create visible portion
visibleOrder := *order
visibleOrder.Size = order.DisplaySize
visibleOrder.Type = Limit
return book.processLimitOrder(&visibleOrder)
}
// processHiddenOrder handles hidden orders
func (book *AdvancedOrderBook) processHiddenOrder(order *AdvancedOrder) ([]Trade, error) {
order.Hidden = true
book.hiddenOrders[order.ID] = order
// Hidden orders still match but aren't displayed
trades := book.matchOrder(order)
if order.RemainingSize > 0 {
book.addToBook(order)
}
return trades, nil
}
// processPeggedOrder handles pegged orders
func (book *AdvancedOrderBook) processPeggedOrder(order *AdvancedOrder) ([]Trade, error) {
// Update price based on peg
if order.Side == Buy {
order.Price = book.bestBid + order.PegOffset
} else {
order.Price = book.bestAsk + order.PegOffset
}
return book.processLimitOrder(order)
}
// processTrailingStop handles trailing stop orders
func (book *AdvancedOrderBook) processTrailingStop(order *AdvancedOrder) ([]Trade, error) {
// Set initial stop price
if order.Side == Sell {
order.StopPrice = book.lastPrice - order.TrailAmount
} else {
order.StopPrice = book.lastPrice + order.TrailAmount
}
order.Type = Stop
return book.processStop(order)
}
// matchOrder matches an order against the book
func (book *AdvancedOrderBook) matchOrder(order *AdvancedOrder) []Trade {
trades := make([]Trade, 0)
var oppositeHeap *OrderHeap
if order.Side == Buy {
oppositeHeap = book.askHeap
} else {
oppositeHeap = book.bidHeap
}
for oppositeHeap.Len() > 0 && order.RemainingSize > 0 {
bestOrder := (*oppositeHeap)[0]
// Price check for limit orders
if order.Type == Limit {
if order.Side == Buy && order.Price < bestOrder.Price {
break
}
if order.Side == Sell && order.Price > bestOrder.Price {
break
}
}
// Self-trade prevention
if book.enableSelfTrade && order.UserID == bestOrder.UserID {
heap.Pop(oppositeHeap)
continue
}
// Execute trade
trade := book.executeTrade(order, bestOrder)
trades = append(trades, trade)
// Update or remove matched order
if bestOrder.RemainingSize == 0 {
heap.Pop(oppositeHeap)
bestOrder.Status = StatusFilled
delete(book.orders, bestOrder.ID)
// Check for iceberg refill
if iceberg, exists := book.icebergOrders[bestOrder.ID]; exists {
book.refillIceberg(bestOrder, iceberg)
}
}
}
return trades
}
// executeTrade executes a trade between two orders
func (book *AdvancedOrderBook) executeTrade(taker, maker *AdvancedOrder) Trade {
// Determine trade size
tradeSize := math.Min(taker.RemainingSize, maker.RemainingSize)
tradePrice := maker.Price
// Update orders
taker.RemainingSize -= tradeSize
taker.ExecutedSize += tradeSize
taker.LastFillTime = time.Now()
taker.LastFillPrice = tradePrice
taker.LastFillSize = tradeSize
maker.RemainingSize -= tradeSize
maker.ExecutedSize += tradeSize
maker.LastFillTime = time.Now()
maker.LastFillPrice = tradePrice
maker.LastFillSize = tradeSize
// Update status
if taker.RemainingSize == 0 {
taker.Status = StatusFilled
} else {
taker.Status = StatusPartiallyFilled
}
if maker.RemainingSize == 0 {
maker.Status = StatusFilled
} else {
maker.Status = StatusPartiallyFilled
}
// Create trade record
trade := Trade{
ID: atomic.AddUint64(&book.totalTrades, 1),
Price: tradePrice,
Size: tradeSize,
BuyOrder: taker.ID,
SellOrder: maker.ID,
Timestamp: time.Now(),
}
if taker.Side == Sell {
trade.BuyOrder = maker.ID
trade.SellOrder = taker.ID
}
// Update book statistics
book.lastPrice = tradePrice
book.lastTradeTime = trade.Timestamp
book.volume24h += tradeSize * tradePrice
book.totalVolume += tradeSize * tradePrice
book.trades = append(book.trades, trade)
// Check stop orders
book.checkStops(tradePrice)
// Update trailing stops
book.updateTrailingStops(tradePrice)
return trade
}
// addToBook adds order to the order book
func (book *AdvancedOrderBook) addToBook(order *AdvancedOrder) {
book.orders[order.ID] = order
if order.Side == Buy {
heap.Push(book.bidHeap, order)
level := book.bidLevels[order.Price]
if level == nil {
level = &PriceLevel{
Price: order.Price,
Size: 0,
Orders: make([]*Order, 0),
}
book.bidLevels[order.Price] = level
}
level.Size += order.RemainingSize
// Convert AdvancedOrder to Order for storage
basicOrder := &Order{
ID: order.ID,
Symbol: order.Symbol,
Side: order.Side,
Type: order.Type,
Price: order.Price,
Size: order.Size,
}
level.Orders = append(level.Orders, basicOrder)
} else {
heap.Push(book.askHeap, order)
level := book.askLevels[order.Price]
if level == nil {
level = &PriceLevel{
Price: order.Price,
Size: 0,
Orders: make([]*Order, 0),
}
book.askLevels[order.Price] = level
}
level.Size += order.RemainingSize
// Convert AdvancedOrder to Order for storage
basicOrder := &Order{
ID: order.ID,
Symbol: order.Symbol,
Side: order.Side,
Type: order.Type,
Price: order.Price,
Size: order.Size,
}
level.Orders = append(level.Orders, basicOrder)
}
order.Status = StatusNew
}
// CancelOrder cancels an order
func (book *AdvancedOrderBook) CancelOrder(orderID uint64) error {
book.mu.Lock()
defer book.mu.Unlock()
order, exists := book.orders[orderID]
if !exists {
// Check stop orders
if stopOrder, exists := book.stopOrders[orderID]; exists {
stopOrder.Status = StatusCanceled
delete(book.stopOrders, orderID)
return nil
}
return errors.New("order not found")
}
order.Status = StatusCanceled
order.UpdateTime = time.Now()
// Remove from book
book.removeFromBook(order)
delete(book.orders, orderID)
// Clean up special order types
delete(book.icebergOrders, orderID)
delete(book.hiddenOrders, orderID)
delete(book.conditionalOrders, orderID)
book.updateBestPrices()
book.publishMarketData("cancel", order)
return nil
}
// ModifyOrder modifies an existing order
func (book *AdvancedOrderBook) ModifyOrder(orderID uint64, newPrice, newSize float64) error {
book.mu.Lock()
defer book.mu.Unlock()
order, exists := book.orders[orderID]
if !exists {
return errors.New("order not found")
}
// Remove from current position
book.removeFromBook(order)
// Update order
order.Price = newPrice
order.Size = newSize
order.RemainingSize = newSize - order.ExecutedSize
order.UpdateTime = time.Now()
// Re-add to book
book.addToBook(order)
book.updateBestPrices()
book.publishMarketData("modify", order)
return nil
}
// removeFromBook removes order from book structures
func (book *AdvancedOrderBook) removeFromBook(order *AdvancedOrder) {
// Remove from heap
if order.Side == Buy {
for i, o := range *book.bidHeap {
if o.ID == order.ID {
heap.Remove(book.bidHeap, i)
break
}
}
// Remove from level
if level := book.bidLevels[order.Price]; level != nil {
level.Size -= order.RemainingSize
for i, o := range level.Orders {
if o.ID == order.ID {
level.Orders = append(level.Orders[:i], level.Orders[i+1:]...)
break
}
}
if len(level.Orders) == 0 {
delete(book.bidLevels, order.Price)
}
}
} else {
for i, o := range *book.askHeap {
if o.ID == order.ID {
heap.Remove(book.askHeap, i)
break
}
}
// Remove from level
if level := book.askLevels[order.Price]; level != nil {
level.Size -= order.RemainingSize
for i, o := range level.Orders {
if o.ID == order.ID {
level.Orders = append(level.Orders[:i], level.Orders[i+1:]...)
break
}
}
if len(level.Orders) == 0 {
delete(book.askLevels, order.Price)
}
}
}
}
// checkStops checks and triggers stop orders
func (book *AdvancedOrderBook) checkStops(lastPrice float64) {
for _, order := range book.stopOrders {
if book.shouldTriggerStop(order) {
book.triggerStop(order)
}
}
}
// shouldTriggerStop checks if stop order should trigger
func (book *AdvancedOrderBook) shouldTriggerStop(order *AdvancedOrder) bool {
if order.Side == Buy {
return book.lastPrice >= order.StopPrice
}
return book.lastPrice <= order.StopPrice
}
// triggerStop converts stop order to market/limit
func (book *AdvancedOrderBook) triggerStop(order *AdvancedOrder) ([]Trade, error) {
delete(book.stopOrders, order.ID)
if order.Type == Stop {
order.Type = Market
} else {
order.Type = Limit
}
return book.AddOrder(order)
}
// updateTrailingStops updates trailing stop orders
func (book *AdvancedOrderBook) updateTrailingStops(lastPrice float64) {
for _, order := range book.stopOrders {
if order.Type != Bracket {
continue
}
if order.Side == Sell {
newStop := lastPrice - order.TrailAmount
if newStop > order.StopPrice {
order.StopPrice = newStop
}
} else {
newStop := lastPrice + order.TrailAmount
if newStop < order.StopPrice {
order.StopPrice = newStop
}
}
}
}
// refillIceberg refills iceberg order
func (book *AdvancedOrderBook) refillIceberg(order *AdvancedOrder, iceberg *IcebergData) {
if iceberg.RemainingSize <= 0 {
return
}
refillSize := math.Min(iceberg.DisplaySize, iceberg.RemainingSize)
iceberg.RemainingSize -= refillSize
iceberg.RefillCount++
// Create new visible order
newOrder := *order
newOrder.ID = atomic.AddUint64(&book.sequenceNumber, 1)
newOrder.Size = refillSize
newOrder.RemainingSize = refillSize
newOrder.ExecutedSize = 0
newOrder.Status = StatusNew
book.addToBook(&newOrder)
}
// updateBestPrices updates best bid/ask prices
func (book *AdvancedOrderBook) updateBestPrices() {
if book.bidHeap.Len() > 0 {
book.bestBid = (*book.bidHeap)[0].Price
} else {
book.bestBid = 0
}
if book.askHeap.Len() > 0 {
book.bestAsk = (*book.askHeap)[0].Price
} else {
book.bestAsk = 0
}
}
// validateOrder validates order parameters
func (book *AdvancedOrderBook) validateOrder(order *AdvancedOrder) error {
if order.Size <= 0 {
return errors.New("order size must be positive")
}
if order.Type == Limit && order.Price <= 0 {
return errors.New("limit order must have positive price")
}
// Check tick size with tolerance for floating point precision
const epsilon = 1e-9
remainder := math.Mod(order.Price, book.tickSize)
if remainder > epsilon && remainder < (book.tickSize-epsilon) {
return fmt.Errorf("price must be multiple of tick size %.2f", book.tickSize)
}
// Check lot size with tolerance for floating point precision
sizeRemainder := math.Mod(order.Size, book.lotSize)
if sizeRemainder > epsilon && sizeRemainder < (book.lotSize-epsilon) {
return fmt.Errorf("size must be multiple of lot size %.3f", book.lotSize)
}
// FOK validation
if order.TimeInForce == TIF_FOK && !book.canFillCompletely(order) {
return errors.New("FOK order cannot be filled completely")
}
return nil
}
// canFillCompletely checks if order can be filled completely
func (book *AdvancedOrderBook) canFillCompletely(order *AdvancedOrder) bool {
availableSize := 0.0
if order.Side == Buy {
for _, o := range *book.askHeap {
if order.Type == Market || order.Price >= o.Price {
availableSize += o.RemainingSize
if availableSize >= order.Size {
return true
}
}
}
} else {
for _, o := range *book.bidHeap {
if order.Type == Market || order.Price <= o.Price {
availableSize += o.RemainingSize
if availableSize >= order.Size {
return true
}
}
}
}
return false
}
// GetDepth returns order book depth
func (book *AdvancedOrderBook) GetDepth(levels int) map[string]interface{} {
book.mu.RLock()
defer book.mu.RUnlock()
bids := make([][]float64, 0)
asks := make([][]float64, 0)
// Aggregate bid levels
bidPrices := make([]float64, 0, len(book.bidLevels))
for price := range book.bidLevels {
bidPrices = append(bidPrices, price)
}
// Sort descending
for i := 0; i < len(bidPrices); i++ {
for j := i + 1; j < len(bidPrices); j++ {
if bidPrices[i] < bidPrices[j] {
bidPrices[i], bidPrices[j] = bidPrices[j], bidPrices[i]
}
}
}
count := 0
for _, price := range bidPrices {
if levels > 0 && count >= levels {
break
}
level := book.bidLevels[price]
if !book.shouldShowLevel(level) {
continue
}
bids = append(bids, []float64{price, level.Size})
count++
}
// Aggregate ask levels
askPrices := make([]float64, 0, len(book.askLevels))
for price := range book.askLevels {
askPrices = append(askPrices, price)
}
// Sort ascending
for i := 0; i < len(askPrices); i++ {
for j := i + 1; j < len(askPrices); j++ {
if askPrices[i] > askPrices[j] {
askPrices[i], askPrices[j] = askPrices[j], askPrices[i]
}
}
}
count = 0
for _, price := range askPrices {
if levels > 0 && count >= levels {
break
}
level := book.askLevels[price]
if !book.shouldShowLevel(level) {
continue
}
asks = append(asks, []float64{price, level.Size})
count++
}
return map[string]interface{}{
"bids": bids,
"asks": asks,
"timestamp": time.Now().Unix(),
"sequence": atomic.LoadUint64(&book.sequenceNumber),
}
}
// shouldShowLevel checks if level should be displayed
func (book *AdvancedOrderBook) shouldShowLevel(level *PriceLevel) bool {
// Don't show levels with only hidden orders
for _, order := range level.Orders {
if !order.Hidden {
return true
}
}
return false
}
// GetSnapshot returns full order book snapshot
func (book *AdvancedOrderBook) GetSnapshot() map[string]interface{} {
book.mu.RLock()
defer book.mu.RUnlock()
return map[string]interface{}{
"symbol": book.symbol,
"bids": book.GetDepth(0)["bids"],
"asks": book.GetDepth(0)["asks"],
"lastPrice": book.lastPrice,
"volume24h": book.volume24h,
"bestBid": book.bestBid,
"bestAsk": book.bestAsk,
"timestamp": time.Now().Unix(),
"sequence": atomic.LoadUint64(&book.sequenceNumber),
}
}
// SubscribeMarketData subscribes to market data updates
func (book *AdvancedOrderBook) SubscribeMarketData() chan MarketUpdate {
book.mu.Lock()
defer book.mu.Unlock()
feed := make(chan MarketUpdate, 1000)
book.marketDataFeeds = append(book.marketDataFeeds, feed)
return feed
}
// publishMarketData publishes updates to subscribers
func (book *AdvancedOrderBook) publishMarketData(updateType string, data interface{}) {
update := MarketUpdate{
Type: updateType,
Timestamp: time.Now(),
Data: data,
}
for _, feed := range book.marketDataFeeds {
select {
case feed <- update:
default:
// Channel full, skip
}
}
}
// GetStatistics returns order book statistics
func (book *AdvancedOrderBook) GetStatistics() map[string]interface{} {
book.mu.RLock()
defer book.mu.RUnlock()
return map[string]interface{}{
"symbol": book.symbol,
"totalOrders": atomic.LoadUint64(&book.totalOrders),
"totalTrades": atomic.LoadUint64(&book.totalTrades),
"totalVolume": book.totalVolume,
"volume24h": book.volume24h,
"bestBid": book.bestBid,
"bestAsk": book.bestAsk,
"spread": book.bestAsk - book.bestBid,
"lastPrice": book.lastPrice,
"lastTradeTime": book.lastTradeTime,
"bidLevels": len(book.bidLevels),
"askLevels": len(book.askLevels),
"activeOrders": len(book.orders),
"stopOrders": len(book.stopOrders),
"hiddenOrders": len(book.hiddenOrders),
"icebergOrders": len(book.icebergOrders),
}
}