当前位置:   article > 正文

CTP_将C++封装为Python可调用接口_ctp python封装

ctp python封装

目录

写在前面:

前置准备:

step 1 与上期所原始代码对比分析源码

 td源码

1 配置属性-》常规-》配置类型 要为 “动态库(.dll)”

 2 VC++目录 -》包含目录

 3 VC++目录 -》 库目录

 4 链接器-》常规-》附加库目录

 5 链接器-》输入-》附加依赖项

vnctp.h 的功能

vnctptd.h 分析

vnctptd.cpp 分析

md源码

step 2 在Visual Studio 中生成项目

step 3 通过setup.py生成pyd文件

在Python中使用

 CtpMdApi类

 CtpTdApi类

执行


写在前面:

1 我最开始是用swig直接对对上期技术的C++包进行处理,处理成python可调用的版本后,行情和交易可以正常连接和登录,但当我使用交易服务器查询合约,等待回调函数时就崩溃提示异常退出 -1073740791 (0xC0000409),而且没有任何错误信息。经过反复重编译,.i文件的修改都无法解决此问题,只能放弃使用swig的方法,转而借鉴VeihgNa Studio的接口。

2 VeihgNa Studio的CTP接口是使用的pybind11和setuptools封装成python可用的包

3 本文直接通过 VeihgNa Studio 的源码记录转换过程

前置准备:

1 安装Visual Studio 2022,安装前选择组件要包含C++动态链接库的模板

2 安装python3,本文安装的版本为python3.7.1

step 1 与上期所原始代码对比分析源码

下载 VeihgNa Studio 的CTP源码 GitHub - vnpy/vnpy_ctp: VeighNa框架的CTP交易接口

下载后解压,解压后文件目录如下:

 在 vnpy_ctp/api/vnctp/ 目录下有C++项目

 右键vnctp.sln , 在弹出菜单中选择打开方式为 Visual Studio 2022

直接按“确定” 

 td源码

vnctptd项目工程文件结构

上期提供的ThostFtdcMdApi.h、ThostFtdcTraderApi.h 、ThostFtdcUserApiDataType.h、ThostFtdcUserApiStruct.h 在【头文件】中

vnctp.h和vnctptd.h 是 VeihgNa Studio 作者写的,以下分析这两个头文件

双击vnctp.h, 会发现 const dict &d 提示错误

这个问题是由于我们只是拷贝了项目代码,项目代码的依赖项没有变更,以下处理这个问题:

右边选中 vnctptd 项目,右键-》属性

1 配置属性-》常规-》配置类型 要为 “动态库(.dll)”

 2 VC++目录 -》包含目录

修改前

 1) D:\veighna_studio\include   这个修改为python安装目录下的include目录,本文python3.7.1对应的目录为 D:\soft\python371\include

2)其他三项不用修改,我们下载的源码包里已经包含

 修改后

 3 VC++目录 -》 库目录

修改前

1)  D:\veighna_studio\libs  修改为python安装目录下的libs文件夹目录

2)..\libs 不用变,下载的源码里有包含这个文件夹

修改后

 4 链接器-》常规-》附加库目录

修改前

 1) D:\veighna_studio\libs  修改为python安装目录下的libs文件夹路径

2) ..\libs 不用修改

修改后

 5 链接器-》输入-》附加依赖项

这个一般不用修改,但最好检查下

6 C/C++ -》预编译投 -》预编译头 -》不使用预编译头

 修改完成后,点击“应用”,“确定”

 代码就没有提示错误了

现在继续分析源码,打开vnctp.h

vnctp.h 的功能

1)处理字符编码,将非UTF-8转UTF-8

2)  处理参数类型

3)定义一个任务队列,任务队列主要是为了避免在与交易所交互过程中我方耽搁太长时间导致我方程序崩溃

vnctptd.h 分析

1)分为两大部分,一部分是常量,将每个回调方法名分别定义一个常量值;一部分是继承 CThostFtdcTraderSpi的类TdApi

