撮合引擎開發:對接黑箱

  • 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:日誌包,實現了日誌消息按日期分割並輸出到文件。

小結

本節我們了解到撮合引擎作為一個通用組件,有標準的輸入和輸出,而且這輸入和輸出都非常簡單。也開始給大家展示了黑箱內部的文件目錄結構,開始探究黑箱的內部邏輯,後續章節將陸續揭露這些內部實現邏輯。

本節預留的思考題:揣測下為什麼我要將下單和撤單定義到同一個介面?而不是分開兩個介面?分兩個介面和用一個介面各有何優劣?

Tags: