ITPub博客

首页 > > > 详解FIX协议的原理、消息格式及配置开发

详解FIX协议的原理、消息格式及配置开发

作者:宜信技术学院 时间:2019-07-11 11:55:48 0 删除 编辑


一、定义

FIX协议是由国际FIX协会组织提供的一个开放式协议,目的是推动国际贸易电子化的进程,在各类参与者之间,包括投资经理、经纪人,买方、卖方建立起实时的电子化通讯协议。FIX协议的目标是把各类证券金融业务需求流程格式化,使之成为一个个可用计算机语言描述的功能流程,并在每个业务功能接口上统一交换格式,方便各个功能模块的连接。

二、协议工作原理

2.1 通信模型及基本概念

通信模型

  • Initiator :发起者,建立通信连路,通过发送初始Logon消息发起会话的参与方。
  • Acceptor :接收方 FIX会话的接收方。负责执行第一层次的认证和通过传输Logon消息的确认正式声明连接请求被接受。
  • 原则:先发起者为Initiator ,接受者为Acceptor 。
  • 标准模式以网关为Acceptor,客户端为Initiator做为常用模式。

Fix connection

FIX连接 由3部分组成:logon登录,message exchange消息传输,logout注销。

logon登录 

logout注销 

Fix session

FIX会话由一个或多个FIX Connection FIX连接组成。一个FIX会话可以有多次登录。

序列号

  • 所有的FIX消息都由一个唯一的序列号进行标示。序列号在每一个FIX会话开始时被初始化为1,并在整个会话期间递增。监控序列号可以使会话参与者识别和处理丢失的消息,当在一个FIX会话中重新连接时能够快速进行应用程序同步。
  • 每个会话将建立一组互不依赖的接受和发送序列。会话参与者将维护一个赋予发送消息的序列和一个监控接受消息的消息块间隙序列号。

心跳

  • 在消息交互期间,FIX应用程序将周期性产生Heartbeat心跳消息。该心跳消息可以监控通信链路状态及识别接收序列号间隙。发送Heartbeat的周期间隔由会话发起者使用在Logon消息中HeartBtInt域进行定义。
  • Heartbeat心跳消息的时间间隔应当在每一个消息发送后复位,即发送一个消息后,在间隔给定的时间内无其它消息发送则发送一个Heartbeat心跳消息。HeartBtInt的值应当被会话双方认同,由会话发起方定义并由会话接收者通过Logon消息进行确认。同一个HeartBtInt被会话双方——登录的发起者和登录的接受者共同使用。

数据完整校验

  • 消息数据内容的完整性可以参用两种方式来验证:消息长度和效验码检查。
  • 程序通过计算BodyLength域到CheckSum标记(“10=”)分界符的字符数,域BodyLength标示的消息长度进行比较来完成完整性效验。
  • ChekSum完整性检查,通过计算从域“8=” 中“8”开始,包括紧跟在CheckSum标记域的分界符每个字符的2进制和同CheckSum进行比较得到。
  • 一个FIX消息校验和通过计算到ChechSum域(但不包括)的消息的每个字节和得到。然后,校验和被转换为模256的数字用于传送和比较。校验和在所有加密操作之后被计算。

校验代码:

样例:8=FIX.4.29=7335=A34=149=CLIENT52=20181119-10:42:48.76856=SERVER98=0108=30141=Y10=208
1、消息长度:9=73
35=A34=149=CLIENT52=20181119-10:42:48.76856=SERVER98=0108=30141=Y(这段长度)
2、效验码检查
char *GenerateCheckSum( char *buf, long bufLen ) {
static char tmpBuf[ 4 ]; long idx;
unsigned int cks;
for( idx = 0L, cks = 0; idx < bufLen; cks += (unsigned int)buf[ idx++ ] ); sprintf( tmpBuf, “%03d”, (unsigned int)( cks % 256 ) );
return( tmpBuf );
}


