首页 > > > 详解FIX协议的原理、消息格式及配置开发
一、定义
FIX协议是由国际FIX协会组织提供的一个开放式协议,目的是推动国际贸易电子化的进程,在各类参与者之间,包括投资经理、经纪人,买方、卖方建立起实时的电子化通讯协议。FIX协议的目标是把各类证券金融业务需求流程格式化,使之成为一个个可用计算机语言描述的功能流程,并在每个业务功能接口上统一交换格式,方便各个功能模块的连接。
二、协议工作原理
2.1 通信模型及基本概念
通信模型
Fix connection
FIX连接 由3部分组成:logon登录,message exchange消息传输,logout注销。
logon登录

logout注销

Fix session
FIX会话由一个或多个FIX Connection FIX连接组成。一个FIX会话可以有多次登录。
序列号
心跳
数据完整校验
校验代码:
样例: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 );
}消息确认
加密
自定义域
三、消息格式
3.1 数据类型
整数int,浮点数float,单个字符char,布尔Boolean,字符串String,数据data
3.2 域
常见域

域语法
安全与加密
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.xmlInitiator 配置文件
[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.xmlFixServer
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/ ,如需转载,请注明出处,否则将追究法律责任。