歡迎關注「Keegan小鋼」公衆號獲取更多文章緩存
撮合引擎開發:開篇數據結構
撮合引擎開發:數據結構設計post
撮合引擎開發:完結篇code
本小節是該系列文章的最後一篇了,將講解剩下的一些東西,包括交易委託帳本中訂單隊列的實現邏輯、更多訂單類型的實現邏輯。另外,很多朋友在問,完結後全部代碼是否會開源放上 Github?我只能說,長期大機率會開源,但短時間內還沒打算開源。
交易委託帳本其實就是由兩個訂單隊列組成的,一個買單隊列,一個賣單隊列。任何對交易委託帳本的查詢和操做,實際上都是查詢和操做這兩個隊列。訂單隊列的設計也直接影響了撮合的性能,前面文章講數據結構設計時也有簡單聊了訂單隊列的設計,咱們主要是用二維連接結合 Map 來保存全部訂單的,依賴的是 container/list 包。
訂單隊列的結構體以下:
type orderQueue struct {
sortBy enum.SortDirection
parentList *list.List
elementMap map[string]*list.Element
}
複製代碼
sortBy 指訂價格排序的方向,買單隊列是降序的,而賣單隊列則是升序的。parentList 保存整個二維鏈表的全部訂單,第一維以價格排序,第二維以時間排序。elementMap 則是 Key 爲價格、Value 爲第二維訂單鏈表的鍵值對。
初始化函數就比較簡單了,對幾個字段賦值而已,代碼以下:
func (q *orderQueue) init(sortBy enum.SortDirection) {
q.sortBy = sortBy
q.parentList = list.New()
q.elementMap = make(map[string]*list.Element)
}
複製代碼
除了初始化函數,還提供了另外五個函數:
以上五個函數就只有第一個函數會比較複雜,爲了讓處理流程更容易理解,我就不貼代碼了,畫一個完整的流程圖給你們看看:
這個流程確實有一點複雜,能夠多看幾遍好好消化,最好本身動手將其轉爲代碼實現。
其餘幾個函數就簡單了,關於最後一個函數須要補充說明一下。讀取深度價格是爲了方便處理 market-opponent、market-top五、market-top10 等類型的訂單時判斷上限價格。請看該函數的代碼以理解該函數的邏輯和用法:
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
}
複製代碼
咱們引擎總共支持了六種訂單類型,以前的文章有簡單介紹過,但沒有深刻講解這幾種不一樣類型的具體業務邏輯應該是怎樣的,所以,在此將這部份內容補充上。
普通限價是最簡單的,前文也已經展現過代碼實現,爲了加深理解,我再給你們畫一張圖:
處理邏輯就是:
IOC 限價與普通限價不一樣的地方只有一個,若是新訂單和頭部訂單不匹配時,普通限價單會被添加到訂單隊列中,而 IOC 限價則是做撤單處理,請看下圖:
默認市價單的邏輯也比較簡單,它不須要判斷價格,只要頭部訂單不爲空,就直接和頭部訂單匹配成交,其處理邏輯以下圖:
最優五檔/十檔市價單與默認市價單的邏輯也是相似的,不一樣點在於:默認市價的成交價格沒有上限或下限,但最優五檔/十檔市價則存在價格上限或下限,超過上下限的委託單不會成交。畫圖太累,仍是直接貼代碼吧,如下是處理買單的:
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)
}
}
複製代碼
最後一種類型,對手方最優價,該類型只與對手方一檔的價位成交,但與最優五檔/十檔還有一點不同:最優五檔/十檔未成交的部分是做撤單處理的,而對手方最優價最後未成交的部分則是轉爲限價單。請看代碼:
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())
}
}
複製代碼
至此,整個系列就此完結。不過,個人撮合程序依然會繼續迭代升級,另外,也將開始開發其餘組件,將會和當前這個撮合引擎結合來用。歡迎關注後續動態。
掃描如下二維碼便可關注公衆號(公衆號名稱:Keegan小鋼)