上交所FAST行情接口对接
一、前言
之前已完成了Binary行情的解析,接着便继续研究FAST(STEP)行情,但花费了将近一个月时间才终于搞定了。前面说过Binary格式的行情不太直观,所以对于初学者有点难度,接触FAST后才知道什么叫“完全看不明白”。还好互联网是伟大的,大佬们偶尔留下的只言片语对我们来说就是难得的启迪了。
一开始我是想人肉解析的,但确实没看明白,找到的参考资料如下:
https://github.com/kuangtu/fixfast
上述链接的资料非常全,但恕我真的没看懂,接着推荐另一份资料:
https://blog.csdn.net/wqfhenanxc/article/details/86512143(这只是一部分,该博客还有同系列的其他文章)
两个结合起来看终于搞明白了“停止位”和“map”,但实际解析还是不对,故放弃转而开始研究openfast。
事实上关于openfast的资料也非常稀有,而且大都只是提了几句完全无法使用,但经过我不懈的努力,还是找到了一些有用的资料。
Openfast下载:
https://sourceforge.net/projects/openfast/
非常有参考价值的一个示例:
有了之前binary的经验以及上述资料的加持,我终于把FAST行情初步给搞出来了,不过截至目前我只是用openfast解析出了内容,并没有做深入研究,所以仅供各位看官参考。
另附上官方参考文档:
《上海证券交易所低延时行情发布系统(LDDS)接口说明书》
《上海证券交易所LDDS系统Level-1 FAST行情接口说明书》
《IS120_上海证券交易所行情网关STEP数据接口规范》
二、FAST接口
要搞明白FAST就得先搞清楚fix、step,参考资料如下:
https://blog.csdn.net/wqfhenanxc/article/details/81042310
简单来说,FIX就是KEY=VALUE这样的一对数据,中间用一个特殊符号分割开,解析起来比较容易而且新增、修改接口也比较简单。但缺点是过于冗长了,传输金融行情数据占用很大的带宽,不是很科学。
STEP跟FIX看起来差不多,只是做了一些本地化,没详细研究也不知道做了啥修改,不多说。
由于FIX、STEP都比较冗长,所以又整出了FAST。首先FAST也是源于KEY=VALUE这样的格式,然后在此基础上做了压缩,所以所谓的FAST解析就是把它还原成KEY=VALUE这样。FAST压缩的大致方法如下:
1、取消了Key,使用模板(temple)来约定各字段的含义
2、使用二进制而不是字符串的形式来传输
3、在二进制的基础上使用了非常特别(而且看不懂)的压缩方法,进一步做了压缩。
而上交所在发FAST行情时,还给他套了个STEP的壳,而且是双重壳,见下图:
STEP的内容直接输出成字符串即可,可读性很强,但FAST的内容全都是乱码了,因为它即是二进制又做了压缩。虽然FAST协议本身很难看懂,但借助openfast我们可以很容易解析出数据,那么现在的问题就是如何获取FAST的内容。
三、登录
与binary类似,只需要按指定的格式发信息到VDE即可,这一块参考官方文档很容易就接入了。其中“\u0001”就是SOH这个分隔符了。
四、STEP解析
你把从接口获取到的内容直接字符串输出,可以看到STEP部分很好处理,而FAST部分全是乱码,所以要把FAST部分完整的截取出来用openfast来解析。如果你用SOH来分割还不行,因为FAST里有很多“00000001”这样的数据,所以你得先解析出FAST的长度,然后根据长度去读byte数组,读刚好那么长的byte数组出来,直接怼到openfast里解析就行了。
这里我偷懒直接通过数SOH找到FAST的长度,也就是tag95,注意因为套娃的关系,是第二个tag95的值。
五、FAST解析
前面把FAST的内容读出来了,解析的话就是用openfast即可,具体openfast的使用好似也比较多样,我这里就参考前面博文随便写了下,没有深入研究。
输出结果如下:
由于中文还得特别处理下,所以这里显示的是乱码,另外我为了简便都是以string格式读的,按说不太合适,大家自行研究吧,可以看openfast的文档看都有啥函数。
完整代码如下:
import java.io.InputStream; import java.io.FileInputStream; import java.io.OutputStream; import java.io.*; import java.net.Socket; import java.nio.ByteBuffer; import java.util.Arrays; import org.openfast.template.MessageTemplate; import org.openfast.template.loader.XMLMessageTemplateLoader; import org.openfast.Context; import org.openfast.codec.FastDecoder; import org.openfast.Message; public class connSHFAST2{ public static String getCheckSum(String str){ byte[] strBytes = str.getBytes(); int sum = 0; for(byte b : strBytes){ int bInt = b&0xff; sum += bInt; sum = sum&0xff; } String sumStr = sum + ""; if(sumStr.length()==1){ sumStr = "00"+sumStr; }else if(sumStr.length()==2){ sumStr = "0"+sumStr; } return sumStr; } /** * 把byte转化成2进制字符串 * @param b * @return */ public static String getBinaryStrFromByte(byte b){ String result =""; byte a = b; ; for (int i = 0; i < 8; i++){ byte c=a; a=(byte)(a>>1);//每移一位如同将10进制数除以2并去掉余数。 a=(byte)(a<<1); if(a==c){ result="0"+result; }else{ result="1"+result; } a=(byte)(a>>1); } return result; } static class ReadThread extends Thread{ InputStream readStream; public ReadThread(InputStream readStream){ this.readStream = readStream; } @Override public void run(){ try{ System.out.println("thread run..."); while(true){ byte[] buffer = new byte[102400]; int len = readStream.read(buffer,0,102400); //读到的数据太少 if(len<28){ System.out.println("len<28"); Thread.sleep(1000); continue; } //读到的数据太多 if(len>=102400){ System.out.println("len>=102400"); continue; } //截取有效部分 byte[] step = Arrays.copyOf(buffer,len); //逐一读取byte,数SOH,数到第27个 int count = 0; int start = 0; len = 0; for(int i=0; i<step.length; i++){ byte b = step[i]; int bInt = b & 0xFF; if(bInt==1){ count = count + 1; if(count == 1){ len = i - start; byte[] BeginString = new byte[len]; System.arraycopy(step,start,BeginString,0,len); System.out.println("BeginString="+new String(BeginString)); start = i+1; } if(count == 2){ len = i - start; byte[] BodyLength = new byte[len]; System.arraycopy(step,start,BodyLength,0,len); System.out.println("BodyLength="+new String(BodyLength)); start = i+1; } if(count == 3){ len = i - start; byte[] MsgType = new byte[len]; System.arraycopy(step,start,MsgType,0,len); System.out.println("MsgType="+new String(MsgType)); start = i+1; if(!"35=UA5302".equals(new String(MsgType))){ System.out.println("MsgType!=UA5302"); break; } } if(count == 26){ start = i+1; } if(count==27){ len = i - start; byte[] Tag95 = new byte[len]; System.arraycopy(step,start,Tag95,0,len); System.out.println("Tag95="+new String(Tag95)); byte[] FastLenBG = new byte[len-3]; System.arraycopy(Tag95,3,FastLenBG,0,len-3); int FastLen = Integer.parseInt(new String(FastLenBG)); System.out.println("FastLen="+FastLen); start = i+1; //读取FAST内容 byte[] FAST = new byte[FastLen]; System.arraycopy(step,start+3,FAST,0,FastLen); //System.out.println("FAST="+new String(FAST)); //解析FATS内容 InputStream inputStream = new FileInputStream("template.xml"); MessageTemplate template = new XMLMessageTemplateLoader().load(inputStream)[0]; Context context = new Context(); context.registerTemplate(4001, template); InputStream sbs = new ByteArrayInputStream(FAST); FastDecoder fastDecoder = new FastDecoder(context, sbs); Message msg111 = fastDecoder.readMessage(); System.out.println("MDStreamID="+msg111.getString("MDStreamID")); System.out.println("SecurityID="+msg111.getString("SecurityID")); System.out.println("Symbol="+msg111.getString("Symbol")); System.out.println("NumTrades="+msg111.getString("NumTrades")); System.out.println("TradeVolume="+msg111.getString("TradeVolume")); System.out.println("TotalValueTraded="+msg111.getString("TotalValueTraded")); System.out.println("PrevClosePx="+msg111.getString("PrevClosePx")); System.out.println("PrevSetPx="+msg111.getString("PrevSetPx")); System.out.println("TotalLongPosition="+msg111.getString("TotalLongPosition")); } } } } }catch(Exception e){ System.out.println(e.getMessage()); } } } public static void main(String[] args){ try{ //head String BeginString = "8=STEP.1.0.0\u0001"; String BodyLength = "9=56\u0001";//除了 8、9、10 域,所 有其他域长度总和 String MsgType = "35=A\u0001"; String SenderCompID = "49=VSS\u0001"; String TargetCompID = "56=VDE\u0001"; String MsgSeqNum = "34=0\u0001"; String SendingTime = "52=20220916-11:00:00\u0001"; //body String EncryptMethod = "98=0\u0001"; String HeartBtInt = "108=0\u0001"; //check body,用于做校验和的 String CheckBody = BeginString+BodyLength+MsgType+SenderCompID +TargetCompID+MsgSeqNum+SendingTime +EncryptMethod+HeartBtInt; //checksum 除了10域本身,对所有其他域的每个字节累加后取256的余数。余数不足3位的,前补0 String CheckSum = "10="+getCheckSum(CheckBody)+"\u0001"; String MsgBody = CheckBody + CheckSum; //connect Socket socket = new Socket("你的IP",端口); OutputStream outStream = socket.getOutputStream(); outStream.write(MsgBody.getBytes()); outStream.flush(); //listion thread ReadThread readThread = new ReadThread(socket.getInputStream()); readThread.start(); while(true){ Thread.sleep(3000); System.out.println("every 3000..."); } }catch(Exception e){ System.out.println(e.getMessage()); } } }