2) TdApi 实现Spi中所有回调函数方法(以On开头)、每个回调函数对应的任务方法(以process开头)、定义在python中调用的回调函数方法(以on开头)、实现Api中所有请求函数方法

vnctptd.cpp 分析

上面vnctptd.h分析中,TdApi分为四部分,在.cpp中四部分分别取其中一个函数为例解说

1)Spi中的回调函数,以OnRspUserLogin为例

创建一个任务Task

将方法名对应的常数 ONRSPUSERLOGIN复制给任务名称 task_name

检查返回结果,如果登录成功,将返回的数据放入 task_data

如果登录失败,将失败信息放入 task_error

将任务推进队列

2) 每个回调函数对应的任务方法,以 processRspUserLogin 为例

将返回的数据或错误信息转换成python能识别的字典dict

调用 onRspUserLogin 

3) 定义在python中调用的回调函数方法

 通过pybind11将onRspUserLogin封装为python可调用的方法

4)实现Api中所有请求函数方法

通过pybind11将每个Api函数封装为python可调用的方法

md源码

先处理错误提示,与td源码中的处理方法一致,这里不再赘述。

vnctpmd.h 和 vnctpmd.cpp 的逻辑和td源码的逻辑相同,这里也同样不再赘述

step 2 在Visual Studio 中生成项目

在Visual Studio生成项目的目的主要是看是否会有报错,有报错的话在VS中分析起来比较方便,如果生成项目没问题,就可以进行step 3

以 vnctptd 项目为例讲述

选中 vnctptd 项目,生成-》生成vnctptd

 生成没有问题。

PS: 在最开始的时候,我生成是有报错的,这里罗列下,以便遇到同样问题的同学可以参考

1)pybind11 报错声明重复,通过更换项目里的 pybind11相关文件修正。https://github.com/pybind/pybind11

 下载解压后在 include 文件夹下有一个pybind11文件夹,直接将这个文件夹替换项目中的 pybind11的文件夹

2)error C2039: "ReqOptionSelfCloseInsert": 不是 "CThostFtdcTraderApi" 的成员

检查了各种可能的情况,看到说可能和中文注释有关,我就在 /// 与 注释内容 之间加了一个空格,再生成,就OK了

step 3 通过setup.py生成pyd文件

在下载的源码跟目录下,有一个setup.py文件

打开电脑的 cmd , 将目录转到 setup.py 所在目录下

在该目录下输入命令  python setup.py build

注意:本人电脑中的python就是执行的python3,如果你的电脑同时有python2和python3又没做设置,那你应该是python3 setup.py build

这个命令执行过程大概一两分钟,过程会输出很多警告,只要不是error,其他都可以不管

 pyd创建完毕了

在项目根目录会有一个build文件夹,文件夹里有一个lib.win-amd64-3.7的文件夹,如果使用的电脑环境和python版本不一样,这个文件夹名字会对应你电脑的环境。在lib.win-amd64-3.7里的vnpy_ctp就是我们要用的python包,将整个vnpy_ctp拷贝到要使用的Python项目就可以使用CTP接口

在Python中使用

直接使用里面的 TdApi和MdApi

1) 将gateway删除

2)将__init__.py清空

创建一个python项目ctp

将vnpy_ctp拷贝到项目ctp目录下

在ctp目录下创建一个test_api.py文件

在test_api.py文件中创建 CtpMdApi类和CtpTdApi(TdApi)类,这两个类分别继承MdApi和TdApi,这两个类基本上是借鉴了VeihgNa Studio里的代码

