初步完成了vn.trader的CTA策略模块,和vn.strategy相比的主要变化包括:
1. 重新设计的策略引擎API 2. 重新设计的策略模板,策略方面的开发更直观 3. 设计了一个基于EMA双均线的演示策略 4. 基于新的策略模板重写了行情记录工具DataRecorder
This commit is contained in:
parent
a6cc6cb339
commit
42df562e60
@ -1,10 +0,0 @@
|
||||
{
|
||||
"DR_IF1512": {
|
||||
"strategyClassName": "DataRecorder",
|
||||
"vtSymbol": "IF1512"
|
||||
},
|
||||
"DR_IH1512": {
|
||||
"strategyClassName": "DataRecorder",
|
||||
"vtSymbol": "IH1512"
|
||||
}
|
||||
}
|
Binary file not shown.
7
vn.trader/ctaAlgo/CTA_setting.json
Normal file
7
vn.trader/ctaAlgo/CTA_setting.json
Normal file
@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "double ema",
|
||||
"className": "DoubleEmaDemo",
|
||||
"vtSymbol": "IF1602"
|
||||
}
|
||||
]
|
Can't render this file because it is too large.
|
0
vn.trader/ctaAlgo/__init__.py
Normal file
0
vn.trader/ctaAlgo/__init__.py
Normal file
@ -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):
|
||||
@ -53,14 +52,16 @@ class BacktestingEngine(object):
|
||||
self.backtestingData = [] # 回测用的数据
|
||||
|
||||
self.dataStartDate = None # 回测数据开始日期,datetime对象
|
||||
self.strategyStartDate = None # 策略启动日期(即前面的数据用于初始化),同上
|
||||
self.strategyStartDate = None # 策略启动日期(即前面的数据用于初始化),datetime对象
|
||||
|
||||
self.limitOrderDict = {} # 限价单字典
|
||||
self.workingLimitOrderDict = {} # 活动限价单字典,用于进行撮合用
|
||||
self.limitOrderDict = OrderedDict() # 限价单字典
|
||||
self.workingLimitOrderDict = OrderedDict() # 活动限价单字典,用于进行撮合用
|
||||
self.limitOrderCount = 0 # 限价单编号
|
||||
|
||||
self.tradeCount = 0 # 成交编号
|
||||
self.tradeDict = {} # 成交字典
|
||||
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.dt = bar.datetime
|
||||
self.crossLimitOrder() # 先撮合限价单
|
||||
self.crossStopOrder() # 再撮合停止单
|
||||
self.strategy.onBar(bar)
|
||||
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()
|
||||
|
||||
|
@ -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):
|
||||
@ -93,4 +126,3 @@ class CtaTickData(object):
|
||||
self.askVolume3 = EMPTY_INT
|
||||
self.askVolume4 = EMPTY_INT
|
||||
self.askVolume5 = EMPTY_INT
|
||||
|
184
vn.trader/ctaAlgo/ctaDemo.py
Normal file
184
vn.trader/ctaAlgo/ctaDemo.py
Normal file
@ -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.fastMa1<self.slowMa1 # 金叉上穿
|
||||
crossBelow = self.fastMa0<self.slowMa0 and self.fastMa1>self.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
|
||||
|
||||
|
@ -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 = {}
|
||||
@ -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
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
@ -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)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
@ -220,12 +237,15 @@ class CtaEngine(object):
|
||||
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 = []
|
||||
if cursor:
|
||||
for d in cursor:
|
||||
bar = CtaBarData()
|
||||
bar.__dict__ = d
|
||||
@ -234,12 +254,15 @@ class CtaEngine(object):
|
||||
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 = []
|
||||
if cursor:
|
||||
for d in cursor:
|
||||
tick = CtaTickData()
|
||||
tick.__dict__ = d
|
||||
@ -247,13 +270,6 @@ class CtaEngine(object):
|
||||
|
||||
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]
|
||||
paramDict[key] = strategy.__getattribute__(key)
|
||||
|
||||
return paramDict
|
||||
else:
|
||||
self.writeCtaLog(u'策略对象不存在:' + name)
|
||||
self.writeCtaLog(u'策略实例不存在:' + name)
|
||||
return None
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def putStrategyEvent(self, name):
|
||||
"""触发策略状态变化事件(通常用于通知GUI更新)"""
|
||||
event = Event(EVENT_CTA_STRATEGY+name)
|
||||
self.eventEngine.put(event)
|
||||
|
||||
|
@ -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):
|
@ -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
|
||||
STRATEGY_CLASS = {}
|
||||
STRATEGY_CLASS['DataRecorder'] = DataRecorder
|
||||
STRATEGY_CLASS['DoubleEmaDemo'] = DoubleEmaDemo
|
@ -1,51 +1,69 @@
|
||||
# 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
|
||||
|
||||
@ -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,104 +139,130 @@ 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)
|
||||
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
'''一个简单的通联数据客户端,主要使用requests开发,比通联官网的python例子更为简洁。'''
|
||||
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
@ -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.setRowCount(1)
|
||||
self.verticalHeader().setVisible(False)
|
||||
self.horizontalHeader().setVisible(False)
|
||||
|
||||
self.setEditTriggers(self.NoEditTriggers)
|
||||
self.setAlternatingRowColors(True)
|
||||
|
||||
self.setMaximumHeight(self.sizeHint().height())
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def updateData(self, data):
|
||||
"""更新数据"""
|
||||
self.data = data
|
||||
self.signal.emit()
|
||||
if not self.inited:
|
||||
self.setColumnCount(len(data))
|
||||
self.setHorizontalHeaderLabels(data.keys())
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def updateTable(self):
|
||||
"""更新表格"""
|
||||
for key, value in self.data.items():
|
||||
if key in self.keyCellDict:
|
||||
cell = self.keyCellDict[key]
|
||||
cell.setText(unicode(value))
|
||||
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:
|
||||
# 创建并保存单元格
|
||||
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
|
||||
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,15 +207,23 @@ 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)
|
||||
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'策略加载成功')
|
@ -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.'
|
@ -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)
|
||||
|
||||
|
||||
|
@ -26,6 +26,7 @@ EVENT_ERROR = 'eError.' # 错误回报事件
|
||||
|
||||
# CTA模块相关
|
||||
EVENT_CTA_LOG = 'eCtaLog' # CTA相关的日志事件
|
||||
EVENT_CTA_STRATEGY = 'eCtaStrategy.' # CTA策略状态变化事件
|
||||
|
||||
# Wind接口相关
|
||||
EVENT_WIND_CONNECTREQ = 'eWindConnectReq' # Wind接口请求连接事件
|
||||
|
@ -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]
|
||||
|
@ -3,7 +3,7 @@
|
||||
import psutil
|
||||
|
||||
from uiBasicWidget import *
|
||||
from uiCtaWidget import CtaEngineManager
|
||||
from ctaAlgo.uiCtaWidget import CtaEngineManager
|
||||
|
||||
|
||||
########################################################################
|
||||
|
@ -39,6 +39,7 @@ PRODUCT_FOREX = u'外汇'
|
||||
PRODUCT_UNKNOWN = u'未知'
|
||||
PRODUCT_SPOT = u'现货'
|
||||
PRODUCT_DEFER = u'延期'
|
||||
PRODUCT_NONE = ''
|
||||
|
||||
# 价格类型常量
|
||||
PRICETYPE_LIMITPRICE = u'限价'
|
||||
@ -69,3 +70,4 @@ EXCHANGE_IDEALPRO = 'IDEALPRO' # IB外汇ECN
|
||||
CURRENCY_USD = 'USD' # 美元
|
||||
CURRENCY_CNY = 'CNY' # 人民币
|
||||
CURRENCY_UNKNOWN = 'UNKNOWN' # 未知货币
|
||||
CURRENCY_NONE = '' # 空货币
|
@ -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
|
||||
|
||||
|
||||
########################################################################
|
||||
|
Loading…
Reference in New Issue
Block a user