CTP Python API及Demo(利用Swig 封裝)Windows版(traderapi)

前言:
目前上期技術CTP系統提供的API版本是C++版本,本文主要介紹Windows 64位平臺下利用Swig工具將CTP C++接口trader API轉換爲python可調用的接口。原先本文是基於python2,現在升級爲基於python3.7.2。


0. 歡迎交流

更新時間:20191208
github: https://github.com/nicai0609/Python-CTPAPI

1. 準備工作

  • 從CTP官網上下載CTP API點擊下載。這裏用的版本是6.3.11_20180109(現在官方升級了版本,此版本不提供下載了,請去官網下載最新版本v6.3.15_20190220),64位的API文件包清單如下:

error.dtd
error.xml
ThostFtdcMdApi.h
ThostFtdcTraderApi.h
ThostFtdcUserApiDataType.h
ThostFtdcUserApiStruct.h
thostmduserapi.dll
thostmduserapi.lib
thosttraderapi.dll
thosttraderapi.lib

  • 安裝Swig軟件,本文中所用的Swig是swigwin-4.0.0版本,點擊下載
  • 安裝python,注意要安裝64位版本,將環境變量配置好。本文所用的是3.7.2版本,如果自用用到別的版本,下面步驟一致。如果別的python版本有問題,可以與我聯繫。
  • 安裝libiconv庫。這個庫主要適用於字節編碼轉換,因爲CTP的中文是GB2312編碼,轉換爲UTF-8編碼,適合python輸出。這個庫我已經編譯成靜態庫了,如果不想自己編譯,可以去羣裏下載或者在CTP JAVA API的github項目裏下載。本文一開始採用的是第三方庫libiconv轉換,下面也以libiconv爲例繼續。C++11庫中已有字節編碼轉換方式,採用這種方式可以不用libiconv庫,下面和libiconv相關的都可以略去,見《Swig轉換C++接口中文亂碼解決方案》

2. 通過Swig得到python接口文件

在剛剛下載得到的API文件夾20180109_tradeapi64_windows內,新建文件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", "gb2312");
        if (cd != reinterpret_cast<iconv_t>(-1)) {
            char buf[4096] = {};
            char **in = &$1;
            char *out = buf;
            size_t inlen = strlen($1), outlen = 4096;

            if (iconv(cd, (const char **)in, &inlen, &out, &outlen) != static_cast<size_t>(-1))
			{
				size_t size = outlen;
				while (size && (buf[size - 1] == '\0')) --size;
				resultobj = SWIG_FromCharPtrAndSize(buf, size);
			}
            iconv_close(cd);
        }
    }
}
%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;  
%feature("director") CThostFtdcTraderSpi; 
%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruct.h" 
%include "ThostFtdcTraderApi.h"

這是一個接口文件,用於告訴swig爲哪些類和方法創建接口。打開windows cmd工具,cd到當前目錄\20180109_tradeapi_windows下。 在cmd中運行命令

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

等到運行完成後,可以看到當前目錄下生成了

thosttraderapi_wrap.h
thosttraderapi_wrap.cxx
thosttradeapi.py

.h.cx文件是用於包裝原來C++接口的文件,下面要用。.py文件是python調用方法的接口文件。

3. 通過C++得到python可調用的pyd動態庫

