导入ctp接口
正式开始写代码,先来试试ctp接口是否能够成功导入。我提供的ctp接口
适用于win7-win10的64位操作系统,python3.6
在ctp接口的四个文件同一目录下,新建一个demo.py如下:
代码: 全选
from vnctpmd import MdApi
from vnctptd import TdApi
如果没有报错。恭喜你已经导入ctp的api!
如果出错,比如“1%不是有效的win32程序”或者找不到模块,先确保操作系统和python版本没错,然后尝试安装Visual C++ Redistributable Packages for VS2013,x86版本和x64版本都装上。
https://www.microsoft.com/zh-cn/downloa ... x?id=40784
连接服务器
首先去
http://www.simnow.com.cn/申请一个simnow模拟账户,并通过找回密码重置一次密码,因为首次登录会要求修改密码。当然,如果你非要用实盘账号我也拦不住你。
代码: 全选
# encoding:utf-8
import os
from PyQt5.QtWidgets import QApplication
from vnctpmd import MdApi
from vnctptd import TdApi
from eventEngine import Event, EventEngine
from eventType import *
class CtpMdApi(MdApi):
"""
Demo中的行情API封装
"""
def __init__(self, eventEngine):
super(CtpMdApi, self).__init__()
self.__eventEngine = eventEngine
self.reqID = 0 # 操作请求编号
self.connectionStatus = False # 连接状态
self.loginStatus = False # 登录状态
self.userID = '' # 账号
self.password = '' # 密码
self.brokerID = '' # 经纪商代码
self.address = '' # 服务器地址
def connect(self, userID, password, brokerID, address):
"""连接服务器"""
self.userID = userID # 账号
self.password = password # 密码
self.brokerID = brokerID # 经纪商代码
self.address = address # 服务器地址
# 如果尚未建立服务器连接,则进行连接
if not self.connectionStatus:
# 创建C++环境中的API对象,这里传入的参数是需要用来保存.con文件的文件夹路径
path = os.getcwd() + '/temp/'
if not os.path.exists(path):
os.makedirs(path)
self.createFtdcMdApi(path)
# 注册服务器地址
self.registerFront(self.address)
# 初始化连接,成功会调用onFrontConnected
self.init()
# 若已经连接但尚未登录,则进行登录
else:
if not self.loginStatus:
self.login()
#----------------------------------------------------------------------
def login(self):
"""登录"""
# 如果填入了用户名密码等,则登录
if self.userID and self.password and self.brokerID:
req = {}
req['UserID'] = self.userID
req['Password'] = self.password
req['BrokerID'] = self.brokerID
self.reqID += 1
self.reqUserLogin(req, self.reqID)
#----------------------------------------------------------------------
def onFrontConnected(self):
"""服务器连接"""
self.connectionStatus = True
log = u'行情服务器连接成功'
self.put_log_event(log)
self.login()
#----------------------------------------------------------------------
def onFrontDisconnected(self, n):
"""服务器断开"""
self.connectionStatus = False
self.loginStatus = False
log = u'行情服务器连接断开'
self.put_log_event(log)
def put_log_event(self, log): # log事件分发
event = Event(type_=EVENT_LOG)
event.dict_['log'] = log
self.__eventEngine.put(event)
########################################################################
class CtpTdApi(TdApi):
"""CTP交易API实现"""
#----------------------------------------------------------------------
def __init__(self, eventEngine):
"""API对象的初始化函数"""
super(CtpTdApi, self).__init__()
self.__eventEngine = eventEngine
self.reqID = 0 # 操作请求编号
self.connectionStatus = False # 连接状态
self.loginStatus = False # 登录状态
self.userID = '' # 账号
self.password = '' # 密码
self.brokerID = '' # 经纪商代码
self.address = '' # 服务器地址
self.frontID = 0 # 前置机编号
self.sessionID = 0 # 会话编号
#----------------------------------------------------------------------
def put_log_event(self, log): # 投放log事件
event = Event(type_=EVENT_LOG)
event.dict_['log'] = log
self.__eventEngine.put(event)
#----------------------------------------------------------------------
def onFrontConnected(self):
"""服务器连接"""
self.connectionStatus = True
log = u'交易服务器连接成功'
self.put_log_event(log)
self.login()
#----------------------------------------------------------------------
def onFrontDisconnected(self, n):
"""服务器断开"""
self.connectionStatus = False
self.loginStatus = False
log = u'交易服务器连接断开'
self.put_log_event(log)
#----------------------------------------------------------------------
def onRspUserLogin(self, data, error, n, last):
"""登陆回报"""
# 如果登录成功,推送日志信息
if error['ErrorID'] == 0:
self.frontID = str(data['FrontID'])
self.sessionID = str(data['SessionID'])
self.loginStatus = True
log = data['UserID'] + u'交易服务器登录完成'
self.put_log_event(log)
# 确认结算信息
req = {}
req['BrokerID'] = self.brokerID
req['InvestorID'] = self.userID
self.reqID += 1
# self.reqSettlementInfoConfirm(req, self.reqID)
# 否则,推送错误信息
else:
log = error['ErrorMsg']
self.put_log_event(log)
#----------------------------------------------------------------------
def onRspUserLogout(self, data, error, n, last):
"""登出回报"""
# 如果登出成功,推送日志信息
if error['ErrorID'] == 0:
self.loginStatus = False
log = u'交易服务器登出完成'
self.put_log_event(log)
# 否则,推送错误信息
else:
log = error['ErrorMsg']
self.put_log_event(log)
#----------------------------------------------------------------------
def connect(self, userID, password, brokerID, address):
"""初始化连接"""
self.userID = userID # 账号
self.password = password # 密码
self.brokerID = brokerID # 经纪商代码
self.address = address # 服务器地址
# 如果尚未建立服务器连接,则进行连接
if not self.connectionStatus:
# 创建C++环境中的API对象,这里传入的参数是需要用来保存.con文件的文件夹路径
path = os.getcwd() + '/temp/'
if not os.path.exists(path):
os.makedirs(path)
self.createFtdcTraderApi(path)
# 注册服务器地址
self.registerFront(self.address)
# 初始化连接,成功会调用onFrontConnected
self.init()
# 若已经连接但尚未登录,则进行登录
else:
if not self.loginStatus:
self.login()
#----------------------------------------------------------------------
def login(self):
"""连接服务器"""
# 如果填入了用户名密码等,则登录
if self.userID and self.password and self.brokerID:
req = {}
req['UserID'] = self.userID
req['Password'] = self.password
req['BrokerID'] = self.brokerID
self.reqID += 1
self.reqUserLogin(req, self.reqID)
########################################################################
class MainEngine:
"""主引擎,负责对API的调度"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.ee = EventEngine() # 创建事件驱动引擎
self.md = CtpMdApi(self.ee) # 创建行情API接口
self.td = CtpTdApi(self.ee) # 创建交易API接口
self.ee.start() # 启动事件驱动引擎
self.ee.register(EVENT_LOG, self.print_log) # 注册日志打印事件
self.userID = '' # 账号
self.password = '' # 密码
self.brokerID = '9999' # 经纪商代码
self.MdIp = 'tcp://180.168.146.187:10011' # 行情服务器地址
self.TdIp = 'tcp://180.168.146.187:10001' # 交易服务器地址
#----------------------------------------------------------------------
def login(self):
"""登陆"""
self.md.connect(self.userID, self.password, self.brokerID, self.MdIp)
self.td.connect(self.userID, self.password, self.brokerID, self.TdIp)
def print_log(self, event):
log = event.dict_['log']
print(log)
# 直接运行脚本可以进行测试
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
main = MainEngine()
main.login()
app.exec_()
运行结果如下:
代码: 全选
交易服务器连接成功
行情服务器连接成功
100897交易服务器登录完成
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
因为api没写完整,似乎调用了某些没写的方法,所以有后面的错误,先忽略。
在上面的代码中,我们从封装的ctp接口继承了行情api和交易api,然后只改写了其中几个用于连接服务器的方法。然后创建一个主引擎,用它来创建一个行情api和交易api的实例,并且创建事件引擎的实例传给它们。这样他们就可以通过事件引擎来实现线程间的通信,同时事件引擎也会负责调度任务。
确认结算单、查询合约、查询行情截面数据
登录成功之后,还有几个要做的动作:
1、确认结算单,就相当于我们登录快期时弹出结算单我们点确认的过程,如果不确认,就无法进行后续操作。
2、查询一次合约信息,期货合约不断会到期更新,所有每次登录一般会查询一次合约信息,缓存到本地。其中的很多信息,也是后续要用到的。
3、查询一次详细合约信息,或者叫行情截面数据,里面包含各合约的价格方面的信息。
4、设置一个循环查询,一般每隔几秒会进行一下持仓和资金的查询,更新账户信息。
完成这几步,基本上就是个正常的连接状态了。后面就是根据你的输入或者策略来进行交互了。
ctp的接口中有两类常见的函数,一类是以req开头,是主动向服务器发出的请求,如reqUserLogin(登录请求)、reqQryTradingAccount(查询账户)、reqOrderInsert(发出委托)。
一类是以on开头,在特定情况下被动触发的回调函数,如onRspSettlementInfoConfirm(结算单确认)、onRspQryInstrument(查询合约回报)、onRtnTrade(成交回报)。
一个请求往往有一个回调函数与之对应,用来处理服务器响应请求发来的数据。所以我们在一个请求的回调函数中发出下一个请求,以实现顺序执行几个任务。
reqUserLogin==>onRspUserLogin(这里加reqSettlementInfoConfirm代码,确认结算单)==>onRspSettlementInfoConfirm(这里加reqQryInstrument代码,查询合约)==>onRspQryInstrument(这里加reqQryDepthMarketData代码,查询行情截面数据)==>onRspQryDepthMarketData(这里通过事件引擎触发下一个任务)
在tdapi中增加这些代码
代码: 全选
def onRspSettlementInfoConfirm(self, data, error, n, last):
"""确认结算信息回报"""
log = u'结算信息确认完成'
self.put_log_event(log)
# 查询合约代码
self.reqID += 1
self.reqQryInstrument({}, self.reqID)
#----------------------------------------------------------------------
def onRspQryInstrument(self, data, error, n, last):
"""
合约查询回报
由于该回报的推送速度极快,因此不适合全部存入队列中处理,
选择先储存在一个本地字典中,全部收集完毕后再推送到队列中
(由于耗时过长目前使用其他进程读取)
"""
if error['ErrorID'] == 0:
event = Event(type_=EVENT_INSTRUMENT)
event.dict_['data'] = data
event.dict_['last'] = last
self.__eventEngine.put(event)
if last:
sleep(1)
self.reqID += 1
self.reqQryDepthMarketData({}, self.reqID) # 查询合约截面数据
else:
log = '合约投资者回报,错误代码:' + str(error['ErrorID']) + ', 错误信息:' + str(error['ErrorMsg'])
self.put_log_event(log)
#----------------------------------------------------------------------
def onRspQryDepthMarketData(self, data, error, n, last):
# 常规行情事件,查询合约截面数据的回报
event = Event(type_=EVENT_MARKETDATA)
event.dict_['data'] = data
event.dict_['last'] = last
self.__eventEngine.put(event)
def qryAccount(self):
"""查询账户"""
self.reqID += 1
self.reqQryTradingAccount({}, self.reqID)
#----------------------------------------------------------------------
def qryPosition(self):
"""查询持仓"""
self.reqID += 1
req = {}
req['BrokerID'] = self.brokerID
req['InvestorID'] = self.userID
self.reqQryInvestorPosition(req, self.reqID)
在mainEngine中增加保存合约数据和行情截面数据的函数,并在事件引擎中把他们分别注册到EVENT_INSTRUMENT和EVENT_MARKETDATA事件上。
代码: 全选
def insertInstrument(self, event):
"""插入合约对象"""
data = event.dict_['data']
last = event.dict_['last']
self.list_instrument.append(data)
if last:#最后一条数据
# 将查询完成的合约信息保存到本地文件,今日登录可直接使用不再查询
event = Event(type_=EVENT_LOG)
log = '合约信息查询完成'
event.dict_['log'] = log
self.ee.put(event)
with open('instrument.json', 'w', encoding="utf-8") as f:
jsonD = json.dumps(self.list_instrument,indent=4)
f.write(jsonD)
self.list_instrument = []
event = Event(type_=EVENT_LOG)
log = '合约信息已经保存'
event.dict_['log'] = log
self.ee.put(event)
# ----------------------------------------------------------------------
def insertMarketData(self, event):
"""插入合约截面数据"""
data = event.dict_['data']
last = event.dict_['last']
self.list_marketdata.append(data)
if last:
#更新交易日
self.md.TradingDay = data['TradingDay']
# 将查询完成的合约信息保存到本地文件,今日登录可直接使用不再查询
event = Event(type_=EVENT_LOG)
log = '合约截面数据查询完成'
event.dict_['log'] = log
self.ee.put(event)
with open('marketdata.json', 'w', encoding="utf-8") as f:
jsonD = json.dumps(self.list_marketdata, indent=4)
f.write(jsonD)
self.list_marketdata = []
event = Event(type_=EVENT_LOG)
log = '合约截面数据已经保存'
event.dict_['log'] = log
self.ee.put(event)
执行结果如下,并在当前文件夹下生成instrument.json和marketdata.json两个文件.用文本编辑器打开看看,里面是合约信息和行情截面数据
代码: 全选
行情服务器连接成功
交易服务器连接成功
100897交易服务器登录完成
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
结算信息确认完成
合约信息查询完成
合约信息已经保存
合约截面数据查询完成
合约截面数据已经保存
查资金和查持仓
在tdapi中加入查持仓和查资金的回调函数
代码: 全选
#----------------------------------------------------------------------
def onRspQryInvestorPosition(self, data, error, n, last):
"""持仓查询回报"""
if not data['InstrumentID']:
return
if error['ErrorID'] == 0:
event = Event(type_=EVENT_POSITION)
event.dict_['data'] = data
event.dict_['last'] = last
self.__eventEngine.put(event)
else:
log = ('持仓查询回报,错误代码:' +str(error['ErrorID']) + ', 错误信息:' +str(error['ErrorMsg']))
self.put_log_event(log)
#----------------------------------------------------------------------
def onRspQryTradingAccount(self, data, error, n, last):
"""资金账户查询回报"""
if error['ErrorID'] == 0:
event = Event(type_=EVENT_ACCOUNT)
event.dict_['data'] = data
self.__eventEngine.put(event)
else:
log = ('账户查询回报,错误代码:' +str(error['ErrorID']) + ', 错误信息:' +str(error['ErrorMsg']))
self.put_log_event(log)
在mainEngine中增加定时循环查询资金和持仓的方法,并把持仓和资金的数据直接打印出来
__init__里面加上相关的变量,在事件引擎注册打印持仓和资金的函数
代码: 全选
# 循环查询持仓和账户相关
self.countGet = 0 # 查询延时计数
self.lastGet = 'Position' # 上次查询的性质,先查询账户
# 注册持仓和账户、委托事件
self.ee.register (EVENT_ACCOUNT, self.account)
self.ee.register (EVENT_POSITION, self.position)
insertMarketData里面加上下面这行,用每秒一次的计时器来触发循环查询
代码: 全选
self.ee.register(EVENT_TIMER, self.getAccountPosition)#定时器事件,循环查询
mainEngine加上
代码: 全选
def getAccountPosition(self, event):
"""循环查询账户和持仓"""
self.countGet += 1
# 每n秒发一次查询
if self.countGet > 5:
self.countGet = 0 # 清空计数
if self.lastGet == 'Account':
self.getPosition()
self.lastGet = 'Position'
else:
self.getAccount()
self.lastGet = 'Account'
def account(self,event):#处理账户事件数据
var = event.dict_['data']
print(var)
def position(self, event):#处理持仓事件数据
var = event.dict_['data']
print(var)
def getAccount(self):
"""查询账户"""
self.td.qryAccount()
# ----------------------------------------------------------------------
def getPosition(self):
"""查询持仓"""
self.td.qryPosition()
运行结果除了之前的内容,增加了每个几秒一次的打印资金信息或打印持仓信息
代码: 全选
{'ReserveBalance': 0.0, 'Reserve': 0.0, 'SpecProductCommission': 0.0, 'FrozenMargin': 0.0, 'BrokerID': '9999', 'CashIn': 0.0, 'FundMortgageOut': 0.0, 'FrozenCommission': 0.0, 'SpecProductPositionProfitByAlg': 0.0, 'Commission': 0.0, 'SpecProductPositionProfit': 0.0, 'Deposit': 0.0, 'DeliveryMargin': 0.0, 'TradingDay': '20180115', 'CurrencyID': 'CNY', 'Interest': 0.0, 'PreDeposit': 932642.0599999999, 'Available': 928742.0599999999, 'SpecProductFrozenMargin': 0.0, 'AccountID': '100897', 'SpecProductMargin': 0.0, 'PreFundMortgageOut': 0.0, 'InterestBase': 0.0, 'SpecProductExchangeMargin': 0.0, 'PreBalance': 953078.5599999999, 'Balance': 949178.5599999999, 'MortgageableFund': 742993.648, 'Withdraw': 0.0, 'SpecProductFrozenCommission': 0.0, 'PreMortgage': 0.0, 'SpecProductCloseProfit': 0.0, 'WithdrawQuota': 742993.648, 'FundMortgageAvailable': 0.0, 'BizType': '\x00', 'PreCredit': 0.0, 'FrozenCash': 0.0, 'SettlementID': 1, 'CloseProfit': 0.0, 'ExchangeDeliveryMargin': 0.0, 'Mortgage': 0.0, 'Credit': 0.0, 'CurrMargin': 20436.5, 'FundMortgageIn': 0.0, 'ExchangeMargin': 20436.5, 'PreFundMortgageIn': 0.0, 'PositionProfit': -3900.0, 'PreMargin': 20436.5}
{'ShortFrozen': 0, 'FrozenMargin': 0.0, 'BrokerID': '9999', 'CashIn': 0.0, 'FrozenCommission': 0.0, 'UseMargin': 10945.0, 'MarginRateByVolume': 0.0, 'CloseProfitByDate': 0.0, 'InstrumentID': 'j1801', 'StrikeFrozen': 0, 'CombLongFrozen': 0, 'CloseProfitByTrade': 0.0, 'TodayPosition': 0, 'TradingDay': '20180115', 'CombShortFrozen': 0, 'YdStrikeFrozen': 0, 'PreSettlementPrice': 2189.0, 'OpenVolume': 0, 'CloseVolume': 0, 'SettlementPrice': 2150.0, 'OpenCost': 190650.0, 'HedgeFlag': '1', 'OpenAmount': 0.0, 'StrikeFrozenAmount': 0.0, 'InvestorID': '100897', 'PositionCost': 218900.0, 'LongFrozenAmount': 0.0, 'ExchangeID': '', 'PreMargin': 0.0, 'CloseProfit': 0.0, 'CloseAmount': 0.0, 'LongFrozen': 0, 'PosiDirection': '2', 'CombPosition': 0, 'YdPosition': 1, 'PositionDate': '1', 'AbandonFrozen': 0, 'ShortFrozenAmount': 0.0, 'FrozenCash': 0.0, 'SettlementID': 1, 'Position': 1, 'ExchangeMargin': 10945.0, 'MarginRateByMoney': 0.0, 'PositionProfit': -3900.0, 'Commission': 0.0}
{'ShortFrozen': 0, 'FrozenMargin': 0.0, 'BrokerID': '9999', 'CashIn': 0.0, 'FrozenCommission': 0.0, 'UseMargin': 4766.5, 'MarginRateByVolume': 0.0, 'CloseProfitByDate': 0.0, 'InstrumentID': 'pp1809', 'StrikeFrozen': 0, 'CombLongFrozen': 0, 'CloseProfitByTrade': 0.0, 'TodayPosition': 0, 'TradingDay': '20180115', 'CombShortFrozen': 0, 'YdStrikeFrozen': 0, 'PreSettlementPrice': 9533.0, 'OpenVolume': 0, 'CloseVolume': 0, 'SettlementPrice': 9533.0, 'OpenCost': 92470.0, 'HedgeFlag': '1', 'OpenAmount': 0.0, 'StrikeFrozenAmount': 0.0, 'InvestorID': '100897', 'PositionCost': 95330.0, 'LongFrozenAmount': 0.0, 'ExchangeID': '', 'PreMargin': 0.0, 'CloseProfit': 0.0, 'CloseAmount': 0.0, 'LongFrozen': 0, 'PosiDirection': '2', 'CombPosition': 0, 'YdPosition': 2, 'PositionDate': '1', 'AbandonFrozen': 0, 'ShortFrozenAmount': 0.0, 'FrozenCash': 0.0, 'SettlementID': 1, 'Position': 2, 'ExchangeMargin': 4766.5, 'MarginRateByMoney': 0.0, 'PositionProfit': 0.0, 'Commission': 0.0}
{'ShortFrozen': 0, 'FrozenMargin': 0.0, 'BrokerID': '9999', 'CashIn': 0.0, 'FrozenCommission': 0.0, 'UseMargin': 4725.0, 'MarginRateByVolume': 0.0, 'CloseProfitByDate': 0.0, 'InstrumentID': 'pp1801', 'StrikeFrozen': 0, 'CombLongFrozen': 0, 'CloseProfitByTrade': 0.0, 'TodayPosition': 0, 'TradingDay': '20180115', 'CombShortFrozen': 0, 'YdStrikeFrozen': 0, 'PreSettlementPrice': 9450.0, 'OpenVolume': 0, 'CloseVolume': 0, 'SettlementPrice': 9450.0, 'OpenCost': 86600.0, 'HedgeFlag': '1', 'OpenAmount': 0.0, 'StrikeFrozenAmount': 0.0, 'InvestorID': '100897', 'PositionCost': 94500.0, 'LongFrozenAmount': 0.0, 'ExchangeID': '', 'PreMargin': 0.0, 'CloseProfit': 0.0, 'CloseAmount': 0.0, 'LongFrozen': 0, 'PosiDirection': '3', 'CombPosition': 0, 'YdPosition': 2, 'PositionDate': '1', 'AbandonFrozen': 0, 'ShortFrozenAmount': 0.0, 'FrozenCash': 0.0, 'SettlementID': 1, 'Position': 2, 'ExchangeMargin': 4725.0, 'MarginRateByMoney': 0.0, 'PositionProfit': 0.0, 'Commission': 0.0}
目前为止的完整代码在附件里可以下载。如果你按照上面的代码自己加的话,可能已经发现出错了。因为依赖的模块增加了,对照附件和自己的出错提示改一下吧。