|
| 1 | +import { Injectable } from '@nestjs/common'; |
| 2 | +import { Order } from '../entities/order.entity'; |
| 3 | +import { OrderBook } from './order-book.service'; |
| 4 | +import { Trade } from '../entities/trade.entity'; |
| 5 | +import { OrderStatus, OrderSide } from '../enums/order.enum'; |
| 6 | + |
| 7 | +@Injectable() |
| 8 | +export class MatchingEngine { |
| 9 | + private readonly orderBooks: Map<string, OrderBook> = new Map(); |
| 10 | + |
| 11 | + addOrder(order: Order): Trade[] { |
| 12 | + const orderBook = this.getOrderBook(order.assetPair); |
| 13 | + orderBook.addOrder(order); |
| 14 | + return this.matchOrders(order.assetPair); |
| 15 | + } |
| 16 | + |
| 17 | + private getOrderBook(assetPair: string): OrderBook { |
| 18 | + let book = this.orderBooks.get(assetPair); |
| 19 | + if (!book) { |
| 20 | + book = new OrderBook(); |
| 21 | + this.orderBooks.set(assetPair, book); |
| 22 | + } |
| 23 | + return book; |
| 24 | + } |
| 25 | + |
| 26 | + private matchOrders(assetPair: string): Trade[] { |
| 27 | + const trades: Trade[] = []; |
| 28 | + const orderBook = this.getOrderBook(assetPair); |
| 29 | + const bids = orderBook.getBids(); |
| 30 | + const asks = orderBook.getAsks(); |
| 31 | + |
| 32 | + while ( |
| 33 | + bids.length > 0 && |
| 34 | + asks.length > 0 && |
| 35 | + bids[0].price >= asks[0].price |
| 36 | + ) { |
| 37 | + const bestBid = bids[0]; |
| 38 | + const bestAsk = asks[0]; |
| 39 | + const tradeQuantity = Math.min( |
| 40 | + bestBid.quantity - bestBid.filledQuantity, |
| 41 | + bestAsk.quantity - bestAsk.filledQuantity, |
| 42 | + ); |
| 43 | + |
| 44 | + const trade = new Trade(); |
| 45 | + trade.assetPair = assetPair; |
| 46 | + trade.price = bestAsk.price; |
| 47 | + trade.quantity = tradeQuantity; |
| 48 | + trade.takerOrderId = bestBid.id; |
| 49 | + trade.makerOrderId = bestAsk.id; |
| 50 | + trade.timestamp = new Date(); |
| 51 | + trades.push(trade); |
| 52 | + |
| 53 | + bestBid.filledQuantity += tradeQuantity; |
| 54 | + bestAsk.filledQuantity += tradeQuantity; |
| 55 | + |
| 56 | + if (bestBid.filledQuantity === bestBid.quantity) { |
| 57 | + bestBid.status = OrderStatus.FILLED; |
| 58 | + bids.shift(); |
| 59 | + } else { |
| 60 | + bestBid.status = OrderStatus.PARTIALLY_FILLED; |
| 61 | + } |
| 62 | + |
| 63 | + if (bestAsk.filledQuantity === bestAsk.quantity) { |
| 64 | + bestAsk.status = OrderStatus.FILLED; |
| 65 | + asks.shift(); |
| 66 | + } else { |
| 67 | + bestAsk.status = OrderStatus.PARTIALLY_FILLED; |
| 68 | + } |
| 69 | + } |
| 70 | + return trades; |
| 71 | + } |
| 72 | +} |
0 commit comments