This section is the last of this series of articles, and will explain the remaining things, including the implementation logic of the order queue in the transaction order book, and the implementation logic of more order types. In addition, many friends are asking, will all the code be open source and put on Github after the completion? All I can say is that there is a high probability that it will be open sourced in the long run, but there is no plan to open source it in the short term.
The transaction order book is actually composed of two order queues, a buy order queue and a sell order queue. Any query and operation on the transaction delegation ledger is actually the two queues of query and operation. The design of the order queue also directly affects the performance of the matchmaking. The previous article also briefly talked about the design of the order queue when talking about the data structure design. We mainly use two-dimensional links combined with Map to save all orders, relying on the container/list package. .
The structure of the order queue is as follows:
type orderQueue struct {
sortBy enum.SortDirection
parentList *list.List
elementMap map[string]*list.Element
}
sortBy specifies the direction of price sorting, the queue for buy orders is in descending order, and the queue for sell orders is in ascending order. parentList holds all orders of the entire two-dimensional linked list, the first dimension is sorted by price, and the second dimension is sorted by time. elementMap is a key-value pair where Key is the price and Value is the second-dimensional order list.
The initialization function is relatively simple. It only assigns values to several fields. The code is as follows:
func (q *orderQueue) init(sortBy enum. SortDirection) {
q.sortBy = sortBy
q.parentList = list.New()
q.elementMap = make(map[string]*list.Element)
}
In addition to the initialization function, five other functions are provided:
addOrder(order): add an order
getHeadOrder(): Read the head order
popHeadOrder(): read and delete the head order
removeOrder(order): remove order
getDepthPrice(depth): read depth price
Only the first function of the above five functions will be more complicated. In order to make the processing flow easier to understand, I will not post the code and draw a complete flow chart for everyone to see:
!Picture
This process is indeed a bit complicated, you can read it several times to digest it well, and it is best to convert it into code by yourself.
The other functions are simple, and the last function needs to be explained. The purpose of reading the depth price is to facilitate the determination of the upper limit price when processing orders of market-opponent, market-top5, market-top10 and other types. Please see the code of the function to understand the logic and usage of the function:
func (q *orderQueue) getDepthPrice(depth int) (string, int) {
if q.parentList.Len() == 0 {
return "", 0
}
p := q.parentList.Front()
i := 1
for ; i < depth; i++ {
t := p.Next()
if t != nil {
p = t
} else {
break;
}
}
o := p.Value.(*list.List).Front().Value.(*Order)
return o.Price.String(), i
}
Our engine supports a total of six types of orders. The previous articles have briefly introduced them, but they have not explained in depth what the specific business logic of these different types should be. Therefore, this part is supplemented here.
limit The ordinary price limit is the simplest, and the code implementation has been shown in the previous article
The processing logic is:
1. Determine whether the new order is a buy order or a sell order.
2. If it is a buy order, read the head sell order from the OrderBook, that is, the head order in the sell order queue; if it is a sell order, read the head buy order from the OrderBook, that is, the head order in the buy order queue.
3. When the new order is a buy order, if the head order is empty, or the new order is smaller than the head order, that is, the transaction cannot be executed, then the new order will be added to the buy order queue, and the processing will end; when the new order is a sell order, if the head order is If the order is empty, or the new order is larger than the head order, that is, the transaction cannot be executed, then the new order is added to the sell order queue, and the processing ends.
4. Otherwise, if the matching conditions are met, the new order and the top order will be matched and traded.
5. After the matching is completed, if the remaining quantity of the new order is zero, it will end. If it is still greater than zero, go back to step 2 and continue to take the next head order, and so on.
limit-ioc There is only one difference between the IOC limit price and the ordinary limit price. If the new order does not match the head order, the ordinary limit price will be added to the order queue, while the IOC limit price will be processed as cancellation.
market The logic of the default market order is also relatively simple, it does not need to judge the price, as long as the head order is not empty, it will directly match the head order.
market-top5/market-top10 The logic of the optimal fifth/tenth market order is similar to that of the default market order. The difference is that the transaction price of the default market price has no upper limit or lower limit, but the optimal fifth/tenth market price has a price upper limit or lower limit. Orders with upper and lower limits will not be filled. Drawing is too tiring, let’s just paste the code directly. The following is the processing of the purchase order:
func dealBuyMarketTop(order *Order, book *orderBook, lastTradePrice *decimal.Decimal, depth int) {
priceStr, _ := book.getSellDepthPrice(depth)
if priceStr == "" {
cancelOrder(order)
return
}
limitPrice, _ := decimal.NewFromString(priceStr)
LOOP:
headOrder := book.getHeadSellOrder()
if headOrder != nil && limitPrice.GreaterThanOrEqual(headOrder.Price) {
matchTrade(headOrder, order, book, lastTradePrice)
if order.Amount.IsPositive() {
goto LOOP
}
} else {
cancelOrder(order)
}
}
market-opponent The last type, the counterparty’s best price, this type only deals with the counterparty’s tier one price, but it’s a little different from the best 5th/10th tier: the untraded part of the best 5th/10th tier is For canceled orders, the last unfilled part of the counterparty's best price is converted to a limit order. Please see the code:
func dealBuyMarketOpponent(order *Order, book *orderBook, lastTradePrice *decimal.Decimal) {
priceStr, _ := book.getSellDepthPrice(1)
if priceStr == "" {
cancelOrder(order)
return
}
limitPrice, _ := decimal.NewFromString(priceStr)
LOOP:
headOrder := book.getHeadSellOrder()
if headOrder != nil && limitPrice.GreaterThanOrEqual(headOrder.Price) {
matchTrade(headOrder, order, book, lastTradePrice)
if order.Amount.IsPositive() {
goto LOOP
}
} else {
order.Price = limitPrice
order.Type = enum.TypeLimit
book.addBuyOrder(order)
cache.UpdateOrder(order.ToMap())
log.Info("engine %s, a order has added to the orderbook: %s", order.Symbol, order.ToJson())
}
}
At this point, the whole series is over. However, my matchmaking program will continue to be iteratively upgraded. In addition, other components will also be developed, which will be used in conjunction with the current matchmaking engine. Welcome to follow-up news.
This article partly refers to the idea of this project:
https://github.com/jammy928/CoinExchange_CryptoExchange_Java