消息确认

  • FIX协议不支持单个消息的确认。采用的是监控消息时隙的方法来进行消息恢复和验证。
  • 普通的数据传送(无单个消息确认)通过消息序列间隙进行错误识别。每个消息由一个唯一的序列号进行标示。接收端应用程序负责监控接收消息序列号以识别消息间隙并产生重传请求。
  • 每个FIX参与方必须为FIX会话维护两个序列号,一个是接收序列号,一个是发送序列号,两者都在建立FIX会话开始时初始化为1。每个消息被赋予一个唯一的序列号值,并在消息发送后递增。此外,每个收到的消息都有一个唯一的序列号,接收序列号计数器在收到每个消息后将会被递增。
  • 当接收序列号与所希望得到的的正确序列号不必配时,必须采取纠错处理。

加密

  • 加密算法由连接双方共同协商。
  • 一个消息的任何一个域可以被加密并放在SecureData域中。然而,一些显示的标志域必须采用明文进行传输。为确保完整性,明文域可以在SecureData域中重复。
  • 当使用加密时,建议但不是必须,所有的消息体都进行加密。如果一个消息中的重复组数据中的部分数据要加密,这个重复组必须全部进行加密。
  • 预先协商好的加密算法在Logon消息中进行声明。

自定义域

  • FIX为给用户提供最大的灵活性,FIX协议允许用户自定义域。这些域在认同的参与者之间实现、应用,并且应注意避免冲突。
  • Tag数在5000 到9999保留用于用户自定义域。这些tag值用于企业联盟的信息交换。可以通过FIX网站进行注册。
  • 10000以上保留用于单一企业内部使用。不用注册。

三、消息格式

3.1 数据类型

整数int,浮点数float,单个字符char,布尔Boolean,字符串String,数据data 

3.2 域

常见域

域语法

  • 开始部分应是消息头,随后是正文,最后是消息尾;
  • 消息头的前 3 个域的次序不能改变:起始串(Tag =8)、消息体长度(Tag =9)、消息类型(Tag =35);
  • 消息尾的最后一个域应是校验和域(Tag=10);
  • 重复组中,域出现的顺序应遵循该重复组在消息或组件中定义时的次序;
  • 在一条消息中,除重复组域外任何其他域不能重复出现。

安全与加密

  • 由于消息有可能在公网或不安全的网络上传输交换,因此需要对相关的敏感数据加密处理。
  • 具体加密的方法由连接双方达成的协议而定。
  • 消息内除某些需要公开识别的域以明文传输外其他任何域都可以加密放置密文数据域 (SecureData)内。当然,这些被加密的域也可以同时保留明文的表示方式。
  • 当决定使用加密方案时,可以对消息正文内所有的域加密。如果消息的重复组内有部分需要加密的,那么要求对整个重复组加密。
  • 本协议还提供的一些域用以支持数字签名、密钥交换和正文加密等安全技术。

3.3 消息

消息头

每一个会话或应用消息有一个消息头,该消息头指明消息类型、消息体长度、发送目的地、消息序号、发送起始点和发送时间。

消息尾

每一个消息(会话或应用消息)有一个消息尾,并以此终止。消息尾可用于分隔多个消息,包含有 3 位数的校验和值。

新订单消息(MsgType=D)

对于在消息头中设置了 PossResend 标志的订单消息,应当使用交易客户方订单编号(ClOrdID)核 实是否已收到该订单,具体实现时还应检查订单参数(买卖方向、证券代码、数量等)进行核实。如果 之前收到该订单,应以执行报告消息回应订单状态。如果之前未收到,则以执行报告消息回应订单确认。

执行报告消息(MsgType=8)

  • 订单确认
  • 订单状态变化确认(如撤单确认)
  • 发送订单的成交回报
  • 订单拒绝


订单状态请求消息(MsgType=H)

订单状态请求用于向交易服务方请求某订单的状态,交易服务方通过执行报告消息返回订单状态。

撤单消息(MsgType=F)

撤单消息用以撤消订单的全部订单剩余数量。

撤单消息也被赋予一个 ClOrdID,可视作另外一个订单。如果被拒绝,撤单拒绝消息的 ClOrdID 放 置撤单消息的 ClOrdID,而原始订单的 ClOrdID 则放入 OrigClOrdID 域。ClOrdID 要保证唯一。

