#encoding:UTF-8
#首先写入系统内置模块
import sys
from datetime import datetime,timedelta,date
from time import sleep
#其次,导入vnpy的基础模块
import sys
sys.path.append('..')
from vtConstant import EMPTY_STRING,EMPTY_INT,DIRECTION_LONG,DIRECTION_SHORT,OFFSET_OPEN,STATUS_CANCELLED
#然后是自己编写的模块
from ctaTemplate import*
from ctaBase import*
from ctaLineBar import*
class Strategy_TripleMa(CtaTemplate):
"""螺纹钢,5分钟级别,三均线策略"""
策略:
10,20,120均线均线做多空过滤
MA120之上
MA10 下穿 MA20,死叉,做空
MA10 上穿 MA20,金叉,平空
更新记录
"""
className='Strategy_TripleMa'
author=u'李来佳'
#策略在外部设置的参数
inputSS=1 #参数SS,下单,范围是1-100,步长为1,默认=1;
miniDiff=1 #商品的最小交易单位
def __init__(self,ctaEngine,setting=None):
"""Constructor"""
super(strategy_TripleMa,self).__init__(ctaEngine,setting)
#增加监控参数项目
self.paramList.append('inputSS') #仓位
self.varlList.append('entrust') #是否正在委托
self.curDateTime=None #当前Tick时间
self.curTick=None #上一次委托时间
self.cancelSeconds=60 #撤单时间
#定义日内交易窗口
self.openWindow=False #开市窗口
self.tradeWindow=False #交易窗口
self.closeWindow=False #收拾平仓窗口
self.inited=False #是否完成策略初始化
self.backtesting=False #是否回测
self.lineM5=None #5分钟K线
if setting:
#根据配置文件更新参数
self.setParam(setting)
#创建的M5 K线
lineM5Setting={}
lineM5Setting['name']=u'M5' #k线名称
lineM5Setting['barTimeInterval']=60*5 #k线的Bar时长
lineM5Setting['inputMa1Len']=10 #第一条均线
lineM5Setting['inputMa2Len']=20 #第二条均线
lineM5Setting['inoutMa3Len']=120 #第三条均线
lineM5Setting['minDiff']=self.minDiff
lineM5Setting['shortSymbol']=self.shortSymbol
self.lineM5=CtaLineBar(self,self.onBarM5,lineM5Setting)
self.onInit()
def onInit(self,force=False):
"""初始化"""
if force:
self.writeCtalog(u'策略强制初始化')
if self.inited:
self.writeCtaLog(u'已经初始化过,不再执行')
return
self.pos=EMPTY_INT #初始化持仓
self.entrust=EMPTY_INT #初始化委托状态
if not self.backtesting:
#这里需要加载前置数据
self.inited=True #更新初始化标志
self.trading=True #启动交易
self.putEvent()
self.writeCtaLog(u'策略初始化完成')
def onStart(self):
"""启动策略(必须由用户继承实现)"""
self.writeCtaLog(u'启动')
def onStop(self):
"""停止策略(必须由用户继承实现)"""
self.uncompleteOrders.clear()
self.pos=EMPTY_INT
self.entrust=EMPTY_INT
self.wrtieCtaLog(u'停止')
self.putEvent()
def onTrade(self,trade):
"""交易更新"""
self.writeCtaLog(u'{0},OnTrade(),当前持仓:{1}'.format(self.curDateTime,self.pos))
def onOrder(self,order):
"""报单更新"""
self.writeCtaLog(u'OnOrder()报单更新,orderID:{0},{1},totalVol:{2},tradeVol:{3},offset:{4},price:{5},direction:{6},status:{7}'
.format(order.orderID,order.vtSymbol,order.totalVolume,order.tradeVolume,
order.offset,order.price,order.direction,order.status))
#委托单主键,vnpy使用"gateway.orderid"的组合
orderkey=order.gatewayName+u'.'+order.orderID
if orderkey in self.uncompleteOrders:
if order.totalVolume==order.tradeVolume:
#开仓,平仓委托单全部成交
#平空仓完成(cover)
if self.uncompletedOrders[orderkey]['DIRECTION']==DIRECTION_LONG and order.offset!=OFFSET_OPEN:
self.writeCtaLog(u'平空仓完成')
#更新仓位
self.pos=EMPTY_INT
#平多仓完成(sell)
if self.uncompletedOrder[orderKey]['DIRECTION']==DIRECTION_SHORT and order.offset!=OFFSET_OPEN:
self.writeCtaLog(u'平空仓完成')
#更新仓位
self.pos=EMPTY_INT
#平多仓完成(sell)
if self.uncomPletedOrders[orderkey]['DIRECTION']==DIRECTION_SHORT and order.offset!=OFFSET_OPEN:
self.writeCtaLog(u'平多仓完成')
#更新仓位
self.pos=order.tradedVolume
#开空仓完成
if self.uncompletedOrders[orderkey]['DIRECTION']==DIRECTION_LONG and order.offset==OFFSET_OPEN:
self.writeCtaLog(u'开空仓完成')
self.pos=0-order.tradeVolume
del self.uncompletedOrders[orderkey]
if len(self.uncompletedOrders)==0:
self.entrust=0
elif order.tradeVolume>0 and not order.totalVolume==order.tradeVolume and order.offset!=OFFSET_OPEN:
#平仓委托单部分成交
pass
elif order.offset==OFFSET_OPEN and order.status==STATUS_CANCELLED:
#平仓委托单被撤销
self.entrust=0
pass
else:
self.writeCtaLog(u'OnOrder()委托单,total:{0},trade:{1}'
.format(order.totalVolume,order.tradeVolume,))
self.putEvent() #更新监控事件
def onStopOrder(self,orderRef):
"""停止单更新"""
self.writeCtaLog(u'{0},停止单触发,orderRef:{1}'.format(self.curDateTime,orderRef))
pass
def onTick(self,tick):
"""
行情更新
:type tick:object
"""
self.curTick=tick
if (tick.datetime.hour>=3 and tick.datetime.hour<=8) or (tick.datetime.hour>=16 and tick.datetime.hour<=20):
self.writeCtaLog(u'休市/集合竞价排名是数据不处理')
return
#更新策略执行的时间(用于回测时记录发生的时间)
self.curDateTime=tick.datetime
#计算交易时间和平仓事件
self.__timeWindow(tick)
#推送Tick到LineM5
self.lineM5.onTick(tick)
#首先检查是否是实盘运行还是数据预处理阶段
if not(self.inited and len(self.lineM5.lineMa3)>0):
return
def onBar(self,bar):
"""分钟K线数据更新(仅用于回测时,从策略外部调用)"""
#更新策略执行的时间(用于回测时记录发生的时间)
#回测数据传送的bar.datetime,为bar的开始时间,所以,到达策略是,当前时间为bar的结束时间
self.curDateTime=bar.datetime+timedelta(seconds=self.lineM5.barTimeInterval)
#2.计算交易时间和平仓时间
self.__timeWindow(bar.datetime)
#self.lineM5.addBar(bar)
#4.交易逻辑
#首先检查是否是实盘运行还是数据预处理阶段
if not self.inited:
if len(self.lineM5.lineBar)>120+5:
self.inited=True
else:
return
#执行撤单逻辑
self.__cancelLogic(dt=self.curDatetime)
#如果未持仓,检查是否符合开仓逻辑
if self.pos==0:
#MA10 上穿MA20,MA10>MA120,bar.close>MA120
if self.lineM5.lineMa1[-2]<self.lineM5.lineMa2[-1]\
and self.lineM5.lineMa1[-1]>self.lineM5.lineMa2[-1]\
and self.lineM5.lineMa1[-1]>self.lineM5.lineMa3[-1]\
and bar.close>self.lineM5.lineMa3[-1]:
self.writeCtaLog(u'{0},开仓空单{1}手,价格:{2}'.format(bar.datetime,self.inputSS,bar.close))
orderid=self.short(price=bar.close,volume=self.inputSS,orderTime=self.curDateTime)
if orderid:
self.lastOrderTime=self.curDateTime
return
#MA10,下穿MA20,MA10<MA120,bar.close<MA120
if self.lineMa5.lineMa1[-2]>self.lineM5.lineMa2[-2]\
and self.lineM5.lineMa1[-1]<self.lineM5.lineMa2[-1]\
and self.lineM5.lineMa1[-1]<self.lineM5.lineMa3[-1]\
and bar.close<self.lineM5.lineMa3[-1]:
self.writeCtaLog(u'{0},开仓空单{1}手,价格:{2}'.format(bar.datetime,self.inpuSS,bar.close))
orderid=self.short(price=bar.close,volume=self.inputSS,orderTime=self.curDateTime)
if orderid:
self.lastOrderTime=self.curDateTime
return
#持仓:检查是否满足平仓条件
else:
#MA10下穿MA20,多单离场
if self.lineM5.lineMa[-1]<self.lineM5.lineMa2[-1]\
and self.pos>0 and self.entrust !=-1:
self.wtriteCtaLog(u'{0},平仓多单{1}手,价格:{2}'.format(bar.datetime,self.inputSS,bar.close))
orderid=self.sell(price=bar.close,volume=self.inputSS,orderTime=self.curDateTime)
if orderid:
self.lastOrderTime=self.curDateTime
return
#MA10上穿MA20,空离场
if self.lineM5.lineMa1[-1]>self.lineM5.lineMa2[-1]\
and self.pos<0 and self.entrust!=1:
self.writeCtaLog(u'{0},平仓空单{1}手,价格:{2}'.format(bar.datetime,self.inputSS,bar.close))
orderid=self.cover(price=bar.close,volume=self.inputSS,orderTime=self.curDateTime)
if orderid:
self.lastOrderTime=self.curDateTime
return
def __cancelLogic(self,dt,force=False):
"""撤单逻辑"""
if len(self.uncompletedOrders)<1:
return
if not self.lastOrderTime:
self.writeCtaLog(u'异常,上一次交易时间为None')
return
#平仓检查时间比开仓时间需要短一倍
if(self.pos>=0 and self.entrust==1)\
or (self.pos<=0 and self.entrust==-1):
i=1
else:
i=1 #原来是2,暂时取消
canceled=False
if ((df-self.lastOrderTime).seconds>self.cancelSeconds/i) or force: #超过设置的时间还未成交
for order in self.uncompletedOrders.keys():
self.writeCtaLog(u'{0}超时{1}秒未成交,取消委托单:{2}'.format(dt,(dt-self.lastOrderTime).
self.cancelOrder(str(order))
canceled=True
if self.uncompletedOrders[order]['OFFSET']==OFFSET_OPEN:
self.lineM5.lineBar[-2].tradesStatus=CTAORDER_OPEN_FAIL
else:
self.lineM5.lineBar[-2].tradeStatus=CTAORDER_CLOSE_FAIL
#取消未完成的订单
self.uncompletedOrder.clear()
if canceled:
self.entrust=0
else:
self.writeCtaLog(u'异常:没有撤单')
def __timeWindow(self,dt):
"""交易与平仓窗口"""
#交易窗口,避开早盘和夜盘的前5分钟,防止隔夜跳空
self.closeWindow=False
self.tradeWindow=False
self.openWindow=False
#初始化当日的首次交易
#if(tick.datetime.hour==9 or tick.datetime.hour==21) and tick.datetime.minute==0 and tick.datetime.second==0:
# self.firstTrade=True
#开市期,波动较大,用于判断止损止盈,或开仓
if (dt.hour==9 or dt.hour==21) and dt.minute<2:
self.openWindow=True
#日盘
if dt.hour==9 and dt.minute>=0:
self.tradeWindow=True
return
if dt.hour==10:
if dt.minute<=15 or dt.minute>=30:
self.tradeWindow=True
return
if dt.hour==11 and dt.minute<=30:
self.tradeWindow=True
return
if dt.hour==13 and dt.minute>=30:
self.tradeWindow=True
return
if dt.hour==14:
if dt.minute<59:
self.tradeWindow=True
return
if dt.minute==59: #日盘平仓
self.closedWindwo=True
return
#夜盘
if dt.hour==21 and dt.minute?=0:
self.tradeWindow=True
return
#上期 贵金属,次日凌晨2:30
if self.shortSymbol in NIGHT_MARKET_SQ1:
if dt.hour==22 or dt.hour==23 or dt.hour==0 or dt.hour==1:
self.tradeWindow=True
return
if dt.hour==2:
if dt.minute<29: #收市前29分钟
self.tradeWindow=True
return
if dt.minute==29: #夜盘平仓
self.closeWindow=True
return
return
# 上期 有色金属,黑色金属,沥青 次日01:00
if self.shortSymbol in NIGHT_MARKET_SQ2:
if dt.hour == 22 or dt.hour == 23:
self.tradeWindow = True
return
if dt.hour == 0:
if dt.minute < 59: # 收市前29分钟
self.tradeWindow = True
return
if dt.minute == 59: # 夜盘平仓
self.closeWindow = True
return
return
# 上期 天然橡胶 23:00
if self.shortSymbol in NIGHT_MARKET_SQ3:
if dt.hour == 22:
if dt.minute < 59: # 收市前1分钟
self.tradeWindow = True
return
if dt.minute == 59: # 夜盘平仓
self.closeWindow = True
return
# 郑商、大连 23:30
if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL:
if dt.hour == 22:
self.tradeWindow = True
return
if dt.hour == 23:
if dt.minute < 29: # 收市前1分钟
self.tradeWindow = True
return
if dt.minute == 29 and dt.second > 30: # 夜盘平仓
self.closeWindow = True
return
return
def strToTime(self,t,ms):
"""从字符串时间转化为time格式的时间"""
hh,mm,ss=t.split(':')
tt=datetime.time(int(hh),int(mm),int(ss),microsecond=ms)
return tt
def saveData(self,id):
"""保存过程数据"""
保存K线
if not self.backtesing:
return
def testRbByTick():
#创建回测引擎
engine=BacktestingEngine()
#设置引擎的回测模式为Tick
engine.setBacktestingMode(engine.TICK_MODE)
#设置回测用的数据开始日期
engine.setStartDate('20160101')
#设置回测用的数据结束日期
engine.setEndDate('20160330')
#engine.connectMysql()
engine.setDataBase(dbName='stockcn',symbol='rb')
#设置产品相关参数
engine.setSlippage(0) #1跳(0.1),2跳0.2
engine.setRate(float(0.0001)) #万1
engine.setSize(10) #合约大小
settings={}
settings['shortSymbol']='RB'
settings['name]'='TripleMa'
settings['mode']='tick'
settings['bactesting']=True
#在引擎中创建策略对象
engine.initStrategy(Strategy_TripleMa,setting=settings)
#使用简单复利模式计算
engine=usageComputing=False #True时,只针对FINAL_MODE有效
#启用实时计算净值模式REALTIME_MODE/FINAL_MODE 回测结束时统一计算模式
engine.calculateMode=engine.REAlTIME_MODE
engine.initCapital=300000 #设置期初资金
engine.percentLimit=30 #设置资金使用上限比例(%)
engine.barTimeInterval=60.5 #bar的周期描述,用于csvw文件自动减时间
engine.fixCommission=10 #固定交易费用(每次开平仓收费)
#开始跑回测
engine.runBacktestingWithMysql()
#显示回测结果
engine.showBacktestingResult()
def testRbByBar():
#创建回测引擎
engine=BacktestingEngine()
#设置引擎的回测模式为bar
engine.settingBacktestingMode(engine.BAR_MODE)
#设置回测用的数据起始日期
engine.setStartDate('20160101')
#设置回测用的数据结束日期
engine.setEndDate('20161231')
engine.setDatabasr(dbName='stockcn',symbol='rb')
#设置产品相关参数
engine.setSlippage(0) #1跳(0.1)2跳0.2
engine.setRate(float(0.0001)) #万1
engine.setSize(10) #合约大小
settings={}
settings['shortSymbol']='RB'
settings['name']='M15RB'
settings['mode']='bar'
setting['backtesting']=True
settings['percentLimit']=30
#在引擎中创建策略对象
engine.initStrategy(Strategy_TripleMa,setting=settings)
#使用简单复利模式计算
engine.usageCompounding=False #True时,只指针FINAL_MODE有效
#启用实时计算净值模块REALTIME_MODE/FINAL_MODE回测结束时统一计算模式
engine.calculateMode=engine.REALTIME_MODE
engine.initCapital=100000 #设置期初资金
engine.percentLimit=30 #设置资金使用上限比例(%)
engine.barTimeInterval=300 #bar的周期秒数,用于CSV文件自动减时间
#开始跑回测
engine.runBackTesingWithBarFile(os.getcwd()+'/cache/RB_20160101_20161231_M5.csv')
#显示回测结果
engine.showBacktestingResult()
#从csv文件进行回测
if __name__=='__main__':
#提供直接双击回测的功能
#导入PyQt4的包是为了保证matplotlib使用PyQt4而不是PySide,防止初始化出错
from ctaBastesting import*
from setup_logger import setup_logger
setup_logger(
filename=u'TestLogs/{0}_{1}'.format(Strategy_TripleMa.className,datetime.now().strftime('%m%d_%H%M')),
debug=False)
#回测螺纹
testRbBybar()