前言
目前上期技术CTP系统提供的API版本是C++
版本,本文主要介绍Windows 64位平台下利用Swig工具将CTP C++接口mduser API转换为Python可调用的接口文件。
0. 欢迎交流
QQ群:446042777(澄明期货研究)
1. 准备工作
和上文CTP Python API 利用Swig 封装Windows版(traderapi)-CSDN博客一致。此版本是在原先版本上的升级,解决了onfrontconnected回调的宕机问题。
另外关于SubscribeMarketData这个函数的C++二级指针也找到了更好的方法解决;还有解决了CTP返回中文字符的问题。
本文使用的环境:CTP v6.7.8_20240918、Swig swigwin-
4.3.0版本、Python 3.12.9、Visual Studio 2022
2. 通过Swig得到python接口文件
基础环境参考上篇traderapi文章
新建文件thostmduserapi.i
,内容如下
%module(directors="1") thostmduserapi
%{
#include "ThostFtdcMdApi.h"
#include <codecvt>
#include <locale>
#include <vector>
#include <string>
using namespace std;
#ifdef _MSC_VER
const static locale g_loc("zh-CN");
#else
const static locale g_loc("zh_CN.GB18030");
#endif
%}
%feature("director") CThostFtdcMdSpi;
%ignore THOST_FTDC_VTC_BankBankToFuture;
%ignore THOST_FTDC_VTC_BankFutureToBank;
%ignore THOST_FTDC_VTC_FutureBankToFuture;
%ignore THOST_FTDC_VTC_FutureFutureToBank;
%ignore THOST_FTDC_FTC_BankLaunchBankToBroker;
%ignore THOST_FTDC_FTC_BrokerLaunchBankToBroker;
%ignore THOST_FTDC_FTC_BankLaunchBrokerToBank;
%ignore THOST_FTDC_FTC_BrokerLaunchBrokerToBank;
%typemap(out) char[ANY], char[] {
const std::string &gb2312($1);
std::vector<wchar_t> wstr(gb2312.size());
wchar_t* wstrEnd = nullptr;
const char* gbEnd = nullptr;
mbstate_t state = {};
int res = use_facet<codecvt<wchar_t, char, mbstate_t> >
(g_loc).in(state,
gb2312.data(), gb2312.data() + gb2312.size(), gbEnd,
wstr.data(), wstr.data() + wstr.size(), wstrEnd);
if (codecvt_base::ok == res)
{
wstring_convert<codecvt_utf8<wchar_t>> cutf8;
std::string result = cutf8.to_bytes(wstring(wstr.data(), wstrEnd));
resultobj = SWIG_FromCharPtrAndSize(result.c_str(), result.size());
}
else
{
std::string result;
resultobj = SWIG_FromCharPtrAndSize(result.c_str(), result.size());
}
}
%typemap(in) char *[] {
/* Check if is a list */
if (PyList_Check($input)) {
int size = PyList_Size($input);
int i = 0;
$1 = (char **) malloc((size+1)*sizeof(char *));
for (i = 0; i < size; i++) {
PyObject *o = PyList_GetItem($input, i);
if (PyString_Check(o)) {
$1[i] = PyString_AsString(PyList_GetItem($input, i));
} else {
free($1);
PyErr_SetString(PyExc_TypeError, "list must contain strings");
SWIG_fail;
}
}
$1[i] = 0;
} else {
PyErr_SetString(PyExc_TypeError, "not a list");
SWIG_fail;
}
}
// This cleans up the char ** array we malloc'd before the function call
%typemap(freearg) char ** {
free((char *) $1);
}
%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruct.h"
%include "ThostFtdcMdApi.h"
typemap函数就是将python list转化为c++的二级指针。
在cmd
中切换到当前文件夹下,运行命令:
swig -threads -c++ -python thostmduserapi.i
更多swig命令用法请在这里查看swig文档地址
等到运行完成后,可以看到当前目录下生成了
thostmduserapi_wrap.h
thostmduserapi_wrap.cxx
thostmduserapi.py
常见问题:
和mduserapi文章中一样,默认运行 swig -threads -c++ -python thostmduserapi.i 后可能会出现以下警告:
ThostFtdcMdApi.h(30) : Warning 514: Director base class CThostFtdcMdSpi has no virtual destructor.
问题原因:
警告 514 表示基类 CThostFtdcMdSpi
没有虚析构函数(virtual destructor)。在 C++ 中,如果一个类被设计为基类并可能被继承,通常需要将析构函数声明为虚的,以确保派生类对象在销毁时能够正确调用基类的析构函数。
解决方法
直接忽略警告或使用 SWIG 的 %ignore
根据网上结果,许多开发者在封装 CTP API 时也遇到了类似的警告,但最终程序仍然可以正常运行。
或者修改SWIG 的接口文件(.i
文件),可以在接口文件中使用 %ignore
来处理警告。在 .i
文件中添加以下内容:
%ignore CThostFtdcMdSpi::~CThostFtdcMdSpi;
这会告诉 SWIG 忽略 CThostFtdcMdSpi
的析构函数,从而避免警告。
3. 通过C++得到python可调用的pyd动态库
与此文CTP Python API 利用Swig 封装Windows版(traderapi)-CSDN博客中一致。
在Visual Studio中建立一个C++工程,建工程的步骤可参考CTP Windows版C++工程建立和编译(traderapi)-CSDN博客这篇文章,需要注意几点:1)工程选择dll类型,2)运行库选多线程(/MT)。然后将如下文件拷贝到工程文件夹下:
ThostFtdcMdApi.h
ThostFtdcUserApiDataType.h
ThostFtdcUserApiStruct.h
thostmduserapi_se.lib
thostmduserapi_wrap.cxx
thostmduserapi_wrap.h
项目目录及编译成功后如下图所示:
编译出dll文件位置如下图所示:
4. Python Demo
新建文件mduserapi_demo.py,注意文件同目录底下要有如下三个文件:
thostmduserapi.py
thostmduserapi.dll
_thostmduserapi.pyd
要注意demo中一点,SubscribeMarketData函数的参数1是python list,在list的每个合约前记得加’b’。详见demo。这个demo只需要改个CTP行情前置的地址就可以运行。订阅合约改成自己想要订阅的合约。
本demo实现登录成功后订阅两个合约行情的功能。完整的demo代码如下:
# -*- coding: utf-8 -*-
import thostmduserapi as mdapi
class CFtdcMdSpi(mdapi.CThostFtdcMdSpi):
tapi=''
def __init__(self,tapi):
mdapi.CThostFtdcMdSpi.__init__(self)
self.tapi=tapi
def OnFrontConnected(self):
print ("OnFrontConnected")
loginfield = mdapi.CThostFtdcReqUserLoginField()
loginfield.BrokerID="9999"
loginfield.UserID="000001"
loginfield.Password="123456"
loginfield.UserProductInfo="python dll"
self.tapi.ReqUserLogin(loginfield,0)
def OnRspUserLogin(self, *args):
print ("OnRspUserLogin")
rsploginfield=args[0]
rspinfofield=args[1]
print ("SessionID=",rsploginfield.SessionID)
print ("ErrorID=",rspinfofield.ErrorID)
print ("ErrorMsg=",rspinfofield.ErrorMsg)
ret=self.tapi.SubscribeMarketData([b"ru2505",b"rb2505"],2)
def OnRtnDepthMarketData(self, *args):
print ("OnRtnDepthMarketData")
field=args[0]
print ("InstrumentID=",field.InstrumentID)
print ("LastPrice=",field.LastPrice)
def OnRspSubMarketData(self, *args):
print ("OnRspSubMarketData")
field=args[0]
print ("InstrumentID=",field.InstrumentID)
rspinfofield=args[1]
print ("ErrorID=",rspinfofield.ErrorID)
print ("ErrorMsg=",rspinfofield.ErrorMsg)
def main():
mduserapi=mdapi.CThostFtdcMdApi_CreateFtdcMdApi()
mduserspi=CFtdcMdSpi(mduserapi)
mduserapi.RegisterFront("tcp://180.168.146.187:10031")
mduserapi.RegisterSpi(mduserspi)
mduserapi.Init()
mduserapi.Join()
if __name__ == '__main__':
main()
5. 常见问题
参考上篇文章CTP Python API 利用Swig 封装Windows版(traderapi)文末
6. 其他
thostmduserapi.py中添加2个方法
def CThostFtdcMdApi_CreateFtdcMdApi(*args) -> "CThostFtdcMdApi *":
return _thostmduserapi.CThostFtdcMdApi_CreateFtdcMdApi(*args)
def CThostFtdcMdApi_GetApiVersion() -> "char const *":
return _thostmduserapi.CThostFtdcMdApi_GetApiVersion()
7.编译好的文件参考:
网速不好可访问下方国内地址:
声明:
本API仅是个人爱好编译,仅为用户提供交易工具参考,对此API引起的你的任何损失不负责任,你应自行对交易工具负责,本API不对用户的交易结果承担任何责任,在任何情况下,不对因技术问题导致的用户损失承担责任。
上篇文章:
CTP Python API 利用Swig 封装Windows版(traderapi)_ctp python swig-CSDN博客
————————————————
感谢景色大佬,参考文章:
CTP Python API及Demo(利用Swig 封装)Windows版(mduserapi)_thostmduserapi-CSDN博客