注意:这里导入TdApi和TdApi 提示错误,不用理会,程序能正常运行

 CtpMdApi类

  1. class CtpMdApi(MdApi):
  2. def __init__(self)->None:
  3. super().__init__()
  4. self.reqid: int = 0
  5. self.connect_status:bool = False
  6. self.login_status:bool = False
  7. self.subscribed: set = set()
  8. self.userid: str = ""
  9. self.password: str = ""
  10. self.brokerid: str = ""
  11. self.current_date: str = date_tools.res_today_str()
  12. pass
  13. def connect(self, address: str, userid: str, password: str, brokerid: str)->None:
  14. self.userid = userid
  15. self.password = password
  16. self.brokerid = brokerid
  17. if not self.connect_status:
  18. path: Path = get_folder_path(self.gateway_name.lower())
  19. self.createFtdcMdApi((str(path) + "\\Md").encode("GBK"))
  20. self.registerFront(address)
  21. self.init()
  22. self.connect_status = True
  23. pass
  24. def login(self) -> None:
  25. ctp_req:dict = {
  26. "UserID": self.userid,
  27. "Password": self.password,
  28. "BrokerID": self.brokerid
  29. }
  30. self.reqid += 1
  31. self.reqUserLogin(ctp_req,self.reqid)
  32. pass
  33. def subscribe(self,req:dict):
  34. if self.login_status:
  35. self.subscribeMarketData(req['symbol'])
  36. self.subscribed.add(req['symbol'])
  37. def close(self)->None:
  38. if self.connect_status:
  39. self.exit()
  40. def update_date(self)->None:
  41. self.current_date = date_tools.res_today_str()
  42. def onFrontConnected(self)->None:
  43. self.login()
  44. pass
  45. def onFrontDisconnected(self,reason:int)->None:
  46. self.login_status = False
  47. def onRspUserLogin(self,data:dict,error:dict,reqid:int,last:bool)->None:
  48. if not error['ErrorID']:
  49. self.login_status = True
  50. for symbol in self.subscribed:
  51. self.subscribeMarketData(symbol)
  52. else:
  53. print(f"行情服务器登录失败。{error['ErrorID']}.{error['ErrorMsg']}")
  54. pass
  55. def onRspError(self, error: dict, reqid: int, last: bool)->None:
  56. print('行情接口报错。',error['ErrorID'],error['ErrorMsg'])
  57. pass
  58. def onRspSubMarketData(self, data: dict, error: dict, reqid: int, last: bool)->None:
  59. if not error or not error['ErrorID']:
  60. return
  61. print('行情订阅失败。',error['ErrorID'],error['ErrorMsg'])
  62. def onRtnDepthMarketData(self,data:dict)->None:
  63. if not data['UpdateTime']:
  64. return
  65. print('tick返回',data['InstrumentID'],data['LastPrice'])
  66. pass

 CtpTdApi类

  1. class CtpTdApi(TdApi):
  2. def __init__(self)->None:
  3. super().__init__()
  4. self.reqid: int = 0
  5. self.order_ref: int = 0
  6. self.connect_status: bool = False
  7. self.login_status: bool = False
  8. self.auth_status: bool = False
  9. self.login_failed: bool = False
  10. self.auth_failed: bool = False
  11. self.contract_inited: bool = False
  12. self.userid: str = ""
  13. self.password: str = ""
  14. self.brokerid: str = ""
  15. self.auth_code: str = ""
  16. self.appid: str = ""
  17. self.frontid: int = 0
  18. self.sessionid: int = 0
  19. pass
  20. def connect(self,address:str,userid:str,password:str,brokerid:str,auth_code:str,appid:str)->None:
  21. self.userid = userid
  22. self.password = password
  23. self.brokerid = brokerid
  24. self.auth_code = auth_code
  25. self.appid = appid
  26. if not self.connect_status:
  27. path: Path = get_folder_path(self.gateway_name.lower())
  28. self.createFtdcTdApi((str(path) + "\\Td").encode("GBK"))
  29. self.subscribePrivateTopic(0)
  30. self.subscribePublicTopic(0)
  31. self.registerFront(address)
  32. self.init()
  33. self.connect_status = True
  34. else:
  35. self.authenticate()
  36. pass
  37. def authenticate(self)->None:
  38. if self.auth_failed:
  39. return
  40. ctp_req: dict = {
  41. "UserID": self.userid,
  42. "BrokerID": self.brokerid,
  43. "AuthCode": self.auth_code,
  44. "AppID": self.appid
  45. }
  46. self.reqid += 1
  47. self.reqAuthenticate(ctp_req, self.reqid)
  48. pass
  49. def login(self)->None:
  50. if self.login_failed:
  51. return
  52. ctp_req: dict = {
  53. "UserID": self.userid,
  54. "Password": self.password,
  55. "BrokerID": self.brokerid,
  56. "AppID": self.appid
  57. }
  58. self.reqid += 1
  59. self.reqUserLogin(ctp_req, self.reqid)
  60. pass
  61. def close(self)->None:
  62. if self.connect_status:
  63. self.exit()
  64. def onFrontConnected(self)->None:
  65. print('onFrontConnected')
  66. if self.auth_code:
  67. self.authenticate()
  68. else:
  69. self.login()
  70. def onFrontDisconnected(self,reason:int)->None:
  71. self.login_status = False
  72. print('onFrontDisconnected',reason)
  73. def onRspAuthenticate(self, data: dict, error: dict, reqid: int, last: bool)->None:
  74. print('onRspAuthenticate')
  75. if not error['ErrorID']:
  76. self.auth_status = True
  77. self.login()
  78. else:
  79. self.auth_failed = True
  80. print('交易服务器验证失败。',error['ErrorID'],error['ErrorMsg'])
  81. pass
  82. def onRspUserLogin(self, data: dict, error: dict, reqid: int, last: bool) -> None:
  83. print('onRspUserLogin')
  84. if not error["ErrorID"]:
  85. self.frontid = data["FrontID"]
  86. self.sessionid = data["SessionID"]
  87. self.login_status = True
  88. # 自动确认结算单
  89. ctp_req: dict = {
  90. "BrokerID": self.brokerid,
  91. "InvestorID": self.userid
  92. }
  93. self.reqid += 1
  94. self.reqSettlementInfoConfirm(ctp_req, self.reqid)
  95. else:
  96. self.login_failed = True
  97. print("交易服务器登录失败", error['ErrorID'],error['ErrorMsg'])
  98. pass
  99. def onRspSettlementInfoConfirm(self, data: dict, error: dict, reqid: int, last: bool) -> None:
  100. print('onRspSettlementInfoConfirm')
  101. while True:
  102. self.reqid += 1
  103. n: int = self.reqQryInstrument({}, self.reqid)
  104. if not n:
  105. break
  106. else:
  107. time.sleep(1)
  108. pass
  109. def onRspQryInstrument(self, data: dict, error: dict, reqid: int, last: bool) -> None:
  110. print(data['ProductClass'],data['InstrumentID'],data['ProductID'],reqid,last)
  111. if last:
  112. self.contract_inited = True
  113. print('合约信息查询完毕')
  114. pass
  115. pass

执行

  1. if __name__ == '__main__':
  2. investorid = ""
  3. brokerid="9999"
  4. password= ""
  5. appid= "simnow_client_test"
  6. auth_code= "0000000000000000"
  7. md_ip= "180.168.146.187:10211"
  8. trader_ip= "180.168.146.187:10201"
  9. temp_api = CtpTdApi()
  10. address = f"tcp://{trader_ip}"
  11. temp_api.connect(address,investorid,password,brokerid,auth_code,appid)
  12. import keyboard
  13. keyboard.wait('esc')
  14. sys.exit()
  15. pass

代码中执行的是请求所有合约,打印到控制台

结果

 可以请求到合约,接口可用。

PS:我在刚开是执行时,address直接写入的ip,提示的 RuntimeError:Invalid location in line 45 of file ..\..\source\network\ServicName.cpp 错误,后来在ip前加上tcp://就可以正常运行

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/555665
推荐阅读
相关标签
  

闽ICP备14008679号