From 0c2483e9f300f2b8db03419725a2ba9b89baa073 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Mon, 17 Oct 2022 12:14:40 +0300 Subject: [PATCH] add match function --- abci/example/orderbook/market.go | 184 ++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 37 deletions(-) diff --git a/abci/example/orderbook/market.go b/abci/example/orderbook/market.go index 4b6a40723..ec768275c 100644 --- a/abci/example/orderbook/market.go +++ b/abci/example/orderbook/market.go @@ -5,69 +5,179 @@ import ( ) type Market struct { - pair Pair // i.e. EUR/USD (a market is bidirectional) - buyingOrders HighestPriceOrderList // i.e. buying EUR for USD - sellingOrders LowestPriceOrderList // i.e. selling EUR for USD or buying USD for EUR + pair Pair // i.e. EUR/USD (a market is bidirectional) + askOrders *AskOrders // i.e. buying EUR for USD + lowestAsk float64 + bidOrders *BidOrders // i.e. selling EUR for USD or buying USD for EUR + highestBid float64 } -func (m *Market) AddBid(b MsgBid) error { - return nil +func NewMarket(p Pair) *Market { + return &Market{pair: p} } -func (m *Market) AddAsk(a MsgAsk) error { - return nil +func (m *Market) AddBid(b MsgBid) { + heap.Push(m.bidOrders, b) + if b.BidOrder.MaxPrice > m.highestBid { + m.highestBid = b.BidOrder.MaxPrice + } } -func (m *Market) Match() (*TradeSet, error) { - return nil, nil -} - - - -type LowestPriceOrderList []*Order - -func (l LowestPriceOrderList) Less(i, j int) bool { - return l[i].MinPrice < l[j].MinPrice +func (m *Market) AddAsk(a MsgAsk) { + heap.Push(m.askOrders, a) + if a.AskOrder.AskPrice < m.lowestAsk { + m.lowestAsk = a.AskOrder.AskPrice + } } -func (l LowestPriceOrderList) Swap(i, j int) { - l[i], l[j] = l[j], l[i] +// Match takes the set of bids and asks and matches them together. +// A bid matches an ask when the MaxPrice is greater than the AskPrice +// and the MaxQuantity is greater than the quantity. +func (m *Market) Match() *TradeSet { + if m.highestBid < m.lowestAsk { + // no orders match, we return early. + return nil + } + + t := &TradeSet{Pair: &m.pair} + bids := make([]*OrderBid, 0) + asks := make([]*OrderAsk, 0) + + // get all the bids that are greater than the lowest ask. In order from heighest bid to lowest bid + for bid := heap.Pop(m.bidOrders).(*OrderBid); bid.MaxPrice >= m.lowestAsk; bid = heap.Pop(m.bidOrders).(*OrderBid) { + bids = append(bids, bid) + } + + // get all the asks that are lower than the highest bid in the bids set. Ordered from lowest to highest ask + for ask := heap.Pop(m.askOrders).(*OrderAsk); ask.AskPrice <= bids[0].MaxPrice; ask = heap.Pop(m.askOrders).(*OrderAsk) { + asks = append(asks, ask) + } + + // this is to keep track of the index of the bids that have been matched + reserved := make(map[int]struct{}) + + // start from the highest ask and the highest bid and for each ask loop downwards through the slice of + // bids until one is matched +OUTER_LOOP: + for i := len(asks) - 1; i >= 0; i-- { + ask := asks[i] + + // start with the highest bid and increment down since we're more likely to find a match + for j := 0 ; j < len(bids); j++ { + bid := bids[j] + if bid.MaxPrice >= ask.AskPrice { + if bid.MaxQuantity >= ask.Quantity { + // yay! we have a match + t.AddFilledOrder(ask, bid) + + // reserve the bid so we don't rematch it with another ask + reserved[j] = struct{}{} + continue OUTER_LOOP + } + } else { + // once we've dropped below the ask price there are no more possible bids and so we break + break + } + } + + // as we go from highest to lowest, asks that aren't matched become the new lowest ask price + m.lowestAsk = ask.AskPrice + + // no match found, add the ask order back into the heap + heap.Push(m.askOrders, ask) + } + + // add back the unmatched bids to the heap so they can be matched again in a later round. + // We also neeed to recalculate the new highest bid. First we tackle an edge case whereby all + // selected bids were matched. In this case we grab the next highest and set that as the new + // highest bid + m.highestBid = 0 + if len(reserved) == len(bids) { + newHighestBid := heap.Pop(m.bidOrders).(*OrderBid) + m.highestBid = newHighestBid.MaxPrice + heap.Push(m.bidOrders, newHighestBid) + } + for j := 0; j < len(bids); j++ { + if _, ok := reserved[j]; !ok { + if bids[j].MaxPrice > m.highestBid { + m.highestBid = bids[j].MaxPrice + } + heap.Push(m.bidOrders, bids[j]) + } + } + + return t } -func (l *LowestPriceOrderList) Push(x any) { - item := x.(*Order) - *l = append(*l, item) +func (m Market) LowestAsk() float64 { + return m.lowestAsk } -func (l *LowestPriceOrderList) Pop() any { - old := *l +func (m Market) HighestBid() float64 { + return m.highestBid +} + +// Heap ordered by lowest price +type AskOrders []*OrderAsk + +var _ heap.Interface = (*AskOrders)(nil) + +func (a AskOrders) Len() int { return len(a) } + +func (a AskOrders) Less(i, j int) bool { + return a[i].AskPrice < a[j].AskPrice +} + +func (a AskOrders) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a *AskOrders) Push(x any) { + item := x.(*OrderAsk) + *a = append(*a, item) +} + +func (a *AskOrders) Pop() any { + old := *a n := len(old) item := old[n-1] old[n-1] = nil - *l = old[0 : n-1] + *a = old[0 : n-1] return item } -type HighestPriceOrderList []*Order +// Heap ordered by highest price +type BidOrders []*OrderBid -func (l HighestPriceOrderList) Less(i, j int) bool { - return l[i].MinPrice > l[j].MinPrice +var _ heap.Interface = (*BidOrders)(nil) + +func (b BidOrders) Len() int { return len(b) } + +func (b BidOrders) Less(i, j int) bool { + return b[i].MaxPrice > b[j].MaxPrice } -func (l HighestPriceOrderList) Swap(i, j int) { - l[i], l[j] = l[j], l[i] +func (b BidOrders) Swap(i, j int) { + b[i], b[j] = b[j], b[i] } -func (l *HighestPriceOrderList) Push(x any) { - item := x.(*Order) - *l = append(*l, item) +func (b *BidOrders) Push(x any) { + item := x.(*OrderBid) + *b = append(*b, item) } -func (l *HighestPriceOrderList) Pop() any { - old := *l +func (b *BidOrders) Pop() any { + old := *b n := len(old) item := old[n-1] old[n-1] = nil - *l = old[0 : n-1] + *b = old[0 : n-1] return item -} \ No newline at end of file +} + +func (t *TradeSet) AddFilledOrder(ask *OrderAsk, bid *OrderBid) { + t.MatchedOrders = append(t.MatchedOrders, &MatchedOrder{ + OrderAsk: ask, + OrderBid: bid, + }) +}