gcx/backend/services/trading-service/internal/orderbook/orderbook.go

116 lines
2.3 KiB
Go

package orderbook
import (
"sort"
"sync"
"github.com/genex/trading-service/internal/domain/entity"
)
type PriceLevel struct {
Price float64
Orders []*entity.Order
}
type OrderBook struct {
CouponID string
Bids []PriceLevel // sorted desc (highest first)
Asks []PriceLevel // sorted asc (lowest first)
mu sync.RWMutex
}
func NewOrderBook(couponID string) *OrderBook {
return &OrderBook{CouponID: couponID}
}
func (ob *OrderBook) AddOrder(order *entity.Order) {
ob.mu.Lock()
defer ob.mu.Unlock()
if order.Side == entity.Buy {
ob.addToPriceLevels(&ob.Bids, order, true)
} else {
ob.addToPriceLevels(&ob.Asks, order, false)
}
}
func (ob *OrderBook) RemoveOrder(orderID string, side entity.OrderSide) bool {
ob.mu.Lock()
defer ob.mu.Unlock()
levels := &ob.Bids
if side == entity.Sell {
levels = &ob.Asks
}
for i, level := range *levels {
for j, o := range level.Orders {
if o.ID == orderID {
level.Orders = append(level.Orders[:j], level.Orders[j+1:]...)
if len(level.Orders) == 0 {
*levels = append((*levels)[:i], (*levels)[i+1:]...)
} else {
(*levels)[i] = level
}
return true
}
}
}
return false
}
func (ob *OrderBook) BestBid() *PriceLevel {
ob.mu.RLock()
defer ob.mu.RUnlock()
if len(ob.Bids) == 0 {
return nil
}
return &ob.Bids[0]
}
func (ob *OrderBook) BestAsk() *PriceLevel {
ob.mu.RLock()
defer ob.mu.RUnlock()
if len(ob.Asks) == 0 {
return nil
}
return &ob.Asks[0]
}
func (ob *OrderBook) Snapshot(depth int) (bids []PriceLevel, asks []PriceLevel) {
ob.mu.RLock()
defer ob.mu.RUnlock()
bidDepth := min(depth, len(ob.Bids))
askDepth := min(depth, len(ob.Asks))
bids = make([]PriceLevel, bidDepth)
copy(bids, ob.Bids[:bidDepth])
asks = make([]PriceLevel, askDepth)
copy(asks, ob.Asks[:askDepth])
return
}
func (ob *OrderBook) addToPriceLevels(levels *[]PriceLevel, order *entity.Order, descending bool) {
for i, level := range *levels {
if level.Price == order.Price {
(*levels)[i].Orders = append((*levels)[i].Orders, order)
return
}
}
*levels = append(*levels, PriceLevel{Price: order.Price, Orders: []*entity.Order{order}})
sort.Slice(*levels, func(i, j int) bool {
if descending {
return (*levels)[i].Price > (*levels)[j].Price
}
return (*levels)[i].Price < (*levels)[j].Price
})
}
func min(a, b int) int {
if a < b {
return a
}
return b
}