116 lines
2.3 KiB
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
|
|
}
|