package matching import ( "fmt" "sync" "time" "github.com/genex/trading-service/internal/domain/entity" "github.com/genex/trading-service/internal/orderbook" ) type MatchResult struct { Trades []*entity.Trade UpdatedOrder *entity.Order } type Engine struct { orderbooks map[string]*orderbook.OrderBook mu sync.RWMutex tradeSeq int64 } func NewEngine() *Engine { return &Engine{ orderbooks: make(map[string]*orderbook.OrderBook), } } func (e *Engine) getOrCreateOrderBook(couponID string) *orderbook.OrderBook { e.mu.Lock() defer e.mu.Unlock() ob, exists := e.orderbooks[couponID] if !exists { ob = orderbook.NewOrderBook(couponID) e.orderbooks[couponID] = ob } return ob } func (e *Engine) PlaceOrder(order *entity.Order) *MatchResult { ob := e.getOrCreateOrderBook(order.CouponID) result := &MatchResult{UpdatedOrder: order} if order.Type == entity.Market { e.matchMarketOrder(ob, order, result) } else { e.matchLimitOrder(ob, order, result) } // If order still has remaining quantity, add to book if order.RemainingQty > 0 && order.Status != entity.OrderCancelled { if order.Type == entity.Limit { ob.AddOrder(order) if order.FilledQty > 0 { order.Status = entity.OrderPartial } } } return result } func (e *Engine) CancelOrder(couponID, orderID string, side entity.OrderSide) bool { ob := e.getOrCreateOrderBook(couponID) return ob.RemoveOrder(orderID, side) } func (e *Engine) GetOrderBookSnapshot(couponID string, depth int) (bids []orderbook.PriceLevel, asks []orderbook.PriceLevel) { ob := e.getOrCreateOrderBook(couponID) return ob.Snapshot(depth) } func (e *Engine) matchLimitOrder(ob *orderbook.OrderBook, order *entity.Order, result *MatchResult) { if order.Side == entity.Buy { e.matchBuyOrder(ob, order, result) } else { e.matchSellOrder(ob, order, result) } } func (e *Engine) matchMarketOrder(ob *orderbook.OrderBook, order *entity.Order, result *MatchResult) { if order.Side == entity.Buy { e.matchBuyOrder(ob, order, result) } else { e.matchSellOrder(ob, order, result) } } func (e *Engine) matchBuyOrder(ob *orderbook.OrderBook, buyOrder *entity.Order, result *MatchResult) { for len(ob.Asks) > 0 && buyOrder.RemainingQty > 0 { bestAsk := &ob.Asks[0] if buyOrder.Type == entity.Limit && bestAsk.Price > buyOrder.Price { break } for len(bestAsk.Orders) > 0 && buyOrder.RemainingQty > 0 { sellOrder := bestAsk.Orders[0] matchQty := min(buyOrder.RemainingQty, sellOrder.RemainingQty) matchPrice := sellOrder.Price trade := e.createTrade(buyOrder, sellOrder, matchPrice, matchQty) result.Trades = append(result.Trades, trade) buyOrder.FilledQty += matchQty buyOrder.RemainingQty -= matchQty sellOrder.FilledQty += matchQty sellOrder.RemainingQty -= matchQty if sellOrder.RemainingQty == 0 { sellOrder.Status = entity.OrderFilled bestAsk.Orders = bestAsk.Orders[1:] } else { sellOrder.Status = entity.OrderPartial } } if len(bestAsk.Orders) == 0 { ob.Asks = ob.Asks[1:] } } if buyOrder.RemainingQty == 0 { buyOrder.Status = entity.OrderFilled } } func (e *Engine) matchSellOrder(ob *orderbook.OrderBook, sellOrder *entity.Order, result *MatchResult) { for len(ob.Bids) > 0 && sellOrder.RemainingQty > 0 { bestBid := &ob.Bids[0] if sellOrder.Type == entity.Limit && bestBid.Price < sellOrder.Price { break } for len(bestBid.Orders) > 0 && sellOrder.RemainingQty > 0 { buyOrder := bestBid.Orders[0] matchQty := min(sellOrder.RemainingQty, buyOrder.RemainingQty) matchPrice := buyOrder.Price trade := e.createTrade(buyOrder, sellOrder, matchPrice, matchQty) result.Trades = append(result.Trades, trade) sellOrder.FilledQty += matchQty sellOrder.RemainingQty -= matchQty buyOrder.FilledQty += matchQty buyOrder.RemainingQty -= matchQty if buyOrder.RemainingQty == 0 { buyOrder.Status = entity.OrderFilled bestBid.Orders = bestBid.Orders[1:] } else { buyOrder.Status = entity.OrderPartial } } if len(bestBid.Orders) == 0 { ob.Bids = ob.Bids[1:] } } if sellOrder.RemainingQty == 0 { sellOrder.Status = entity.OrderFilled } } func (e *Engine) createTrade(buyOrder, sellOrder *entity.Order, price float64, qty int) *entity.Trade { e.tradeSeq++ takerFee := price * float64(qty) * 0.005 // 0.5% taker fee makerFee := price * float64(qty) * 0.001 // 0.1% maker fee return &entity.Trade{ ID: fmt.Sprintf("trade-%d", e.tradeSeq), CouponID: buyOrder.CouponID, BuyOrderID: buyOrder.ID, SellOrderID: sellOrder.ID, BuyerID: buyOrder.UserID, SellerID: sellOrder.UserID, Price: price, Quantity: qty, BuyerFee: takerFee, SellerFee: makerFee, CreatedAt: time.Now(), } } // GetAllOrderBooks returns a snapshot of all active orderbooks for admin use. func (e *Engine) GetAllOrderBooks() map[string]*orderbook.OrderBook { e.mu.RLock() defer e.mu.RUnlock() result := make(map[string]*orderbook.OrderBook, len(e.orderbooks)) for k, v := range e.orderbooks { result[k] = v } return result } // GetTradeCount returns the total number of trades executed. func (e *Engine) GetTradeCount() int64 { e.mu.RLock() defer e.mu.RUnlock() return e.tradeSeq } func min(a, b int) int { if a < b { return a } return b }