编写国外期货合约报单程序,同样基于郑州易盛的sdk做二次开发,通过调用易盛的交易api完成交易报单。毕竟都是一家公司发布的sdk,所以交易api设计风格与行情api基本一致,调用逻辑也基本一致。编写代码时同样需要处理两方面内容,一个是调用方,即发出请求,另一个是回调方,即响应请求,这里不再赘述。整体上讲,交易开发过程稍微繁琐,要处理的东西多一些。主要用到的头文件为:EsForeignApiErrCode.h、EsForeignApiStruct.h、EsForeignApiType.h及EsunnyForeignApi.h,动态库为:libForeignTradeApi.so。下面是流程图及一些代码示例:
1. 创建易盛TapAPI实例:
IEsunnyTradeApi *pTradeApi = CreateEsunnyForeignTradeApi(szCertInfo, &result, szLogFilePath, szAppId);
if (pTradeApi == NULL)
{
LOG_INFO << "创建trade api实例失败,错误码:" << result;
return;
}
通过调用CreateEsunnyForeignTradeApi()创建api实例——pTradeApi,随后调用该实例发起各种请求,比如连接服务器、用户登录、报单、撤单、查询持仓、查询资金等等。
2. 创建TapAPI回调实例:
EsunnyTradeSpi *pTradeSpi = new EsunnyTradeSpi(pTradeApi, this);
这个需要自己编写相应实现类,该类需继承易盛提供的IEsunnyTradeSpi类。重写该类里面的方法,以处理易盛服务器发过来的各类数据。
3. 将上述两个实例关联起来,并发起连接服务器及用户登录:
pTradeApi->SetSpi(pTradeSpi);
pTradeSpi->connect(serverAddr, port, username, password);
连接服务器部分代码:
void EsunnyTradeSpi::connect(string serverAddr, uint16_t port, string username, string password)
{
// 保存登录信息
serverAddr_ = serverAddr;
port_ = port;
username_ = username;
password_ = password;
// 设置服务器IP、端口, 并发起连接
TEsAddressField addrField;
strcpy(addrField.Ip, serverAddr_.c_str());
addrField.Port = port_;
bool ret = (pTradeApi_ != NULL) ? pTradeApi_->Open(addrField) : false;
if (!ret)
{
LOG_INFO << username_ << " 请求: 连接服务器出错" << Err_Unknown;
}
}
请求发出后,OnOpen()会被回调以响应上述请求,可以在OnOpen()函数内编写用户登录逻辑,调用pTradeApi_->Login()发起登录后,OnLogin()及OnInitFinished()会依次响应上述请求,根据返回的信息,可以确定是否完成登录,以及API是否初始化完成。一旦出错,也会有相应错误码返回,方便查找问题。
void __cdecl EsunnyTradeSpi::OnOpen()
{
}
void __cdecl EsunnyTradeSpi::OnLogin(const TEsLoginRspField *rsp, int errCode, const int iReqID)
{
}
void __cdecl EsunnyTradeSpi::OnInitFinished(int errCode)
{
}
4. 期货报单:
报单逻辑应该是整个交易系统的核心部分了。报单主要涉及以下函数,其中以On开头的函数均继承自IEsunnyTradeSpi类,需要开发者重写:
(1)OrderInsert():报单请求
TEsOrderInsertReqField reqField;
memset(&reqField, 0, sizeof(TEsOrderInsertReqField));
......
......
pTradeApi_->OrderInsert(reqField, reqId);
报单参数很多,需要正确填写买卖/方向、开仓/平仓、市价/限价、委托数量、委托价格等等。这个需要查询文档及易盛提供的示例demo,填写完毕就可以调用OrderInsert()报单了。
(2)OnRspOrderInsert():报单请求应答
void __cdecl EsunnyTradeSpi::OnRspOrderInsert(const TEsOrderInsertRspField *rsp, int errCode, const int iReqID)
{
}
报单成功后,该函数就会被回调,会明确通知是否委托成功,便于开发者处理业务逻辑。
(3)OnRtnOrderState():委托变化通知
void __cdecl EsunnyTradeSpi::OnRtnOrderState(const TEsOrderStateNoticeField &rsp)
{
}
当委托状态发生变化时,会被回调。一般常见的委托状态主要有:正在排队、部分成交、完全成交,一次报单,如果数量比较多,一般不会一次全部成交,而是会分多批次成交,所以该函数会不断被回调。
(4)OnRtnMatchState():成交变化推送通知
void __cdecl EsunnyTradeSpi::OnRtnMatchState(const TEsMatchStateNoticeField &rsp)
{
}
感觉该函数与(5)有重复之嫌,所以我在处理时,直接在该函数内调用函数(5),没有编写太多逻辑。
(5)OnRtnMatchInfo():成交信息变化推送通知
void __cdecl EsunnyTradeSpi::OnRtnMatchInfo(const TEsMatchInfoNoticeField &rsp)
{
}
该函数比较重要,返回的每一条信息都是成交信息,里面包含成交量、成交价、成交费用等等,这些都是投资者关心的数据。总体来说,OnRsp开头的函数均为发起请求后的应答函数,OnRtn开头函数均为服务器主动推送信息函数。报单需要注意,易盛对报单频率做了限制,对于普通期货账户,报单频率为10单/秒,所以这里在实际编写代码时需要编写报单流控逻辑。
5. 查询期货账号持仓:
查询持仓主要调用pTradeApi_->QryHold(),按照文档说明填写合适的参数即可。查询持仓响应函数为:
void __cdecl EsunnyTradeSpi::OnQryHold(const TEsHoldQryRspField *rsp, TEsIsLastType islast, int errCode, const int iReqID)
{
}
该函数需要由开发者自己重写,比较麻烦的是持仓数据不会一次全部返回,而是一批一批返回,需要自己做一次汇总计算才行。
6. 查询期货账号资金:
查询资金主要调用pTradeApi_->QryMoney(),按照文档说明填写合适的参数即可。查询资金响应函数为:
void __cdecl EsunnyTradeSpi::OnQryMoney(const TEsMoneyQryRspField *rsp, TEsIsLastType islast, int errCode, const int iReqID)
{
}
同样,该函数需要开发者自己重写,这个比较简单,会一次返回帐户资金相关信息,如:可用资金、今资金、昨资金、手续费、帐户市值等。
编写完代码,如何测试程序呢?这一点还好,可以到易盛官网(http://www.esunny.com.cn/)注册交易测试账号,注册成功后账号内既有100万,这样就可以测试开仓及平仓了。上面只是些基本常用操作,其他操作则需要自己查阅api文档编写相应功能了。同样,实时交易程序必须高效而稳定,这样才能不错过任何行情,一旦出现交易信号,及时完成开仓或平仓,实现收益最大化。这里顺便提一下,易盛的外盘交易系统是有对冲机制的,比如同时报一手多单和一手空单,经过易盛的交易系统对冲后,实际是不会报单的。这样当多人同时使用同一账号报单时,实际自己也可以写对冲池先对冲一下,即同期货账号下的同品种多空对冲后在向易盛报单,这样可以进一步降低交易手续费。
参考资料: