歡迎關注「Keegan小鋼」公衆號獲取更多文章緩存
撮合引擎開發:開篇 撮合引擎開發:MVP版本 撮合引擎開發:數據結構設計 撮合引擎開發:對接黑箱 撮合引擎開發:解密黑箱流程 撮合引擎開發:流程的代碼實現 撮合引擎開發:緩存和MQ 撮合引擎開發:日誌輸出 撮合引擎開發:完結篇數據結構
本小節是該系列文章的最後一篇了,將講解剩下的一些東西,包括交易委託帳本中訂單隊列的實現邏輯、更多訂單類型的實現邏輯。另外,很多朋友在問,完結後全部代碼是否會開源放上 Github?我只能說,長期大機率會開源,但短時間內還沒打算開源。函數
訂單隊列
交易委託帳本其實就是由兩個訂單隊列組成的,一個買單隊列,一個賣單隊列。任何對交易委託帳本的查詢和操做,實際上都是查詢和操做這兩個隊列。訂單隊列的設計也直接影響了撮合的性能,前面文章講數據結構設計時也有簡單聊了訂單隊列的設計,咱們主要是用二維連接結合 Map 來保存全部訂單的,依賴的是 container/list 包。post
訂單隊列的結構體以下:性能
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) }
除了初始化函數,還提供了另外五個函數:code
- addOrder(order):添加訂單
- getHeadOrder():讀取頭部訂單
- popHeadOrder():讀取並刪除頭部訂單
- removeOrder(order):移除訂單
- getDepthPrice(depth):讀取深度價格
以上五個函數就只有第一個函數會比較複雜,爲了讓處理流程更容易理解,我就不貼代碼了,畫一個完整的流程圖給你們看看:blog
這個流程確實有一點複雜,能夠多看幾遍好好消化,最好本身動手將其轉爲代碼實現。排序
其餘幾個函數就簡單了,關於最後一個函數須要補充說明一下。讀取深度價格是爲了方便處理 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 }
多種訂單類型
咱們引擎總共支持了六種訂單類型,以前的文章有簡單介紹過,但沒有深刻講解這幾種不一樣類型的具體業務邏輯應該是怎樣的,所以,在此將這部份內容補充上。
1. limit
普通限價是最簡單的,前文也已經展現過代碼實現,爲了加深理解,我再給你們畫一張圖:
處理邏輯就是:
- 判斷新訂單是買單仍是賣單。
- 若是是買單,那從 OrderBook 中讀取出頭部賣單,即賣單隊列中的頭部訂單;若是是賣單,那從 OrderBook 中讀取出頭部買單,即買單隊列中的頭部訂單。
- 新訂單爲買單時,若是頭部訂單爲空,或者新訂單小於頭部訂單,即沒法成交,那就把新訂單添加到買單隊列中,處理結束;新訂單爲賣單時,若是頭部訂單爲空,或者新訂單大於頭部訂單,即沒法成交,那就把新訂單添加到賣單隊列中,處理結束。
- 不然,符合匹配條件,新訂單和頭部訂單進行撮合成交。
- 撮合完成後,若是新訂單剩餘數量爲零則結束,若是還大於零,則回到第2步繼續取下一個頭部訂單,如此循環。
2. limit-ioc
IOC 限價與普通限價不一樣的地方只有一個,若是新訂單和頭部訂單不匹配時,普通限價單會被添加到訂單隊列中,而 IOC 限價則是做撤單處理,請看下圖:
3. market
默認市價單的邏輯也比較簡單,它不須要判斷價格,只要頭部訂單不爲空,就直接和頭部訂單匹配成交,其處理邏輯以下圖:
4. market-top5/market-top10
最優五檔/十檔市價單與默認市價單的邏輯也是相似的,不一樣點在於:默認市價的成交價格沒有上限或下限,但最優五檔/十檔市價則存在價格上限或下限,超過上下限的委託單不會成交。畫圖太累,仍是直接貼代碼吧,如下是處理買單的:
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) } }
5. market-opponent
最後一種類型,對手方最優價,該類型只與對手方一檔的價位成交,但與最優五檔/十檔還有一點不同:最優五檔/十檔未成交的部分是做撤單處理的,而對手方最優價最後未成交的部分則是轉爲限價單。請看代碼:
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小鋼)