撤单拒绝消息(MsgType=9)

本消息用于撤单消息的拒绝。

交易服务方接收到撤单发现无法执行(已成交订单不可更改等),将发送撤单拒绝。

拒绝撤单时,撤单拒绝消息应用 ClOrdID 指示撤单的 ClOrdID,用 OrigClOrdID 指示之前最后接受的订单(除非拒绝原因是“未知订单”)。

四、FIX配置

4.1 会话配置(SESSION)

4.2 验证配置

4.3 Initiator

4.4 Acceptor

4.5 Storage

4.6 File Storage


4.7 Logging

五、FIX开发

5.1 FIX引擎

官网:FIX引擎(http://www.quickfixengine.org/)

github:QFJ GitHub Repository(https://github.com/quickfix-j/quickfixj)

5.2 DEMO

Acceptor 配置文件

# 定义会话的默认配置(default节点)
[DEFAULT]
FileStorePath=store
FileLogPath=log
ConnectionType=acceptor
ReconnectInterval=60
SenderCompID=SERVER
ResetOnDisconnect=Y
ResetOnLogout=Y
ResetOnLogon=Y

[SESSION]
BeginString=FIX.4.2
TargetCompID=CLIENT
StartTime=00:00:00
EndTime=23:59:59
HeartBtInt=30
SocketAcceptHost=127.0.0.1
SocketAcceptPort=6666
DataDictionary=FIX42.xml


Initiator 配置文件

[DEFAULT]
ConnectionType=initiator
ReconnectInterval=60
FileLogPath=log
FileStorePath=store
StartTime=00:00:00
EndTime=23:59:59
HeartBtInt=30
ResetOnDisconnect=Y
ResetOnLogout=Y
ResetOnLogon=Y

[SESSION]
BeginString=FIX.4.2
SenderCompID=CLIENT
TargetCompID=SERVER
SocketConnectPort=6666
SocketConnectHost=127.0.0.1
DataDictionary=FIX42.xml


FixServer

package com.app.fix;

import quickfix.*;

/**
 * 服务启动主类(线程)
 */
public class FixServer {
    private static ThreadedSocketAcceptor acceptor = null;

    /**
     * 指定配置文件启动
     *
     * @param propFile
     * @throws ConfigError
     * @throws FieldConvertError
     */
    public FixServer(String propFile) throws ConfigError, FieldConvertError {
        // 设置配置文件
        SessionSettings settings = new SessionSettings(propFile);

        // 设置一个APPlication
        Application application = new FixServerApplication();

        /**
         *
         * quickfix.MessageStore 有2种实现。 quickfix.JdbcStore,quickfix.FileStore .
         * JdbcStoreFactory 负责创建JdbcStore , FileStoreFactory 负责创建FileStorequickfix
         * 默认用文件存储,因为文件存储效率高。
         */
        MessageStoreFactory storeFactory = new FileStoreFactory(settings);

        LogFactory logFactory = new FileLogFactory(settings);

        MessageFactory messageFactory = new DefaultMessageFactory();

        acceptor = new ThreadedSocketAcceptor(application, storeFactory, settings, logFactory, messageFactory);

    }

    private void startServer() throws RuntimeError, ConfigError {
        acceptor.start();
    }

    /**
     * 测试本地使用的main方法
     *
     * @param args
     * @throws FieldConvertError
     * @throws ConfigError
     */
    public static void main(String[] args) throws ConfigError, FieldConvertError {
        FixServer fixServer = new FixServer("res/acceptor.config");
        fixServer.startServer();
    }

}


FixServerApplication

package com.app.fix;

import quickfix.Application;
import quickfix.DoNotSend;
import quickfix.FieldNotFound;
import quickfix.IncorrectDataFormat;
import quickfix.IncorrectTagValue;
import quickfix.Message;
import quickfix.MessageCracker;
import quickfix.RejectLogon;
import quickfix.Session;
import quickfix.SessionID;
import quickfix.UnsupportedMessageType;
import quickfix.field.MsgType;

/**
 * 
 */
public class FixServerApplication extends MessageCracker implements Application {
    @Override
    protected void onMessage(Message message, SessionID sessionID) {
        try {
            String msgType = message.getHeader().getString(35);
            Session session = Session.lookupSession(sessionID);
            switch (msgType) {
                case MsgType.LOGON: // 登陆
                    session.logon();
                    session.sentLogon();
                    break;
                case MsgType.HEARTBEAT: // 心跳
                    session.generateHeartbeat();
                    break;
            }

        } catch (FieldNotFound e) {
            e.printStackTrace();
        }

    }

    @Override
    public void onCreate(SessionID sessionId) {
        System.out.println(" 服务器启动时候调用此方法创建");

    }

    @Override
    public void onLogon(SessionID sessionId) {
        System.out.println("客户端登陆成功时候调用此方法");

    }

    @Override
    public void onLogout(SessionID sessionId) {
        System.out.println("客户端断开连接时候调用此方法");

    }

    @Override
    public void toAdmin(Message message, SessionID sessionId) {
        System.out.println("发送会话消息时候调用此方法");

    }

    @Override
    public void toApp(Message message, SessionID sessionId) throws DoNotSend {
        System.out.println("发送业务消息时候调用此方法");

    }

    @Override
    public void fromAdmin(Message message, SessionID sessionId)
            throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon {
        System.out.println("接收会话类型消息时调用此方法");
        try {
            crack(message, sessionId);
        } catch (UnsupportedMessageType | FieldNotFound | IncorrectTagValue e) {
            e.printStackTrace();
        }

    }

    @Override
    public void fromApp(Message message, SessionID sessionId)
            throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {
        System.out.println("接收业务消息时调用此方法");
        crack(message, sessionId);

    }

}


FixClient

package com.app.fix;

import quickfix.*;
import quickfix.field.*;
import quickfix.fix42.NewOrderSingle;

import java.io.FileNotFoundException;
import java.util.Date;

public class FixClient implements Application {

    private static volatile SessionID sessionID;

    @Override
    public void onCreate(SessionID sessionID) {
        System.out.println("OnCreate");
    }

    @Override
    public void onLogon(SessionID sessionID) {
        System.out.println("OnLogon");
        FixClient.sessionID = sessionID;
    }

    @Override
    public void onLogout(SessionID sessionID) {
        System.out.println("OnLogout");
        FixClient.sessionID = null;
    }

    @Override
    public void toAdmin(Message message, SessionID sessionID) {
        System.out.println("ToAdmin");
    }

    @Override
    public void fromAdmin(Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon {
        System.out.println("FromAdmin");
    }

    @Override
    public void toApp(Message message, SessionID sessionID) throws DoNotSend {
        System.out.println("ToApp: " + message);
    }

    @Override
    public void fromApp(Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {
        System.out.println("FromApp");
    }

    public static void main(String[] args) throws ConfigError, FileNotFoundException, InterruptedException, SessionNotFound {
        SessionSettings settings = new SessionSettings("res/initiator.config");

        Application application = new FixClient();
        MessageStoreFactory messageStoreFactory = new FileStoreFactory(settings);
        LogFactory logFactory = new ScreenLogFactory(true, true, true);
        MessageFactory messageFactory = new DefaultMessageFactory();

        Initiator initiator = new SocketInitiator(application, messageStoreFactory, settings, logFactory, messageFactory);
        initiator.start();

        while (sessionID == null) {
            Thread.sleep(1000);
        }

        final String orderId = "342";
        NewOrderSingle newOrder = new NewOrderSingle(new ClOrdID(orderId), new HandlInst('1'), new Symbol("YRD"),
                new Side(Side.BUY), new TransactTime(new Date()), new OrdType(OrdType.MARKET));
        Session.sendToTarget(newOrder, sessionID);
        Thread.sleep(5000);
    }
}


作者:姜永念

来源:来源:宜信技术学院

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69918724/viewspace-3118283/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
宜信技术学院是宜信旗下的金融科技能力展示与输出平台。通过分享在金融科技领域的开源成果、研发实践促进金融科技生态圈企业创新升级。

注册时间:2019-04-30

  • 博文量
    579
  • 访问量
    307724