在當前文件夾下建立一個C++工程,工程的應用程序類型選DLL,工程名爲_thosttraderapi(建工程的步驟也可參考https://blog.csdn.net/pjjing/article/details/53186394這篇文章第3步開頭),需要注意幾點:1)工程建64位dll類型,2)運行庫選多線程(/MT)。然後將如下文件拷貝到_thosttraderapi\文件夾下:

ThostFtdcTraderApi.h
ThostFtdcUserApiDataType.h
ThostFtdcUserApiStruct.h
thosttraderapi.lib
thosttraderapi_wrap.cxx
thosttraderapi_wrap.h
libiconv.lib
iconv.h

c++工程中添加現有項,將這些文件全部添加到工程中去。下面還要做幾步:

  • 將你安裝的python下include文件夾的路徑添加至C++附加包含目錄。我的路徑是C:\Python37\include;C++附加包含目錄在工程-屬性-配置屬性-c/c++處。
  • 將你安裝的python中python37.lib添加至工程附加依賴項中。我的lib路徑是C:\Python37\libs\python37.lib;,附加依賴項在工程-屬性-配置屬性-鏈接器-輸入處。

這樣全部完成之後,選擇Release版本,我們按F7編譯,在\_thosttraderapi\Release目錄底下可見_thosttraderapi.dll動態庫文件,說明編譯成功,將其重命名爲_thosttraderapi.pyd,這樣CTP Python API就編譯成功了。
如果編譯出現一些問題,可以百度解決。可能涉及到要修改pyconfig.h,object.h,Python.h三個文件。

4. Python Demo

新建文件traderapi_demo.py,注意文件同目錄底下要有如下三個文件:

thosttradeapi.py
thosttraderapi.dll
_thosttradeapi.pyd

本demo實現登錄成功後報單,收報單回報的功能。完整的demo代碼如下:

# -*- coding: utf-8 -*-
import thosttraderapi as api

#Addr
FrontAddr="tcp://180.168.146.187:10100"
#FrontAddr="tcp://180.168.146.187:10130"
#LoginInfo
BROKERID="9999"
USERID="00001"
PASSWORD="00001"
#OrderInfo
INSTRUMENTID="rb1909"
PRICE=3200
VOLUME=1
DIRECTION=api.THOST_FTDC_D_Sell
#DIRECTION=api.THOST_FTDC_D_Buy
#open
OFFSET="0"
#close
#OFFSET="1"

def ReqorderfieldInsert(tradeapi):
	print ("ReqOrderInsert Start")
	orderfield=api.CThostFtdcInputOrderField()
	orderfield.BrokerID=BROKERID
	orderfield.InstrumentID=INSTRUMENTID
	orderfield.UserID=USERID
	orderfield.InvestorID=USERID
	orderfield.Direction=DIRECTION
	orderfield.LimitPrice=PRICE
	orderfield.VolumeTotalOriginal=VOLUME
	orderfield.OrderPriceType=api.THOST_FTDC_OPT_LimitPrice
	orderfield.ContingentCondition = api.THOST_FTDC_CC_Immediately
	orderfield.TimeCondition = api.THOST_FTDC_TC_GFD
	orderfield.VolumeCondition = api.THOST_FTDC_VC_AV
	orderfield.CombHedgeFlag="1"
	orderfield.CombOffsetFlag=OFFSET
	orderfield.GTDDate=""
	orderfield.OrderRef="1"
	orderfield.MinVolume = 0
	orderfield.ForceCloseReason = api.THOST_FTDC_FCC_NotForceClose
	orderfield.IsAutoSuspend = 0
	tradeapi.ReqOrderInsert(orderfield,0)
	print ("ReqOrderInsert End")
	

class CTradeSpi(api.CThostFtdcTraderSpi):
	tapi=''
	def __init__(self,tapi):
		api.CThostFtdcTraderSpi.__init__(self)
		self.tapi=tapi
		
	def OnFrontConnected(self) -> "void":
		print ("OnFrontConnected")
		loginfield = api.CThostFtdcReqUserLoginField()
		loginfield.BrokerID=BROKERID
		loginfield.UserID=USERID
		loginfield.Password=PASSWORD
		loginfield.UserProductInfo="python dll"
		self.tapi.ReqUserLogin(loginfield,0)
		print ("send login ok")
		
	def OnRspUserLogin(self, pRspUserLogin: 'CThostFtdcRspUserLoginField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
		print ("OnRspUserLogin")
		print ("TradingDay=",pRspUserLogin.TradingDay)
		print ("SessionID=",pRspUserLogin.SessionID)
		print ("ErrorID=",pRspInfo.ErrorID)
		print ("ErrorMsg=",pRspInfo.ErrorMsg)

		qryinfofield = api.CThostFtdcQrySettlementInfoField()
		qryinfofield.BrokerID=BROKERID
		qryinfofield.InvestorID=USERID
		qryinfofield.TradingDay=pRspUserLogin.TradingDay
		self.tapi.ReqQrySettlementInfo(qryinfofield,0)
		print ("send ReqQrySettlementInfo ok")
		

	def OnRspQrySettlementInfo(self, pSettlementInfo: 'CThostFtdcSettlementInfoField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
		print ("OnRspQrySettlementInfo")
		if  pSettlementInfo is not None :
			print ("content:",pSettlementInfo.Content)
		else :
			print ("content null")
		if bIsLast :
			pSettlementInfoConfirm=api.CThostFtdcSettlementInfoConfirmField()
			pSettlementInfoConfirm.BrokerID=BROKERID
			pSettlementInfoConfirm.InvestorID=USERID
			self.tapi.ReqSettlementInfoConfirm(pSettlementInfoConfirm,0)
			print ("send ReqSettlementInfoConfirm ok")
		
	def OnRspSettlementInfoConfirm(self, pSettlementInfoConfirm: 'CThostFtdcSettlementInfoConfirmField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
		print ("OnRspSettlementInfoConfirm")
		print ("ErrorID=",pRspInfo.ErrorID)
		print ("ErrorMsg=",pRspInfo.ErrorMsg)
		ReqorderfieldInsert(self.tapi)
		print ("send ReqorderfieldInsert ok")


	def OnRtnOrder(self, pOrder: 'CThostFtdcOrderField') -> "void":
		print ("OnRtnOrder")
		print ("OrderStatus=",pOrder.OrderStatus)
		print ("StatusMsg=",pOrder.StatusMsg)
		print ("LimitPrice=",pOrder.LimitPrice)
		
	def OnRspOrderInsert(self, pInputOrder: 'CThostFtdcInputOrderField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
		print ("OnRspOrderInsert")
		print ("ErrorID=",pRspInfo.ErrorID)
		print ("ErrorMsg=",pRspInfo.ErrorMsg)
		
def main():
	tradeapi=api.CThostFtdcTraderApi_CreateFtdcTraderApi()
	tradespi=CTradeSpi(tradeapi)
	tradeapi.RegisterSpi(tradespi)
	tradeapi.SubscribePrivateTopic(api.THOST_TERT_QUICK)
	tradeapi.SubscribePublicTopic(api.THOST_TERT_QUICK)
	tradeapi.RegisterFront(FrontAddr)	
	tradeapi.Init()
	tradeapi.Join()
	
if __name__ == '__main__':
	main()

5. 常見問題

感謝網友 weixin_41707895 整理

5.1 invalid conversion from ‘const char**’ to ‘char**’ [-fpermissive]

解決方法:將i文件中,if (iconv(cd, (const char **)in, &inlen, &out, &outlen) != static_cast<size_t>(-1)) 中的(const char **)刪掉

5.2 ‘strcpy’: This function or variable may be unsafe. Consider using strcpy_s instead.

解決方法:項目屬性->配置屬性->C/C+±>預處理器->預處理器定義中添加:_CRT_SECURE_NO_WARNINGS

5.3 fatal error LNK1112: 模塊計算機類型“X86”與目標計算機類型“x64”衝突

解決方法:選擇編譯64位庫時,vs中需要如下設置:解決方案屬性->配置屬性->將平臺選爲X64->配置管理器->選擇X64平臺 右鍵項目清理即可

5.4 對象或庫文件“EDLib.lib”是使用比創建其他對象所用編譯器舊的編譯器創建的;請重新生成舊的對象和庫

解決方法:項目屬性->配置屬性->高級->全程序優化改爲無全程序優化

5.5 節數超過對象文件格式限制: 請使用 /bigobj 進行編譯

解決方法:項目屬性->配置屬性->C/C+±>命令行->其它選項中鍵入/bigobj

5.6 無法解析的外部符號 __imp_sprintf_s

解決方法:項目屬性->配置屬性->鏈接器->附加依賴項中添加legacy_stdio_definitions.lib

聲明:僅是個人愛好編譯,對此API引起的你的任何損失不負責任。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章