lmax是欧洲的一家外汇黄金交易所,本篇文章解决c++对接lmax行情的痛点,并提供c++对接源码。
对接文件准备
lmax会提供相关的对接文件,其中最重要的是账号密码,行情地址,以及FIX数据字典文件(本人对接时字典文件是:brokerFixMarketDataGateway-QuickFix-DataDictionary.xml
)。
还需要lmax的代码列表地址:https://www.lmax.com/global/uk#instrument-list, 订阅行情的时候需要代码列表数据,如果失效,找lmax要新的。
一般使用这份代码列表
stunnel配置
lmax行情需要ssl连接,本文使用的是quickfix进行对接,虽然quickfix本身支持ssl连接,但是对接lmax行情时使用quickfix的ssl会有问题,而且lmax也推荐使用stunnel,所以本文使用stunnel进行ssl转发。
lmax提供的文件里面也有stunnel的案例配置: Example SSL Config.txt
。
先安装stunnel,以centos7为例:
-
安装openssl:
yum install openssl yum install openssl-devel
-
stunnel官网下载
.tar.gz
的源码包,例:stunnel-5.62.tar.gz
,解压后进入源码目录:
./configure --enable-fips make make install
-
安装好后,
cd /usr/local/etc/stunnel/
,vim stunnel.conf
,然后将Example SSL Config.txt
里面的推荐配置复制进去,并修改相关配置。
接下来还需要安装windows版的stunnel,装它是因为lmax的案例配置里面需要stunnel.pem
文件,这个文件只有在windows版的安装目录里有。
在stunnel官网下载windows版并安装后,复制安装目录下stunnel\config\[stunnel.pem](https://stunnel.pem)
文件到centos7的/usr/local/etc/stunnel/
目录下,然后stunnel
启动程序。
我这里也贴一下stunnel.conf的案例配置:
socket=l:TCP_NODELAY=1
socket=r:TCP_NODELAY=1
debug=7
fips=yes
[LmaxQuote]
cert=stunnel.pem
client=yes
accept=0.0.0.0:40003
connect=fix-marketdata.london-demo.lmax.com:443
sslVersion=TLSv1
options=NO_SSLv2
options=NO_SSLv3
quickfix对接
这里就按quickfix的接口实现对接就好,注意quickfix连接的地址和端口是上一节配置的stunnel地址和端口,而不是直接连lmax的行情地址(链路就是lmax行情→stunnel→quickfix)。
quickfix配置文件
quickfix配置文件:LmaxFix.cfg(随便起名,代码里匹配就好)
[DEFAULT]
ConnectionType=initiator
HeartBtInt=30
FileStorePath=.\lmaxstore
FileLogPath=.\lmaxlog
StartDay=Sunday
StartTime=00:00:00
EndDay=Saturday
EndTime=00:00:00
ValidateUserDefinedFields=N
ValidateFieldsHaveValues=N
ValidateFieldsOutOfOrder=N
ReconnectInterval=60
ResetOnDisconnect=Y
ResetSeqNumFlag=Y
ResetOnLogout=Y
ResetOnLogon=Y
SendResetSeqNumFlag=Y
ContinueInitializationOnError=Y
UseDataDictionary=Y
DataDictionary=conf/brokerFixMarketDataGateway-QuickFix-DataDictionary.xml
Username=xxxxxx
Password=password1
SocketConnectHost=148.70.202.62
SocketConnectPort=40003
[SESSION]
BeginString=FIX.4.4
SenderCompID=xxxxxx
TargetCompID=LMXBDM
DataDictionary对应的是lmax的fix数据字典文件;
Username和Password是lmax提供的账号和密码;
SocketConnectHost和SocketConnectPort是stunnel的地址和端口;
SenderCompID也是配置lmax提供的账号。
对接代码的实现
废话不多说,这里直接贴上.h .cpp实现源码,将两个文件结合到自己工程就可以了。
里面有两处实现需要你们自己做,我都用to-do标志出来了。
本人已经调试通过了的,应该没啥问题,如果有问题,可以在联系方式里联系我。
FixApplication.h:
/*************************************************
* @brief quickfix连接lmax处理行情
* @author 49042765@qq.com
* @date 2022-02-09
*************************************************/
#ifndef FIXAPPLICATION_H
#define FIXAPPLICATION_H
#include <iostream>
#include "quickfix/Application.h"
#include "quickfix/FileLog.h"
#include "quickfix/FileStore.h"
#include "quickfix/fix44/MarketDataRequest.h"
#include "quickfix/fix44/MarketDataRequestReject.h"
#include "quickfix/fix44/MarketDataSnapshotFullRefresh.h"
#include "quickfix/MessageCracker.h"
#include "quickfix/Session.h"
#include "quickfix/SessionID.h"
#include "quickfix/SessionSettings.h"
#include "quickfix/SocketInitiator.h"
using namespace FIX;
#define LMAX_PRICE_AMT 5 //行情档数
//报价档数
struct LmaxEntry
{
int64_t iAsk[LMAX_PRICE_AMT];
int64_t iAskSize[LMAX_PRICE_AMT];
int64_t iBid[LMAX_PRICE_AMT];
int64_t iBidSize[LMAX_PRICE_AMT];
LmaxEntry() { clear(); }
void clear() { memset(this, 0, sizeof(LmaxEntry)); }
};
struct Quote
{
std::string lmaxid;
std::string symbol = "";
std::string exchange = "";
std::string date = "";
std::string time = "";
int64_t ask = 0;
int64_t bid = 0;
int64_t last = 0;
int64_t high = 0;
int64_t low = 0;
int64_t open = 0;
int64_t preclose = 0;
int64_t volume = 0;
LmaxEntry entry;
};
class FixApplication : public MessageCracker, public Application
{
public:
FixApplication();
// FIX Namespace. These are callbacks which indicate when the session is created,
// when we logon and logout, and when messages are exchanged
void onCreate(const SessionID& session_ID);
void onLogon(const SessionID& session_ID);
void onLogout(const SessionID& session_ID);
void toAdmin(Message& message, const SessionID& session_ID)throw (FIX::DoNotSend);
void toApp(Message& message, const SessionID& session_ID)throw (FIX::DoNotSend);
void fromAdmin(const Message& message, const SessionID& session_ID)throw (FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon);
void fromApp(const Message& message, const SessionID& session_ID)throw (FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::UnsupportedMessageType);
// Overloaded onMessage methods used in conjuction with MessageCracker class. FIX::MessageCracker
// receives a generic Message in the FIX fromApp and fromAdmin callbacks, constructs the
// message sub type and invokes the appropriate onMessage method below.
void onMessage(const FIX44::MarketDataRequestReject& mdr, const SessionID& session_ID);
void onMessage(const FIX44::MarketDataSnapshotFullRefresh& mds, const SessionID& session_ID);
// Starts the FIX session. Throws FIX::ConfigError exception if our configuration settings
void StartSession();
// Logout and end session
void EndSession();
// Subscribes quote
void SubscribeMarketData(std::vector<std::string> vecSecurityID);
// Unsubscribes quote
void UnsubscribeMarketData(std::vector<std::string> vecSecurityID);
// Generate string value used to populate the fields in each message
std::string NextRequestID();
private:
SessionSettings* m_settings;
FileStoreFactory* m_store_factory;
FileLogFactory* m_log_factory;
SocketInitiator* m_initiator;
// Used as a counter for producing unique request identifiers
unsigned int m_requestID;
SessionID m_sessionID;
bool m_connect;
};
#endif // FIXAPPLICATION_H
FixApplication.cpp:
#include "FixApplication.h"
FixApplication::FixApplication()
{
m_requestID = 1;
}
void FixApplication::onCreate(const SessionID& session_ID)
{
// FIX Session created. We must now logon. QuickFIX will automatically send
printf("Session -> created");
m_sessionID = session_ID;
}
void FixApplication::onLogon(const SessionID& session_ID)
{
// Session logon successful.
printf("Session -> logon");
m_connect = true;
//登录成功,订阅行情
std::vector<std::string> vecids;
//to-do 获取lmax代码列表
SubscribeMarketData(vecids);
}
// Notifies you when an FIX session is no longer online. This could happen during a normal logout
// exchange or because of a forced termination or a loss of network connection.
void FixApplication::onLogout(const SessionID& session_ID)
{
// Session logout
printf("Session -> logout");
m_connect = false;
}
void FixApplication::toAdmin(Message& message, const SessionID& session_ID)throw (FIX::DoNotSend)
{
std::string msg_type = message.getHeader().getField(FIELD::MsgType);
if (msg_type == "A") {
std::string user = m_settings->get().getString("Username");
std::string pass = m_settings->get().getString("Password");
message.setField(Username(user));
message.setField(Password(pass));
}
}
// A callback for application messages that you are being sent to a counterparty.
void FixApplication::toApp(Message& message, const SessionID& session_ID)throw (FIX::DoNotSend)
{
//printf("toApp: %s", message.toString().c_str());
}
// Notifies you when an administrative message is sent from FXCM to your FIX engine.
void FixApplication::fromAdmin(const Message& message, const SessionID& session_ID)throw (FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon)
{
//printf("fromAdmin: %s", message.toString().c_str());
crack(message, session_ID);
}
// One of the core entry points for your FIX application. Every application level request will come through here.
void FixApplication::fromApp(const Message& message, const SessionID& session_ID)throw (FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::UnsupportedMessageType)
{
//printf("fromApp: %s", message.toString().c_str());
crack(message, session_ID);
}
void FixApplication::onMessage(const FIX44::MarketDataRequestReject& mdr, const SessionID& session_ID)
{
// If MarketDataRequestReject is returned as the result of a MarketDataRequest message,
// print out the contents of the Text field but first check that it is set
std::string reason = mdr.isSetField(FIELD::MDReqRejReason) ? mdr.getField(FIELD::MDReqRejReason) : "";
std::string text = mdr.isSetField(FIELD::Text) ? mdr.getField(FIELD::Text) : "";
printf("MarketDataRequestReject -> Reason:%s, Text:%s", reason.c_str(), text.c_str());
}
void FixApplication::onMessage(const FIX44::MarketDataSnapshotFullRefresh& mds, const SessionID& session_ID)
{
Quote quote;
quote.lmaxid = mds.getField(FIELD::SecurityID);
int entry_count = IntConvertor::convert(mds.getField(FIELD::NoMDEntries));
int32_t bidpos = 0;
int32_t askpos = 1;
for (int iIndex = 0; iIndex < entry_count; iIndex++)
{
FIX44::MarketDataSnapshotFullRefresh::NoMDEntries group;
mds.getGroup(iIndex+1, group);
std::string entry_type = group.getField(FIELD::MDEntryType);
if (iIndex == 0)
{
quote.time = group.getField(FIELD::MDEntryTime);
quote.date = group.getField(FIELD::MDEntryDate);
}
if (entry_type == "0") // Bid
{
quote.entry.iBid[iIndex] = (DoubleConvertor::convert(group.getField(FIELD::MDEntryPx)) * 1000000);
quote.entry.iBidSize[iIndex] = (DoubleConvertor::convert(group.getField(FIELD::MDEntrySize)));
bidpos += 1;
}
else if (entry_type == "1") // Ask (Offer)
{
quote.entry.iAsk[iIndex - 5] = (DoubleConvertor::convert(group.getField(FIELD::MDEntryPx)) * 1000000);
quote.entry.iAskSize[iIndex - 5] = (DoubleConvertor::convert(group.getField(FIELD::MDEntrySize)));
askpos += 1;
}
}
quote.bid = quote.entry.iBid[0];
quote.ask = quote.entry.iAsk[0];
//to-do 将行情转发到自己的业务处理线程
printf("%s:[%s %s] bid:%lld, bidsize:%lld, ask:%lld, asksize:%lld, H:%lld, L:%lld ",
quote.lmaxid.c_str(), quote.date.c_str(), quote.time.c_str(), quote.bid, quote.entry.iBidSize[0], quote.ask, quote.entry.iAskSize[0], quote.high, quote.low);
}
// Starts the FIX session. Throws FIX::ConfigError exception if our configuration settings
// do not pass validation required to construct SessionSettings
void FixApplication::StartSession()
{
try {
m_settings = new SessionSettings("conf/LmaxFix.cfg");//读取配置文件
m_store_factory = new FileStoreFactory(*m_settings);//创建store文件对象
m_log_factory = new FileLogFactory(*m_settings);//创建日志文件对象
m_initiator = new SocketInitiator(*this, *m_store_factory, *m_settings, *m_log_factory);//开启会话
m_initiator->start();
}
catch (ConfigError error) {
printf("session err:%s",error.what());
}
}
// Logout and end session
void FixApplication::EndSession()
{
m_initiator->stop();
delete m_initiator;
delete m_settings;
delete m_store_factory;
delete m_log_factory;
}
void FixApplication::SubscribeMarketData(std::vector<std::string> vecSecurityID)
{
FIX44::MarketDataRequest request;
request.setField(MDReqID(NextRequestID()));
request.setField(SubscriptionRequestType(SubscriptionRequestType_SNAPSHOT_PLUS_UPDATES));
request.setField(MarketDepth(5));
request.setField(MDUpdateType(0));
//MDReqGrp组件
FIX44::MarketDataRequest::NoMDEntryTypes entryTypeGroup0;
entryTypeGroup0.set(MDEntryType('0'));
FIX44::MarketDataRequest::NoMDEntryTypes entryTypeGroup1;
entryTypeGroup1.set(MDEntryType('1'));
request.addGroup(entryTypeGroup0);
request.addGroup(entryTypeGroup1);
//InstrmtMDReqGrp组件
for (auto& i : vecSecurityID)
{
FIX44::MarketDataRequest::NoRelatedSym codeGroup;
codeGroup.set(SecurityID(i));
codeGroup.set(SecurityIDSource("8"));
request.addGroup(codeGroup);
}
Session::sendToTarget(request, m_sessionID);
}
void FixApplication::UnsubscribeMarketData(std::vector<std::string> vecSecurityID)
{
FIX44::MarketDataRequest request;
request.setField(MDReqID(NextRequestID()));
request.setField(SubscriptionRequestType(SubscriptionRequestType_DISABLE_PREVIOUS_SNAPSHOT_PLUS_UPDATE_REQUEST));
request.setField(MarketDepth(5));
//MDReqGrp组件
FIX44::MarketDataRequest::NoMDEntryTypes entryTypeGroup0;
entryTypeGroup0.set(MDEntryType('0'));
FIX44::MarketDataRequest::NoMDEntryTypes entryTypeGroup1;
entryTypeGroup1.set(MDEntryType('1'));
request.addGroup(entryTypeGroup0);
request.addGroup(entryTypeGroup1);
//InstrmtMDReqGrp组件
for (auto& i : vecSecurityID)
{
FIX44::MarketDataRequest::NoRelatedSym codeGroup;
codeGroup.set(SecurityID(i));
codeGroup.set(SecurityIDSource("8"));
request.addGroup(codeGroup);
}
Session::sendToTarget(request, m_sessionID);
}
// Generate string value used to populate the fields in each message
// which are used as a custom identifier
std::string FixApplication::NextRequestID()
{
if (m_requestID == 65535)
m_requestID = 1;
m_requestID++;
std::string next_ID = IntConvertor::convert(m_requestID);
return next_ID;
}
联系方式
qq:49042765
微信:easykline