Swig转换CTPAPI为Python攻略

手把手介绍了使用 swig 将 CTPAPI 从 C++ 转换为 Python 的详细过程

C++ CTPAPI 是上期技术提供一个交易接口,用于连接柜台进行交易。由于 C++ 本身的难度以及不便于进行一般性测试,得益于热心网友努力,出现了Python版本的 CTPAPI。

这里参考 景色 的文章,对这一流程在不同平台进行了整理,同时已将转换过程涉及到的额外文件上传至 github仓库gitee仓库。 同时也提供了也提供了更完善友好的 openctp-ctp 库以对接 CTPAPI 柜台, 欢迎使用。

Windows 平台转换

环境准备

编译前,需要准备好依赖的各种环境。 这是我的本地环境,仅供参考,也可以根据自己的本地环境做适当调整。

创建DLL项目

分别创建 _thosttraderapi_thostmduserapi 项目 vs1 vs2

PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        08/01/2024     14:30                _thostmduserapi
d-----        08/01/2024     10:46                _thosttraderapi

解压CTPAPI-6.7.0

解压后进入到路径 v6.7.0_20230209_winApi\traderapi\20230209_traderapi64_se_windows

PS E:\v6.7.0_20230209_winApi\traderapi\20230209_traderapi64_se_windows> dir
PS E:\projects\ctp-resources\6.7.0\v6.7.0_20230209_winApi\traderapi\20230209_traderapi64_se_windows> dir
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        28/06/2022     11:59            184 error.dtd
-a----        30/01/2023     14:42          21886 error.xml
-a----        09/02/2023     10:02           5906 ThostFtdcMdApi.h
-a----        09/02/2023     10:00          41046 ThostFtdcTraderApi.h
-a----        09/02/2023     10:02         267340 ThostFtdcUserApiDataType.h
-a----        09/02/2023     10:02         284938 ThostFtdcUserApiStruct.h
-a----        09/02/2023     10:04        2960384 thostmduserapi_se.dll
-a----        09/02/2023     10:04           3822 thostmduserapi_se.lib
-a----        09/02/2023     10:02        3346944 thosttraderapi_se.dll
-a----        09/02/2023     10:02           3960 thosttraderapi_se.lib

将头文件、库文件复制到 E:\projects\ctpapi2python-swig-VisualStudio\vs2022 路径下

PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
(base) PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        08/01/2024     10:46                _thosttraderapi
d-----        08/01/2024     14:30                _thostmduserapi
-a----        09/02/2023     10:02           5906 ThostFtdcMdApi.h
-a----        09/02/2023     10:00          41046 ThostFtdcTraderApi.h
-a----        09/02/2023     10:02         267340 ThostFtdcUserApiDataType.h
-a----        09/02/2023     10:02         284938 ThostFtdcUserApiStruct.h
-a----        09/02/2023     10:04        2960384 thostmduserapi_se.dll
-a----        09/02/2023     10:04           3822 thostmduserapi_se.lib
-a----        09/02/2023     10:02        3346944 thosttraderapi_se.dll
-a----        09/02/2023     10:02           3960 thosttraderapi_se.lib

添加 .i 文件

thosttraderapi.i thostmduserapi.i 文件也放到 E:\projects\ctpapi2python-swig-VisualStudio\vs2022 路径下

thosttraderapi.i
%module(directors="1") thosttraderapi
%{
#include "ThostFtdcTraderApi.h"
#include "iconv.h"
%}
%typemap(out) char[ANY], char[] {
if ($1) {
iconv_t cd = iconv_open("utf-8", "GBK");
if (cd != (iconv_t)(-1)) {
char buf[4096] = {};
char **in = &$1;
char *out = buf;
size_t inlen = strlen($1), outlen = 4096;

            if (iconv(cd, in, &inlen, &out, &outlen) != (size_t)(-1))
            {
                $result = SWIG_FromCharPtrAndSize(buf, sizeof buf - outlen);
            }
            iconv_close(cd);
        }
    }
}
%feature("python:annotations", "c");
%feature("director") CThostFtdcTraderSpi;
%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;

