• 上交所Binary行情接口demo


    一、前言

    目前沪深交易所都有Binary和STEP两种行情接口,本篇是我尝试对接沪市VDE的Binary接口。因为不太直观,Binary接口对于初学者来说还是有点难度的,但对于掌握了Binary通讯的大佬来说其实非常简单。网上相关的资料不能说非常少,只能说是极度少,幸运的是我找到了下面这篇博文,他讲的是对接深交所行情网关,我在他这个基础上改吧改吧居然就弄通了。

    深圳证券交易所Binary行情数据,MDC_VSS_DEMO数据接收示例代码_s732的博客-CSDN博客

    因为只是示例,所以我没有关注太多行情接口解析的内容,而主要关注Binary行情接口的原理、思路和对接。另外我是通过VDE对接的,不是行情网关,可能会有些区别但估计大差不差。

    参考文档:《上海证券交易所低延时行情发布系统(LDDS)接口说明书》、《IS120_上海证券交易所行情网关BINARY数据接口规范》。

    完整代码附在文末。

    二、Binary接口

    什么是Binary接口?我理解就是以byte来通信的接口。

    常见的通信接口,比如telnet,我们建立socket连接后,读写都是通过“流”的形式,比如InputStream和OutputStream。但是我们在读到流以后直接就将流转为String来解析,发送也是直接将String通过接口发出去,这样比较简单,通信就好像两个人对话一样,我们只需要处理String即可,但问题是这样非常“浪费”。

    浪费主要体现在对数字的传输,比如我们要传输一个10位的数字“1234567890”,如果是字符串格式,每一个数字占用一个char,也就是这个字符串占用char[10],一个char占用8位,那么传输这个10位数字字符串使用80个位。如果我们现在传输的不是字符串而是Int整数,我们知道一个Int32整数最大值是2147483647(有符号)和4294967295(无符号),占用32个位。如果传输Int而不是直接传输String,我们可以省下60%的流量。而行情数据中数字就是最大的传输量,通过Binary来传输行情数据可以有效的节省流量并提升传输效率。

    但行情数据中又不只是数字,还有字符串的类型,这咋办呢。很简单,我们将要传输的数据以“字段”来进行定义和规范,转为byte后拼接起来即可。比如下面这个接口:

    MsgType这个字段我们规定好就是char[4],那么它占用4x8=32个位。

    SendingTime这个字段我们规定好是uint64,那么它占用64个位。

    同理MsgSeqNum占用64个位,BodyLength占用32个位。

    如果是二进制的格式就像下面这样,传输的时候只需要首位相连拼在一起,然后发出去即可。接收方则按前面约定的格式将二进制流按指定位数截断,接着再转化成需要的格式进行处理。

    不过我们一般不会直接在二进制做处理,而使用byte(8位一组)来处理比较合适,但byte与Int、String的转换需要注意,特别是Int还分Int8、Int16、Int32、Int64。

    另外浮点数怎么转换呢?通用的办法是将所有浮点数都转为整数,并约定好整数位和小数位。比如uint64,N13(5),其含义是字段占用为64位的无符号整数,传输的数值是13位的,其中最后5位是小数位,比如我们传输1234567890123,其中整数位是12345678,小数位是90123,结果是12345678.90123。

    三、登录

    登录消息

    我们先看接口:

    简单来说,一个登录消息包含了头、尾、体三个部分,每个部分又由若干字段组成,需要注意的是checksum是对除checksum字段外所有字段的校验和,另外关于校验和的计算也有一些要注意的后面说。

    其中str2Byte、int2Byte因为涉及字段的位数,所以都是需要自己写的,byteMerger是将两个byte数组拼接在一起,最终MsgBody是包含了头、尾、体三个部分的数据,只需要发给VDE就能登录了。

    Socket连接

    Socket连接比较简单了,其中IP是你VDE所在服务器IP,端口是在VDE配置文件中配的。ReadThread是处理Binary行情流的。

    四、行情解析

    先解析head

    这段代码就是从socket接口中读取最大102400字节的数据,存到buffer中。这里我做了一些简化处理,后面详细说明。

    根据接口,我们将buffer中指定位的内容存放到一个个特定大小的byte数组中,也就是MsgType、SendingTime、MsgSeqNum、BodyLength这些数组。

    这段代码是根据接口将byte数组转化为string、int、long类型,其中int64我是用long来代替的。

    校验和

    把读到的校验和与我们自己计算的校验和做个比较,必须校验通过才能进行下一步的解析。

    关于校验和的计算,注意文档示例代码中是uint8,所以不是简单将byte转int就可以了,计算校验和过程中存在数值的溢出丢弃。

    根据不同类型解析内容

    这里我只做了MsgType=S001、M102两种情况,对应logon消息和快照消息。

    运行结果如上图,对于行情更详细的解析请参考接口文档。

    流的问题

    代码中,我使用了比较大的缓存来读流数据,因为处理过程中发现了一个问题:

    “流行情不是逐条传输的”

    也就是每次你收到的行情,不一定是完整的++这样,而可能存在下面几种情况:

    1、一条完整的++

    2、多条完整的++

    3、一条完整的++再加上下一条行情的或半个

    意思是流行情传输时像一条不停向你流动的河,在任意时间你从buffer中截取的数据可能有多有少,也可能只有一半,所以从socket获取到数据后,你先要把数据拼接起来,再找到有效的head和checksum,这两个之间的数据才是完整的数据。这个过程比较复杂,作为示例我就不搞了,你们加油。

    五、总结

    本篇我们做了个简单的上交所流行情Binary接口示例,注意只是能简单跑起来的示例,其中存在很多隐患和问题,出了毛病别找我。

    完整的代码:

    1. import java.io.InputStream;
    2. import java.io.OutputStream;
    3. import java.io.*;
    4. import java.net.Socket;
    5. import java.nio.ByteBuffer;
    6. import java.util.Arrays;
    7. public class connSH{
    8. public static byte[] getNewByteArr(int n){
    9. byte[] newByteArr = new byte[n];
    10. for(int i=0; i
    11. newByteArr[i] = 32;
    12. }
    13. return newByteArr;
    14. }
    15. public static byte[] int2Byte(int n, int size){
    16. byte[] b = new byte[size];
    17. int s = size;
    18. for(int i=s-1; i>=0; i-- ){
    19. b[i] = (byte)(n>>(size-i-1)*8&0xff);
    20. }
    21. return b;
    22. }
    23. public static byte[] str2Byte(String str, int size){
    24. byte[] strByte = str.getBytes();
    25. byte[] strByteArr = new byte[size];
    26. for(int i=0; i
    27. strByteArr[i] = 32;
    28. }
    29. System.arraycopy(strByte, 0, strByteArr, 0, strByte.length);
    30. return strByteArr;
    31. }
    32. public static int getCheckSum(byte[] bytes){
    33. int sum = 0;
    34. for(byte b : bytes){
    35. int bInt = b&0xff;
    36. sum += bInt;
    37. sum = sum&0xff;
    38. }
    39. return sum;
    40. }
    41. public static byte[] byteMerger(byte[] bt1, byte[] bt2){
    42. byte[] bt3 = new byte[bt1.length + bt2.length];
    43. System.arraycopy(bt1, 0, bt3, 0, bt1.length);
    44. System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
    45. return bt3;
    46. }
    47. public static long byte2Long(byte[] buffer){
    48. long values = 0;
    49. for(int i=0;i<8;i++){
    50. values<<=8;
    51. values|=(buffer[i]&0xff);
    52. }
    53. return values;
    54. }
    55. public static int byte2Int(byte[] buffer){
    56. int values = 0;
    57. for(int i =0; i<4; i++){
    58. values <<= 8;
    59. values |= (buffer[i]&0xff);
    60. }
    61. return values;
    62. }
    63. public static int byte2Int16(byte[] buffer){
    64. int values = 0;
    65. for(int i=0; i<2; i++){
    66. values <<= 8;
    67. values |= (buffer[i]&0xff);
    68. }
    69. return values;
    70. }
    71. public static int byte2Int8(byte[] buffer){
    72. int values = 0;
    73. for(int i=0; i<1; i++){
    74. values <<= 8;
    75. values |= (buffer[i]&0xff);
    76. }
    77. return values;
    78. }
    79. static class ReadThread extends Thread{
    80. InputStream readStream;
    81. public ReadThread(InputStream readStream){
    82. this.readStream = readStream;
    83. }
    84. @Override
    85. public void run(){
    86. try{
    87. System.out.println("thread run...");
    88. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    89. while(true){
    90. //注意从socket获取的数据有多有少,不一定就是一条完整的,这里我做了简化处理
    91. //也就是我这里只处理了一条完整的,其他的都丢弃了
    92. byte[] buffer = new byte[102400];
    93. int len = readStream.read(buffer,0,102400);
    94. //读到的数据太少
    95. if(len<28){
    96. System.out.println("len<28");
    97. Thread.sleep(1000);
    98. continue;
    99. }
    100. //先解析head
    101. System.out.print("len=");
    102. System.out.println(len);
    103. byte[] MsgType = new byte[4];
    104. byte[] SendingTime = new byte[8];
    105. byte[] MsgSeqNum = new byte[8];
    106. byte[] BodyLength = new byte[4];
    107. System.arraycopy(buffer,0,MsgType,0,4);
    108. System.arraycopy(buffer,4,SendingTime,0,8);
    109. System.arraycopy(buffer,4+8,MsgSeqNum,0,8);
    110. System.arraycopy(buffer,4+8+8,BodyLength,0,4);
    111. String MsgTypeStr = new String(MsgType);
    112. System.out.print("MsgType=");
    113. System.out.println(MsgTypeStr);
    114. long SendingTimeInt = byte2Long(SendingTime);
    115. System.out.print("SendingTime=");
    116. System.out.println(SendingTimeInt);
    117. long MsgSeqNumInt = byte2Long(MsgSeqNum);
    118. System.out.print("MsgSeqNum=");
    119. System.out.println(MsgSeqNumInt);
    120. int BodyLengthInt = byte2Int(BodyLength);
    121. System.out.print("BodyLength=");
    122. System.out.println(BodyLengthInt);
    123. byte[] MsgBody = new byte[BodyLengthInt];
    124. byte[] CheckSum = new byte[4];
    125. System.arraycopy(buffer,4+8+8+4,MsgBody,0,BodyLengthInt);
    126. System.arraycopy(buffer,4+8+8+4+BodyLengthInt,CheckSum,0,4);
    127. byte[] CheckBody = new byte[4+8+8+4+BodyLengthInt];
    128. System.arraycopy(buffer,0,CheckBody,0,CheckBody.length);
    129. int CheckSumInt = byte2Int(CheckSum);
    130. //校验和错误
    131. if(CheckSumInt!=getCheckSum(CheckBody)){
    132. System.out.println("checkSum error");
    133. System.out.print("CheckSumInt=");
    134. System.out.println(CheckSumInt);
    135. System.out.print("getCheckSum=");
    136. System.out.println(getCheckSum(CheckBody));
    137. //校验和正确
    138. }else{
    139. System.out.println("checkSum ok");
    140. //Logon Msg
    141. if(MsgTypeStr.equals("S001")){
    142. byte[] SenderCompID = new byte[32];
    143. byte[] TargetCompID = new byte[32];
    144. byte[] HeartBtInt = new byte[2];
    145. byte[] ApplVerID = new byte[8];
    146. System.arraycopy(buffer,24,SenderCompID,0,32);
    147. System.arraycopy(buffer,24+32,TargetCompID,0,32);
    148. System.arraycopy(buffer,24+32+32,HeartBtInt,0,2);
    149. System.arraycopy(buffer,24+32+32+2,ApplVerID,0,8);
    150. System.out.println("SenderCompID="+new String(SenderCompID));
    151. System.out.println("TargetCompID="+new String(TargetCompID));
    152. System.out.println("HeartBtInt="+byte2Int16(HeartBtInt));
    153. System.out.println("ApplVerID="+new String(ApplVerID));
    154. //快照
    155. }else if(MsgTypeStr.equals("M102")){
    156. byte[] SecurityType = new byte[1];
    157. System.arraycopy(buffer,24,SecurityType,0,1);
    158. System.out.println("SecurityType="+byte2Int8(SecurityType));
    159. byte[] TradSesMode = new byte[1];
    160. System.arraycopy(buffer,24+1,TradSesMode,0,1);
    161. System.out.println("TradSesMode="+byte2Int8(TradSesMode));
    162. byte[] TradeDate = new byte[4];
    163. System.arraycopy(buffer,24+1+1,TradeDate,0,4);
    164. System.out.println("TradeDate="+byte2Int(TradeDate));
    165. byte[] LastUpdateTime = new byte[4];
    166. System.arraycopy(buffer,24+1+1+4,LastUpdateTime,0,4);
    167. System.out.println("LastUpdateTime="+byte2Int(LastUpdateTime));
    168. byte[] MDStreamID = new byte[5];
    169. System.arraycopy(buffer,24+1+1+4+4,MDStreamID,0,5);
    170. System.out.println("MDStreamID="+new String(MDStreamID));
    171. byte[] SecurityID = new byte[8];
    172. System.arraycopy(buffer,24+1+1+4+4+5,SecurityID,0,8);
    173. System.out.println("SecurityID="+new String(SecurityID));
    174. byte[] Symbol = new byte[8];
    175. System.arraycopy(buffer,24+1+1+4+4+5+8,Symbol,0,8);
    176. System.out.println("Symbol="+new String(Symbol,"GBK"));
    177. byte[] PreClosePx = new byte[8];
    178. System.arraycopy(buffer,24+1+1+4+4+5+8+8,PreClosePx,0,8);
    179. System.out.println("PreClosePx="+byte2Long(PreClosePx));
    180. byte[] TotalVolumeTraded = new byte[8];
    181. System.arraycopy(buffer,24+1+1+4+4+5+8+8+8,TotalVolumeTraded,0,8);
    182. System.out.println("TotalVolumeTraded="+byte2Long(TotalVolumeTraded));
    183. byte[] NumTrades = new byte[8];
    184. System.arraycopy(buffer,24+1+1+4+4+5+8+8+8+8,NumTrades,0,8);
    185. System.out.println("NumTrades="+byte2Long(NumTrades));
    186. byte[] TotalValueTraded = new byte[8];
    187. System.arraycopy(buffer,24+1+1+4+4+5+8+8+8+8+8,TotalValueTraded,0,8);
    188. System.out.println("TotalValueTraded="+byte2Long(TotalValueTraded));
    189. byte[] TradingPhaseCode = new byte[8];
    190. System.arraycopy(buffer,24+1+1+4+4+5+8+8+8+8+8+8,TradingPhaseCode,0,8);
    191. System.out.println("TradingPhaseCode="+new String(TradingPhaseCode));
    192. }
    193. }
    194. }
    195. }catch(Exception e){
    196. System.out.println(e.getMessage());
    197. }
    198. }
    199. }
    200. public static void main(String[] args){
    201. try{
    202. //head
    203. byte[] MsgType = str2Byte("S001", 4);
    204. byte[] SendingTime = int2Byte(0,8);
    205. byte[] MsgSeqNum = int2Byte(0,8);
    206. byte[] BodyLength = int2Byte(32+32+2+8,4);
    207. //logon body
    208. byte[] SenderCompID = str2Byte("VSS",32);
    209. byte[] TargetCompID = str2Byte("VDE",32);
    210. byte[] HeartBtInt = int2Byte(0,2);
    211. byte[] AppVerID = str2Byte("1.00",8);
    212. byte[] MsgBody = byteMerger(MsgType, SendingTime);
    213. MsgBody = byteMerger(MsgBody, MsgSeqNum);
    214. MsgBody = byteMerger(MsgBody, BodyLength);
    215. MsgBody = byteMerger(MsgBody, SenderCompID);
    216. MsgBody = byteMerger(MsgBody, TargetCompID);
    217. MsgBody = byteMerger(MsgBody, HeartBtInt);
    218. MsgBody = byteMerger(MsgBody, AppVerID);
    219. //checksum
    220. byte[] CheckSum = int2Byte(getCheckSum(MsgBody),4);
    221. MsgBody = byteMerger(MsgBody, CheckSum);
    222. //connect
    223. Socket socket = new Socket("xxx你的IP",9126);
    224. OutputStream outStream = socket.getOutputStream();
    225. outStream.write(MsgBody);
    226. outStream.flush();
    227. //listion thread
    228. ReadThread readThread = new ReadThread(socket.getInputStream());
    229. readThread.start();
    230. while(true){
    231. Thread.sleep(3000);
    232. System.out.println("every 3000...");
    233. }
    234. }catch(Exception e){
    235. System.out.println(e.getMessage());
    236. }
    237. }
    238. }

  • 相关阅读:
    ASP.NET Core 5.0中的Host.CreateDefaultBuilder执行过程
    C语言之const
    行为型模式-解释器模式
    naive-ui-admin跨域失败
    Excel表列序号
    SQL常用脚本整理,建议收藏
    和鲸技术!国家气象信息中心人工智能气象应用基础技术平台上线
    人人都会数据分析
    Kafka的存储机制和可靠性
    新手做独立站需要掌握哪些技能
  • 原文地址:https://blog.csdn.net/weixin_40402375/article/details/126851919