撮合引擎開發:完結篇

歡迎關注「Keegan小鋼」公衆號獲取更多文章緩存


撮合引擎開發:開篇數據結構

撮合引擎開發:MVP版本函數

撮合引擎開發:數據結構設計post

撮合引擎開發:對接黑箱性能

撮合引擎開發:解密黑箱流程ui

撮合引擎開發:流程的代碼實現spa

撮合引擎開發:緩存和MQ設計

撮合引擎開發:日誌輸出日誌

撮合引擎開發:完結篇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)
}
複製代碼

除了初始化函數,還提供了另外五個函數:

  • addOrder(order):添加訂單
  • getHeadOrder():讀取頭部訂單
  • popHeadOrder():讀取並刪除頭部訂單
  • removeOrder(order):移除訂單
  • getDepthPrice(depth):讀取深度價格

以上五個函數就只有第一個函數會比較複雜,爲了讓處理流程更容易理解,我就不貼代碼了,畫一個完整的流程圖給你們看看:

這個流程確實有一點複雜,能夠多看幾遍好好消化,最好本身動手將其轉爲代碼實現。

其餘幾個函數就簡單了,關於最後一個函數須要補充說明一下。讀取深度價格是爲了方便處理 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

普通限價是最簡單的,前文也已經展現過代碼實現,爲了加深理解,我再給你們畫一張圖:

處理邏輯就是:

  1. 判斷新訂單是買單仍是賣單。
  2. 若是是買單,那從 OrderBook 中讀取出頭部賣單,即賣單隊列中的頭部訂單;若是是賣單,那從 OrderBook 中讀取出頭部買單,即買單隊列中的頭部訂單。
  3. 新訂單爲買單時,若是頭部訂單爲空,或者新訂單小於頭部訂單,即沒法成交,那就把新訂單添加到買單隊列中,處理結束;新訂單爲賣單時,若是頭部訂單爲空,或者新訂單大於頭部訂單,即沒法成交,那就把新訂單添加到賣單隊列中,處理結束。
  4. 不然,符合匹配條件,新訂單和頭部訂單進行撮合成交。
  5. 撮合完成後,若是新訂單剩餘數量爲零則結束,若是還大於零,則回到第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小鋼)

相關文章
相關標籤/搜索