%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruct.h"
%include "ThostFtdcTraderApi.h" 
thostmduserapi.i
%module(directors="1") thostmduserapi
%{
#include "ThostFtdcMdApi.h"
#include "iconv.h"
%}

%feature("python:annotations", "c");
%feature("director") CThostFtdcMdSpi;

%typemap(out) char[ANY], char[] {
if ($1) {
iconv_t cd = iconv_open("utf-8", "GBK");
if (cd != (iconv_t)(-1)) {
char buf[4096] = {};
char **in = &$1;
char *out = buf;
size_t inlen = strlen($1), outlen = 4096;

            if (iconv(cd, in, &inlen, &out, &outlen) != (size_t)(-1))
            {
                $result = SWIG_FromCharPtrAndSize(buf, sizeof buf - outlen);
            }
            iconv_close(cd);
        }
    }
}

%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);
}

%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;

%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruct.h"
%include "ThostFtdcMdApi.h"
PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        08/01/2024     14:30                _thostmduserapi
d-----        08/01/2024     10:46                _thosttraderapi
-a----        09/02/2023     10:02           5906 ThostFtdcMdApi.h
-a----        09/02/2023     10:00          41046 ThostFtdcTraderApi.h
-a----        09/02/2023     10:02         267340 ThostFtdcUserApiDataType.h
-a----        09/02/2023     10:02         284938 ThostFtdcUserApiStruct.h
-a----        08/01/2024     14:25           1752 thostmduserapi.i
-a----        09/02/2023     10:04        2960384 thostmduserapi_se.dll
-a----        09/02/2023     10:04           3822 thostmduserapi_se.lib
-a----        08/01/2024     14:25           1110 thosttraderapi.i
-a----        09/02/2023     10:02        3346944 thosttraderapi_se.dll
-a----        09/02/2023     10:02           3960 thosttraderapi_se.lib

添加iconv库

Python3 统一使用 UTF-8 编码, CTPAPI使用 GBK 编码, 需要 libiconv 进行编码转换

PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        08/01/2024     14:30                _thostmduserapi
d-----        08/01/2024     10:46                _thosttraderapi
-a----        08/01/2024     10:34           9511 iconv.h
-a----        08/01/2024     10:34        1094144 iconv.dll
-a----        09/02/2023     10:02           5906 ThostFtdcMdApi.h
-a----        09/02/2023     10:00          41046 ThostFtdcTraderApi.h
-a----        09/02/2023     10:02         267340 ThostFtdcUserApiDataType.h
-a----        09/02/2023     10:02         284938 ThostFtdcUserApiStruct.h
-a----        08/01/2024     14:25           1752 thostmduserapi.i
-a----        09/02/2023     10:04        2960384 thostmduserapi_se.dll
-a----        09/02/2023     10:04           3822 thostmduserapi_se.lib
-a----        08/01/2024     14:25           1110 thosttraderapi.i
-a----        09/02/2023     10:02        3346944 thosttraderapi_se.dll
-a----        09/02/2023     10:02           3960 thosttraderapi_se.lib

swig根据 .i 文件生成 python接口文件和wrap文件

swig -threads -c++ -python thosttraderapi.i
swig -threads -c++ -python thostmduserapi.i

thosttraderapi 生成3个新文件:

thosttraderapi.py
thosttraderapi_wrap.h
thosttraderapi_wrap.cxx

thostmduserapi 生成3个新文件:

