一篇搞定C++对接LMAX行情

在这里插入图片描述


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰雪积木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值