1. 云服務(wù)器上安裝近十GB的MATLAB過(guò)于麻煩,而Python的Anaconda發(fā)布版只有幾百M(fèi)B
2.Python開(kāi)源免費(fèi),而正版MATLAB則價(jià)格不菲。
3. 國(guó)內(nèi)與Quantopian類(lèi)似的在線策略編寫(xiě)網(wǎng)站開(kāi)始流行,Python是它們的策略語(yǔ)言,建立了良性的互動(dòng)交流社區(qū),有大量的案例可參考。
4. Python有大量用于科學(xué)計(jì)算、統(tǒng)計(jì)分析、機(jī)器學(xué)習(xí)的開(kāi)源工具庫(kù),適合用于做量化交易。
因此,已經(jīng)有越來(lái)越多的用戶向Python遷移。我們也緊跟趨勢(shì),在推出MATLAB版XAPI統(tǒng)一行情交易接口后也推出了Python版。在總結(jié)了社區(qū)中常遇到的問(wèn)題后,在本文將進(jìn)行專(zhuān)門(mén)的解答。
目前國(guó)內(nèi)可以合法交易的金融工具主要是股票、期貨、黃金T+D等品種,一些電子盤(pán)、比特幣等暫時(shí)不在我們討論范圍內(nèi),有興趣的朋友可以網(wǎng)上查閱相關(guān)資料。一定要選擇國(guó)家承認(rèn)的正規(guī)合法平臺(tái),以免上當(dāng)受騙。表1. 常見(jiàn)金融工具與對(duì)應(yīng)的接口
綜合交易平臺(tái)CTP(ComprehensiveTransaction Platform)是由上海期貨信息技術(shù)有限公司(上海期貨交易所旗下子公司)開(kāi)發(fā)的經(jīng)紀(jì)業(yè)務(wù)管理系統(tǒng)。
目前除了在期貨市場(chǎng)占有率第一,它還有證券版支持股票,國(guó)際版支持外盤(pán)。
公司網(wǎng)址:http://www.sfit.com.cn/
期貨市場(chǎng)占有率第二的老牌柜臺(tái)系統(tǒng),由于開(kāi)放性不夠,被CTP從第一的位置擠下,目前因歷史遺留還有部分期貨公司在使用。
金仕達(dá)所接的市場(chǎng)很多,期貨、個(gè)股期權(quán)、證券,以及貴金屬現(xiàn)貨。
金仕達(dá)證券接口還是老式的不支持主動(dòng)推送的接口,所以在ETF期權(quán)上市時(shí),在期貨公司推擴(kuò)證券柜臺(tái)也不順利。而在貴金屬現(xiàn)貨接口上又推廣不力,現(xiàn)已被飛鼠占去先機(jī)。
公司網(wǎng)址:http:// www.sungard.cn/
分別由中國(guó)金融期貨信息技術(shù)有限公司(是中國(guó)金融期貨交易所期下子公司)、大連飛創(chuàng)信息技術(shù)有限公司(大連商品交易所旗下子公司)、易盛信息技術(shù)有限公司(鄭州商品交易所期下子公司)。
在2015年股災(zāi)之前,程序化交易監(jiān)管較弱,高頻交易火爆,如果使用每家專(zhuān)用的API,期貨公司可以提供離交易所機(jī)柜更近的主機(jī)托管業(yè)務(wù)。但現(xiàn)在高頻交易策略無(wú)法施展的情況下,用流行的CTP更省事。
他們的公司網(wǎng)址分別為:
Femas:http://www.cffexit.com.cn/
XSpeed:http://www.dfitc.com.cn/
Esunny:http://www.esunny.com.cn/
上海飛鼠軟件科技有限公司,一家主要提供跨市場(chǎng)交易解決方案的公司,提供了飛鼠API,可以接入期貨、外盤(pán)、黃金現(xiàn)貨。目前需要做黃金T+D程序化交易的用戶一般會(huì)選擇此接口。公司網(wǎng)址:http://www.feishutech.com.cn/
恒生電子同時(shí)提供了證券、期貨經(jīng)紀(jì)業(yè)務(wù)解決方案的提供商,在機(jī)構(gòu)用戶中占有率極高。機(jī)構(gòu)通過(guò)UFX接入到恒生O32系統(tǒng)中進(jìn)行事前風(fēng)控。
因?yàn)榻鹑诋a(chǎn)品太多、業(yè)務(wù)復(fù)雜,導(dǎo)致字段也多。UFX更多的是類(lèi)似于通訊協(xié)議數(shù)據(jù)包打包器。用戶在開(kāi)發(fā)時(shí)需要對(duì)照數(shù)據(jù)字典一個(gè)個(gè)自己組織請(qǐng)求包,使用起來(lái)比較麻煩。
公司網(wǎng)址:http://www.hundsun.com/
基本上,所有官方提供的接口都是C++接口,少量老的證券接口還提供C接口。C++接口導(dǎo)出了C++類(lèi),而其它語(yǔ)言中的類(lèi)都是自己語(yǔ)言中經(jīng)過(guò)特殊設(shè)計(jì)的,無(wú)法直接利用C++類(lèi)。因此必須將C++接口轉(zhuǎn)換成簡(jiǎn)單的C接口,將原接口函數(shù)中隱式的this指針改成顯式的void*指針傳入,這樣才能在其它語(yǔ)言中進(jìn)行調(diào)用。
還需要解決結(jié)構(gòu)體的傳入、傳出與回調(diào)函數(shù)的實(shí)現(xiàn),然后再進(jìn)一步簡(jiǎn)單的用各種語(yǔ)言封裝調(diào)用時(shí)的請(qǐng)求與響應(yīng),使用起來(lái)就方便了。
目前市面上主要的開(kāi)源Python封裝有vn.py與XAPI兩款,其它的Python開(kāi)源項(xiàng)目,已經(jīng)不再維護(hù)或用戶量極少,所以這就不再介紹了,有興趣的朋友請(qǐng)網(wǎng)上搜索。
由于vn.py在官網(wǎng)已經(jīng)有齊全的文檔,本人對(duì)它也沒(méi)有更深入的研究,所以在這只介紹自己開(kāi)源的XAPI。
一開(kāi)始是為了對(duì)接OpenQuant3這款軟件的個(gè)人項(xiàng)目,參考了海風(fēng)開(kāi)源的CTP C#版接口,不同的地方是將一些復(fù)雜的邏輯由C#層移動(dòng)到C層,簡(jiǎn)化上層的開(kāi)發(fā)。
為了推廣OpenQuant3,所以決定將代碼整理轉(zhuǎn)成開(kāi)源項(xiàng)目。由于Femas和XSpeed的推出,當(dāng)時(shí)參考了CTP的封裝方法,封裝了其它幾個(gè)接口和OpenQuant3的插件。但這種老式的封裝方法工作量大,C#層和應(yīng)用層有多少種接口就需要封裝多少套,難于維護(hù)。
2015年同樣是海風(fēng)推出的新版封裝里有動(dòng)態(tài)延遲加載dll的示例,學(xué)習(xí)研究后,決定對(duì)接口進(jìn)行重新設(shè)計(jì),統(tǒng)一不同API的結(jié)構(gòu)體為同一套,命名為XAPI。
后來(lái)根據(jù)網(wǎng)友的建議,對(duì)項(xiàng)目的命名空間、目錄結(jié)構(gòu)重新調(diào)整,升級(jí)為XAPI2。
專(zhuān)門(mén)設(shè)計(jì)的C接口,所以各種語(yǔ)言版本的封裝陸續(xù)推出,.NET、Java、MATLAB、COM、Python。
1.2.4 XAPI設(shè)計(jì)思想:
1. 能接入主要的接口。根據(jù)流行程序來(lái)考慮是否優(yōu)先接入;
2. 滿足基本的交易功能。拋棄了不常用的功能,如銀期轉(zhuǎn)賬功能;
3. 能支持不同的語(yǔ)言。一定要導(dǎo)出C接口;
4. 簡(jiǎn)化上層代碼的開(kāi)發(fā)。將流控,請(qǐng)求ID,請(qǐng)求發(fā)送隊(duì)列等各項(xiàng)基本功能都封裝在C層。
XAPI的核心就是隊(duì)列。XAPI的隊(duì)列數(shù)據(jù)格式與只有一個(gè)XRequest導(dǎo)出函數(shù)的dll風(fēng)格完全一樣。一個(gè)字節(jié)大小的數(shù)據(jù)包類(lèi)型,兩個(gè)指針,兩個(gè)雙精度數(shù)字,三個(gè)內(nèi)存區(qū)指針和三個(gè)內(nèi)存區(qū)大小數(shù)值,在目前的行情交易接口開(kāi)發(fā)中基本夠用。
上層的函數(shù)調(diào)用被封裝成數(shù)據(jù)包,添加到請(qǐng)求隊(duì)列中,另一線程從請(qǐng)求隊(duì)列中取內(nèi)容,然后調(diào)用API的各種函數(shù),用這種方法來(lái)解決流控,請(qǐng)求ID遞增等問(wèn)題。
收到的響應(yīng)也復(fù)制打包好后放入響應(yīng)隊(duì)列,另一線程從響應(yīng)隊(duì)列中取內(nèi)容,調(diào)用注冊(cè)好的回調(diào)函數(shù)通知到上層,解決應(yīng)用層策略耗時(shí)過(guò)久可能導(dǎo)致底層崩潰的問(wèn)題。
由于大部分接口只推出了32位版,而在Windows中同一進(jìn)程中32位與64位不能相互調(diào)用,所以Python也只能選擇32位的進(jìn)行安裝。目前CTP接口有64位版,有興趣的朋友可以自行編譯XAPI項(xiàng)目為64位版。
Python2.7不再推薦,推薦使用3.6及以上版本,做量化交易要用到的庫(kù)基本都已經(jīng)支持Python3。
訪問(wèn)https://github.com/QuantBox/XAPI2/,點(diǎn)擊Clone or download按鈕,選擇Download ZIP進(jìn)行下載。
下載比同步repo的方式要快,對(duì)于沒(méi)有能力貢獻(xiàn)代碼的朋友來(lái)說(shuō),zip方式最快速直接。
解壓zip文件,目錄眾多,對(duì)于Python用戶來(lái)說(shuō),如果不自行編譯,只需要關(guān)注languages\Python這個(gè)文件夾即可,如果涉及需要編譯dll的工作才需要其它文件夾下的項(xiàng)目。由于編譯工作對(duì)一些用戶來(lái)說(shuō)有難度,還得下載安裝VisualStudio 2015,用戶可以加入Readme.md中提到的QQ群,到群中下載已經(jīng)編譯打包好的dll。
“缺少依賴(lài)庫(kù)”是很多用戶遇到最基礎(chǔ)最常見(jiàn)的問(wèn)題,本項(xiàng)目為了接入不同的API,每套API都要做一套C封裝,這些封裝都需要C++運(yùn)行時(shí)庫(kù),為了減少發(fā)布包的大小,運(yùn)行時(shí)庫(kù)使用的動(dòng)態(tài)編譯,這樣C++運(yùn)行時(shí)庫(kù)就能共享一套,它們默認(rèn)放在了C:\Windows\System32或C:\Windows\SysWOW64\。這就導(dǎo)致在一臺(tái)電腦上能正常使用,復(fù)制到另一臺(tái)電腦上由于忘記復(fù)制C++運(yùn)行時(shí)庫(kù)就不能用了。
其實(shí)還有XAPI下還有一個(gè)隊(duì)列庫(kù)Queue_x86.dll/Queue_x86d.dll也是每個(gè)C封裝都需要用到,并且它還有Debug與Release版本,由于C++中的new/delete必須與C++運(yùn)行時(shí)庫(kù)Debug/Release對(duì)應(yīng)的問(wèn)題,所以這兩個(gè)版本不能混用。
不能運(yùn)行一定是缺少依賴(lài)庫(kù)導(dǎo)致嗎?網(wǎng)上下載depends(http://www.dependencywalker.com/)查看CTP_Trade_x86.dll,從能正常運(yùn)行的電腦上將缺少的庫(kù)復(fù)制到其它電腦相應(yīng)系統(tǒng)文件夾下即可。
同時(shí)也需要注意32位與64位的問(wèn)題,很多同名文件是是同時(shí)存在32位與64位的區(qū)別的,32位版本放C:\Windows\SysWOW64\下,64位版本放C:\Windows\System32下。
圖1.depends查看dll示例
圖中演示的是用depends查看CTP_Trade_x86.dll,由于缺少Q(mào)ueue_x86d.dll, Python在調(diào)用它時(shí)會(huì)提示“找不到指定的模塊”。CTP_Trade_x86.dll是32位的dll,圖中顯示的c:\windows\system32路徑實(shí)際上是訪問(wèn)的c:\windows\syswow64。將Queue_x86d.dll文件復(fù)制到C:\Windows\SysWOW64下即可。
對(duì)這些細(xì)節(jié)不想了解的用戶直接運(yùn)行群共享提供的安裝包(統(tǒng)一接口完整版.zip)中的“X1.復(fù)制bin和System32目錄_需右鍵以管理員身份運(yùn)行.bat”就可以省去以上所有麻煩。
默認(rèn)提供了test_ctp_api.py/test_tdx_api.py兩個(gè)完整的腳本用來(lái)測(cè)試,它們分別實(shí)現(xiàn)了期貨和股票的目標(biāo)調(diào)倉(cāng)功能,只要在文件中設(shè)置好每個(gè)金融產(chǎn)品的目標(biāo)持倉(cāng)、多空方向和數(shù)量,就能以最快的速度調(diào)整成指定的倉(cāng)位。
對(duì)于每天交易頻率不高,使用日線數(shù)據(jù)計(jì)算策略,然后第二天早上進(jìn)行交易的機(jī)構(gòu)用戶來(lái)說(shuō),這兩個(gè)腳本完全可以直接使用。
目錄下出現(xiàn)了config.py、config_default.py、config_override.py和config_tdx.py四個(gè)以config開(kāi)頭的文件。
config_default.py/config_override.py:分別是CTP的默認(rèn)配置和CTP的特殊配置,為了實(shí)現(xiàn)特殊配置覆蓋默認(rèn)配置,config.py中實(shí)現(xiàn)了覆蓋重復(fù)字段的功能。
config_tdx.py中沒(méi)有CTP配置中那么復(fù)雜,直接就是配置信息。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
configs = {
# 根據(jù)目錄,存放交易清單和中間文件等信息
'root_dir': r'd:' + '\\test_tdx',
# 交易,這下面的配置要求需要參考每種API的說(shuō)明文檔
'td': {
#TDX安裝目錄,注意最后有一個(gè)\
'ExtInfoChar128': br'D:\new_hbzq' + b'\\',
# 登錄腳本。注意路徑出現(xiàn)中文需要轉(zhuǎn)碼
'Address':r'd:\test_tdx\Login_東方財(cái)富證券.lua'.encode('GBK'),
# 資金賬號(hào)
'UserID': b'123456',
# 用戶密碼
'Password': b'654321',
# 通迅密碼。注意,不是驗(yàn)證碼,不需要通迅密碼的券商請(qǐng)留空
'ExtInfoChar64': br'',
},
}
注意配置文件中很多字符串出現(xiàn)了b前綴。這是因?yàn)閄API的結(jié)構(gòu)體對(duì)應(yīng)字段就是C中的字?jǐn)?shù)數(shù)組,b就是原始的bytes,我們將字符串直接傳入。中文路徑不能直接使用b前綴,需要先encode轉(zhuǎn)換成bytes后才能使用。
前面在config_default.py中已經(jīng)配置了root_dir文件路徑為d:\test_ctp。編輯其中的target_position.csv文件,下單時(shí)將從這它讀取目標(biāo)倉(cāng)位。
不知道配置怎么辦?登錄成功后先輸入2查詢當(dāng)前持倉(cāng),然后輸入12將當(dāng)前持倉(cāng)保存到target_position.csv。
Symbol,InstrumentID,HedgeFlag,Side,Position,InstrumentName
i1709.,i1709,0,1,5.0,
i1709.,i1709,0,-1,0.0,
rb1710.,rb1710,0,-1,6.0,
Symbol:合約唯一代碼,XAPI內(nèi)部使用。由InstruemntID與ExchangeID組合而成;
InstrumentID:合約代碼,供API使用,必須與對(duì)應(yīng)API的合約代碼完全一樣,如果對(duì)應(yīng)API區(qū)分大小寫(xiě),這里也得區(qū)分;
HedgeFlag:投機(jī)套保標(biāo)志,默認(rèn)為0,這個(gè)功能一般是機(jī)構(gòu)使用,機(jī)構(gòu)可能出現(xiàn)同時(shí)持有投機(jī)倉(cāng)位與套保倉(cāng)位的情況;
Side:多空方向,1表示多,-1表示空;
Position:持倉(cāng)數(shù)量,正數(shù);
InstrumentName:合約名稱(chēng)。只用于顯示,可為空,股票中表示股票名稱(chēng)。
Symbol,InstrumentID,HedgeFlag,Side,Position,InstrumentName
000001,000001,0,1,400.0,平安銀行
000002,000002,0,1,200.0,萬(wàn) 科A
300001,300001,0,1,300.0,特銳德
300024,300024,0,1,200.0,機(jī)器人
對(duì)于前面提到的csv文件,如何清倉(cāng)、反手、鎖倉(cāng)呢?
清倉(cāng):即沒(méi)有項(xiàng)目,或?qū)?yīng)的Position為0即可
Symbol,InstrumentID,HedgeFlag,Side,Position,InstrumentName
反手:只要改對(duì)應(yīng)項(xiàng)目的Side的正負(fù)號(hào)即可
Symbol,InstrumentID,HedgeFlag,Side,Position,InstrumentName
i1709.,i1709,0,-1,5.0,
rb1710.,rb1710,0,1,6.0,
鎖倉(cāng):同合約多空持倉(cāng)數(shù)量相等即可
Symbol,InstrumentID,HedgeFlag,Side,Position,InstrumentName
i1709.,i1709,0,1,5.0,
i1709.,i1709,0,-1,5.0,
rb1710.,rb1710,0,1,6.0,
rb1710.,rb1710,0,-1,6.0,
直接運(yùn)行會(huì)打印一個(gè)選擇菜單,然后等待用戶輸入。菜單的主要內(nèi)容如下:
1 - 讀取目標(biāo)倉(cāng)位
2 - 查詢實(shí)盤(pán)倉(cāng)位
3 - 訂閱行情
4 - 計(jì)算交易清單
5 - 批量下單
6 - 需延遲通過(guò)回報(bào)批量撤單
它就是目標(biāo)倉(cāng)位調(diào)整的主要流程:
1 - 首先從本地的target_position.csv讀取設(shè)置的目標(biāo)倉(cāng)位;
2 - 從實(shí)盤(pán)柜臺(tái)上查詢當(dāng)前的持倉(cāng);
3 - 根據(jù)目標(biāo)倉(cāng)位和實(shí)盤(pán)持倉(cāng)合并得到要下單的合約集合,訂閱合約的行情,后面下單時(shí)需要用到最新的行情價(jià)格;
4 - 對(duì)比目標(biāo)倉(cāng)位和實(shí)盤(pán)持倉(cāng),得到交易清單,這個(gè)清單已經(jīng)處理好了買(mǎi)賣(mài)方向與開(kāi)平方向等問(wèn)題,對(duì)于上期所的的今倉(cāng)與昨倉(cāng)分兩筆下單;
5 - 根據(jù)上一步生成的交易清單直接下單;
6 - 下單后并不是瞬間成交,還需要等待幾秒,然后撤單。
人工循環(huán)執(zhí)行2到6步,直到交易清單為空。注意有些情況下可能成交失敗,交易清單永遠(yuǎn)不為空。例如:
1. 資金不夠
2. 漲跌停,買(mǎi)入或賣(mài)出無(wú)法成交
3. 進(jìn)入交割月了,交易手限制為整數(shù)倍,而下單手?jǐn)?shù)不合要求
4. 股票停牌
有時(shí)輸入這些數(shù)字也可能輸入錯(cuò)誤,我們提供了一個(gè)更簡(jiǎn)化的選項(xiàng):
7 - 順序執(zhí)行1-6
只要輸入7,就會(huì)自動(dòng)執(zhí)行1至6三次,直到交易清單為空或出錯(cuò)。
這些是相當(dāng)重要的菜單,也需要關(guān)注一下。
21 - 查合約列表(至少執(zhí)行一次)
22 - 查資金
23 - 取消訂閱行情
24 - 打印訂單
33– 切換行情顯示
q - 退出
21 - 從柜臺(tái)上查詢合約列表,并保存在本地。它保存了每個(gè)合約的最小變動(dòng)價(jià)位,用于計(jì)算買(mǎi)賣(mài)時(shí)加幾跳時(shí)具體加的是多少價(jià)格。如果不查詢默認(rèn)最小變動(dòng)價(jià)位為1。對(duì)于某些合約使用默認(rèn)1將產(chǎn)生錯(cuò)誤。
對(duì)于cu銅,最小變動(dòng)價(jià)位是10,如果使用默認(rèn)1,加2跳,價(jià)格將不滿足最小變動(dòng)價(jià)格整數(shù)倍的條件。
對(duì)于IF滬深300,最小變動(dòng)價(jià)位是0.2,如果使用默認(rèn)1,加2跳,價(jià)格實(shí)際上加了10跳。
所以這個(gè)地方至少要執(zhí)行一次,每次有新品種上市時(shí)也得查一次。
22 - 查賬號(hào)資金
33 – 切換行情顯示。對(duì)于CTP的主推行情,由于行情一直在界面中打印,干擾使用,所以提供了一個(gè)開(kāi)關(guān)進(jìn)行切換。
q – 人工輸入模式會(huì)一直等待用戶輸入,輸入‘q’可以退出
11 - 合并對(duì)沖多個(gè)組合到目標(biāo)持倉(cāng)
12 - 回寫(xiě)查詢持倉(cāng)到目標(biāo)持倉(cāng)
13 - 合并對(duì)沖目標(biāo)持倉(cāng)和增量倉(cāng)到目標(biāo)持倉(cāng)
1 - 讀取目標(biāo)倉(cāng)位
2 - 查詢實(shí)盤(pán)倉(cāng)位
是否覺(jué)得要自己手工編寫(xiě)target_position.csv很麻煩呢?只要運(yùn)行菜單2后再運(yùn)行12就會(huì)將前一步查詢出來(lái)的持倉(cāng)寫(xiě)入到target_position.csv,然后再手工編輯少量即可。
一個(gè)實(shí)盤(pán)賬號(hào)下跑了N個(gè)策略,分別生成了不同的持倉(cāng)文件,能否先內(nèi)部對(duì)沖一下?我們提供了11這個(gè)菜單項(xiàng),它能將portfolio_1.csv/portfolio_2.csv/portfolio_3.csv三個(gè)文件中的持倉(cāng)合并,對(duì)沖,然后寫(xiě)入到target_position.csv中。
如果投資組合數(shù)超過(guò)3個(gè),請(qǐng)自己手工編輯代碼支持更多組合。
每天的投資組合清單都已經(jīng)生成,但我盤(pán)中想改總持倉(cāng)怎么辦?這個(gè)改動(dòng)是算在哪個(gè)策略對(duì)應(yīng)的投資組合呢?建議根據(jù)策略數(shù)量N創(chuàng)建N+1個(gè)投資組合,第N+1個(gè)組合文件中內(nèi)容為空,當(dāng)需要人工調(diào)整時(shí)倉(cāng)位都放在第N+1個(gè)投資組合中。
如果我的策略邏輯并不是根據(jù)持倉(cāng)數(shù)來(lái)計(jì)算,而是根據(jù)買(mǎi)賣(mài)數(shù)量呢?例如今天買(mǎi)入2手,而不是今天倉(cāng)位是2手。這時(shí)就要用到菜單項(xiàng)13了,它會(huì)將target_position.csv和incremental_position.csv的持倉(cāng)合并,不對(duì)沖,然后再寫(xiě)回target_position.csv。
用戶一定要根據(jù)自己的實(shí)際情況靈活運(yùn)用以上倉(cāng)位相關(guān)的菜單,建議大家策略邏輯使用目標(biāo)倉(cāng)位法,而不是買(mǎi)賣(mài)法。
1.4.5股票相關(guān)功能
目前Tdx接口沒(méi)有實(shí)現(xiàn)查詢股票列表功能,所以菜單項(xiàng)21無(wú)效。股票無(wú)法賣(mài)空,所以target_position.csv中的Side都只能填1。
原本Tdx柜臺(tái)就沒(méi)有實(shí)現(xiàn)主動(dòng)推送委托回報(bào)和成交回報(bào)的功能,只能過(guò)一會(huì)后去主動(dòng)查詢委托列表,所以股票接口只能等待一會(huì)后直接撤單,然后直接查持倉(cāng),不再關(guān)心委托回報(bào)這些細(xì)節(jié)。
無(wú)人值守是每個(gè)程序化交易員的目標(biāo)。這里提供一個(gè)可用的參考。每天晚上數(shù)據(jù)都自動(dòng)下載到服務(wù)器上,策略程序定時(shí)加載數(shù)據(jù)生成交易清單,第二天早上開(kāi)盤(pán)自動(dòng)全下,下完后日志自動(dòng)發(fā)郵件到QQ郵箱,微信立即在手機(jī)上提醒新郵件??梢詤⒖纪夸浵聄unme_auto2.bat等文件。
1.5.1 命令行參數(shù)
test_ctp_api.py/test_tdx_api.py默認(rèn)在運(yùn)行時(shí)都是手工輸入,其實(shí)還提供了一個(gè)參數(shù)“—input”。
“--input=11;7”表示先運(yùn)行菜單項(xiàng)11,然后運(yùn)行菜單項(xiàng)7。即先從幾個(gè)子組合csv文件中合并持倉(cāng),然后循環(huán)下單,下完單后退出。
REM 11 合并持倉(cāng)
REM 22 查資金
REM 7 循環(huán)下單
set date_Ymd=%date:~0,4%%date:~5,2%%date:~8,2%
python.exe test_ctp_api.py --input=11;22;7;221>log/%date_Ymd%.log 2>&1
如何自動(dòng)保存日志呢?我們使用到了DOS重定向。
<>
>:新建模式輸出到文件
>>:追加模式輸出到文件
>&:將一個(gè)句柄輸出寫(xiě)入到另一個(gè)句柄的輸入中
<>
0:標(biāo)準(zhǔn)輸入,令在執(zhí)行時(shí)所要的輸入數(shù)據(jù)通過(guò)它來(lái)取得
1:標(biāo)準(zhǔn)輸出,命令執(zhí)行后的輸出結(jié)果從該端口送出
2:標(biāo)準(zhǔn)錯(cuò)誤,命令執(zhí)行時(shí)的錯(cuò)誤信息通過(guò)該端口送出
1>log/%date_Ymd%.log2>&1 表示將標(biāo)準(zhǔn)輸出指向log日志文件,然后將標(biāo)準(zhǔn)錯(cuò)誤都輸出到標(biāo)準(zhǔn)輸出中。這樣標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤就都輸出到log文件了。如果將2>&1放到前面,則達(dá)不到效果,錯(cuò)誤還是輸出到了窗口中,因?yàn)檫@時(shí)標(biāo)準(zhǔn)輸出還沒(méi)有被重定向。
python mail.py --username=123456--password=654321 --from=123456@qq.com
--to=123456@qq.com;654321@qq.com--log=log --bat=%~f0
推薦使用QQ郵箱來(lái)收發(fā)通知郵件,收件箱使用QQ郵箱是因?yàn)槭盏洁]件后微信能立即提醒。發(fā)件箱與收件箱使用同一家是因?yàn)檫@樣郵件到達(dá)更快。
--username=用戶名
--password=密碼/授權(quán)碼。QQ郵箱為了防止在第三方客戶端登錄時(shí)密碼被盜,使用授權(quán)碼來(lái)代替密碼進(jìn)行登錄。
--from=發(fā)件箱。username賬號(hào)所對(duì)應(yīng)的發(fā)件箱
--to=收件箱??梢詫?xiě)多個(gè)郵箱。使用英文“;”進(jìn)行分隔
通過(guò)QQ郵箱的,設(shè)置->賬戶->SMTP服務(wù),開(kāi)啟SMTP發(fā)信功能,同時(shí)申請(qǐng)授權(quán)碼。
按Win鍵后,輸入taskschd就會(huì)定位到“任務(wù)計(jì)劃程序”。
1. 創(chuàng)建基本任務(wù)
2. 觸發(fā)器選擇“每周”,將“星期一”至“星期五”都選上,開(kāi)始時(shí)間選擇正確,比如8:59:55開(kāi)始,這樣算上登錄和查持倉(cāng)的時(shí)間,差不多9點(diǎn)正好下到柜臺(tái)。請(qǐng)保證服務(wù)器時(shí)間與互聯(lián)網(wǎng)時(shí)間同步。
3. 操作選擇“啟動(dòng)程序”,“程序或腳本”填為bat文件路徑,“起始于”填為bat文件所在路徑。由于bat和python代碼中大量用到了相對(duì)路徑,所以“起始于”這一項(xiàng)絕對(duì)不能為空。
4. 注意。計(jì)劃任務(wù)中不能出現(xiàn)阻塞進(jìn)程的代碼,比如不能在bat中加入pause或調(diào)用的python代碼長(zhǎng)時(shí)間不返回。否則任務(wù)運(yùn)行中,第二天的計(jì)劃任務(wù)不會(huì)觸發(fā)。
前面介紹了基本的使用方法,對(duì)于很多用戶來(lái)說(shuō)就已經(jīng)完全足夠,但這里還是要對(duì)代碼簡(jiǎn)單解讀一下,方便遇到問(wèn)題時(shí)自行處理。
XApi.py/XSpi.py/XStruct.py/XEnum.py是核心代碼,使用ctypes模塊實(shí)現(xiàn)Python調(diào)用C。
XEnum.py中定義了枚舉類(lèi)型,包含值以及對(duì)應(yīng)的英文名。當(dāng)在結(jié)構(gòu)體中取到枚舉數(shù)值時(shí),通過(guò)這里的定義得到對(duì)應(yīng)的英文名。
XStruct.py中定義了結(jié)構(gòu)體,與C接口的結(jié)構(gòu)體一一對(duì)應(yīng),同時(shí)還為結(jié)構(gòu)體添加了一些函數(shù),方便使用,如__str__等。
XApi.py進(jìn)行API的各項(xiàng)調(diào)用,對(duì)收到的響應(yīng)進(jìn)行轉(zhuǎn)發(fā),如登錄、查詢、下單等。
XSpi.py回調(diào)接口,提供給第三方繼承使用。
MySpy.py繼承了XSpi的類(lèi),目前用來(lái)進(jìn)行實(shí)際目標(biāo)持倉(cāng)調(diào)整功能。
以CTP期貨交易接口封裝為例,CTP_Trade_x86.dll只導(dǎo)出了一個(gè)接口XRequest,需要對(duì)XRequest請(qǐng)求格式特別了解才能正確的調(diào)用,所以又在這之上套了一層X(jué)API_CPP_x86.dll,它導(dǎo)出了一些常用的C函數(shù)。接口封裝人員可以按自己的能力選擇合適自己的調(diào)用方式。
目前XApi.py中提供的方式是通過(guò)XAPI_CPP_x86.dll來(lái)調(diào)用CTP_Trade_x86.dll。
# 創(chuàng)建XApi對(duì)象,設(shè)置服務(wù)器地址與賬號(hào)
td = XApi(r'C:\Program Files\SmartQuantLtd\OpenQuant 2014\XAPI\x86\XAPI_CPP_x86.dll')
td.ServerInfo.Address = config['td']['Address']
td.ServerInfo.BrokerID = config['td']['BrokerID']
td.UserInfo.UserID = config['td']['UserID']
td.UserInfo.Password = config['td']['Password']
# 指定加載的是CTP的交易模塊,設(shè)置不同的路徑可以加載其它模塊
ret = td.init(br'C:\Program Files\SmartQuantLtd\OpenQuant 2014\XAPI\x86\CTP\CTP_Trade_x86.dll')
if not ret:
print(td.get_last_error())
exit(-1)
print(ord(td.get_api_type()))
print(td.get_api_name())
print(td.get_api_version())
# 關(guān)鍵一步,注冊(cè)回調(diào)事件處理函數(shù)
td.register_spi(self)
# 連接并登錄
td.connect()
init()中的路徑如果加載的是CTP_Quote_x86.dll那實(shí)現(xiàn)的就是期貨行情相關(guān)功能,如果加載的是Tdx_Trade_x86.dll實(shí)現(xiàn)的就是股票交易功能。
register_spi()中需要傳入繼承了XSpi的類(lèi),在test_ctp_api.py中當(dāng)前類(lèi)MySpi繼承了XSpi。
connect()時(shí)會(huì)異步收到登錄狀態(tài)回報(bào),MySpi.OnConnectionStatus最先得到觸發(fā)。某一交易接口的登錄日志如下,最后的Done表示登錄的所有動(dòng)作都已經(jīng)執(zhí)行完,可以用來(lái)下單了。
OnConnectionStatus=Initialized
OnConnectionStatus=Connecting
OnConnectionStatus=Connected
OnConnectionStatus=Logining
OnConnectionStatus=Logined
[TradingDay=20170905;LoginTime=205957;SessionID=1:1314163388;InvestorName=;XErrorID=0;RawErrorID=0;Text=]
OnConnectionStatus=Confirming
OnConnectionStatus=Confirmed
OnConnectionStatus=Done
OnConnectionStatus等一些事件響應(yīng)函數(shù)中能下斷點(diǎn)調(diào)試嗎?實(shí)測(cè)在PyCharm中下斷點(diǎn),有輸出日志,但斷點(diǎn)完全不生效。
Stack Overflow上的網(wǎng)友是這樣解答的:在非Python線程中,你必須設(shè)置調(diào)試器機(jī)制才能正常工作(在Python線程創(chuàng)建時(shí)自動(dòng)設(shè)置了,但在非Python線程創(chuàng)建時(shí)沒(méi)有任何構(gòu)造函數(shù)鉤子,所以得自己做)。在你需要下斷點(diǎn)的代碼前加入如下代碼即可。
import pydevd
pydevd.settrace(suspend=False,trace_only_current_thread=True)
你需要使用pip install pydevd先安裝pydevd。
創(chuàng)建行情XAPI實(shí)例,對(duì)它設(shè)置行情接口庫(kù)CTP_Quote_x86.dll,設(shè)置行情服務(wù)器的地址和端口號(hào)。不要弄混,對(duì)交易接口訂閱行情是無(wú)效的。
ret = md.init(br'C:\Program Files\SmartQuantLtd\OpenQuant 2014\XAPI\x86\CTP\CTP_Quote_x86.dll')
訂閱行情傳入的合約代碼也必須是有b前綴。
# 可直接傳字一個(gè)用;分隔的字符串
md.subscribe(b'cu1709;SR801',b'')
# 或做一次轉(zhuǎn)碼再傳入
symbols_ = pd.Series(symbols).str.encode('gbk')
for i in range(len(symbols_)):
md.subscribe(symbols_[i],b'')
會(huì)在OnRtnDepthMarketData(self,ptr1, size1)中收到行情回報(bào),ptr1是行情數(shù)據(jù)指針,size1是行情數(shù)據(jù)大小。其它OnXxx事件,輸出的參數(shù)都是Python對(duì)象,只有行情接口特殊輸出的是內(nèi)存指針。因?yàn)闉榱酥С侄鄼n行情,行情結(jié)構(gòu)體設(shè)計(jì)成了可變內(nèi)存塊。就算是股票的五檔行情,在漲跌停時(shí),這個(gè)內(nèi)存塊的大小都是不一樣的。
那如何取數(shù)據(jù)呢?使用的ctypes的cast即可
obj = cast(ptr1,POINTER(DepthMarketDataNField)).contents
# 打印行情,一般情況下都是關(guān)閉,因?yàn)閮?nèi)容太多了
print(obj)
#賣(mài)五價(jià)
ask_count = obj.get_ask_count()
if ask_count > 0:
ask =obj.get_ask(ptr1, ask_count - 1)
# 買(mǎi)五價(jià)
bid_count = obj.get_bid_count()
if bid_count > 0:
bid = obj.get_bid(ptr1, bid_count - 1)
對(duì)于CTP這種主動(dòng)行情推送的接口,只要訂閱了,行情有變化就會(huì)推送,OnRtnDepthMarketData會(huì)不停的被調(diào)用。而Tdx這種查詢模式的接口,查一次推送一次,所以需要根據(jù)策略需求查詢,不能高頻率查詢,否則嚴(yán)重影響服務(wù)器。
# 查詢持倉(cāng)請(qǐng)求
query =ReqQueryField()
td.req_query(QueryType.ReqQryInvestorPosition, query)
# 持倉(cāng)響應(yīng)
def OnRspQryInvestorPosition(self, pPosition,size1, bIsLast):
if size1<=>
return
# 一定要用copy,不然最后一個(gè)會(huì)覆蓋前面的
self.position_dict[pPosition.get_id()]= copy.copy(pPosition)
if notbIsLast:
return
當(dāng)賬號(hào)上沒(méi)有持倉(cāng),但還是需要通知到客戶端請(qǐng)求已經(jīng)得到響應(yīng)了,所以會(huì)傳回一個(gè)空數(shù)據(jù),所以需要對(duì)size1進(jìn)行判斷。
# 查資金請(qǐng)求
query = ReqQueryField()
td.req_query(QueryType.ReqQryTradingAccount,query)
# 資金響應(yīng)
def OnRspQryTradingAccount(self, pAccount, size1,bIsLast):
if size1<=>
return
print(pAccount)
1.6.6 下單
# 提供臨時(shí)變量用于下單
order = (OrderField * 1)()
orderid = (OrderIDTypeField * 1)()
orderid[0].OrderIDType = b''
# 訂單參數(shù)
order[0].InstrumentID = b'IF1710'
order[0].ExchangeID = b''
order[0].Type = OrderType.Limit
order[0].Side = OrderSide.Buy
order[0].Qty = 1
order[0].OpenClose = OpenCloseType.Open
order[0].Price = 3500.0
# 下單
ret = td.send_order(order[0], orderid[0], 1)
# 打印
print('LocalID:%s'% ret)
注意,涉及到字符串的地方都需要b前綴。有關(guān)OrderType、OrderSide、OpenCloseType的取值,可以參考XEnum.py文件中對(duì)應(yīng)部分。
下完單后回收到委托回報(bào)和成交回報(bào),委托回報(bào)可以存儲(chǔ)起來(lái)用于立即撤單。
# 委托回報(bào)
def OnRtnOrder(self, pOrder):
self.order_dict[pOrder.get_id()]= copy.copy(pOrder)
print(pOrder)
# 成交回報(bào)
def OnRtnTrade(self, pTrade):
print(pTrade)
在CTP接口中撤單需要三個(gè)字段,而XSpeed需要兩個(gè)字段,Tdx一個(gè)字段,由于每種接口都不一樣,為了簡(jiǎn)化,在XAPI中統(tǒng)一設(shè)計(jì)成通過(guò)一個(gè)唯一字段進(jìn)行撤單。這需要XAPI內(nèi)部維護(hù)一張映射表,在登錄時(shí)底層會(huì)查詢委托列表進(jìn)行映射表重建。所以如果今天下單極多,重新登錄時(shí)要等映射表重建完才能成功撤單。
OrderField中有三個(gè)字段跟識(shí)別訂單有關(guān):
OrderID是由交易所傳回來(lái)的id。如果想比較不同柜臺(tái)的速度,可以同時(shí)報(bào)單相同合約后比較OrderID的大小。
ID是XAPI維護(hù)的唯一ID,可用于撤單。需要保證在下一次重新登錄后還能通過(guò)委托回報(bào)計(jì)算出這個(gè)唯一ID。
LocalID是本地XAPI在下單時(shí)立即返回的id,它只供內(nèi)部臨時(shí)映射時(shí)使用,Tdx這類(lèi)的接口是下單返回后才能知道ID值,所以在柜臺(tái)返回之前XAPI維護(hù)一個(gè)臨時(shí)LocalID,等收到回報(bào)后就棄用LocalID。CTP接口中LocalID與ID是相等的,在Tdx中他們不相等
# 臨時(shí)變量
orderid = (OrderIDTypeField * 2)()
orderid[0].OrderIDType = b''
orderid[1].OrderIDType = b''
# 設(shè)置要撤單的ID
orderid[0].OrderIDType = order.ID
# 撤單
td.cancel_order(orderid[0],orderid[1], 1)
撤單ID最好從委托回報(bào)中取,而不是手工填,因?yàn)椴糠諥PI的ID前后可能出現(xiàn)空格,如果缺失了空格會(huì)導(dǎo)致找不到訂單。
聯(lián)系客服