diff --git a/vn.trader/CTA_setting.json b/vn.trader/CTA_setting.json deleted file mode 100644 index 8c245b0b..00000000 --- a/vn.trader/CTA_setting.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "DR_IF1512": { - "strategyClassName": "DataRecorder", - "vtSymbol": "IF1512" - }, - "DR_IH1512": { - "strategyClassName": "DataRecorder", - "vtSymbol": "IH1512" - } -} \ No newline at end of file diff --git a/vn.trader/ContractData.vt b/vn.trader/ContractData.vt deleted file mode 100644 index 38075a44..00000000 Binary files a/vn.trader/ContractData.vt and /dev/null differ diff --git a/vn.trader/ctaAlgo/CTA_setting.json b/vn.trader/ctaAlgo/CTA_setting.json new file mode 100644 index 00000000..a792abfa --- /dev/null +++ b/vn.trader/ctaAlgo/CTA_setting.json @@ -0,0 +1,7 @@ +[ + { + "name": "double ema", + "className": "DoubleEmaDemo", + "vtSymbol": "IF1602" + } +] \ No newline at end of file diff --git a/vn.trader/IF0000_1min.csv b/vn.trader/ctaAlgo/IF0000_1min.csv similarity index 100% rename from vn.trader/IF0000_1min.csv rename to vn.trader/ctaAlgo/IF0000_1min.csv diff --git a/vn.trader/ctaAlgo/__init__.py b/vn.trader/ctaAlgo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vn.trader/ctaBacktestingEngine.py b/vn.trader/ctaAlgo/ctaBacktesting.py similarity index 68% rename from vn.trader/ctaBacktestingEngine.py rename to vn.trader/ctaAlgo/ctaBacktesting.py index ca186e2b..9f65eedc 100644 --- a/vn.trader/ctaBacktestingEngine.py +++ b/vn.trader/ctaAlgo/ctaBacktesting.py @@ -1,22 +1,21 @@ # encoding: UTF-8 -from datetime import datetime, timedelta -import json -from collections import OrderedDict +''' +本文件中包含的是CTA模块的回测引擎,回测引擎的API和CTA引擎一致, +可以使用和实盘相同的代码进行回测。 +''' +from datetime import datetime, timedelta +from collections import OrderedDict +import json import pymongo +from ctaBase import * +from ctaSetting import * + from vtConstant import * from vtGateway import VtOrderData, VtTradeData -from ctaConstant import * -from ctaObject import * -from ctaStrategies import strategyClassDict - -from ctaStrategyTemplate import TestStrategy -from ctaHistoryData import MINUTE_DB_NAME - - ######################################################################## class BacktestingEngine(object): @@ -45,22 +44,24 @@ class BacktestingEngine(object): self.strategy = None # 回测策略 self.mode = self.BAR_MODE # 回测模式,默认为K线 - self.dbClient = None # 数据库客户端 - self.dbCursor = None # 数据库指针 + self.dbClient = None # 数据库客户端 + self.dbCursor = None # 数据库指针 - self.historyData = [] # 历史数据的列表,回测用 - self.initData = [] # 初始化用的数据 + self.historyData = [] # 历史数据的列表,回测用 + self.initData = [] # 初始化用的数据 self.backtestingData = [] # 回测用的数据 self.dataStartDate = None # 回测数据开始日期,datetime对象 - self.strategyStartDate = None # 策略启动日期(即前面的数据用于初始化),同上 + self.strategyStartDate = None # 策略启动日期(即前面的数据用于初始化),datetime对象 - self.limitOrderDict = {} # 限价单字典 - self.workingLimitOrderDict = {} # 活动限价单字典,用于进行撮合用 - self.limitOrderCount = 0 # 限价单编号 + self.limitOrderDict = OrderedDict() # 限价单字典 + self.workingLimitOrderDict = OrderedDict() # 活动限价单字典,用于进行撮合用 + self.limitOrderCount = 0 # 限价单编号 - self.tradeCount = 0 # 成交编号 - self.tradeDict = {} # 成交字典 + self.tradeCount = 0 # 成交编号 + self.tradeDict = OrderedDict() # 成交字典 + + self.logList = [] # 日志记录 # 当前最新数据,用于模拟成交用 self.tick = None @@ -68,7 +69,7 @@ class BacktestingEngine(object): self.dt = None # 最新的时间 #---------------------------------------------------------------------- - def setStartDate(self, startDate='20100416', initDays=30): + def setStartDate(self, startDate='20100416', initDays=10): """设置回测的启动日期""" self.dataStartDate = datetime.strptime(startDate, '%Y%m%d') @@ -107,16 +108,26 @@ class BacktestingEngine(object): else: self.backtestingData.append(data) - self.output(u'载入完成,数据量%s' %len(self.backtestingData)) + self.output(u'载入完成,数据量:%s' %len(self.backtestingData)) #---------------------------------------------------------------------- def runBacktesting(self): """运行回测""" - self.strategy.start() + self.output(u'开始回测') + self.strategy.inited = True + self.strategy.onInit() + self.output(u'策略初始化完成') + + self.strategy.trading = True + self.strategy.onStart() + self.output(u'策略启动完成') + + self.output(u'开始回放数据') if self.mode == self.BAR_MODE: for data in self.backtestingData: self.newBar(data) + #print str(data.datetime) else: for data in self.backtestingData: self.newTick(data) @@ -125,9 +136,10 @@ class BacktestingEngine(object): def newBar(self, bar): """新的K线""" self.bar = bar - self.crossLimitOrder() # 先撮合限价单 - self.crossStopOrder() # 再撮合停止单 - self.strategy.onBar(bar) + self.dt = bar.datetime + self.crossLimitOrder() # 先撮合限价单 + self.crossStopOrder() # 再撮合停止单 + self.strategy.onBar(bar) # 推送K线到策略中 #---------------------------------------------------------------------- def newTick(self, tick): @@ -138,9 +150,13 @@ class BacktestingEngine(object): self.strategy.onTick(tick) #---------------------------------------------------------------------- - def initStrategy(self, name, strategyClass, paramDict=None): - """初始化策略""" - self.strategy = strategyClass(self, name, paramDict) + def initStrategy(self, strategyClass, setting=None): + """ + 初始化策略 + setting是策略的参数设置,如果使用类中写好的默认设置则可以不传该参数 + """ + self.strategy = strategyClass(self, setting) + self.strategy.name = self.strategy.className #---------------------------------------------------------------------- def sendOrder(self, vtSymbol, orderType, price, volume, strategy): @@ -261,6 +277,7 @@ class BacktestingEngine(object): trade.price = order.price trade.volume = order.totalVolume trade.tradeTime = str(self.dt) + trade.dt = self.dt self.strategy.onTrade(trade) self.tradeDict[tradeID] = trade @@ -284,7 +301,7 @@ class BacktestingEngine(object): buyCrossPrice = self.tick.lastPrice sellCrossPrice = self.tick.lastPrice - # 遍历限价单字典中的所有限价单 + # 遍历停止单字典中的所有停止单 for stopOrderID, so in self.workingStopOrderDict.items(): # 判断是否会成交 buyCross = so.direction==DIRECTION_LONG and so.price<=buyCrossPrice @@ -310,6 +327,7 @@ class BacktestingEngine(object): trade.price = so.price trade.volume = so.volume trade.tradeTime = str(self.dt) + trade.dt = self.dt self.strategy.onTrade(trade) self.tradeDict[tradeID] = trade @@ -331,6 +349,8 @@ class BacktestingEngine(object): order.orderTime = trade.tradeTime self.strategy.onOrder(order) + self.limitOrderDict[orderID] = order + # 从字典中删除该限价单 del self.workingStopOrderDict[stopOrderID] @@ -349,33 +369,129 @@ class BacktestingEngine(object): """直接返回初始化数据列表中的Tick""" return self.initData - #---------------------------------------------------------------------- - def getToday(self): - """获取代表今日的datetime对象""" - # 这个方法本身主要用于在每日初始化时确定日期,从而知道该读取之前从某日起的数据 - # 这里选择策略启动的日期 - return self.strategyStartDate - #---------------------------------------------------------------------- def writeCtaLog(self, content): """记录日志""" - print content + log = str(self.dt) + ' ' + content + self.logList.append(log) #---------------------------------------------------------------------- def output(self, content): """输出内容""" print content + #---------------------------------------------------------------------- + def showBacktestingResult(self): + """ + 显示回测结果 + """ + self.output(u'显示回测结果') + + # 首先基于回测后的成交记录,计算每笔交易的盈亏 + pnlDict = OrderedDict() # 每笔盈亏的记录 + longTrade = [] # 未平仓的多头交易 + shortTrade = [] # 未平仓的空头交易 + + for trade in self.tradeDict.values(): + # 多头交易 + if trade.direction == DIRECTION_LONG: + # 如果尚无空头交易 + if not shortTrade: + longTrade.append(trade) + # 当前多头交易为平空 + else: + entryTrade = shortTrade.pop(0) + pnl = (trade.price - entryTrade.price) * trade.volume * (-1) + pnlDict[trade.dt] = pnl + # 空头交易 + else: + # 如果尚无多头交易 + if not longTrade: + shortTrade.append(trade) + # 当前空头交易为平多 + else: + entryTrade = longTrade.pop(0) + pnl = (trade.price - entryTrade.price) * trade.volume + pnlDict[trade.dt] = pnl + + # 然后基于每笔交易的结果,我们可以计算具体的盈亏曲线和最大回撤等 + timeList = pnlDict.keys() + pnlList = pnlDict.values() + + capital = 0 + maxCapital = 0 + drawdown = 0 + + capitalList = [] # 盈亏汇总的时间序列 + maxCapitalList = [] # 最高盈利的时间序列 + drawdownList = [] # 回撤的时间序列 + + for pnl in pnlList: + capital += pnl + maxCapital = max(capital, maxCapital) + drawdown = capital - maxCapital + + capitalList.append(capital) + maxCapitalList.append(maxCapital) + drawdownList.append(drawdown) + + # 绘图 + import matplotlib.pyplot as plt + + pCapital = plt.subplot(3, 1, 1) + pCapital.set_ylabel("capital") + pCapital.plot(capitalList) + + pDD = plt.subplot(3, 1, 2) + pDD.set_ylabel("DD") + pDD.bar(range(len(drawdownList)), drawdownList) + + pPnl = plt.subplot(3, 1, 3) + pPnl.set_ylabel("pnl") + pPnl.hist(pnlList, bins=20) + + # 输出 + self.output('-' * 50) + self.output(u'第一笔交易时间:%s' % timeList[0]) + self.output(u'最后一笔交易时间:%s' % timeList[-1]) + self.output(u'总交易次数:%s' % len(pnlList)) + self.output(u'总盈亏:%s' % capitalList[-1]) + self.output(u'最大回撤: %s' % min(drawdownList)) + + #---------------------------------------------------------------------- + def putStrategyEvent(self, name): + """发送策略更新事件,回测中忽略""" + pass -#---------------------------------------------------------------------- -def test(): - """""" + +if __name__ == '__main__': + # 以下内容是一段回测脚本的演示,用户可以根据自己的需求修改 + # 建议使用ipython notebook或者spyder来做回测 + # 同样可以在命令模式下进行回测(一行一行输入运行) + from ctaDemo import * + + # 创建回测引擎 engine = BacktestingEngine() + + # 设置引擎的回测模式为K线 engine.setBacktestingMode(engine.BAR_MODE) - engine.initStrategy(u'测试', TestStrategy) - engine.setStartDate() + + # 设置回测用的数据起始日期 + engine.setStartDate('20100416') + + # 载入历史数据到引擎中 engine.loadHistoryData(MINUTE_DB_NAME, 'IF0000') + + # 在引擎中创建策略对象 + engine.initStrategy(DoubleEmaDemo, {}) + + # 开始跑回测 engine.runBacktesting() + # 显示回测结果 + # spyder或者ipython notebook中运行时,会弹出盈亏曲线图 + # 直接在cmd中回测则只会打印一些回测数值 + engine.showBacktestingResult() + \ No newline at end of file diff --git a/vn.trader/ctaObject.py b/vn.trader/ctaAlgo/ctaBase.py similarity index 81% rename from vn.trader/ctaObject.py rename to vn.trader/ctaAlgo/ctaBase.py index 92b4ff88..4d91d33e 100644 --- a/vn.trader/ctaObject.py +++ b/vn.trader/ctaAlgo/ctaBase.py @@ -1,7 +1,40 @@ # encoding: UTF-8 -from vtConstant import EMPTY_UNICODE, EMPTY_STRING, EMPTY_FLOAT, EMPTY_INT +''' +本文件中包含了CTA模块中用到的一些基础设置、类和常量等。 +''' +from __future__ import division + + +# 把vn.trader根目录添加到python环境变量中 +import sys +sys.path.append('..') + + +# 常量定义 +# CTA引擎中涉及到的交易方向类型 +CTAORDER_BUY = u'买开' +CTAORDER_SELL = u'卖平' +CTAORDER_SHORT = u'卖开' +CTAORDER_COVER = u'买平' + +# 本地停止单状态 +STOPORDER_WAITING = u'等待中' +STOPORDER_CANCELLED = u'已撤销' +STOPORDER_TRIGGERED = u'已触发' + +# 本地停止单前缀 +STOPORDERPREFIX = 'CtaStopOrder.' + +# 数据库名称 +TICK_DB_NAME = 'VtTrader_Tick_Db' +DAILY_DB_NAME = 'VtTrader_Daily_Db' +MINUTE_DB_NAME = 'VtTrader_1Min_Db' + + +# CTA引擎中涉及的数据类定义 +from vtConstant import EMPTY_UNICODE, EMPTY_STRING, EMPTY_FLOAT, EMPTY_INT ######################################################################## class StopOrder(object): @@ -92,5 +125,4 @@ class CtaTickData(object): self.askVolume2 = EMPTY_INT self.askVolume3 = EMPTY_INT self.askVolume4 = EMPTY_INT - self.askVolume5 = EMPTY_INT - + self.askVolume5 = EMPTY_INT \ No newline at end of file diff --git a/vn.trader/ctaAlgo/ctaDemo.py b/vn.trader/ctaAlgo/ctaDemo.py new file mode 100644 index 00000000..ea460dd2 --- /dev/null +++ b/vn.trader/ctaAlgo/ctaDemo.py @@ -0,0 +1,184 @@ +# encoding: UTF-8 + +""" +这里的Demo是一个最简单的策略实现,并未考虑太多实盘中的交易细节,如: +1. 委托价格超出涨跌停价导致的委托失败 +2. 委托未成交,需要撤单后重新委托 +3. 断网后恢复交易状态 +4. 等等 + +这些点是作者选择特意忽略不去实现,因此想实盘的朋友请自己多多研究CTA交易的一些细节, +做到了然于胸后再去交易,对自己的money和时间负责。 + +也希望社区能做出一个解决了以上潜在风险的Demo出来。 +""" + + +from ctaBase import * +from ctaTemplate import CtaTemplate + + +######################################################################## +class DoubleEmaDemo(CtaTemplate): + """双指数均线策略Demo""" + className = 'DoubleEmaDemo' + author = u'用Python的交易员' + + # 策略参数 + fastK = 0.9 # 快速EMA参数 + slowK = 0.1 # 慢速EMA参数 + initDays = 10 # 初始化数据所用的天数 + + # 策略变量 + bar = None + barMinute = EMPTY_STRING + + fastMa = [] # 快速EMA均线数组 + fastMa0 = EMPTY_FLOAT # 当前最新的快速EMA + fastMa1 = EMPTY_FLOAT # 上一根的快速EMA + + slowMa = [] # 与上面相同 + slowMa0 = EMPTY_FLOAT + slowMa1 = EMPTY_FLOAT + + # 参数列表,保存了参数的名称 + paramList = ['name', + 'className', + 'author', + 'vtSymbol', + 'fastK', + 'slowK'] + + # 变量列表,保存了变量的名称 + varList = ['inited', + 'trading', + 'pos', + 'fastMa0', + 'fastMa1', + 'slowMa0', + 'slowMa1'] + + #---------------------------------------------------------------------- + def __init__(self, ctaEngine, setting): + """Constructor""" + super(DoubleEmaDemo, self).__init__(ctaEngine, setting) + + #---------------------------------------------------------------------- + def onInit(self): + """初始化策略(必须由用户继承实现)""" + self.writeCtaLog(u'双EMA演示策略初始化') + + initData = self.loadBar(self.initDays) + for bar in initData: + self.onBar(bar) + + self.putEvent() + + #---------------------------------------------------------------------- + def onStart(self): + """启动策略(必须由用户继承实现)""" + self.writeCtaLog(u'双EMA演示策略启动') + self.putEvent() + + #---------------------------------------------------------------------- + def onStop(self): + """停止策略(必须由用户继承实现)""" + self.writeCtaLog(u'双EMA演示策略停止') + self.putEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """收到行情TICK推送(必须由用户继承实现)""" + # 计算K线 + tickMinute = tick.datetime.minute + + if tickMinute != self.barMinute: + if self.bar: + self.onBar(self.bar) + + bar = CtaBarData() + bar.vtSymbol = tick.vtSymbol + bar.symbol = tick.symbol + bar.exchange = tick.exchange + + bar.open = tick.lastPrice + bar.high = tick.lastPrice + bar.low = tick.lastPrice + bar.close = tick.lastPrice + + bar.date = tick.date + bar.time = tick.time + bar.datetime = tick.datetime # K线的时间设为第一个Tick的时间 + + # 实盘中用不到的数据可以选择不算,从而加快速度 + #bar.volume = tick.volume + #bar.openInterest = tick.openInterest + + self.bar = bar # 这种写法为了减少一层访问,加快速度 + self.barMinute = tickMinute # 更新当前的分钟 + + else: # 否则继续累加新的K线 + bar = self.bar # 写法同样为了加快速度 + + bar.high = max(bar.high, tick.lastPrice) + bar.low = min(bar.low, tick.lastPrice) + bar.close = tick.lastPrice + + #---------------------------------------------------------------------- + def onBar(self, bar): + """收到Bar推送(必须由用户继承实现)""" + # 计算快慢均线 + if not self.fastMa0: + self.fastMa0 = bar.close + self.fastMa.append(self.fastMa0) + else: + self.fastMa1 = self.fastMa0 + self.fastMa0 = bar.close * self.fastK + self.fastMa0 * (1 - self.fastK) + self.fastMa.append(self.fastMa0) + + if not self.slowMa0: + self.slowMa0 = bar.close + self.slowMa.append(self.slowMa0) + else: + self.slowMa1 = self.slowMa0 + self.slowMa0 = bar.close * self.slowK + self.slowMa0 * (1 - self.slowK) + self.slowMa.append(self.slowMa0) + + # 判断买卖 + crossOver = self.fastMa0>self.slowMa0 and self.fastMa1self.slowMa1 # 死叉下穿 + + # 金叉和死叉的条件是互斥 + # 所有的委托均以K线收盘价委托(这里有一个实盘中无法成交的风险,考虑添加对模拟市价单类型的支持) + if crossOver: + # 如果金叉时手头没有持仓,则直接做多 + if self.pos == 0: + self.buy(bar.close, 1) + # 如果有空头持仓,则先平空,再做多 + elif self.pos < 0: + self.cover(bar.close, 1) + self.buy(bar.close, 1) + # 死叉和金叉相反 + elif crossBelow: + if self.pos == 0: + self.short(bar.close, 1) + elif self.pos > 0: + self.sell(bar.close, 1) + self.short(bar.close, 1) + + # 发出状态更新事件 + self.putEvent() + + #---------------------------------------------------------------------- + def onOrder(self, order): + """收到委托变化推送(必须由用户继承实现)""" + # 对于无需做细粒度委托控制的策略,可以忽略onOrder + pass + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """收到成交推送(必须由用户继承实现)""" + # 对于无需做细粒度委托控制的策略,可以忽略onOrder + pass + + \ No newline at end of file diff --git a/vn.trader/ctaEngine.py b/vn.trader/ctaAlgo/ctaEngine.py similarity index 78% rename from vn.trader/ctaEngine.py rename to vn.trader/ctaAlgo/ctaEngine.py index a23efc6f..efbbf305 100644 --- a/vn.trader/ctaEngine.py +++ b/vn.trader/ctaAlgo/ctaEngine.py @@ -1,22 +1,26 @@ # encoding: UTF-8 -from datetime import datetime -import json -from collections import OrderedDict +''' +本文件中实现了CTA策略引擎,针对CTA类型的策略,抽象简化了部分底层接口的功能。 +''' +import json +import os +from collections import OrderedDict +from datetime import datetime, timedelta + +from ctaBase import * +from ctaSetting import STRATEGY_CLASS from eventEngine import * from vtConstant import * from vtGateway import VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData -from ctaConstant import * -from ctaObject import * -from ctaStrategies import strategyClassDict - ######################################################################## class CtaEngine(object): """CTA策略引擎""" settingFileName = 'CTA_setting.json' + settingFileName = os.getcwd() + '\\ctaAlgo\\' + settingFileName #---------------------------------------------------------------------- def __init__(self, mainEngine, eventEngine): @@ -24,11 +28,14 @@ class CtaEngine(object): self.mainEngine = mainEngine self.eventEngine = eventEngine - # 保存策略对象的字典 - # key为策略名称,value为策略对象,注意策略名称不允许重复 + # 当前日期 + self.today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + + # 保存策略实例的字典 + # key为策略名称,value为策略实例,注意策略名称不允许重复 self.strategyDict = {} - # 保存vtSymbol和策略对象映射的字典(用于推送tick数据) + # 保存vtSymbol和策略实例映射的字典(用于推送tick数据) # 由于可能多个strategy交易同一个vtSymbol,因此key为vtSymbol # value为包含所有相关strategy对象的list self.tickStrategyDict = {} @@ -48,7 +55,7 @@ class CtaEngine(object): # 注册事件监听 self.registerEvent() - + #---------------------------------------------------------------------- def sendOrder(self, vtSymbol, orderType, price, volume, strategy): """发单""" @@ -79,6 +86,9 @@ class CtaEngine(object): vtOrderID = self.mainEngine.sendOrder(req, contract.gatewayName) # 发单 self.orderStrategyDict[vtOrderID] = strategy # 保存vtOrderID和策略的映射关系 + + #self.writeCtaLog(u'发送委托:' + str(req.__dict__)) + return vtOrderID #---------------------------------------------------------------------- @@ -99,7 +109,7 @@ class CtaEngine(object): req.sessionID = order.sessionID req.orderID = order.orderID self.mainEngine.cancelOrder(req, order.gatewayName) - + #---------------------------------------------------------------------- def sendStopOrder(self, vtSymbol, orderType, price, volume, strategy): """发停止单(本地实现)""" @@ -141,7 +151,7 @@ class CtaEngine(object): so = self.workingStopOrderDict[stopOrderID] so.status = STOPORDER_CANCELLED del self.workingStopOrderDict[stopOrderID] - + #---------------------------------------------------------------------- def processStopOrder(self, tick): """收到行情后处理本地停止单(检查是否要立即发出)""" @@ -165,7 +175,7 @@ class CtaEngine(object): so.status = STOPORDER_TRIGGERED self.sendOrder(so.vtSymbol, so.orderType, price, so.volume, so.strategy) del self.workingStopOrderDict[so.stopOrderID] - + #---------------------------------------------------------------------- def procecssTickEvent(self, event): """处理行情推送""" @@ -173,7 +183,7 @@ class CtaEngine(object): # 收到tick行情后,先处理本地停止单(检查是否要立即发出) self.processStopOrder(tick) - # 推送tick到对应的策略对象进行处理 + # 推送tick到对应的策略实例进行处理 if tick.vtSymbol in self.tickStrategyDict: # 将vtTickData数据转化为ctaTickData ctaTick = CtaTickData() @@ -184,7 +194,7 @@ class CtaEngine(object): # 添加datetime字段 ctaTick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f') - # 逐个推送到策略对象中 + # 逐个推送到策略实例中 l = self.tickStrategyDict[tick.vtSymbol] for strategy in l: strategy.onTick(ctaTick) @@ -204,7 +214,14 @@ class CtaEngine(object): trade = event.dict_['data'] if trade.vtOrderID in self.orderStrategyDict: - strategy = self.orderStrategyDict[order.vtOrderID] + strategy = self.orderStrategyDict[trade.vtOrderID] + + # 计算策略持仓 + if trade.direction == DIRECTION_LONG: + strategy.pos += trade.volume + else: + strategy.pos -= trade.volume + strategy.onTrade(trade) #---------------------------------------------------------------------- @@ -213,47 +230,46 @@ class CtaEngine(object): self.eventEngine.register(EVENT_TICK, self.procecssTickEvent) self.eventEngine.register(EVENT_ORDER, self.processOrderEvent) self.eventEngine.register(EVENT_TRADE, self.processTradeEvent) - + #---------------------------------------------------------------------- def insertData(self, dbName, collectionName, data): """插入数据到数据库(这里的data可以是CtaTickData或者CtaBarData)""" self.mainEngine.dbInsert(dbName, collectionName, data.__dict__) #---------------------------------------------------------------------- - def loadBar(self, dbName, collectionName, startDate): + def loadBar(self, dbName, collectionName, days): """从数据库中读取Bar数据,startDate是datetime对象""" + startDate = self.today - timedelta(days) + d = {'datetime':{'$gte':startDate}} cursor = self.mainEngine.dbQuery(dbName, collectionName, d) l = [] - for d in cursor: - bar = CtaBarData() - bar.__dict__ = d - l.append(bar) + if cursor: + for d in cursor: + bar = CtaBarData() + bar.__dict__ = d + l.append(bar) return l #---------------------------------------------------------------------- - def loadTick(self, dbName, collectionName, startDate): + def loadTick(self, dbName, collectionName, days): """从数据库中读取Tick数据,startDate是datetime对象""" + startDate = self.today - timedelta(days) + d = {'datetime':{'$gte':startDate}} cursor = self.mainEngine.dbQuery(dbName, collectionName, d) l = [] - for d in cursor: - tick = CtaTickData() - tick.__dict__ = d - l.append(tick) + if cursor: + for d in cursor: + tick = CtaTickData() + tick.__dict__ = d + l.append(tick) return l - #---------------------------------------------------------------------- - def getToday(self): - """获取代表今日的datetime对象""" - today = datetime.today() - today = today.replace(hour=0, minute=0, second=0, microsecond=0) - return today - #---------------------------------------------------------------------- def writeCtaLog(self, content): """快速发出CTA模块日志事件""" @@ -264,12 +280,27 @@ class CtaEngine(object): self.eventEngine.put(event) #---------------------------------------------------------------------- - def initStrategy(self, name, strategyClass, paramDict=None): - """初始化策略""" + def loadStrategy(self, setting): + """载入策略""" + try: + name = setting['name'] + className = setting['className'] + except Exception, e: + self.writeCtaLog(u'载入策略出错:%s' %e) + return + + # 获取策略类 + strategyClass = STRATEGY_CLASS.get(className, None) + if not strategyClass: + self.writeCtaLog(u'找不到策略类:%s' %className) + return + # 防止策略重名 - if name not in self.strategyDict: - # 创建策略对象 - strategy = strategyClass(self, name, paramDict) + if name in self.strategyDict: + self.writeCtaLog(u'策略实例重名:%s' %name) + else: + # 创建策略实例 + strategy = strategyClass(self, setting) self.strategyDict[name] = strategy # 保存Tick映射关系 @@ -289,8 +320,16 @@ class CtaEngine(object): self.mainEngine.subscribe(req, contract.gatewayName) else: self.writeCtaLog(u'%s的交易合约%s无法找到' %(name, strategy.vtSymbol)) + + #---------------------------------------------------------------------- + def initStrategy(self, name): + """初始化策略""" + if name in self.strategyDict: + strategy = self.strategyDict[name] + strategy.inited = True + strategy.onInit() else: - self.writeCtaLog(u'存在策略对象重名:' + name) + self.writeCtaLog(u'策略实例不存在:%s' %name) #--------------------------------------------------------------------- def startStrategy(self, name): @@ -298,11 +337,11 @@ class CtaEngine(object): if name in self.strategyDict: strategy = self.strategyDict[name] - if not strategy.trading: + if strategy.inited and not strategy.trading: strategy.trading = True - strategy.start() + strategy.onStart() else: - self.writeCtaLog(u'策略对象不存在:' + name) + self.writeCtaLog(u'策略实例不存在:%s' %name) #---------------------------------------------------------------------- def stopStrategy(self, name): @@ -312,7 +351,7 @@ class CtaEngine(object): if strategy.trading: strategy.trading = False - strategy.stop() + strategy.onStop() # 对该策略发出的所有限价单进行撤单 for vtOrderID, s in self.orderStrategyDict.items(): @@ -324,55 +363,45 @@ class CtaEngine(object): if so.strategy is strategy: self.cancelStopOrder(stopOrderID) else: - self.writeCtaLog(u'策略对象不存在:' + name) + self.writeCtaLog(u'策略实例不存在:%s' %name) #---------------------------------------------------------------------- - def saveStrategySetting(self): - """保存引擎中的策略配置""" + def saveSetting(self): + """保存策略配置""" with open(self.settingFileName, 'w') as f: - d = {} + l = [] - for name, strategy in self.strategyDict.items(): + for strategy in self.strategyDict.values(): setting = {} - setting['strategyClassName'] = strategy.strategyClassName for param in strategy.paramList: setting[param] = strategy.__getattribute__(param) - d[name] = setting + l.append(setting) - jsonD = json.dumps(d, indent=4) - f.write(jsonD) + jsonL = json.dumps(l, indent=4) + f.write(jsonL) #---------------------------------------------------------------------- - def loadStrategySetting(self): - """读取引擎中的策略配置""" + def loadSetting(self): + """读取策略配置""" with open(self.settingFileName) as f: - d = json.load(f) + l = json.load(f) - for name, setting in d.items(): - strategyClassName = setting['strategyClassName'] - - if strategyClassName in strategyClassDict: - strategyClass = strategyClassDict[strategyClassName] - self.initStrategy(name, strategyClass, setting) - else: - self.writeCtaLog(u'无法找到策略类:' + strategyClassName) - break + for setting in l: + self.loadStrategy(setting) #---------------------------------------------------------------------- def getStrategyVar(self, name): """获取策略当前的变量字典""" if name in self.strategyDict: strategy = self.strategyDict[name] - d = strategy.__dict__ varDict = OrderedDict() for key in strategy.varList: - if key in d: - varDict[key] = d[key] + varDict[key] = strategy.__getattribute__(key) return varDict else: - self.writeCtaLog(u'策略对象不存在:' + name) + self.writeCtaLog(u'策略实例不存在:' + name) return None #---------------------------------------------------------------------- @@ -380,16 +409,20 @@ class CtaEngine(object): """获取策略的参数字典""" if name in self.strategyDict: strategy = self.strategyDict[name] - d = strategy.__dict__ paramDict = OrderedDict() - for key in strategy.paramList: - if key in d: - paramDict[key] = d[key] + for key in strategy.paramList: + paramDict[key] = strategy.__getattribute__(key) return paramDict else: - self.writeCtaLog(u'策略对象不存在:' + name) - return None + self.writeCtaLog(u'策略实例不存在:' + name) + return None + + #---------------------------------------------------------------------- + def putStrategyEvent(self, name): + """触发策略状态变化事件(通常用于通知GUI更新)""" + event = Event(EVENT_CTA_STRATEGY+name) + self.eventEngine.put(event) diff --git a/vn.trader/ctaHistoryData.py b/vn.trader/ctaAlgo/ctaHistoryData.py similarity index 98% rename from vn.trader/ctaHistoryData.py rename to vn.trader/ctaAlgo/ctaHistoryData.py index eda87ed1..c35cc563 100644 --- a/vn.trader/ctaHistoryData.py +++ b/vn.trader/ctaAlgo/ctaHistoryData.py @@ -1,13 +1,20 @@ # encoding: UTF-8 +""" +本模块中主要包含: +1. 从通联数据下载历史行情的引擎 +2. 用来把MultiCharts导出的历史数据载入到MongoDB中用的函数 +""" + from datetime import datetime, timedelta import pymongo from time import time from multiprocessing.pool import ThreadPool -from vtConstant import EXCHANGE_CFFEX, EXCHANGE_DCE, EXCHANGE_CZCE, EXCHANGE_SHFE +from ctaBase import * +from vtConstant import * from datayesClient import DatayesClient -from ctaObject import CtaBarData + # 以下为vn.trader和通联数据规定的交易所代码映射 VT_TO_DATAYES_EXCHANGE = {} @@ -15,14 +22,8 @@ VT_TO_DATAYES_EXCHANGE[EXCHANGE_CFFEX] = 'CCFX' # 中金所 VT_TO_DATAYES_EXCHANGE[EXCHANGE_SHFE] = 'XSGE' # 上期所 VT_TO_DATAYES_EXCHANGE[EXCHANGE_CZCE] = 'XZCE' # 郑商所 VT_TO_DATAYES_EXCHANGE[EXCHANGE_DCE] = 'XDCE' # 大商所 - DATAYES_TO_VT_EXCHANGE = {v:k for k,v in VT_TO_DATAYES_EXCHANGE.items()} -# 数据库名称 -DAILY_DB_NAME = 'VtTrader_Daily_Db' -MINUTE_DB_NAME = 'VtTrader_1Min_Db' -SETTING_DB_NAME = 'VtTrader_Setting_Db' - ######################################################################## class HistoryDataEngine(object): diff --git a/vn.trader/ctaStrategies.py b/vn.trader/ctaAlgo/ctaSetting.py similarity index 62% rename from vn.trader/ctaStrategies.py rename to vn.trader/ctaAlgo/ctaSetting.py index 50cd0d74..81816b76 100644 --- a/vn.trader/ctaStrategies.py +++ b/vn.trader/ctaAlgo/ctaSetting.py @@ -8,9 +8,9 @@ 在CTA_setting.json中写入具体每个策略对象的类和合约设置。 ''' -from ctaStrategyTemplate import TestStrategy -from ctaDataRecorder import DataRecorder +from ctaTemplate import DataRecorder +from ctaDemo import DoubleEmaDemo -strategyClassDict = {} -strategyClassDict['TestStrategy'] = TestStrategy -strategyClassDict['DataRecorder'] = DataRecorder \ No newline at end of file +STRATEGY_CLASS = {} +STRATEGY_CLASS['DataRecorder'] = DataRecorder +STRATEGY_CLASS['DoubleEmaDemo'] = DoubleEmaDemo \ No newline at end of file diff --git a/vn.trader/ctaStrategyTemplate.py b/vn.trader/ctaAlgo/ctaTemplate.py similarity index 50% rename from vn.trader/ctaStrategyTemplate.py rename to vn.trader/ctaAlgo/ctaTemplate.py index 0bba12e4..66c5b104 100644 --- a/vn.trader/ctaStrategyTemplate.py +++ b/vn.trader/ctaAlgo/ctaTemplate.py @@ -1,59 +1,77 @@ # encoding: UTF-8 +''' +本文件包含了CTA引擎中的策略开发用模板,开发策略时需要继承CtaTemplate类。 +''' + +from ctaBase import * from vtConstant import * -from ctaConstant import * ######################################################################## -class CtaStrategyTemplate(object): +class CtaTemplate(object): """CTA策略模板""" - # 策略类的名称 - strategyClassName = 'Template' + + # 策略类的名称和作者 + className = 'CtaTemplate' + author = EMPTY_UNICODE + + # MongoDB数据库的名称,K线数据库默认为1分钟 + tickDbName = TICK_DB_NAME + barDbName = MINUTE_DB_NAME + + # 策略的基本参数 + name = EMPTY_UNICODE # 策略实例名称 + vtSymbol = EMPTY_STRING # 交易的合约vt系统代码 + + # 策略的基本变量,由引擎管理 + inited = False # 是否进行了初始化 + trading = False # 是否启动交易,由引擎管理 + pos = 0 # 持仓情况 # 参数列表,保存了参数的名称 - paramList = ['vtSymbol'] + paramList = ['name', + 'className', + 'author', + 'vtSymbol'] # 变量列表,保存了变量的名称 - varList = ['trading'] + varList = ['inited', + 'trading', + 'pos'] #---------------------------------------------------------------------- - def __init__(self, ctaEngine, name, setting=None): + def __init__(self, ctaEngine, setting): """Constructor""" self.ctaEngine = ctaEngine - self.name = name - - self.vtSymbol = EMPTY_STRING # 交易的合约vt系统代码 - - self.tickDbName = EMPTY_STRING # tick数据库名称 - self.barDbName = EMPTY_STRING # bar数据库名称 - - self.trading = False # 控制是否启动交易 - - self.init() # 初始化策略 - + + # 设置策略的参数 if setting: - self.setParam(setting) + d = self.__dict__ + for key in self.paramList: + if key in setting: + d[key] = setting[key] #---------------------------------------------------------------------- - def init(self): + def onInit(self): """初始化策略(必须由用户继承实现)""" raise NotImplementedError #---------------------------------------------------------------------- - def start(self): + def onStart(self): """启动策略(必须由用户继承实现)""" - self.trading = True + raise NotImplementedError #---------------------------------------------------------------------- - def stop(self): + def onStop(self): """停止策略(必须由用户继承实现)""" raise NotImplementedError - + #---------------------------------------------------------------------- def onTick(self, tick): """收到行情TICK推送(必须由用户继承实现)""" raise NotImplementedError - + #---------------------------------------------------------------------- def onOrder(self, order): """收到委托变化推送(必须由用户继承实现)""" @@ -72,51 +90,32 @@ class CtaStrategyTemplate(object): #---------------------------------------------------------------------- def buy(self, price, volume, stop=False): """买开""" - # 如果stop为True,则意味着发本地停止单 - if self.trading: - if stop: - orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, CTAORDER_BUY, price, volume, self) - else: - orderID = self.ctaEngine.sendOrder(self.vtSymbol, CTAORDER_BUY, price, volume, self) - return orderID - else: - return None + return self.sendOrder(CTAORDER_BUY, price, volume, stop) #---------------------------------------------------------------------- def sell(self, price, volume, stop=False): """卖平""" - # 如果stop为True,则意味着发本地停止单 - if self.trading: - if stop: - orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, CTAORDER_SELL, price, volume, self) - else: - orderID = self.ctaEngine.sendOrder(self.vtSymbol, CTAORDER_SELL, price, volume, self) - return orderID - else: - return None + return self.sendOrder(CTAORDER_SELL, price, volume, stop) #---------------------------------------------------------------------- def short(self, price, volume, stop=False): """卖开""" - # 如果stop为True,则意味着发本地停止单 - if self.trading: - if stop: - orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, CTAORDER_SHORT, price, volume, self) - else: - orderID = self.ctaEngine.sendOrder(self.vtSymbol, CTAORDER_SHORT, price, volume, self) - return orderID - else: - return None + return self.sendOrder(CTAORDER_SHORT, price, volume, stop) #---------------------------------------------------------------------- def cover(self, price, volume, stop=False): """买平""" + return self.sendOrder(CTAORDER_COVER, price, volume, stop) + + #---------------------------------------------------------------------- + def sendOrder(self, orderType, price, volume, stop=False): + """发送委托""" if self.trading: # 如果stop为True,则意味着发本地停止单 if stop: - orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, CTAORDER_COVER, price, volume, self) + orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, orderType, price, volume, self) else: - orderID = self.ctaEngine.sendOrder(self.vtSymbol, CTAORDER_COVER, price, volume, self) + orderID = self.ctaEngine.sendOrder(self.vtSymbol, orderType, price, volume, self) return orderID else: return None @@ -140,105 +139,131 @@ class CtaStrategyTemplate(object): self.ctaEngine.insertData(self.barDbName, self.vtSymbol, bar) #---------------------------------------------------------------------- - def loadTick(self, startDate): + def loadTick(self, days): """读取tick数据""" - return self.ctaEngine.loadTick(self.tickDbName, self.vtSymbol, startDate) + return self.ctaEngine.loadTick(self.tickDbName, self.vtSymbol, days) #---------------------------------------------------------------------- - def loadBar(self, startDate): + def loadBar(self, days): """读取bar数据""" - return self.ctaEngine.loadBar(self.barDbName, self.vtSymbol, startDate) - - #---------------------------------------------------------------------- - def setParam(self, setting): - """设置参数""" - d = self.__dict__ - for key in self.paramList: - if key in setting: - d[key] = setting[key] - - #---------------------------------------------------------------------- - def getToday(self): - """查询当前日期""" - return self.ctaEngine.getToday() + return self.ctaEngine.loadBar(self.barDbName, self.vtSymbol, days) #---------------------------------------------------------------------- def writeCtaLog(self, content): """记录CTA日志""" + content = self.name + ':' + content self.ctaEngine.writeCtaLog(content) + + #---------------------------------------------------------------------- + def putEvent(self): + """发出策略状态变化事件""" + self.ctaEngine.putStrategyEvent(self.name) - ######################################################################## -class TestStrategy(CtaStrategyTemplate): - """测试策略""" +class DataRecorder(CtaTemplate): + """ + 纯粹用来记录历史数据的工具(基于CTA策略), + 建议运行在实际交易程序外的一个vn.trader实例中, + 本工具会记录Tick和1分钟K线数据。 + """ + className = 'DataRecorder' + author = u'用Python的交易员' + # 策略的基本参数 + name = EMPTY_UNICODE # 策略实例名称 + vtSymbol = EMPTY_STRING # 交易的合约vt系统代码 + + # 策略的变量 + bar = None # K线数据对象 + barMinute = EMPTY_STRING # 当前的分钟,初始化设为-1 + + # 变量列表,保存了变量的名称 + varList = ['inited', + 'trading', + 'pos', + 'barMinute'] + #---------------------------------------------------------------------- - def __init__(self, ctaEngine, name, setting=None): + def __init__(self, ctaEngine, setting): """Constructor""" - super(TestStrategy, self).__init__(ctaEngine, name, setting) - - self.strategyClassName = 'TestStrategy' - - self.author = u'用Python的交易员' # 作者 - - self.pos = EMPTY_INT # 持仓 - self.lastPrice = EMPTY_FLOAT # 最新价 - - # 参数和变量列表设置 - self.paramList.append('author') - - self.varList.append('pos') - self.varList.append('lastPrice') - - # 测试用计数 - self.count = 0 - + super(DataRecorder, self).__init__(ctaEngine, setting) + #---------------------------------------------------------------------- - def init(self): - """初始化策略(必须由用户继承实现)""" - self.writeCtaLog(u'测试策略%s初始化' %self.name) - + def onInit(self): + """初始化""" + self.writeCtaLog(u'数据记录工具初始化') + #---------------------------------------------------------------------- - def start(self): + def onStart(self): """启动策略(必须由用户继承实现)""" - self.writeCtaLog(u'测试策略%s启动' %self.name) + self.writeCtaLog(u'数据记录工具启动') + self.putEvent() #---------------------------------------------------------------------- - def stop(self): + def onStop(self): """停止策略(必须由用户继承实现)""" - self.writeCtaLog(u'测试策略%s停止' %self.name) + self.writeCtaLog(u'数据记录工具停止') + self.putEvent() #---------------------------------------------------------------------- def onTick(self, tick): - """收到行情TICK推送(必须由用户继承实现)""" - self.writeCtaLog(u'测试策略%s收到Tick' %self.name) - self.lastPrice = tick.lastPrice + """收到行情TICK推送""" + # 收到Tick后,首先插入到数据库里 + self.insertTick(tick) + + # 计算K线 + tickMinute = tick.datetime.minute + + if tickMinute != self.barMinute: # 如果分钟变了,则把旧的K线插入数据库,并生成新的K线 + if self.bar: + self.onBar(self.bar) + + bar = CtaBarData() # 创建新的K线,目的在于防止之前K线对象在插入Mongo中被再次修改,导致出错 + bar.vtSymbol = tick.vtSymbol + bar.symbol = tick.symbol + bar.exchange = tick.exchange + + bar.open = tick.lastPrice + bar.high = tick.lastPrice + bar.low = tick.lastPrice + bar.close = tick.lastPrice + + bar.date = tick.date + bar.time = tick.time + bar.datetime = tick.datetime # K线的时间设为第一个Tick的时间 + + bar.volume = tick.volume + bar.openInterest = tick.openInterest + + self.bar = bar # 这种写法为了减少一层访问,加快速度 + self.barMinute = tickMinute # 更新当前的分钟 + + else: # 否则继续累加新的K线 + bar = self.bar # 写法同样为了加快速度 + + bar.high = max(bar.high, tick.lastPrice) + bar.low = min(bar.low, tick.lastPrice) + bar.close = tick.lastPrice + + bar.volume = bar.volume + tick.volume # 成交量是累加的 + bar.openInterest = tick.openInterest # 持仓量直接更新 #---------------------------------------------------------------------- def onOrder(self, order): - """收到委托变化推送(必须由用户继承实现)""" - print u'收到委托回报,委托编号%s' %order.orderID + """收到委托变化推送""" + pass #---------------------------------------------------------------------- def onTrade(self, trade): - """收到成交推送(必须由用户继承实现)""" - print u'收到成交回报,成交编号%s' %order.orderID + """收到成交推送""" + pass #---------------------------------------------------------------------- def onBar(self, bar): - """收到Bar推送(必须由用户继承实现)""" - self.count += 1 - - if self.count == 10: - self.buy(bar.close, 1) - if self.count == 20: - self.sell(bar.close, 1) - self.count = 0 - - #print u'收到推送' - - + """收到Bar推送""" + self.insertBar(bar) + \ No newline at end of file diff --git a/vn.trader/datayes.json b/vn.trader/ctaAlgo/datayes.json similarity index 100% rename from vn.trader/datayes.json rename to vn.trader/ctaAlgo/datayes.json diff --git a/vn.trader/datayesClient.py b/vn.trader/ctaAlgo/datayesClient.py similarity index 95% rename from vn.trader/datayesClient.py rename to vn.trader/ctaAlgo/datayesClient.py index e9fa6c32..2659d897 100644 --- a/vn.trader/datayesClient.py +++ b/vn.trader/ctaAlgo/datayesClient.py @@ -1,5 +1,8 @@ # encoding: UTF-8 +'''一个简单的通联数据客户端,主要使用requests开发,比通联官网的python例子更为简洁。''' + + import requests import json diff --git a/vn.trader/uiCtaWidget.py b/vn.trader/ctaAlgo/uiCtaWidget.py similarity index 71% rename from vn.trader/uiCtaWidget.py rename to vn.trader/ctaAlgo/uiCtaWidget.py index d37935bd..730783cc 100644 --- a/vn.trader/uiCtaWidget.py +++ b/vn.trader/ctaAlgo/uiCtaWidget.py @@ -1,68 +1,63 @@ # encoding: UTF-8 -'''CTA模块相关的GUI控制组件''' +''' +CTA模块相关的GUI控制组件 +''' + from uiBasicWidget import QtGui, QtCore, BasicCell from eventEngine import * ######################################################################## -class ValueMonitor(QtGui.QTableWidget): - """数值监控""" - signal = QtCore.pyqtSignal() +class CtaValueMonitor(QtGui.QTableWidget): + """参数监控""" #---------------------------------------------------------------------- def __init__(self, parent=None): """Constructor""" - super(ValueMonitor , self).__init__(parent) + super(CtaValueMonitor, self).__init__(parent) self.keyCellDict = {} - self.row = 0 self.data = None + self.inited = False self.initUi() - self.signal.connect(self.updateTable) #---------------------------------------------------------------------- def initUi(self): """初始化界面""" - self.setColumnCount(2) - - self.verticalHeader().setVisible(False) - self.horizontalHeader().setVisible(False) - + self.setRowCount(1) + self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) - self.setAlternatingRowColors(True) + + self.setMaximumHeight(self.sizeHint().height()) #---------------------------------------------------------------------- def updateData(self, data): """更新数据""" - self.data = data - self.signal.emit() - - #---------------------------------------------------------------------- - def updateTable(self): - """更新表格""" - for key, value in self.data.items(): - if key in self.keyCellDict: - cell = self.keyCellDict[key] - cell.setText(unicode(value)) - else: - # 创建并保存单元格 - keyCell = BasicCell(unicode(key)) - cell = BasicCell(unicode(value)) - self.keyCellDict[key] = cell - - # 移动到下一行 - self.insertRow(self.row) - self.setItem(self.row, 0, keyCell) - self.setItem(self.row, 1, cell) - self.row += 1 + if not self.inited: + self.setColumnCount(len(data)) + self.setHorizontalHeaderLabels(data.keys()) + + col = 0 + for k, v in data.items(): + cell = QtGui.QTableWidgetItem(unicode(v)) + self.keyCellDict[k] = cell + self.setItem(0, col, cell) + col += 1 + + self.inited = True + else: + for k, v in data.items(): + cell = self.keyCellDict[k] + cell.setText(unicode(v)) ######################################################################## class CtaStrategyManager(QtGui.QGroupBox): """策略管理组件""" + signal = QtCore.pyqtSignal(type(Event())) #---------------------------------------------------------------------- def __init__(self, ctaEngine, eventEngine, name, parent=None): @@ -82,28 +77,37 @@ class CtaStrategyManager(QtGui.QGroupBox): """初始化界面""" self.setTitle(self.name) - paramLabel = QtGui.QLabel(u'参数') - varLabel = QtGui.QLabel(u'变量') + self.paramMonitor = CtaValueMonitor(self) + self.varMonitor = CtaValueMonitor(self) - self.paramMonitor = ValueMonitor(self) - self.varMonitor = ValueMonitor(self) + maxHeight = 60 + self.paramMonitor.setMaximumHeight(maxHeight) + self.varMonitor.setMaximumHeight(maxHeight) + buttonInit = QtGui.QPushButton(u'初始化') buttonStart = QtGui.QPushButton(u'启动') buttonStop = QtGui.QPushButton(u'停止') + buttonInit.clicked.connect(self.init) buttonStart.clicked.connect(self.start) buttonStop.clicked.connect(self.stop) - hbox = QtGui.QHBoxLayout() - hbox.addWidget(buttonStart) - hbox.addWidget(buttonStop) - hbox.addStretch() + hbox1 = QtGui.QHBoxLayout() + hbox1.addWidget(buttonInit) + hbox1.addWidget(buttonStart) + hbox1.addWidget(buttonStop) + hbox1.addStretch() + + hbox2 = QtGui.QHBoxLayout() + hbox2.addWidget(self.paramMonitor) + + hbox3 = QtGui.QHBoxLayout() + hbox3.addWidget(self.varMonitor) vbox = QtGui.QVBoxLayout() - vbox.addLayout(hbox) - vbox.addWidget(paramLabel) - vbox.addWidget(self.paramMonitor) - vbox.addWidget(varLabel) - vbox.addWidget(self.varMonitor) + vbox.addLayout(hbox1) + vbox.addLayout(hbox2) + vbox.addLayout(hbox3) + self.setLayout(vbox) #---------------------------------------------------------------------- @@ -120,7 +124,13 @@ class CtaStrategyManager(QtGui.QGroupBox): #---------------------------------------------------------------------- def registerEvent(self): """注册事件监听""" - self.eventEngine.register(EVENT_TIMER, self.updateMonitor) + self.signal.connect(self.updateMonitor) + self.eventEngine.register(EVENT_CTA_STRATEGY+self.name, self.signal.emit) + + #---------------------------------------------------------------------- + def init(self): + """初始化策略""" + self.ctaEngine.initStrategy(self.name) #---------------------------------------------------------------------- def start(self): @@ -133,7 +143,6 @@ class CtaStrategyManager(QtGui.QGroupBox): self.ctaEngine.stopStrategy(self.name) - ######################################################################## class CtaEngineManager(QtGui.QWidget): """CTA引擎管理组件""" @@ -162,23 +171,28 @@ class CtaEngineManager(QtGui.QWidget): # 按钮 loadButton = QtGui.QPushButton(u'加载策略') + initAllButton = QtGui.QPushButton(u'全部初始化') startAllButton = QtGui.QPushButton(u'全部启动') stopAllButton = QtGui.QPushButton(u'全部停止') loadButton.clicked.connect(self.load) + initAllButton.clicked.connect(self.initAll) startAllButton.clicked.connect(self.startAll) stopAllButton.clicked.connect(self.stopAll) # 滚动区域,放置所有的CtaStrategyManager self.scrollArea = QtGui.QScrollArea() + self.scrollArea.setWidgetResizable(True) # CTA组件的日志监控 self.ctaLogMonitor = QtGui.QTextEdit() self.ctaLogMonitor.setReadOnly(True) + self.ctaLogMonitor.setMaximumHeight(200) # 设置布局 hbox2 = QtGui.QHBoxLayout() hbox2.addWidget(loadButton) + hbox2.addWidget(initAllButton) hbox2.addWidget(startAllButton) hbox2.addWidget(stopAllButton) hbox2.addStretch() @@ -193,14 +207,22 @@ class CtaEngineManager(QtGui.QWidget): def initStrategyManager(self): """初始化策略管理组件界面""" w = QtGui.QWidget() - hbox = QtGui.QHBoxLayout() + vbox = QtGui.QVBoxLayout() for name in self.ctaEngine.strategyDict.keys(): strategyManager = CtaStrategyManager(self.ctaEngine, self.eventEngine, name) - hbox.addWidget(strategyManager) + vbox.addWidget(strategyManager) - w.setLayout(hbox) - self.scrollArea.setWidget(w) + vbox.addStretch() + + w.setLayout(vbox) + self.scrollArea.setWidget(w) + + #---------------------------------------------------------------------- + def initAll(self): + """全部初始化""" + for name in self.ctaEngine.strategyDict.keys(): + self.ctaEngine.initStrategy(name) #---------------------------------------------------------------------- def startAll(self): @@ -218,7 +240,7 @@ class CtaEngineManager(QtGui.QWidget): def load(self): """加载策略""" if not self.strategyLoaded: - self.ctaEngine.loadStrategySetting() + self.ctaEngine.loadSetting() self.initStrategyManager() self.strategyLoaded = True self.ctaEngine.writeCtaLog(u'策略加载成功') diff --git a/vn.trader/ctaConstant.py b/vn.trader/ctaConstant.py deleted file mode 100644 index c01c0ed8..00000000 --- a/vn.trader/ctaConstant.py +++ /dev/null @@ -1,15 +0,0 @@ -# encoding: UTF-8 - -# CTA引擎中涉及到的交易方向类型 -CTAORDER_BUY = u'买开' -CTAORDER_SELL = u'卖平' -CTAORDER_SHORT = u'卖开' -CTAORDER_COVER = u'买平' - -# 本地停止单状态 -STOPORDER_WAITING = u'等待中' -STOPORDER_CANCELLED = u'已撤销' -STOPORDER_TRIGGERED = u'已触发' - -# 本地停止单前缀 -STOPORDERPREFIX = 'CtaStopOrder.' \ No newline at end of file diff --git a/vn.trader/ctaDataRecorder.py b/vn.trader/ctaDataRecorder.py deleted file mode 100644 index 7731c4c7..00000000 --- a/vn.trader/ctaDataRecorder.py +++ /dev/null @@ -1,106 +0,0 @@ -# encoding: UTF-8 - -from ctaStrategyTemplate import * -from ctaObject import CtaBarData - - -######################################################################## -class DataRecorder(CtaStrategyTemplate): - """ - 纯粹用来记录历史数据的工具(基于CTA策略), - 建议运行在实际交易程序外的一个vn.trader实例中, - 本工具会记录Tick和1分钟K线数据。 - """ - - #---------------------------------------------------------------------- - def __init__(self, ctaEngine, name, setting=None): - """Constructor""" - super(DataRecorder, self).__init__(ctaEngine, name, setting) - - self.strategyClassName = 'DataRecorder' - - self.author = u'用Python的交易员' - self.tickDbName = 'VtTrader_Tick_Db' - self.barDbName = 'VtTrader_1Min_Db' - - self.paramList.append('author') - - # 数据记录相关 - self.bar = None # K线数据对象 - self.barMinute = -1 # 当前的分钟,初始化设为-1 - - #---------------------------------------------------------------------- - def init(self): - """初始化""" - self.writeCtaLog(u'数据记录工具%s初始化' %self.name) - - #---------------------------------------------------------------------- - def start(self): - """启动策略(必须由用户继承实现)""" - self.writeCtaLog(u'数据记录工具%s启动' %self.name) - - #---------------------------------------------------------------------- - def stop(self): - """停止策略(必须由用户继承实现)""" - self.writeCtaLog(u'数据记录工具%s停止' %self.name) - - #---------------------------------------------------------------------- - def onTick(self, tick): - """收到行情TICK推送""" - # 收到Tick后,首先插入到数据库里 - self.insertTick(tick) - - # 计算K线 - tickMinute = tick.datetime.minute - - if tickMinute != self.barMinute: # 如果分钟变了,则把旧的K线插入数据库,并生成新的K线 - if self.bar: - self.onBar(self.bar) - - bar = CtaBarData() # 创建新的K线,目的在于防止之前K线对象在插入Mongo中被再次修改,导致出错 - bar.vtSymbol = tick.vtSymbol - bar.symbol = tick.symbol - bar.exchange = tick.exchange - - bar.open = tick.lastPrice - bar.high = tick.lastPrice - bar.low = tick.lastPrice - bar.close = tick.lastPrice - - bar.date = tick.date - bar.time = tick.time - bar.datetime = tick.datetime # K线的时间设为第一个Tick的时间 - - bar.volume = tick.volume - bar.openInterest = tick.openInterest - - self.bar = bar # 这种写法为了减少一层访问,加快速度 - self.barMinute = tickMinute # 更新当前的分钟 - - else: # 否则继续累加新的K线 - bar = self.bar # 写法同样为了加快速度 - - bar.high = max(bar.high, tick.lastPrice) - bar.low = min(bar.low, tick.lastPrice) - bar.close = tick.lastPrice - - bar.volume = bar.volume + tick.volume # 成交量是累加的 - bar.openInterest = tick.openInterest # 持仓量直接更新 - - #---------------------------------------------------------------------- - def onOrder(self, order): - """收到委托变化推送""" - pass - - #---------------------------------------------------------------------- - def onTrade(self, trade): - """收到成交推送""" - pass - - #---------------------------------------------------------------------- - def onBar(self, bar): - """收到Bar推送""" - self.insertBar(bar) - - - \ No newline at end of file diff --git a/vn.trader/eventType.py b/vn.trader/eventType.py index 10cfd10c..6ddaaacc 100644 --- a/vn.trader/eventType.py +++ b/vn.trader/eventType.py @@ -26,6 +26,7 @@ EVENT_ERROR = 'eError.' # 错误回报事件 # CTA模块相关 EVENT_CTA_LOG = 'eCtaLog' # CTA相关的日志事件 +EVENT_CTA_STRATEGY = 'eCtaStrategy.' # CTA策略状态变化事件 # Wind接口相关 EVENT_WIND_CONNECTREQ = 'eWindConnectReq' # Wind接口请求连接事件 diff --git a/vn.trader/uiBasicWidget.py b/vn.trader/uiBasicWidget.py index 2b76b79d..12003bf8 100644 --- a/vn.trader/uiBasicWidget.py +++ b/vn.trader/uiBasicWidget.py @@ -276,7 +276,7 @@ class BasicMonitor(QtGui.QTableWidget): for header in self.headerList: content = safeUnicode(data.__getattribute__(header)) cell = d[header] - cell.setContent(content, self.mainEngine) + cell.setContent(content) if self.saveData: # 如果设置了保存数据对象,则进行对象保存 cell.data = data @@ -575,10 +575,11 @@ class TradingWidget(QtGui.QFrame): EXCHANGE_GLOBEX, EXCHANGE_IDEALPRO] - currencyList = [CURRENCY_CNY, + currencyList = [CURRENCY_NONE, + CURRENCY_CNY, CURRENCY_USD] - productClassList = [PRODUCT_UNKNOWN, + productClassList = [PRODUCT_NONE, PRODUCT_EQUITY, PRODUCT_FUTURES, PRODUCT_OPTION] diff --git a/vn.trader/uiMainWindow.py b/vn.trader/uiMainWindow.py index f10b6c2a..1d993410 100644 --- a/vn.trader/uiMainWindow.py +++ b/vn.trader/uiMainWindow.py @@ -3,7 +3,7 @@ import psutil from uiBasicWidget import * -from uiCtaWidget import CtaEngineManager +from ctaAlgo.uiCtaWidget import CtaEngineManager ######################################################################## diff --git a/vn.trader/vtConstant.py b/vn.trader/vtConstant.py index db6cb70f..6efc3cdd 100644 --- a/vn.trader/vtConstant.py +++ b/vn.trader/vtConstant.py @@ -39,6 +39,7 @@ PRODUCT_FOREX = u'外汇' PRODUCT_UNKNOWN = u'未知' PRODUCT_SPOT = u'现货' PRODUCT_DEFER = u'延期' +PRODUCT_NONE = '' # 价格类型常量 PRICETYPE_LIMITPRICE = u'限价' @@ -68,4 +69,5 @@ EXCHANGE_IDEALPRO = 'IDEALPRO' # IB外汇ECN # 货币类型 CURRENCY_USD = 'USD' # 美元 CURRENCY_CNY = 'CNY' # 人民币 -CURRENCY_UNKNOWN = 'UNKNOWN' # 未知货币 \ No newline at end of file +CURRENCY_UNKNOWN = 'UNKNOWN' # 未知货币 +CURRENCY_NONE = '' # 空货币 \ No newline at end of file diff --git a/vn.trader/vtEngine.py b/vn.trader/vtEngine.py index af0a622a..fe5f5ff3 100644 --- a/vn.trader/vtEngine.py +++ b/vn.trader/vtEngine.py @@ -8,8 +8,7 @@ from pymongo.errors import ConnectionFailure from eventEngine import * from vtGateway import * -import uiBasicWidget -from ctaEngine import CtaEngine +from ctaAlgo.ctaEngine import CtaEngine ########################################################################