價值超5萬的撮合引擎:開篇
前言
自從有人在微信群裡開價5萬求購Golang版的撮合引擎之後,我就想自己開發一款,畢竟,以我的經驗來說,開發個高效能的撮合引擎並沒什麼難度。
說幹就幹,於是,利用業餘時間慢慢開發出了一款Golang版的高效能撮合引擎,前前後後花了大概一個月的時間。再想想自己好久沒更新文章了,我的個人IP都已經生鏽了,也應該發大招磨一磨了。因此決定,乾脆就以連載的方式,分享下我是如何設計與實現這款價值超5萬的撮合引擎的。
本來,想發成掘金小冊,收點稿費,畢竟這是個具有很大商業價值的軟體,但問了掘金的人員,他們目前不接收這類主題。最終決定免費釋出,還可以多發幾個渠道,說不定還能給我多帶來些關注量。
好了,下面開始進入撮合引擎系列的正題。
撮合引擎簡介
撮合引擎是所有撮合交易系統的核心元件,不管是股票交易系統——包括現貨交易、期貨交易、期權交易等,還是數字貨幣交易系統——包括幣幣交易、合約交易、槓桿交易等,以及各種不同的貴金屬交易系統、大宗商品交易系統等,雖然各種不同交易系統的交易標的不同,但只要都是採用撮合交易模式,都離不開撮合引擎。
撮合引擎是可以具有通用性的,一套具有通用性的撮合引擎實現理論上可以應用到任何撮合交易系統中,而無需做任何程式碼上的調整。即是說,同一套撮合引擎實現,既可以應用在股票交易系統,也可以應用在數字貨幣交易系統,可以用於現貨交易,也可以用於合約交易等。
那麼,一套具有通用性的撮合引擎應該具備哪些功能呢?確定該問題的答案之前,我們先簡單梳理一下一個完整的交易流程是怎樣的?一般會包括以下步驟:
- 系統開放某個交易標的的交易功能。
- 使用者提交該交易標的的買賣申報,即委託單。
- 系統驗證委託單是否有效,包括交易標的是否處於可交易的狀態、訂單的價格和數量是否符合要求等。
- 確定該委託單的**掛單(Maker)費率和吃單(Taker)**費率。
- 檢查使用者的資產賬戶情況,包括賬戶狀態是否交易受限,是否有足夠資金用於下單等。
- 將詳細的委託單資料持久化到資料庫,並凍結使用者賬戶中相應數量的資金。
- 將委託單進行撮合處理,即在**交易委託賬本(OrderBook)**中尋找能與該委託單匹配成交的訂單,匹配的結果可能是:全部成交、部分成交或無匹配。全部成交或部分成交時,可能在交易委託賬本中存在一個或多個匹配的訂單,即會產生一條或多條成交記錄。當無匹配或部分成交時,委託單的部分資料包括剩餘未成交的數量會暫時儲存到交易委託賬本中,等待與後續的委託單匹配撮合。
- 將撮合產生的成交記錄持久化到資料庫,並根據歷史成交記錄生成市場資料,如K線資料、今日漲跌幅等。
- 更新資料庫中所有成交訂單的委託單資料,以及更新訂單使用者的資產賬戶餘額。
- 將更新的訂單資料、市場資料等傳送給到前臺。
整個交易流程中涉及到多個服務,包括使用者服務、賬戶服務、訂單服務、撮合服務、市場資料服務等。其中,只有第7步是撮合引擎處理的。從單一職責原則來說,撮合引擎就應該只做一件事,那就是負責撮合訂單。撮合之前的委託單持久化、凍結資金等,以及撮合之後生成K線資料等,都不應該屬於撮合引擎的職責。
撮合競價方式
撮合競價方式一般有兩種,一是集合競價,二是連續競價。股票交易系統一般會在不同交易時間段採用不同的競價方式,比如在開盤或收盤時採用集合競價,從而產生開盤價或收盤價,其餘時間採用連續競價。而大多數字貨幣交易系統則沒有集合競價,只有連續競價,開盤價一般是在開始交易之前就設定好的。
集合競價
所謂集合競價,是指對一段時間內接收的買賣委託單一次性集中撮合的競價方式。以深滬的股票交易系統為例,在每個交易日的 9:15~9:25 期間是集合競價時間。在該時間段內,系統陸續接收到的委託單不會即時成交,而是先將所有委託單按照價格優先、時間優先的原則排序,並在此基礎上,找出一個基準價格,使它能同時滿足以下三個條件:
- 可實現最大成交量的價格;
- 高於該價格的買單與低於該價格的賣單能全部成交的價格;
- 與該價格相同的買方或賣方至少有一方全部成交的價格。
在 9:25 分結束的時候,該基準價格就被確定為成交價格,所有高於該價格的買單與低於該價格的賣單都將以該價格成交。未能成交的委託單,則自動轉入連續競價。
不過,如果滿足以上三個條件的價格存在兩個或兩個以上呢?對此,深交所和上交所的處理方案有所不同,深交所會取距前收盤價最近的價格為成交價,而上交所則取使未成交量最小的價格為成交價,如果未成交量最小的價格仍不止一個,則取中間價為成交價。
集合競價的主要目的就是為了確定開盤價或收盤價。
連續競價
所謂連續競價,也是我們所熟悉的競價方式,是指對買賣委託單逐筆連續撮合的競價方式。使用者的掛單,只要滿足成交條件,就能即時成交。而集合競價,則要等到最後一刻才會成交。
連續競價時,依然要滿足價格優先、時間優先的成交原則:
- 價格優先:買單則價格較高者能優先成交,賣單則是價格較低者能優先成交。
- 時間優先:買賣方向和價格相同的委託單,先申報的委託單會比後申報的委託單優先成交。
另外,買入價必須大於或等於賣出價才能撮合成交。當買入價等於賣出價時,成交價就是買入價或賣出價。當買入價大於賣出價時,則還要參考前一筆成交價來確定最新成交價。假設買入價為 B,賣出價為 S,前一筆成交價為 P,最新成交價為 N,那麼:
- 如果 P >= B,則 N = B
- 如果 P <= S,則 N = S
- 如果 B > P > S,則 N = P
一套通用的撮合引擎應該兩種競價方式都支援,但對於同一交易標的來說,兩種競價方式不能同時進行,因此設計上需要考慮如何在兩種競價方式之間切換,具體的實現思路在後續章節我們再展開來講。
質量需求
我們的撮合引擎除了要滿足以上所說的功能需求,還應該滿足一些質量需求,尤其對可用性、可伸縮性和效能的要求較高。另外,為了達到通用,也要滿足可複用性的需求。
先說下可複用性,我們期望的是該撮合引擎既能用於股票交易系統,也能用於數字貨幣交易系統,既能用於幣幣交易,也能用於合約交易。因此,該撮合引擎要避免引入與具體系統強相關的業務邏輯,以加強它的可複用性。
再看看效能,要衡量一個撮合引擎的效能,就看它處理每個交易對的 TPS 有多高,即每秒鐘能處理多少筆相同交易對的委託單。以前,基於資料庫的撮合技術,TPS 一般只有10筆/秒。而現在基本都是採用記憶體撮合技術,TPS 很容易就能達到1000筆/秒,如果使用獨佔的高效能伺服器,1萬筆/秒甚至更高的 TPS 都不難達到。
接著談談可伸縮性,我們的每一個撮合引擎既可以同時處理多個交易標的,也可以只處理單個交易標的。當交易標的和併發量增多的時候,可以增加伺服器,部署成撮合引擎叢集,分別用來處理不同的交易標的,從而能夠實現負載均衡。
最後聊聊可用性,高可用主要體現在兩點,一是故障率要低,二是對故障維修的時間要短。要降低故障率,那撮合引擎就需要有較高的健壯性,對於可能導致引擎出故障的各種異常情況要考慮好並設計好解決方案。另外,還可以採用多機熱備份技術來提高可用性,而且要保證互備伺服器之間的資料一致,那就需要引入記憶體狀態機複製方案,實現上會複雜很多。
不過,我們並非一下子就要達到很高的質量要求,因為要求越高,其架構和實現會越複雜。我們可以先從簡單的版本開始,然後不斷升級迭代。
小結
我們目的是實現一套通用的撮合引擎,要支援集合競價和連續競價,還要實現一些質量需求,提高系統的可複用性、效能、可伸縮性、可用性等。後續章節會對這些需求不斷深入探討其設計與實現。另外,我們將採用不斷升級迭代的方式來設計和實現多個版本的撮合引擎。
留兩個思考題:
- 集合競價結束的時候,如果不存在符合那三個條件的基準價格,那開盤價又將如何確定?
- 對於單個交易對,是否可通過橫向增加伺服器的方式提高其效能?
掃描以下二維碼即可關注訂閱號。