thostmduserapi.py
thostmduserapi_wrap.h
thostmduserapi_wrap.cxx
PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> dir
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        08/01/2024     14:30                _thostmduserapi
d-----        08/01/2024     10:46                _thosttraderapi
-a----        08/01/2024     10:34           9511 iconv.h
-a----        08/01/2024     10:34        1094144 iconv.dll
-a----        09/02/2023     10:02           5906 ThostFtdcMdApi.h
-a----        09/02/2023     10:00          41046 ThostFtdcTraderApi.h
-a----        09/02/2023     10:02         267340 ThostFtdcUserApiDataType.h
-a----        09/02/2023     10:02         284938 ThostFtdcUserApiStruct.h
-a----        08/01/2024     14:25           1752 thostmduserapi.i
-a----        08/01/2024     15:00        1188716 thostmduserapi.py
-a----        09/02/2023     10:04        2960384 thostmduserapi_se.dll
-a----        09/02/2023     10:04           3822 thostmduserapi_se.lib
-a----        08/01/2024     15:00       15030685 thostmduserapi_wrap.cxx
-a----        08/01/2024     15:00           3365 thostmduserapi_wrap.h
-a----        08/01/2024     14:25           1110 thosttraderapi.i
-a----        08/01/2024     15:00        1246410 thosttraderapi.py
-a----        09/02/2023     10:02        3346944 thosttraderapi_se.dll
-a----        09/02/2023     10:02           3960 thosttraderapi_se.lib
-a----        08/01/2024     15:00       15745002 thosttraderapi_wrap.cxx
-a----        08/01/2024     15:00          20855 thosttraderapi_wrap.h

