撮合引擎開發:對接黑箱
- 2019 年 11 月 22 日
- 筆記
黑箱引擎
我們的撮合引擎作為一個相對通用的組件,其實就是一個黑箱,如果想將它應用到各種不同的交易系統,只要有標準的輸入和輸出,對接是很容易的。
寫作此文時的撮合引擎為 1.3 版本,我將其編譯構建成可在 Linux amd64 環境運行的可執行文件,並與依賴的配置文件一起壓縮成一個壓縮包 matching.zip。這就成為一個黑箱引擎了。
不過,該黑箱引擎除了對運行系統有要求,還對 Redis 有要求。由於使用了 Redis 的新版 MQ 功能,即 stream 數據結構,要求 Redis 至少是 5.0 版本。
其實,我也可以編譯構建成其他系統環境的可執行文件,如 Windows 或 Mac 系統。但作為一款商業軟體以及對性能的一些要求,運行在 Linux 環境更合適。
後面,我們就來看看,如果要將這個黑箱引擎應用到自己的交易系統中,如何對接?
安裝部署
安裝部署的系統環境需是 Linux amd64 的,另外,如果要讓撮合性能更快,建議 Redis 與撮合引擎可以使用同一伺服器,這可以減少不同伺服器之前的傳輸耗時。
按照以下步驟操作就可以將撮合引擎安裝部署到運行環境了:
1.將 matching.zip 壓縮包上傳到運行環境;2.在運行環境解壓 matching.zip 壓縮包,解壓後有一個可執行文件和一個文件夾:
•matching:這是撮合引擎程式的可執行文件•conf:存放配置文件的目錄,裡面就一個配置文件 config.yaml
3.修改配置文件為自己想要的配置值:
server: port: :9466 //撮合引擎程式啟動監聽的埠 log: //輸出日誌配置 fileDir: logs //輸出日誌存放的目錄 fileName: matching //日誌文件名,會按日期分割 prefix: //日誌消息前綴 level: debug //日誌級別,由低到高為:debug、info、warn、error redis: addr: 127.0.0.1:6379 //Redis地址
1.如果使用默認配置,請確認運行環境本地已安裝並啟動了Redis並運行在6379埠;2.如果不用默認配置,依然需要確認Redis能正確連接;3.運行以下命令,後台啟動撮合引擎程式:
./matching &
1.運行以下命令,檢查程式是否啟動成功:
ps aux|grep matching
1.程式啟動成功後,會在配置的日誌目錄生成日誌文件,默認為與 matching 可執行文件同目錄下的 logs/matching.log;2.至此,撮合引擎程式安裝部署成功。
對接輸入
接入撮合引擎只需要對接三個 HTTP 介面,介面統一採用 POST 方法,參數統一用 json 格式,傳 body。
1. 開啟撮合
開啟指定交易標的(交易對)的撮合功能。
HTTP請求
•POST /openMatching
請求參數
•symbol:字元串類型,必傳欄位,交易標的(交易對)的標識,如 BTC_USDT•price:數字類型,非必傳欄位,默認為 0,開盤價
請求示例
POST /openMatching Body: { "symbol": "BTC_USDT", "price": 8219.85 }響應數據
{"code":0,"msg":"OK"}2. 關閉撮合
關閉指定交易標的(交易對)的撮合功能。
HTTP請求
•POST /closeMatching
請求參數
•symbol:字元串類型,必傳欄位,交易標的(交易對)的標識,如 BTC_USDT
請求示例
POST /closeMatching Body: { "symbol": "BTC_USDT" }響應數據
{"code":0,"msg":"OK"}3. 處理訂單
接收下單和撤單請求。
HTTP請求
•POST /handleOrder
請求參數
•symbol:字元串類型,必傳欄位,交易標的(交易對)的標識,如 BTC_USDT•action:字元串類型,必傳欄位,訂單動作,下單=create,撤單=cancel•orderId:字元串類型,必傳欄位,訂單ID•side:字元串類型,必傳欄位,買賣方向,買入=buy,賣出=sell•type:字元串類型,必傳欄位,訂單類型,包括:limit、limit-ioc、market、market-top5、market-top10、market-opponent,說明見下文•amount:數字類型,非必傳欄位,默認為 0,訂單交易量,下單時必傳,撤單時可不傳•price:數字類型,非必傳欄位,默認為 0,委託價格,訂單類型為市價時可不傳
訂單類型說明:
•limit:普通限價•limit-ioc:IOC限價-即時成交剩餘撤銷•market:默認市價-即時成交剩餘撤銷•market-top5:市價-最優五檔即時成交剩餘撤銷•market-top10:市價-最優十檔即時成交剩餘撤銷•market-opponent:市價-對手方最優價
請求示例
POST /handleOrder Body: { "symbol": "BTC_USDT", "action": "create", "orderId": "a0001", "side": "buy", "type": "limit", "amount": 0.012, "price": 8230.74 }響應數據
{"code":0,"msg":"OK"}對接輸出
撮合引擎有兩種輸出:撤單結果和成交記錄。統一用 MQ 的方式進行輸入,MQ 是保存為 Redis 5.0 版本之後引入的新數據結構 Stream 類型。每個消息隊列其實就是一條 stream,關於 stream 的具體用法我就不展開了,大夥自行去網上搜索學習即可。
1. 撤單結果
每個不同的 symbol 設置一個 stream,其 key 的格式為:matching:cancelresults:{symbol},value 包含兩個欄位:
•orderId:訂單號•ok:1=成功;0=失敗
2. 成交記錄
每個不同的 symbol 也設置一個 MQ,key 的格式為:matching:trades:{symbol},value 包含的欄位如下:
•makerId:maker訂單ID•takerId:taker訂單ID•takerSide:taker買賣方向•amount:成交數量•price:成交價格•timestamp:成交時間
下游服務就可以通過訂閱監聽這兩種輸出,之後做後續的處理,比如 K 線行情服務訂閱監聽成交記錄來生成 K 線數據。等撮合引擎完成之後,我下一個要開發的組件就是 K 線行情服務。
項目結構
最後,先給大夥看看我們這個黑箱內部的整個 Go 項目的文件目錄結構:
├── conf # 配置文件存放目錄,在1.1版本時增加 │ ├── config.yaml # 配置文件,在1.1版本時增加 ├── engine # 引擎包 │ ├── init.go # 初始化 │ ├── order.go # 委託單 │ ├── order_book.go # 交易委託賬本 │ ├── order_queue.go # 訂單隊列 │ ├── run.go # 具體交易對的撮合引擎啟動入口 │ └── trade.go # 成交記錄 ├── enum # 枚舉類型的包 │ ├── order_action.go # 訂單行為,create為下單,cancel為撤單 │ ├── order_side.go # 買賣方向,buy為買入,sell為賣出 │ ├── order_type.go # 訂單類型,MVP版本(1.0版本)只支援limit,1.3版本共支援7種類型 │ └── sort_direction.go # 排序方向,asc為升序,desc為降序 ├── errcode # │ ├── code.go # 定義了各種不同的錯誤碼 │ └── errcode.go # 錯誤碼的數據結構,包括code和msg兩個欄位 ├── handler # │ ├── close_matching.go # 接收關閉撮合的請求 │ ├── handle_order.go # 接收處理訂單的請求 │ └── open_matching.go # 接收開啟撮合的請求 ├── log # 日誌包,在1.2版本增加 │ ├── log.go # 日誌輸出,在1.2版本增加 ├── main.go # Go程式唯一入口 ├── middleware # 中間件的包 │ ├── cache # 快取包 │ │ └── cache.go # 快取操作 │ ├── mq # 消息隊列包 │ │ └── mq.go # MQ操作 │ └── redis.go # 主要做Redis初始化操作 └── process # ├── close_engine.go # 關閉引擎 ├── dispatch.go # 分發訂單 ├── init.go # 初始化 └── new_engine.go # 啟動新引擎
包括 main 包,整個項目總共劃分為了 10 個包和 1 個配置文件目錄:
•conf:存放配置文件的目錄。•main:main 包只有一個 main.go 文件,定義了程式入口函數。•enum:枚舉包實現了幾個枚舉類型的數據結構,包括訂單行為、買賣方向、訂單類型和排序方向。•errcode:存放錯誤碼的包,errcode.go 定義了錯誤碼的數據結構,有 code 和 msg 兩個屬性;code.go 則定義了一些錯誤碼對象。•handler:接收 HTTP 請求的函數都放在這個包里,目前也就只有三個 handler 函數。•process:啟動、關閉引擎和分發訂單的處理過程都在這個包里,包里還維護著不同交易對的訂單通道,用來分發不同交易對的訂單。•engine:引擎包,包含了委託單、交易委託賬本、訂單隊列、成交記錄這幾個核心的數據結構,以及用於處理交易對撮合的入口函數。•middleware:存放中間件的包,目前只用到了 Redis 一個中間件。•cache:快取包,只有一個 cache.go 文件,快取操作都在這個文件里定義。•mq:消息隊列包,也只有一個 mq.go 文件,消息的發送就定義在這裡。•log:日誌包,實現了日誌消息按日期分割並輸出到文件。
小結
本節我們了解到撮合引擎作為一個通用組件,有標準的輸入和輸出,而且這輸入和輸出都非常簡單。也開始給大家展示了黑箱內部的文件目錄結構,開始探究黑箱的內部邏輯,後續章節將陸續揭露這些內部實現邏輯。
本節預留的思考題:揣測下為什麼我要將下單和撤單定義到同一個介面?而不是分開兩個介面?分兩個介面和用一個介面各有何優劣?