交易系统开发(十二)——QuickFIX官方文档
http://www.quickfixengine.org/quickfix/doc/html/?quickfix/doc/html
一、QuickFIX编译构建
1、Windows
使用VS Studio打开quickfix_vs12.sln、quickfix_vs14.sln、quickfix_vs15.sln。
链接库:链接lib\quickfix.lib和lib\debug\quickfix.lib到应用。
头文件:拷贝头文件到include目录。
编译控制:src目录的config_windows.h文件用于控制编译时选项。
#define HAVE_STLPORT 1
使用stlport替换Visual C++ STL进行编译。
#define HAVE_ODBC 1
QuickFIX支持ODBC数据库。
#define HAVE_MYSQL 1
QuickFIX支持MySQL。如果开启,MySQL的include和lib目录必须在Visual Studio的搜索路径内。
#define HAVE_POSTGRESQL 1
QuickFIX支持PostgreSQL数据库,如果开启,则PostgreSQL的include和lib目录必须在Visual Studio的搜索路径内。
2、Linux
Linux、Solaris、FreeBSD、Mac OS X使用相同的编译构建流程。
编译配置:
configure
编译配置选项如下:
--prefix=directory:指定安装目录。
--with-python2:编译Python2 API
--with-python3:编译Python3 API
--with-ruby:编译Ruby API
--with-mysql:支持MySQL
--with-postgresql:支持PostgreSQL
--with-stlport=directory:使用stlport进行编译,替换标准的GCC STL实现。
编译:
make
安装:
make install
二、QuickFIX数据库支持
1、MySQL
运行src/sql/mysql目录中的创建脚本,需要传递创建数据库的授权用户,必须安装MySQL数据库。
create.sh mysql
create.bat mysql
2、MSSQL
必须安装MSSQL,运行src/sql/mssql目录的创建脚本,必须传递创建MSSQL数据库的用户。create.bat sa
3、PostgreSQL
必须安装PostgreSQL,运行src/sql/postgresql脚本,必须传递创建PostgreSQL数据库的用户。
create.sh postgres
create.bat postgres
三、测试
1、测试
QuickFIX开发由功能测试和单元测试组件驱动。
2、Windows
(1)单元测试
test目录下执行:
runut release [port]
端口用于测试socket功能。
(2)验证测试
在test目录执行:
runat release [port]
runat_threaded release [port]
端口用于Socket Server监听连接。
3、Linux
Linux、Solaris、FreeBSD、Mac OS X中单元测试和功能测试相同。
(1)单元测试
test目录执行:runut.sh [port]
端口用于测试Socket功能,如果QuickFIX要支持数据库,需要更新数据库设置cfg/ut.cfg。
(2)验证测试
在test目录执行:
runat.sh [port]
runat_threaded.sh [port]
端口用于监听Socket服务器的连接。
四、工程设置
1、Windows
在Microsoft Visual Studio打开project | properties。
(1)设置C/C++ | Code Generation | Enable C++ Exceptions为Yes。
(2)C/C++ | Code Generation | Runtime Library设置Multithreaded DLL或Debug Multithreaded DLL。
(3)C/C++ | General | Additional Include Directories增加quickfix根目录。
(4)Linker | Input | Additional Dependencies必须包含quickfix.lib和ws2_32.lib。
(5)Linker | General | Additional Library Directories增加quickfix/lib目录。
2、Linux
(1)使用-fexceptions选项开启异常。
(2)推荐使用-finline-functions选项优化。
(3)QuickFIX必须使用-lquickfix选项进行链接。如果QuickFIX使用pthreads、libxml,则使用-lpthread选项、-lxml2选项、-lz选项链接。
(4)Solaris系统必须使用-lnsl选项和-lsocket选项链接。
五、应用创建
1、Application接口
使用QuickFIX创建FIX应用只需要实现QuickFIX Application接口。QuickFIX Application接口如下:
namespace FIX
{
class Application
{
public:
virtual ~Application() {};
virtual void onCreate( const SessionID& ) = 0;
virtual void onLogon( const SessionID& ) = 0;
virtual void onLogout( const SessionID& ) = 0;
virtual void toAdmin( Message&, const SessionID& ) = 0;
virtual void toApp( Message&, const SessionID& )
throw( DoNotSend ) = 0;
virtual void fromAdmin( const Message&, const SessionID& )
throw( FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon ) = 0;
virtual void fromApp( const Message&, const SessionID& )
throw( FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType ) = 0;
};
}
onCreate:QuickFIX创建Session时调用。无论对等方是否连接,在应用程序生命周期内Session会一直存在。一旦Session建立,并可以向其发送消息。如果没有登录,消息会在对等方建立连接时发送。
onLogon:通知和对等方的有效登录连接已经建立。连接建立,FIX logon进程完成双方有效登录消息交换后调用。
onLogout:通知FIX Session不再在线。正常登出或网络连接中断时调用。
toAdmin:可以查看FIX引擎发送给对等方的管理类型消息,应用程序通常不使用,可以在管理类型消息发送前增加字段。
toApp:发送到对等方的应用类型消息的回调函数,如果函数内抛出DoNotSend异常,则应用程序不会发送消息。
fromAdmin:通知FIX Session,对等方已经发送一条管理类型消息到FIX引擎,用于进行验证密码等登录消息的其它验证操作。抛出RejectLogon异常将会断开与对等方连接。
fromApp:用于接收应用类型消息。如果应用程序是卖方OMS,本函数内可以获取新的订单请求;如果应用程序是买方应用,则在本函数内获取成交回报。如果抛出FieldNotFound异常,对等方会收到表明消息缺失必需字段拒绝通知。如果试图索引缺失字段,Message类会抛出异常,因此不需要显式抛出异常。可以抛出UnsupportedMessageType异常,但会导致对等方收到拒绝通知,通知对等方本地应用程序无法处理这些类型的消息。当字段包含不支持的值时会抛出IncorrectTagValue异常。
2、示例
FIX Acceptor示例代码如下:
#include "quickfix/FileStore.h"
#include "quickfix/FileLog.h"
#include "quickfix/SocketAcceptor.h"
#include "quickfix/Session.h"
#include "quickfix/SessionSettings.h"
#include "quickfix/Application.h"
int main( int argc, char** argv )
{
try
{
if(argc < 2) return 1;
std::string fileName = argv[1];
FIX::SessionSettings settings(fileName);
MyApplication application;
FIX::FileStoreFactory storeFactory(settings);
FIX::FileLogFactory logFactory(settings);
FIX::SocketAcceptor acceptor
(application, storeFactory, settings, logFactory /*optional*/);
acceptor.start();
// while( condition == true ) { do something; }
acceptor.stop();
return 0;
}
catch(FIX::ConfigError& e)
{
std::cout << e.what();
return 1;
}
}
如果需要使用FIX Initiator,则需要使用SocketInitiator。
六、QuickFix配置
1、QuickFix配置简介
QuickFix中initiator或acceptor会维护多个Fix Session。QuickFix中使用BeginString(Fix版本号)、SenderCompID、TargetCompID的组合标识一个Session,Session标识用于区分其它不同的Session。
Session配置文件包含[DEFAULT]和[SESSION]两种分节,[SESSION]分节表示QuickFix中定义一个Session,[DEFAULT]表示所有Session默认使用的配置项,如果不提供QuickFix所需的配置,QuickFix会抛出ConfigError异常,表示配置缺失或格式不正确。
如果[DEFAULT]和[SESSION]分区中都包含相同的配置项,则[SESSION]分区的配置项会覆盖[DEFAULT]分区相应的配置项。
QuickFix配置参见官网:
http://quickfixengine.org/quickfix/doc/html/configuration.html
2、QuickFix配置文件
QuickFix配置文件sessions.ini如下:
# default settings for all sessions
[DEFAULT]
ConnectionType=initiator
ReconnectInterval=60
SenderCompID=TW
# session definition
[SESSION]
# inherit ConnectionType, ReconnectInterval and SenderCompID from default
BeginString=FIX.4.1
TargetCompID=ARCA
StartTime=12:30:00
EndTime=23:30:00
HeartBtInt=20
SocketConnectPort=9823
SocketConnectHost=123.123.123.123
DataDictionary=somewhere/FIX41.xml
[SESSION]
BeginString=FIX.4.2
TargetCompID=INCA
StartTime=12:30:00
EndTime=21:30:00
# overide default setting for RecconnectInterval
ReconnectInterval=30
HeartBtInt=30
SocketConnectPort=6523
SocketConnectHost=3.3.3.3
# (optional) alternate connection ports and hosts to cycle through on failover
SocketConnectPort1=8392
SocketConnectHost1=8.8.8.8
SocketConnectPort2=2932
SocketConnectHost2=12.12.12.12
DataDictionary=somewhere/FIX42.xml
3、Session配置
BeginString:Session使用的Fix版本,可选值为FIXT.1.1、FIX.4.4、FIX.4.3、FIX.4.2、FIX.4.1、FIX.4.0。
SenderCompID:关联Fix Session的本地ID,包含字母和数字大小写敏感的字符串。
TargetCompID:Fix Session的对等方ID,包含字母和数字大小写敏感的字符串。
SessionQualifier:Session标识,用于区分其它Session,包含字母和数字大小写敏感的字符串。
DefaultApplVerID:只支持FIXT 1.1及后续版本,早期版本会忽略,用于指定Session的默认应用版本ID,可选值可以是ApplVerID枚举值或BeginString值,包括FIX.5.0SP2、FIX.5.0SP1、FIX.5.0、FIX.4.4、FIX.4.3、FIX.4.2、FIX.4.1、FIX.4.0、9、8、7、6、5、4、3、2。
ConnectionType:定义Session的角色,可选值为initiator、acceptor。
StartTime:Session每天开始工作的时间,时间为UTC时间,时间格式为HH:MM:SS。
EndTime:Session每天停止工作的时间,时间为UTC时间,时间格式为HH:MM:SS。
StartDay:对于周级别的长连接Session,指示Session在周中的开始工作日。
EndDay:对于周级别的长连接Session,指示Session在周中的结束工作日。
LogonTime:Session每天登录的时间,时间为UTC时间,时间格式为HH:MM:SS。
LogoutTime:Session每天登出的时间,时间为UTC时间,时间格式为HH:MM:SS。
LogonDay:对于周级别的长连接Session,指示Session在周中登录的工作日。
LogoutDay:对于周级别的长连接Session,指示Session在周中登出的工作日。
UseLocalTime:指示StartTime和EndTime使用本地时间,而不是UTC时间,FIX消息中的按照FIX协议要求仍然使用UTC时间,可选值为Y、N,默认值为N。
MillisecondsInTimeStamp:是否将毫秒增加到时间戳上,只对Fix 4.2及后续版本有效。
TimestampPrecision:指定时间戳的小数部分,可选值为0-9,如果设置,则会覆盖MillisecondsInTimeStamp。
SendRedundantResendRequests:可选值为Y、N,如果设置为Y,QuickFix会发送所有需要的重发请求,即使冗余。如果设置为N,QuickFix会试图最小化重发请求,通常用于高吞吐量的系统中。
ResetOnLogon:如果Session为Acceptors,当收到一个登录请求时确定是否要重置序列号,可选值为Y、N,默认值为N。
ResetOnLogout:当收到一个登出请求时确定是否要重置序列号为1,可选值为Y、N,默认值为N。
ResetOnDisconnect:当Session异常终止时确定是否要重置序列号为1,可选值为Y、N,默认值为N。
RefreshOnLogon:当登录时确定Session状态是否从持久层恢复,用于创建热切换的Session,可选值为Y、N,默认值为N。
4、认证配置
UseDataDictionary:Session是否使用数据字典,如果使用重复分组,应该使用数据字典。可选值为Y、N,默认值为Y。
DataDictionary:XML格式的数据字典文件,如果没有提供数据字典,只能提供基本FIX消息验证。对于FIXT.1.1以及后续版本,使用TransportDataDictionary和AppDataDictionary进行指定。
可选数据字典包括FIX44.xml、FIX43.xml、FIX42.xml、FIX41.xml、FIX40.xml。
TransportDataDictionary:XML格式定义的数据字典,只对FIXT.1.1以及后续版本有效,可选数据字典为FIXT1.1.xml。
AppDataDictionary:XML格式的验证应用消息的数据字典,只对FIXT.1.1以及后续版本有效,可选数据字典为FIX50SP2.xml、FIX50SP1.xml、FIX50.xml、FIX44.xml、FIX43.xml、FIX42.xml、FIX41.xml、FIX40.xml。可以使用前缀指定多个应用字典,如:
DefaultApplVerID=FIX.4.2
# For default application version ID
AppDataDictionary=FIX42.xml
# For nondefault application version ID
# Use BeginString suffix for app version
AppDataDictionary.FIX.4.4=FIX44.xml
ValidateLengthAndChecksum:可选值为Y、N,默认值为Y。如果设置为N,则消息的长度和校验和不正确将不会被拒绝。
ValidateFieldsOutOfOrder:可选值为Y、N,默认值为Y。如果设置为N,则订单外的字段(如body字段在header中,header字段在body中)将不会被拒绝。
ValidateFieldsHaveValues:可选值为Y、N,默认值为Y。如果设置为N,则没有值的字段将不会被拒绝。用于那些会不恰当发送控tag的系统。
ValidateUserDefinedFields:可选值为Y、N,默认值为Y。如果设置为N,则没有在数据字典中定义的用户自定义字段将不会被拒绝,或是显示在不属于的消息中。
PreserveMessageFieldsOrder:是否按照配置文件中定义的保留发送消息体中的字段顺序,可选值为Y、N,默认值为N。
CheckCompID:可选值为Y、N,默认值为Y。如果设置为N,则消息必须从包含正确的SenderCompID和TargetCompID的对等方中接收。
CheckLatency:可选值为Y、N,默认值为Y。如果设置为Y,消息必须在最大延迟内被接收。
MaxLatency:如果CheckLatency设置为Y,则表示消息可以延迟处理的时间,默认为120秒。
5、Initiator配置
ReconnectInterval:重连接时间间隔,只用于Initiator,正数,默认只为30。
HeartBtInt:心跳时间间隔,只用于Initiator,正数。
LogonTimeout:登录超时时间,正数,默认为10。
LogoutTimeout:登出超时时间,正数,默认为2。
SocketConnectPort:Session连接端口,只用于SocketInitiator。
SocketConnectHost:Session连接主机,只用于SocketInitiator,可以是IP地址或域名。
SocketConnectPort<n>
:对于热切换Session的备份连接端口。
SocketConnectHost<n>
:对于热切换Session的备份连接主机。
SocketNodelay:指定使用TCP_NODELAY创建Socket,目前只能定义在[DEFAULT]分区,可选值为Y、N,默认值为N。
SocketSendBufferSize:指定使用SO_SNDBUF创建Socket,目前只能定义在[DEFAULT]分区,正数,默认值为0。
SocketReceiveBufferSize:指定使用SO_RCVBUF创建Socket,目前只能定义在[DEFAULT]分区,正数,默认值为0。
6、Acceptor
SocketAcceptPort:监听端口,只用于SocketAcceptor,目前只能定义在[DEFAULT]分区。
SocketReuseAddress:指定SO_REUSADDR使用创建Socket,只用于SocketAcceptor,可选值为Y、N,默认值为Y。
SocketNodelay:指定使用TCP_NODELAY创建Socket,目前只能定义在[DEFAULT]分区,可选值为Y、N,默认值为N。
7、Storage
PersistMessages:可选值为Y、N,默认值为N。如果设置N,消息不会被持久化,QuickFix会使用填充空白消息取代正在重发的消息。
FileStorePath:存储序列号和消息的目录,必须有写权限。
MySQLStoreDatabase:存储消息和Session状态的MySQL数据库名称,默认为quickfix。
MySQLStoreUser:登录MySQL数据库的用户名,默认为root。
MySQLStorePassword:登录MySQL数据库的用户名的用户密码,默认为空。
MySQLStoreHost:MySQL数据库的地址,可以是IP或域名,默认值为localhost。
MySQLStorePort:MySQL数据库的端口,默认值为标准数据库端口3306。
MySQLStoreUseConnectionPool:是否使用数据库连接池,可选值为Y、N,默认值为N。
PostgreSQLStoreDatabase:存储消息和Session状态的PostgreSQL数据库名称,默认为quickfix。
PostgreSQLStoreUser:登录PostgreSQL数据库的用户名,默认为postgres。
PostgreSQLStorePassword:登录PostgreSQL数据库的用户名的用户密码,默认为空。
PostgreSQLStoreHost:PostgreSQL数据库的地址,可以是IP或域名,默认值为localhost。
PostgreSQLStorePort:PostgreSQL数据库的端口,默认值为标准数据库端口5432。
PostgreSQLStoreUseConnectionPool:是否使用数据库连接池,可选值为Y、N,默认值为N。
OdbcStoreUser:登录ODBC数据库的用户名
OdbcStorePassword:登录ODBC数据库的用户密码
OdbcStoreConnectionString:ODBC数据库连接字符串,默认值为DATABASE=quickfix;DRIVER={SQL Server};SERVER=(local);
8、日志配置
FileLogPath:日志存储路径,必须有写权限。
FileLogBackupPath:日志备份路径,必须有写权限。
ScreenLogShowIncoming:打印Incoming消息到标准输出,可选值为Y、N,默认值为Y。
ScreenLogShowOutgoing:打印Outgoing消息到标准输出,可选值为Y、N,默认值为Y。
ScreenLogShowEvents:打印Events消息到标准输出,可选值为Y、N,默认值为Y。
日志也可以存储到数据库中,支持MySQL、PostgreSQL、ODBC数据库。
9、SSL配置
SSLProtocol:用于控制应用程序在建立其环境时应使用的SSL协议,可选值为SSLv2、SSLv3、TLSv1、TLSv1_1、TLSv1_2、all,默认为all -SSLv2。
七、消息验证
QuickFIX会在消息到达应用程序前对其进行验证,拒绝任何格式错误的消息。XML文件定义会话支持的消息、字段和值。spec目录包含多个标准的FIX协议字典。FIX字典基本骨架如下:
<fix type="FIX" major="4" minor="1">
<header>
<field name="BeginString" required="Y"/>
...
</header>
<trailer>
<field name="CheckSum" required="Y"/>
...
</trailer>
<messages>
<message name="Heartbeat" msgtype="0" msgcat="admin">
<field name="TestReqID" required="N"/>
</message>
...
<message name="NewOrderSingle" msgtype="D" msgcat="app">
<field name="ClOrdID" required="Y"/>
...
</message>
...
</messages>
<fields>
<field number="1" name="Account" type="CHAR" />
...
<field number="4" name="AdvSide" type="CHAR">
<value enum="B" description="BUY" />
<value enum="S" description="SELL" />
<value enum="X" description="CROSS" />
<value enum="T" description="TRADE" />
</field>
...
</fields>
</fix>
验证器不会有条件地拒绝必填字段,因为其规则没有明确定义。使用非预设的有条件必填字段将导致消息被拒绝。
八、接收消息
开发者感兴趣的大部分消息在应用程序重载的fromApp函数内收到,所有消息都有消息头和消息尾,消息头和消息尾分别使用 getHeader和getTrailer函数进行访问。
1、类型安全消息和字段
QuickFIX为不同FIX标准规范中定义的所有消息提供了一个类MessageCracker。
#include "quickfix/Application.h"
#include "quickfix/MessageCracker.h"
class FIXApplication: public FIX::Application, public FIX::MessageCracker
{
public:
/**************************************************
* reimplementation from Application
* ***********************************************/
/// Notification of a session begin created
virtual void onCreate( const SessionID& )
{
}
/// Notification of a session successfully logging on
virtual void onLogon( const SessionID& )
{
}
/// Notification of a session logging off or disconnecting
virtual void onLogout( const SessionID& )
{
}
/// Notification of admin message being sent to target
virtual void toAdmin( Message&, const SessionID& )
{
}
/// Notification of app message being sent to target
void fromApp( const FIX::Message& message, const FIX::SessionID& sessionID )
throw( FIX::FieldNotFound&, FIX::IncorrectDataFormat&, FIX::IncorrectTagValue&, FIX::UnsupportedMessageType& )
{
crack(message, sessionID);
}
/// Notification of admin message being received from target
virtual void fromAdmin( const Message&, const SessionID& )
throw ( FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon )
{
}
/// Notification of app message being received from target
virtual void fromApp( const Message&, const SessionID& )
throw ( FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::UnsupportedMessageType )
{
}
/**************************************************
* reimplementation from MessageCracker
* ***********************************************/
void onMessage( const FIX42::NewOrderSingle& message, const FIX::SessionID& )
{
FIX::ClOrdID clOrdID;
message.get(clOrdID);
FIX::ClearingAccount clearingAccount;
message.get(clearingAccount);
}
void onMessage( const FIX42::OrderCancelRequest& message, const FIX::SessionID& )
{
FIX::ClOrdID clOrdID;
message.get(clOrdID);
// compile time error!! field not defined for OrderCancelRequest
FIX::Price price;
message.get(price);
}
};
通过继承MessageCracker,可以使用crack函数并可以重载不同的FIX协议的消息函数,未重载的函数默认抛出UnsupportedMessageType异常。
2、类型安全字段
使用getField函数从消息中获取字段。
void fromApp( const FIX::Message& message, const FIX::SessionID& sessionID )
throw( FIX::FieldNotFound&, FIX::IncorrectDataFormat&, FIX::IncorrectTagValue&, FIX::UnsupportedMessageType& )
{
// retrieve value into field class
FIX::Price price;
message.getField(price);
// another field...
FIX::ClOrdID clOrdID;
message.getField(clOrdID);
}
3、非类型安全
使用tag数字创建自定义字段。
void fromApp( const FIX::Message& message, const FIX::SessionID& sessionID )
throw( FIX::FieldNotFound&, FIX::IncorrectDataFormat&, FIX::IncorrectTagValue&, FIX::UnsupportedMessageType& )
{
// retreive value into string with integer field ID
std::string value;
value = message.getField(44);
// retrieve value into a field base with integer field ID
FIX::FieldBase field(44, "");
message.getField(field);
// retreive value with an enumeration, a little better
message.getField(FIX::FIELD::Price);
}
九、发送消息
1、发送消息
使用静态函数方法Session::sendToTarget发送消息到对等方。
// send a message that already contains a BeginString, SenderCompID, and a TargetCompID
static bool sendToTarget( Message&, const std::string& qualifier = "" )
throw(SessionNotFound&);
// send a message based on the sessionID, convenient for use
// in fromApp since it provides a session ID for incoming
// messages
static bool sendToTarget( Message&, const SessionID& )
throw(SessionNotFound&);
// append a SenderCompID and TargetCompID before sending
static bool sendToTarget( Message&, const SenderCompID&, const TargetCompID&, const std::string& qualifier = "" )
throw(SessionNotFound&);
// pass SenderCompID and TargetCompID in as strings
static bool sendToTarget( Message&, const std::string&, const std::string&, const std::string& qualifier = "" )
throw(SessionNotFound&);
2、类型安全消息和字段
Message构造函数接收所有必需字段,并为开发者添加正确的MsgType和BeginString。使用set方法,编译器不允许添加不是FIX字典中定义的消息的字段。
void sendOrderCancelRequest()
{
FIX41::OrderCancelRequest message(
FIX::OrigClOrdID("123"),
FIX::ClOrdID("321"),
FIX::Symbol("LNUX"),
FIX::Side(FIX::Side_BUY));
message.set(FIX::Text("Cancel My Order!"));
FIX::Session::sendToTarget(message, SenderCompID("TW"), TargetCompID("TARGET"));
}
3、类型安全字段
setField方法用于增加任何字段到消息。
void sendOrderCancelRequest()
{
FIX::Message message;
FIX::Header header& = message.getHeader();
header.setField(FIX::BeginString("FIX.4.2"));
header.setField(FIX::SenderCompID(TW));
header.setField(FIX::TargetCompID("TARGET"));
header.setField(FIX::MsgType(FIX::MsgType_OrderCancelRequest));
message.setField(FIX::OrigClOrdID("123"));
message.setField(FIX::ClOrdID("321"));
message.setField(FIX::Symbol("LNUX"));
message.setField(FIX::Side(FIX::Side_BUY));
message.setField(FIX::Text("Cancel My Order!"));
FIX::Session::sendToTarget(message);
}
4、非类型安全
可以使用setField方法传递消息原语。
void sendOrderCancelRequest()
{
FIX::Message message;
// BeginString
message.getHeader().setField(8, "FIX.4.2");
// SenderCompID
message.getHeader().setField(49, "TW");
// TargetCompID, with enumeration
message.getHeader().setField(FIX::FIELD::TargetCompID, "TARGET");
// MsgType
message.getHeader().setField(35, 'F');
// OrigClOrdID
message.setField(41, "123");
// ClOrdID
message.setField(11, "321");
// Symbol
message.setField(55, "LNUX");
// Side, with value enumeration
message.setField(54, FIX::Side_BUY);
// Text
message.setField(58, "Cancel My Order!");
FIX::Session::sendToTarget(message);
}
十、循环组
QuickFIX能够发送包含循环组甚至递归循环组的消息。所有循环组都以一个字段开头,用于指示一个集合中有多少个循环组,可以通过引用在父消息或父组中以该字段命名的类来创建组。
1、使用循环组创建消息
创建消息时,声明循环组的数量的必填字段设置为零。当添加组时,QuickFIX会自动增加字段值。
// create a market data message
FIX42::MarketDataSnapshotFullRefresh message(FIX::Symbol("QF"));
// repeating group in the form of MessageName::NoField
FIX42::MarketDataSnapshotFullRefresh::NoMDEntries group;
group.set(FIX::MDEntryType('0'));
group.set(FIX::MDEntryPx(12.32));
group.set(FIX::MDEntrySize(100));
group.set(FIX::OrderID("ORDERID"));
message.addGroup(group);
// no need to create a new group class if we are reusing the fields
group.set(FIX::MDEntryType('1'));
group.set(FIX::MDEntryPx(12.32));
group.set(FIX::MDEntrySize(100));
group.set(FIX::OrderID("ORDERID"));
message.addGroup(group);
2、使用循环组读取消息
要从消息中拉出组,需要提供要拉出的组对象。应该首先检查实体字段的数量以获得组的总数。
// should be 2
FIX::NoMDEntries noMDEntries;
message.get(noMDEntries);
FIX42::MarketDataSnapshotFullRefresh::NoMDEntries group;
FIX::MDEntryType MDEntryType;
FIX::MDEntryPx MDEntryPx;
FIX::MDEntrySize MDEntrySize;
FIX::OrderID orderID;
message.getGroup(1, group);
group.get(MDEntryType);
group.get(MDEntryPx);
group.get(MDEntrySize);
group.get(orderID);
message.getGroup(2, group);
group.get(MDEntryType);
group.get(MDEntryPx);
group.get(MDEntrySize);
group.get(orderID);
十一、用户自定义字段
FIX允许用户定义未在FIX协议规范中定义的字段。
非类型安全的字段操作如下:
message.setField(6123, "value");
message.getField(6123);
QuickFIX提供了创建类型安全字段对象的宏。
#include "quickfix/Field.h"
namespace FIX
{
USER_DEFINE_STRING(MyStringField, 6123);
USER_DEFINE_PRICE(MyPriceField, 8756);
}
用户自定义字段必须定义在FIX命名空间内,应用程序中可以如下使用:
MyStringField stringField("string");
MyPriceField priceField(14.54);
message.setField(stringField);
message.setField(priceField);
message.getField(stringField);
message.getField(priceField);
下列宏允许定义所有支持的FIX类型的字段。只要提供一个新的宏和转换器,可以将类型与字符串进行转换,就可以编写任何类型的字段。
USER_DEFINE_STRING( NAME, NUM )
USER_DEFINE_CHAR( NAME, NUM )
USER_DEFINE_PRICE( NAME, NUM )
USER_DEFINE_INT( NAME, NUM )
USER_DEFINE_AMT( NAME, NUM )
USER_DEFINE_QTY( NAME, NUM )
USER_DEFINE_CURRENCY( NAME, NUM )
USER_DEFINE_MULTIPLEVALUESTRING( NAME, NUM )
USER_DEFINE_EXCHANGE( NAME, NUM )
USER_DEFINE_UTCTIMESTAMP( NAME, NUM )
USER_DEFINE_BOOLEAN( NAME, NUM )
USER_DEFINE_LOCALMKTDATE( NAME, NUM )
USER_DEFINE_DATA( NAME, NUM )
USER_DEFINE_FLOAT( NAME, NUM )
USER_DEFINE_PRICEOFFSET( NAME, NUM )
USER_DEFINE_MONTHYEAR( NAME, NUM )
USER_DEFINE_DAYOFMONTH( NAME, NUM )
USER_DEFINE_UTCDATE( NAME, NUM )
USER_DEFINE_UTCTIMEONLY( NAME, NUM )
USER_DEFINE_NUMINGROUP( NAME, NUM )
USER_DEFINE_SEQNUM( NAME, NUM )
USER_DEFINE_LENGTH( NAME, NUM )
USER_DEFINE_PERCENTAGE( NAME, NUM )
USER_DEFINE_COUNTRY( NAME, NUM )
十二、测试
1、单元测试
QuickFIX提供了一套综合的自动化单元测试套件。测试组件运行在UnitTest++框架上。UnTest++框架允许开发人员通过编写调用对象上的函数并声明正确行为的代码来测试C++代码。测试不仅验证了代码的正确工作,还验证了它在所有平台上的工作原理。
下列示例显示测试的设置和执行,测试用例测试验证解析器对象是否可以从流中正确提取消息。
struct readFixMessageFixture
{
readFixMessageFixture()
{
fixMsg1 = "8=FIX.4.2\0019=12\00135=A\001108=30\00110=31\001";
fixMsg2 = "8=FIX.4.2\0019=17\00135=4\00136=88\001123=Y\00110=34\001";
fixMsg3 = "8=FIX.4.2\0019=19\00135=A\001108=30\0019710=8\00110=31\001";
object.addToStream( fixMsg1 + fixMsg2 + fixMsg3 );
}
std::string fixMsg1;
std::string fixMsg2;
std::string fixMsg3;
Parser object;
};
TEST_FIXTURE(readFixMessageFixture, readFixMessage)
{
std::string readFixMsg1;
CHECK( object.readFixMessage( readFixMsg1 ) );
CHECK_EQUAL( fixMsg1, readFixMsg1 );
std::string readFixMsg2;
CHECK( object.readFixMessage( readFixMsg2 ) );
CHECK_EQUAL( fixMsg2, readFixMsg2 );
std::string readFixMsg3;
CHECK( object.readFixMessage( readFixMsg3 ) );
CHECK_EQUAL( fixMsg3, readFixMsg3 );
}
2、验证测试
QuickFIX由一个脚本化的测试运行器,其附带一系列自动化验收测试。QuickFIX附带的基本测试基于FIX协议组织提供的《FIX会话层测试用例和预期行为》。QuickFIX的测试会验证QuickFIX是否符合FIX协议规范。QuickFIX测试的自动化特性保证了QuickFIX的未来版本不会破坏任何当前功能。
也许更重要的是如何使用测试驱动QuickFIX的开发。在编写任何一行支持FIX协议的代码前,必须先编写测试用例。
这种测试优先的方法为开发人员建立了一个目标,开发人员将客观地验证其是否正确地实现了FIX标准。
下列测试脚本的示例测试在接收到小于预期MsgSeqNum的NewSeqNo值时FIX引擎的行为。
iCONNECT
I8=FIX.4.235=A34=149=TW52=>TIME>56=ISLD98=0108=30
E8=FIX.4.29=5735=A34=149=ISLD52=00000000-00:00:0056=TW98=0108=3010=0
# sequence reset without gap fill flag (default to N)
I8=FIX.4.235=434=049=TW52=>TIME>56=ISLD36=1
E8=FIX.4.29=11235=334=249=ISLD52=00000000-00:00:0056=TW45=058=Value is incorrect (out of range) for this tag372=4373=510=0
I8=FIX.4.235=134=249=TW52=>TIME>56=ISLD112=HELLO
E8=FIX.4.29=5535=034=349=ISLD52=00000000-00:00:0056=TW112=HELLO10=0
# sequence reset without gap fill flag (default to N)
I8=FIX.4.235=434=049=TW52=>TIME>56=ISLD36=1123=N
E8=FIX.4.29=11235=334=449=ISLD52=00000000-00:00:0056=TW45=058=Value is incorrect (out of range) for this tag372=4373=510=0
I8=FIX.4.235=134=349=TW52=>TIME>56=ISLD112=HELLO
E8=FIX.4.29=5535=034=549=ISLD52=00000000-00:00:0056=TW112=HELLO10=0
iDISCONNECT
示例脚本中有两种命令类型,action命令和messages命令,action命令以小写字母开头,messages命令以大写字母开头。
(1)action命令
i<ACTION> :初始化action
e<ACTION> :预期action
支持的Action如下:
iCONNECT :初始化到FIX Acceptor的连接
eCONNECT:等待来自FIX Initiator的连接
iDISCONNECT:断开到FIX Acceptor的连接
eDISCONNECT:等待来自FIX Initiator的连接断开
(2)messages命令
I<MESSAGE>:初始化(发送)一条消息
E<MESSAGE>:等待一条消息
使用I命令时,不必增加Length(9)、CheckSum(10)字段,命令会自动增加相应的值到合适位置。开发者只有故意使这些字段不正确时才需要增加字段。
I命令也为字段提供了TIME宏,通过设置字段等于<TIME>,当前系统时间会被替换,也可以使用TIME的偏移,如52=<TIME-120>或52=<TIME+15>。
E命令验证是否收到正确消息。E命令会比较每个字段的值确保其是否正确。有些字段在运行时前无法确定地验证,例如SendingTime和CheckSum字段。这些字段可以添加到fields.fmt文件,文件中可以定义正则表达式至少验证字段的格式是否正确。10=\d{3}
,,checksum必须是三个数字。52=\d{8}-\d{2}:\d{2}:\d{2}
,发送时间必须是DDDDDDDD-DD:DD:DD格式。
I命令和E命令可以包含FILE宏,用于将文件内容传递给字段,如:58=<FILE:test.txt>