_thosttraderapi 项目配置

  1. 添加头文件

    iconv.h
    ThostFtdcTraderApi.h
    ThostFtdcUserApiDataType.h
    ThostFtdcUserApiStruct.h
    thosttraderapi_wrap.cxx
    

    4

  2. 添加源文件 thosttraderapi_wrap.cxx 5

  3. 添加库文件 6

  4. 附加包含目录 8

  5. 附加库目录 9

  6. 附加依赖项 10

  7. 不使用预编译头 3

  8. 添加编译项 /bigobj 11

  9. 运行库 /MT 12

  10. 选择平台 Release x64 7

  11. 构建生成 dll 文件

    _thosttraderapi\x64\Release 路径下会生成一个 _thosttraderapi.dll 文件, 将 _thosttraderapi.dll 重命名为 _thosttraderapi.pyd 并移动至路径 E:\projects\ctpapi2python-swig-VisualStudio\vs2022

    已启动重新生成...
    1>------ 已启动全部重新生成: 项目: _thosttraderapi, 配置: Release x64 ------
    1>pch.cpp
    1>thosttraderapi_wrap.cxx
    1>E:\projects\ctpapi2python-swig-VisualStudio\vs2022\ThostFtdcUserApiDataType.h(3706,1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失
    1>E:\projects\ctpapi2python-swig-VisualStudio\vs2022\ThostFtdcUserApiDataType.h(4468,1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失
    1>E:\projects\ctpapi2python-swig-VisualStudio\vs2022\iconv.h(1,1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失
    1>dllmain.cpp
    1>  正在创建库 E:\projects\ctpapi2python-swig-VisualStudio\vs2022\_thosttraderapi\x64\Release\_thosttraderapi.lib 和对象 E:\projects\ctpapi2python-swig-VisualStudio\vs2022\_thosttraderapi\x64\Release\_thosttraderapi.exp
    1>正在生成代码
    1>Previous IPDB not found, fall back to full compilation.
    1>All 11713 functions were compiled because no usable IPDB/IOBJ from previous compilation was found.
    1>已完成代码的生成
    1>_thosttraderapi.vcxproj -> E:\projects\ctpapi2python-swig-VisualStudio\vs2022\_thosttraderapi\x64\Release\_thosttraderapi.dll
    1>已完成生成项目“_thosttraderapi.vcxproj”的操作。
    ========== “全部重新生成”: 1 成功,0 失败,0已跳过 ==========
    ========= 重新生成 开始于 20:23,并花费了 16.648 秒 ==========
    

_thostmduserapi 项目配置

_thosttraderapi 的配置逻辑一致, 区别如下:

  1. 添加头文件
    iconv.h
    ThostFtdcTraderApi.h
    ThostFtdcUserApiDataType.h
    ThostFtdcUserApiStruct.h
    thostmduserapi_wrap.cxx
    
  2. 添加源文件 thostmduserapi_wrap.cxx
  3. 构建生成 dll 文件 在 _thostmduserapi\x64\Release 路径下会生成一个 _thostmduserapi.dll 文件, 将 _thostmduserapi.dll 重命名为 _thostmduserapi.pyd 并移动至路径 E:\projects\ctpapi2python-swig-VisualStudio\vs2022

测试交易、行情接口

交易接口、行情接口 前置地址,参考 openctp监控 SimNow

  1. 交易接口

    测试需要用到文件:

    demo_td.py
    iconv.dll
    charset.dll
    msvcp140.dll
    thosttraderapi_se.dll
    _thosttraderapi.pyd
    thosttraderapi.py
    
    demo_td.py
    import sys
    import time
    import os
    
    import thosttraderapi as tdapi
    
    # 前置地址 参考 http://openctp.cn  SimNow
    FrontAddr = ""
    
    # 登录信息 需要在 SimNow https://www.simnow.com.cn/ 平台注册账号
    userid = ""
    password = ""
    brokerid = "9999"
    authcode = "0000000000000000"
    appid = "simnow_client_test"
    
    
    class CTdSpiImpl(tdapi.CThostFtdcTraderSpi):
    """交易回调实现类"""
    
        def __init__(self):
            super().__init__()
    
            flow_path = os.path.join(os.getcwd(), "flow_logs", userid)
            if not os.path.exists(flow_path):
                os.makedirs(flow_path)
            flow_path = os.path.join(flow_path, userid)
            self._api: tdapi.CThostFtdcTraderApi = (
                tdapi.CThostFtdcTraderApi.CreateFtdcTraderApi(flow_path)
            )
    
            print("CTP交易API版本号:", self._api.GetApiVersion())
    
            # 注册交易前置
            self._api.RegisterFront(FrontAddr)
            # 注册交易回调实例
            self._api.RegisterSpi(self)
            # 订阅私有流
            self._api.SubscribePrivateTopic(tdapi.THOST_TERT_QUICK)
            # 订阅公有流
            self._api.SubscribePublicTopic(tdapi.THOST_TERT_QUICK)
            # 初始化交易实例
            self._api.Init()
    
        def OnFrontConnected(self):
            """交易前置连接成功"""
            print("交易前置连接成功")
            self.authenticate()
    
        def OnFrontDisconnected(self, nReason: int):
            """交易前置连接断开"""
            print("交易前置连接断开: nReason=", nReason)
    
        def authenticate(self):
            """认证 demo"""
            print("> 认证请求")
            _req = tdapi.CThostFtdcReqAuthenticateField()
            _req.BrokerID = brokerid
            _req.UserID = userid
            _req.AppID = appid
            _req.AuthCode = authcode
            self._api.ReqAuthenticate(_req, 0)
    
        def OnRspAuthenticate(
            self,
            pRspAuthenticateField: tdapi.CThostFtdcRspAuthenticateField,
            pRspInfo: tdapi.CThostFtdcRspInfoField,
            nRequestID: int,
            bIsLast: bool,
        ):
            """客户端认证响应"""
            if pRspInfo:
                print("认证响应", pRspInfo.ErrorID, pRspInfo.ErrorMsg)
            if pRspInfo and pRspInfo.ErrorID != 0:
                return
    
            # 登录
            self.login()
    
        def login(self):
            """登录 demo"""
            print("> 登录请求")
    
            _req = tdapi.CThostFtdcReqUserLoginField()
            _req.BrokerID = brokerid
            _req.UserID = userid
            _req.Password = password
            if sys.platform == "darwin":
                self._api.ReqUserLogin(_req, 0, 0, "")
            else:
                self._api.ReqUserLogin(_req, 0)
    
        def OnRspUserLogin(
            self,
            pRspUserLogin: tdapi.CThostFtdcRspUserLoginField,
            pRspInfo: tdapi.CThostFtdcRspInfoField,
            nRequestID: int,
            bIsLast: bool,
        ):
            """登录响应"""
            if pRspInfo:
                print("登录响应", pRspInfo.ErrorID, pRspInfo.ErrorMsg)
    
    
    if __name__ == "__main__":
        if len(sys.argv) != 4:
            print("Usage:    python demo_td <frontaddr> <userid> <password>")
            exit(-1)
    
        FrontAddr = sys.argv[1]
        userid = sys.argv[2]
        password = sys.argv[3]
    
        spi = CTdSpiImpl()
        time.sleep(1)
        input("################# 按任意键退出 \n")
    

    因为使用 Python3.10 编译的,所以使用 Python3.10 测试。在路径PS E:\projects\ctpapi2python-swig-VisualStudio\vs2022> 下执行以下命令, userid/password 是在 simnow 平台注册的账号、密码

    > python demo_td.py tcp://180.168.146.187:10130 <userid> <password>
    CTP交易API版本号: v6.7.0_20230209  9:52:16.3535
    Connect to 180.168.146.187:10130
    Session -312410111 Connected
    交易前置连接成功
    > 认证请求
    认证响应 0 正确
    > 登录请求
    登录响应 0 正确
    ################# 按任意键退出
    
  2. 行情接口

    测试需要用到文件:

    demo_md.py
    iconv.dll
    charset.dll
    msvcp140.dll
    thostmduserapi_se.dll
    _thostmduserapi.pyd
    thosttmduserapi.py
    
    demo_md.py
    """
    行情API demo
    
    注意选择有效合约, 没有行情可能是过期合约或者不再交易时间内导致
    """
    
    import inspect
    import os
    import sys
    
    import thostmduserapi as mdapi
    
    # 前置地址 参考 http://openctp.cn  SimNow
    FrontAddr = ""
    # 注意选择有效合约, 没有行情可能是过期合约或者不在交易时间内的原因
    # instruments = ["AP410"]
    
    
    class CMdSpiImpl(mdapi.CThostFtdcMdSpi):
        def __init__(self):
            super().__init__()
    
            flow_path = os.path.join(os.getcwd(), "flow_logs", 'market')
            if not os.path.exists(flow_path):
                os.makedirs(flow_path)
            flow_path = os.path.join(flow_path, 'market')
    
            self._api = mdapi.CThostFtdcMdApi.CreateFtdcMdApi(flow_path)
    
            print("CTP行情API版本号:", self._api.GetApiVersion())
    
            # 注册行情前置
            self._api.RegisterFront(FrontAddr)
            # 注册行情回调实例
            self._api.RegisterSpi(self)
            # 初始化行情实例
            self._api.Init()
            print("初始化成功")
    
        def OnFrontConnected(self):
            """行情前置连接成功"""
            print("行情前置连接成功")
    
            # 登录请求, 行情登录不进行信息校验
            print("登录请求")
            req = mdapi.CThostFtdcReqUserLoginField()
            self._api.ReqUserLogin(req, 0)
    
        def OnRspUserLogin(
                self,
                pRspUserLogin: mdapi.CThostFtdcRspUserLoginField,
                pRspInfo: mdapi.CThostFtdcRspInfoField,
                nRequestID: int,
                bIsLast: bool,
        ):
            """登录响应"""
            if pRspInfo and pRspInfo.ErrorID != 0:
                print(
                    f"登录失败: ErrorID={pRspInfo.ErrorID}, ErrorMsg={pRspInfo.ErrorMsg}"
                )
                return
    
            print("登录成功")
    
            if len(instruments) == 0:
                return
    
            # 订阅行情
            print("订阅行情请求:", instruments)
            self._api.SubscribeMarketData(
                [i.encode("utf-8") for i in instruments], len(instruments)
            )
    
        def OnRtnDepthMarketData(
                self, pDepthMarketData: mdapi.CThostFtdcDepthMarketDataField
        ):
            """深度行情通知"""
            params = []
            for name, value in inspect.getmembers(pDepthMarketData):
                if name[0].isupper():
                    params.append(f"{name}={value}")
            print("深度行情通知:", ",".join(params))
    
        def OnRspSubMarketData(
                self,
                pSpecificInstrument: mdapi.CThostFtdcSpecificInstrumentField,
                pRspInfo: mdapi.CThostFtdcRspInfoField,
                nRequestID: int,
                bIsLast: bool,
        ):
            """订阅行情响应"""
            if pRspInfo:
                print("订阅行情", pRspInfo.ErrorID, pRspInfo.ErrorMsg)
                return
    
    
    if __name__ == "__main__":
        if len(sys.argv) < 3:
            print("Usage:    python demo_td <frontaddr> <instrument_id1> <instrument_id2> ...")
            exit(-1)
    
        FrontAddr = sys.argv[1]
        # 注意选择有效合约, 没有行情可能是过期合约或者不在交易时间内的原因
        instruments = sys.argv[2:]
        spi = CMdSpiImpl()
        input("############# 按任意键退出 \n")
    

    执行如下命令, 命令后可跟多个有效合约:

    > python .\demo_md.py tcp://180.168.146.187:10131 AP401
     登录请求
     登录成功
     订阅行情请求 ['AP401']
     订阅行情 0 CTP:No Error
     深度行情通知: ActionDay=20240104,AskPrice1=8650.0,AskPrice2=1.7976931348623157e+308,AskPrice3=1.7976931348623157e+308,AskPrice4=1.7976931348623157e+308,AskPrice5=1.7976931348623157e+308,AskVolume1=1,AskVolume2=0,AskVolume3=0,AskVolume4=0,AskVolume5=0,AveragePrice=8600.0,BandingLowerPrice=0.0,BandingUpperPrice=0.0,BidPrice1=8600.0,BidPrice2=1.7976931348623157e+308,BidPrice3=1.7976931348623157e+308,BidPrice4=1.7976931348623157e+308,BidPrice5=1.7976931348623157e+308,BidVolume1=7,BidVolume2=0,BidVolume3=0,BidVolume4=0,BidVolume5=0,ClosePrice=1.7976931348623157e+308,CurrDelta=1.7976931348623157e+308,ExchangeID=,ExchangeInstID=,HighestPrice=8601.0,InstrumentID=AP401,LastPrice=8600.0,LowerLimitPrice=7745.0,LowestPrice=8600.0,OpenInterest=229.0,OpenPrice=8600.0,PreClosePrice=8557.0,PreDelta=0.0,PreOpenInterest=234.0,PreSettlementPrice=8606.0,SettlementPrice=1.7976931348623157e+308,TradingDay=20240105,Turnover=232200.0,UpdateMillisec=0,UpdateTime=21:39:43,UpperLimitPrice=9467.0,Volume=27 
    

以上就是使用 VisualStudio 2022 IDE 通过 swig 将 CTPAPI,装换为 python 的整个流程了。如果是使用其他版本的 VisualStudio 或 构建目标为 win32 平台,逻辑是一样的,只要参照以上,同时将相应库改为win32的即可。

Linux 平台转换

参考环境

  • Debian 12.4
  • g++ 12.2.0
  • swig 4.1.0
  • ctpapi 6.7.2

下载解压

下载地址 解压后得到 linux64 的头文件、库文件,如下:

$ ls
thostmduserapi_se.so  ThostFtdcMdApi.h      ThostFtdcUserApiDataType.h  
thosttraderapi_se.so  ThostFtdcTraderApi.h  ThostFtdcUserApiStruct.h    

将 so 文件重命名为 libthostmduserapi_se.so / libthosttraderapi_se.so

添加 .i文件

添加 thostmduserapi.i / thosttraderapi.i 文件:

thosttraderapi.i
%module(directors="1") thosttraderapi
%{
#include "ThostFtdcTraderApi.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 %}
%typemap(out) char[ANY], char[] {
    const std::string &gbk($1);
    std::vector<wchar_t> wstr(gbk.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, gbk.data(), gbk.data() + gbk.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());
    }
}
%feature("python:annotations", "c");
%feature("director") CThostFtdcTraderSpi;
%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;

%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruCT.H"
%INCLUDE "THOSTFTDCTRADERAPI.H"
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("python:annotations", "c");
%feature("director") CThostFtdcMdSpi;

%typemap(out) char[ANY], char[] {
    const std::string &gbk($1);
    std::vector<wchar_t> wstr(gbk.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,
            gbk.data(), gbk.data() + gbk.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);
}

%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;

%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruct.h"
%include "ThostFtdcMdApi.h"

文件如下:

$ ls
libthostmduserapi_se.so  ThostFtdcMdApi.h      ThostFtdcUserApiDataType.h  thostmduserapi.i
libthosttraderapi_se.so  ThostFtdcTraderApi.h  ThostFtdcUserApiStruct.h    thosttraderapi.i

swig根据 .i 文件生成 python接口文件和wrap文件

swig -threads -c++ -python thostmduserapi.i
swig -threads -c++ -python thosttraderapi.i

生成如下文件:

thosttraderapi.py
thostmduserapi.py
thosttraderapi_wrap.h
thosttraderapi_wrap.cxx
thostmduserapi_wrap.h
thostmduserapi_wrap.cxx

构建编译

先创建 makefile 文件

makefile_traderapi
OBJS=thosttraderapi_wrap.o
INCLUDE=-I./ -I/root/.pyenv/versions/3.7.17/include/python3.7m/
TARGET=_thosttraderapi.so
CPPFLAG=-shared -fPIC
CC=g++
LDLIB=-L. -lthosttraderapi_se
$(TARGET) : $(OBJS)
        $(CC) $(CPPFLAG) -Wl,-enable-new-dtags,-rpath,./ $(INCLUDE) -o $(TARGET) $(OBJS) $(LDLIB)
$(OBJS) : %.o : %.cxx
        $(CC) -c -fPIC $(INCLUDE) $< -o $@
clean:
        rm -f $(OBJS)
        -rm -f $(TARGET)
makefile_mduserapi
OBJS=thostmduserapi_wrap.o
INCLUDE=-I./ -I/root/.pyenv/versions/3.7.17/include/python3.7m/
TARGET=_thostmduserapi.so
CPPFLAG=-shared -fPIC
CC=g++
LDLIB=-L. -lthostmduserapi_se
$(TARGET) : $(OBJS)
        $(CC) $(CPPFLAG) -Wl,-enable-new-dtags,-rpath,./ $(INCLUDE) -o $(TARGET) $(OBJS) $(LDLIB)
$(OBJS) : %.o : %.cxx
        $(CC) -c -fPIC $(INCLUDE) $< -o $@
clean:
        -rm -f $(OBJS)
        -rm -f $(TARGET)

注意makefile文件中的python包含路径/root/.pyenv/versions/3.7.17/include/python3.7m/要换成自己的

执行编译命令:

make --file=makefile_traderapi
make --file=makefile_mduserapi

会得到两个动态库: _thosttraderapi.so / _thostmduserapi.so

测试

参考windows中的测试文件和命令

MacOS 平台转换

待续…

注:swig 转换逻辑参考 景色的文章