-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4d6da52
commit 61c3696
Showing
1 changed file
with
226 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,115 +1,245 @@ | ||
Differential Information Flow for Finance (DIFF) 协议 | ||
======================================================================= | ||
目前市面上的接口通常以事件回调的方式进行信息交互,导致业务层对当前状况的全景缺乏了解,不便于编写复杂业务逻辑。而 DIFF 协议将异步的事件回调转为同步的数据访问,使得业务层能简单同步的访问业务数据,简化了编码复杂度。 | ||
在任一时刻, 一个用户的交易账户可以包含以下业务信息 | ||
|
||
* ``1~N 个资金账户(ACCOUNT)`` | ||
* ``0~N 个持仓记录(POSITION)`` | ||
* ``0~N 个委托单(ORDER)`` | ||
* ``0~N 个成交单(TRADES)`` | ||
* ``0~N 个行情订阅(QUOTES)`` | ||
* ``其他账户状态信息`` | ||
|
||
这些信息完整描述了用户交易账户的 **当前状态** 。我们称之为 **业务信息截面** 。如下为采用 DIFF 协议的交易数据包片段。 | ||
|
||
:: | ||
|
||
{ | ||
"aid": "rtn_data", | ||
"data": [ | ||
{ | ||
"trade": { | ||
"022000": { | ||
"user_id": "022000", | ||
"trading_day": "20200604", | ||
"trade_more_data": false, | ||
"accounts": { | ||
"CNY": { | ||
"user_id": "022000", | ||
"currency": "CNY", | ||
"pre_balance": 999996.818, | ||
"deposit": 0.0, | ||
"withdraw": 0.0, | ||
"close_profit": 0.0, | ||
"commission": 0.0, | ||
"premium": 0.0, | ||
"static_balance": 999996.818, | ||
"position_profit": 2480.0, | ||
"float_profit": 2480.0, | ||
"balance": 1002476.818, | ||
"margin": 2886.3999999999998, | ||
"frozen_margin": 8659.199999999999, | ||
"frozen_commission": 10.824, | ||
"frozen_premium": 0.0, | ||
"available": 990920.394, | ||
"risk_ratio": 0.0028792685757647116, | ||
"market_value": 0.0, | ||
"ctp_balance": "-", | ||
"ctp_available": "-" | ||
} | ||
}, | ||
"positions": { | ||
"SHFE.rb2006": { | ||
"user_id": "022000", | ||
"exchange_id": "SHFE", | ||
"instrument_id": "rb2006", | ||
"volume_long_today": 0, | ||
"volume_long_his": 1, | ||
"volume_long": 1, | ||
"volume_long_frozen_today": 0, | ||
"volume_long_frozen_his": 0, | ||
"volume_long_frozen": 0, | ||
"volume_short_today": 0, | ||
"volume_short_his": 0, | ||
"volume_short": 0, | ||
"volume_short_frozen_today": 0, | ||
"volume_short_frozen_his": 0, | ||
"volume_short_frozen": 0, | ||
"volume_long_yd": 1, | ||
"volume_short_yd": 0, | ||
"pos_long_his": 1, | ||
"pos_long_today": 0, | ||
"pos_short_his": 0, | ||
"pos_short_today": 0, | ||
"open_price_long": 3289.0, | ||
"open_price_short": 0.0, | ||
"open_cost_long": 32890.0, | ||
"open_cost_short": 0.0, | ||
"position_price_long": 3289.0, | ||
"position_price_short": 0.0, | ||
"position_cost_long": 32890.0, | ||
"position_cost_short": 0.0, | ||
"last_price": 3537.0, | ||
"float_profit_long": 2480.0, | ||
"float_profit_short": 0.0, | ||
"float_profit": 2480.0, | ||
"position_profit_long": 2480.0, | ||
"position_profit_short": 0.0, | ||
"position_profit": 2480.0, | ||
"margin_long": 2886.3999999999998, | ||
"margin_short": 0.0, | ||
"margin": 2886.3999999999998, | ||
"market_value_long": 0.0, | ||
"market_value_short": 0.0, | ||
"market_value": 0.0 | ||
} | ||
}, | ||
"orders": { | ||
"abcdefg12": { | ||
"seqno": 5, | ||
"user_id": "022000", | ||
"order_id": "abcdefg12", | ||
"exchange_id": "SHFE", | ||
"instrument_id": "rb2006", | ||
"direction": "BUY", | ||
"offset": "OPEN", | ||
"volume_orign": 1, | ||
"price_type": "LIMIT", | ||
"limit_price": 3400.0, | ||
"time_condition": "GFD", | ||
"volume_condition": "ANY", | ||
"insert_date_time": 1591176309204730158, | ||
"exchange_order_id": "abcdefg12", | ||
"status": "ALIVE", | ||
"volume_left": 1, | ||
"last_msg": "", | ||
"frozen_margin": 2886.3999999999998, | ||
"frozen_premium": 0.0, | ||
"frozen_commission": 3.608 | ||
} | ||
}, | ||
"trades": {}, | ||
"banks": {}, | ||
"transfers": {} | ||
} | ||
} | ||
} | ||
] | ||
} | ||
|
||
DIFF 协议分为两部分: **数据访问** 和 **数据传输** | ||
|
||
数据访问 | ||
- 业务截面可能会包含协议中未写出的额外字段,使用方应忽略这些字段的信息,因此在扩展或新增业务模块时可以保持向后兼容性 | ||
- 部分字段可能会有多种数据类型,例如上述例子中的收盘和结算价在收盘前是字符串,在收盘后会更新为对应的数值 | ||
|
||
DIFF 协议约定了业务信息截面的 **数据格式**、 **数据传输** 和 **数据同步** 方式。 | ||
|
||
数据格式 | ||
------------------------------------------------------ | ||
DIFF 协议要求服务端维护一个业务信息截面,例如: | ||
DIFF 协议要求采用 Json 格式进行数据交换。数据包按照功能划分三种类型: **业务信息截面更新**、 **业务信息截面更新请求** 和 **交易包** ,由数据包中 ``aid`` 字段进行区分。 | ||
|
||
业务信息截面更新 | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
DIFF 协议要求服务端维护每个账户的业务信息截面,同时将业务信息的变化以 JSON Merge Patch 的格式推送给客户端,下面是业务信息截面更新包样例。 | ||
|
||
:: | ||
|
||
{ | ||
"account_id": "41007684", # 账号 | ||
"static_balance": 9954306.319000003, # 静态权益 | ||
"balance": 9963216.550000003, # 账户资金 | ||
"available": 9480176.150000002, # 可用资金 | ||
"float_profit": 8910.231, # 浮动盈亏 | ||
"risk_ratio": 0.048482375, # 风险度 | ||
"using": 11232.23, # 占用资金 | ||
"position_volume": 12, # 持仓总手数 | ||
"ins_list": "SHFE.cu1609,...." # 行情订阅的合约列表 | ||
"quotes":{ # 所有订阅的实时行情 | ||
"SHFE.cu1612": { | ||
"instrument_id": "SHFE.cu1612", | ||
"datetime": "2016-12-30 13:21:32.500000", | ||
"ask_priceN": 36590.0, #卖N价 | ||
"ask_volumeN": 121, #卖N量 | ||
"bid_priceN": 36580.0, #买N价 | ||
"bid_volumeN": 3, #买N量 | ||
"last_price": 36580.0, # 最新价 | ||
"highest": 36580.0, # 最高价 | ||
"lowest": 36580.0, # 最低价 | ||
"amount": 213445312.5, # 成交额 | ||
"volume": 23344, # 成交量 | ||
"open_interest": 23344, # 持仓量 | ||
"pre_open_interest": 23344, # 昨持 | ||
"pre_close": 36170.0, # 昨收 | ||
"open": 36270.0, # 今开 | ||
"close" : "-", # 收盘 | ||
"lower_limit": 34160.0, #跌停 | ||
"upper_limit": 38530.0, #涨停 | ||
"average": 36270.1 #均价 | ||
"pre_settlement": 36270.0, # 昨结 | ||
"settlement": "-", # 结算价 | ||
"aid": "rtn_data", # 业务信息截面更新 | ||
"data": [ # 数据更新数组 | ||
{ | ||
"balance": 10237421.1, # 账户资金 | ||
}, | ||
... | ||
} | ||
{ | ||
"float_profit": 283114.780999997, # 浮动盈亏 | ||
}, | ||
{ | ||
"quotes":{ | ||
"SHFE.cu1612": { | ||
"datetime": "2016-12-30 14:31:02.000000", | ||
"last_price": 36605.0, # 最新价 | ||
"volume": 25431, # 成交量 | ||
"pre_close": 36170.0, # 昨收 | ||
} | ||
} | ||
} | ||
] | ||
} | ||
|
||
- 以上数据包中的 ``"aid": "rtn_data"`` 表示该包的类型为业务信息截面更新包 | ||
- 整个 data 数组相当于一个事务,其中的每一个元素都是一个 JSON Merge Patch,处理完整个数组后,业务截面即完成了从上一个截面推进到下一个截面过程。 | ||
- 处理过程中,截面可能处于内部不一致的状态,例如上述例子中的 balance 更新后,float_profit 更新前,并不满足 balance = static_balance + float_profit,因此除非有特殊需求,否则业务层应等整个 data 数组都处理完成后再从业务截面中提取所需的数据 | ||
- 没有变化的字段服务端也可能发送,例如上述例子中的 pre_close。 | ||
- 服务端可以自行决定 data 数组的元素个数及每个元素中包含哪些更新,例如客户端不能假定更新行情最新价和成交量一定是在一个 JSON Merge Patch 中。 | ||
- 如果在处理完一个 JSON Merge Patch 后,某个 object 下的所有字段都被删除则也应将该 object 删除 | ||
|
||
业务信息截面更新请求 | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
DIFF 协议要求客户端主动发送 ``peek_message`` 数据包以获得业务信息截面更新。 | ||
一个简单的客户端实现可以在连接成功后及每收到一个 rtn_data 数据包后发送一个 peek_message 数据包,这样当客户端带宽不足时会自动降低业务信息截面的更新频率以适应低带宽 | ||
|
||
:: | ||
|
||
{ | ||
"aid": "peek_message" | ||
} | ||
|
||
对应的客户端也维护了一个该截面的镜像,因此业务层可以简单同步的访问到全部业务数据。 | ||
指令包 | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
当数据包中的 ``aid`` 字段不是 ``rtn_data`` 或 ``peek_message`` 时,则表示该包为一个指令包,具体指令由各业务模块定义,例如 subscribe_quote 表示订阅行情,insert_order 表示下单。 | ||
|
||
* 业务截面的内容由各业务模块定义,例如 ref:`quote` ref:`trade` ref:`mdhis` | ||
* 除非由业务模块的文档另行说明,否则业务截面中的数据应是自恰的。例如:任何时刻的业务截面都应包含 balance, static_balance 和 float_profit 字段,并且满足 balance = static_balance + float_profit | ||
* 业务截面可能会包含协议中未写出的额外字段,使用方应忽略这些字段的信息,因此在扩展或新增业务模块时可以保持向后兼容性 | ||
* 部分字段可能会有多种数据类型,例如上述例子中的收盘和结算价在收盘前是字符串,在收盘后会更新为对应的数值 | ||
由于客户端和服务端存在网络通讯延迟,客户端的指令需要过一段时间才会影响到截面中的业务数据,为了使客户端能分辨出服务端是否处理了该指令,通常服务端会将 | ||
客户端的请求以某种方式体现在截面中(具体方式由各业务模块定义)。例如 subscribe_quote 订阅行情时服务端会将业务截面中的 ins_list 字段更新为客户端订阅的合约列表, | ||
这样当客户端检查业务截面时如果 ins_list 包含了客户端订阅的某个合约,但是 quotes 没有该合约则说明该合约不存在。 | ||
|
||
数据传输 | ||
------------------------------------------------------ | ||
DIFF 协议使用 json 编码通过 websocket 传输,因此可以使用 ssl 实现传输层安全加密,permessage-deflate 实现数据压缩。协议通讯为全双工模式,任何一方都可以随时向对方发送数据包,也应随时准备接收对方发来的数据包,发出的包之间无需等待对方回应。每个数据包中均有一个 aid 字段,此字段值即为数据包类型。 | ||
DIFF 协议要求数据传输采用 websocket 协议,该协议的标准见`The WebSocket Protocol <https://tools.ietf.org/html/rfc6455>`_ 。 | ||
websocket 协议通讯为全双工模式,任何一方都可以随时向对方发送数据包,也应随时准备接收对方发来的数据包,发出的包之间无需等待对方回应。 | ||
|
||
数据包类型 | ||
数据加密与压缩 | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
DIFF 协议数据传输过程中,数据的加密与压缩参考 websocket 实现,**推荐** ssl 实现传输层安全加密,`permessage-deflate <https://tools.ietf.org/html/rfc7692>`_ 实现数据压缩。 | ||
|
||
* 业务信息截面更新: | ||
DIFF 协议要求服务端将业务信息的变化以 JSON Merge Patch (https://tools.ietf.org/html/rfc7386) 的格式推送给客户端,例如: | ||
|
||
:: | ||
{ | ||
"aid": "rtn_data", # 业务信息截面更新 | ||
"data": [ # 数据更新数组 | ||
{ | ||
"balance": 10237421.1, # 账户资金 | ||
}, | ||
{ | ||
"float_profit": 283114.780999997, # 浮动盈亏 | ||
}, | ||
{ | ||
"quotes":{ | ||
"SHFE.cu1612": { | ||
"datetime": "2016-12-30 14:31:02.000000", | ||
"last_price": 36605.0, # 最新价 | ||
"volume": 25431, # 成交量 | ||
"pre_close": 36170.0, # 昨收 | ||
} | ||
} | ||
} | ||
] | ||
} | ||
- 以上数据包中的 ``"aid": "rtn_data"`` 表示该包的类型为业务信息截面更新包 | ||
- 整个 data 数组相当于一个事务,其中的每一个元素都是一个 JSON Merge Patch,处理完整个数组后业务截面即完成了从上一个时间截面推进到下一个时间截面。 | ||
- 处理过程中业务截面可能处于内部不一致的状态,例如上述例子中的 balance 更新后,float_profit 更新前,并不满足 balance = static_balance + float_profit,因此除非有特殊需求,否则业务层应等整个 data 数组都处理完成后再从业务截面中提取所需的数据 | ||
- 没有变化的字段服务端也可能发送,例如上述例子中的 pre_close。 | ||
- 服务端可以自行决定 data 数组的元素个数及每个元素中包含哪些更新,例如客户端不能假定更新行情最新价和成交量一定是在一个 JSON Merge Patch 中。 | ||
- 如果在处理完一个 JSON Merge Patch 后,某个 object 下的所有字段都被删除则也应将该 object 删除 | ||
|
||
* 业务信息截面更新请求: | ||
DIFF 协议要求客户端发送 peek_message 数据包以获得业务信息截面更新 | ||
|
||
:: | ||
{ | ||
"aid": "peek_message" | ||
} | ||
- 服务端在收到 peek_message 数据包后应检查是否有数据更新,如果有则应将更新内容立即发送给客户端,如果没有则应等到有更新发生时再回应客户端。 | ||
- 服务端发送 rtn_data 数据包后可以等收到下一个 peek_message 后再发送下一个 rtn_data 数据包。 | ||
- 一个简单的客户端实现可以在连接成功后及每收到一个 rtn_data 数据包后发送一个 peek_message 数据包,这样当客户端带宽不足时会自动降低业务信息截面的更新频率以适应低带宽 | ||
|
||
* 指令包: | ||
当数据包中的 aid 字段不是 rtn_data 或 peek_message 则表示该包为一个指令包,具体指令由各业务模块定义,例如 subscribe_quote 表示订阅行情,insert_order 表示下单 | ||
|
||
- 由于客户端和服务端存在网络通讯延迟,客户端的指令需要过一段时间才会影响到截面中的业务数据,为了使客户端能分辨出服务端是否处理了该指令,通常服务端会将客户端的请求以某种方式体现在截面中(具体方式由各业务模块定义)。例如 subscribe_quote 订阅行情时服务端会将业务截面中的 ins_list 字段更新为客户端订阅的合约列表,这样当客户端检查业务截面时如果 ins_list 包含了客户端订阅的某个合约,但是 quotes 没有该合约则说明该合约不存在 | ||
交互流程 | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
目前市面上的接口通常以事件回调的方式进行信息交互,导致业务层对当前状况的全景缺乏了解,不便于编写复杂业务逻辑。而 DIFF 协议将异步的事件回调转为同步的数据访问,使得业务层能简单同步的访问业务数据,简化了编码复杂度。 | ||
下图给出了 DIFF 协议下数据交互流程的样例。 | ||
|
||
:: | ||
|
||
client server | ||
+ + | ||
| 1.insert_order | | ||
+-------------------------------->+ | ||
| | | ||
| 2.peek_message | | ||
+-------------------------------->+ | ||
| | | ||
| 3.rtn_data | | ||
+<--------------------------------+ | ||
| | | ||
| 4.peek_message | | ||
+-------------------------------->+ | ||
| | | ||
| 5.peek_message | | ||
+-------------------------------->+ | ||
| | | ||
| 6.rtn_data | | ||
+<--------------------------------+ | ||
| | | ||
+ + | ||
Diff Transmission Example | ||
|
||
|
||
1. 客户端发起交易指令 | ||
2. 客户端发起 peek_message 数据包 ,请求服务端推送账户最新业务截面 | ||
3. 确定业务截面更新后,服务端将业务截面变化推送给客户端 | ||
|
||
其中,业务信息截面更新 ``rtn_data`` 的推送依赖于 业务信息截面更新请求包 peek_message。当服务端收到 peek_message 包之后,会检查截面是否发生 | ||
变化,如果有则将更新推送给客户端,否则应等到有更新时再回应客户端;对于截面发生变化之前,客户端多次发起的 peek_message 请求包, 服务端会当作为一次请求处理, | ||
如上图中, 对于客户端 4 和 5 peek_message包,服务端仅做一次推送。 | ||
|
||
数据同步 | ||
------------------------------------------------------ | ||
DIFF 协议要求客户端也维护了一个业务信息截面,该截面来源于服务端下发的 JSON Merge Patch 格式数据。客户端同步业务信息截面遵循 `JSON Merge Patch 标准 <https://tools.ietf.org/html/rfc7386>`_ |