修改回测模型,支持多次逐步加仓

This commit is contained in:
msincenselee 2016-10-16 17:25:34 +08:00
parent f79a63d19d
commit 5a3ad183a0

View File

@ -22,6 +22,7 @@ from vtConstant import *
from vtGateway import VtOrderData, VtTradeData
from vtFunction import loadMongoSetting
import logging
import copy
########################################################################
class BacktestingEngine(object):
@ -87,6 +88,8 @@ class BacktestingEngine(object):
self.dt = None # 最新的时间
self.gatewayName = u'BackTest'
self.usageCompounding = False # 是否使用简单复利
#----------------------------------------------------------------------
def setStartDate(self, startDate='20100416', initDays=10):
"""设置回测的启动日期"""
@ -903,6 +906,12 @@ class BacktestingEngine(object):
def calculateBacktestingResult(self):
"""
计算回测结果
Modified by Incense Lee
增加了支持逐步加仓的计算
例如前面共有6次开仓1手开仓+5次加仓每次1手平仓只有1次六手那么交易次数是6次开仓+平仓
暂不支持每次加仓数目不一致的核对因为比较复杂
增加组合的支持
"""
self.output(u'计算回测结果')
@ -911,7 +920,9 @@ class BacktestingEngine(object):
longTrade = [] # 未平仓的多头交易
shortTrade = [] # 未平仓的空头交易
i = 0
i = 1
tradeUnit = 1
longid = EMPTY_STRING
shortid = EMPTY_STRING
@ -920,9 +931,6 @@ class BacktestingEngine(object):
trade = self.tradeDict[tradeid]
if tradeid == '127':
pass
# 多头交易
if trade.direction == DIRECTION_LONG:
# 如果尚无空头交易
@ -931,18 +939,46 @@ class BacktestingEngine(object):
longid = tradeid
# 当前多头交易为平空
else:
entryTrade = shortTrade.pop(0)
gId = i # 交易组(多个平仓数为一组)
gt = 1 # 组合的交易次数
gr = None # 组合的交易结果
result = TradingResult(entryTrade.price, trade.price, -trade.volume,
self.rate, self.slippage, self.size)
if trade.volume >tradeUnit:
self.writeCtaLog(u'平仓数{0},组合编号:{1}'.format(trade.volume,gId))
gt = int(trade.volume/tradeUnit)
resultDict[trade.dt] = result
for tv in range(gt):
i = i+1
self.writeCtaLog(u'{6}.{7}.开空{0},short:{1},{8}.平空{2},cover:{3},vol:{4},净盈亏:{5}'
.format(entryTrade.tradeTime, entryTrade.price,
trade.tradeTime,trade.price, trade.volume,result.pnl,
i,shortid,tradeid))
entryTrade = shortTrade.pop(0)
result = TradingResult(entryTrade.price, trade.price, -tradeUnit,
self.rate, self.slippage, self.size, groupId=gId)
if tv == 0:
if gt==1:
resultDict[entryTrade.dt] = result
else:
gr = copy.deepcopy(result)
else:
gr.turnover = gr.turnover + result.turnover
gr.commission = gr.commission + result.commission
gr.slippage = gr.slippage + result.slippage
gr.pnl = gr.pnl + result.pnl
if tv == gt -1:
gr.volume = trade.volume
resultDict[entryTrade.dt] = gr
self.writeCtaLog(u'{9}@{6} [{7}:开空{0},short:{1}]-[{8}:平空{2},cover:{3},vol:{4}],净盈亏:{5}'
.format(entryTrade.tradeTime, entryTrade.price,
trade.tradeTime, trade.price, tradeUnit, result.pnl,
i, shortid, tradeid,gId))
i = i+1
if type(gr) != type(None):
self.writeCtaLog(u'组合净盈亏:{0}'.format(gr.pnl))
self.writeCtaLog(u'-------------')
# 空头交易
else:
@ -952,17 +988,47 @@ class BacktestingEngine(object):
shortid = tradeid
# 当前空头交易为平多
else:
entryTrade = longTrade.pop(0)
gId = i # 交易组(多个平仓数为一组)
gt = 1 # 组合的交易次数
gr = None # 组合的交易结果
result = TradingResult(entryTrade.price, trade.price, trade.volume,
self.rate, self.slippage, self.size)
resultDict[trade.dt] = result
if trade.volume >tradeUnit:
self.writeCtaLog(u'平仓数{0},组合编号:{1}'.format(trade.volume,gId))
gt = int(trade.volume/tradeUnit)
i = i+1
self.writeCtaLog(u'{6}.{7}开多{0},buy:{1},{8}.平多{2},sell:{3},vol:{4},净盈亏:{5}'
.format(entryTrade.tradeTime, entryTrade.price,
trade.tradeTime,trade.price, trade.volume,result.pnl,
i,longid,tradeid))
for tv in range(gt):
entryTrade = longTrade.pop(0)
result = TradingResult(entryTrade.price, trade.price, tradeUnit,
self.rate, self.slippage, self.size, groupId= gId)
if tv == 0:
if gt==1:
resultDict[entryTrade.dt] = result
else:
gr = copy.deepcopy(result)
else:
gr.turnover = gr.turnover + result.turnover
gr.commission = gr.commission + result.commission
gr.slippage = gr.slippage + result.slippage
gr.pnl = gr.pnl + result.pnl
if tv == gt -1:
gr.volume = trade.volume
resultDict[entryTrade.dt] = gr
self.writeCtaLog(u'{9}@{6} [{7}:开多{0},buy:{1}]-[{8}.平多{2},sell:{3},vol:{4}],净盈亏:{5}'
.format(entryTrade.tradeTime, entryTrade.price,
trade.tradeTime,trade.price, tradeUnit, result.pnl,
i, longid, tradeid, gId))
i = i+1
if type(gr) != type(None):
self.writeCtaLog(u'组合净盈亏:{0}'.format(gr.pnl))
self.writeCtaLog(u'-------------')
# 检查是否有交易
if not resultDict:
@ -970,9 +1036,15 @@ class BacktestingEngine(object):
return {}
# 然后基于每笔交易的结果,我们可以计算具体的盈亏曲线和最大回撤等
capital = 0 # 资金
maxCapital = 0 # 资金最高净值
initCapital = 40000 # 期初资金
capital = initCapital # 资金
maxCapital = initCapital # 资金最高净值
drawdown = 0 # 回撤
maxPnl = 0 # 最高盈利
minPnl = 0 # 最大亏损
maxVolume = 1 # 最大仓位数
compounding = 1 # 简单的复利基数如果资金是期初资金的x倍就扩大开仓比例,例如3w开1手6w开2手12w开4手)
wins = 0
totalResult = 0 # 总成交数量
totalTurnover = 0 # 总成交金额(合约面值)
@ -983,27 +1055,42 @@ class BacktestingEngine(object):
pnlList = [] # 每笔盈亏序列
capitalList = [] # 盈亏汇总的时间序列
drawdownList = [] # 回撤的时间序列
drawdownRateList = [] # 最大回撤比例的时间序列
for time, result in resultDict.items():
capital += result.pnl
maxCapital = max(capital, maxCapital)
drawdown = capital - maxCapital
pnlList.append(result.pnl)
# 是否使用简单复利
if self.usageCompounding:
compounding = int(capital/initCapital)
if result.pnl > 0:
wins += 1
capital += result.pnl*compounding
maxCapital = max(capital, maxCapital)
maxVolume = max(maxVolume, result.volume*compounding)
drawdown = capital - maxCapital
drawdownRate = round(float(drawdown*100/maxCapital),4)
pnlList.append(result.pnl*compounding)
timeList.append(time)
capitalList.append(capital)
drawdownList.append(drawdown)
drawdownRateList.append(drawdownRate)
totalResult += 1
totalTurnover += result.turnover
totalCommission += result.commission
totalSlippage += result.slippage
totalTurnover += result.turnover*compounding
totalCommission += result.commission*compounding
totalSlippage += result.slippage*compounding
# 返回回测结果
d = {}
d['capital'] = capital
d['initCapital'] = initCapital
d['capital'] = capital - initCapital
d['maxCapital'] = maxCapital
d['drawdown'] = drawdown
d['maxPnl'] = max(pnlList)
d['minPnl'] = min(pnlList)
d['maxVolume'] = maxVolume
d['totalResult'] = totalResult
d['totalTurnover'] = totalTurnover
d['totalCommission'] = totalCommission
@ -1012,6 +1099,8 @@ class BacktestingEngine(object):
d['pnlList'] = pnlList
d['capitalList'] = capitalList
d['drawdownList'] = drawdownList
d['drawdownRateList'] = drawdownRateList
d['winRate'] = round(100*wins/len(pnlList),4)
return d
#----------------------------------------------------------------------
@ -1028,8 +1117,17 @@ class BacktestingEngine(object):
self.output(u'最后一笔交易:\t%s' % d['timeList'][-1])
self.output(u'总交易次数:\t%s' % formatNumber(d['totalResult']))
self.output(u'期初资金:\t%s' % formatNumber(d['initCapital']))
self.output(u'总盈亏:\t%s' % formatNumber(d['capital']))
self.output(u'最大回撤: \t%s' % formatNumber(min(d['drawdownList'])))
self.output(u'资金最高净值:\t%s' % formatNumber(d['maxCapital']))
self.output(u'每笔最大盈利:\t%s' % formatNumber(d['maxPnl']))
self.output(u'每笔最大亏损:\t%s' % formatNumber(d['minPnl']))
self.output(u'净值最大回撤: \t%s' % formatNumber(min(d['drawdownList'])))
self.output(u'净值最大回撤率: \t%s' % formatNumber(min(d['drawdownRateList'])))
self.output(u'胜率:\t%s' % formatNumber(d['winRate']))
self.output(u'最大持仓:\t%s' % formatNumber(d['maxVolume']))
self.output(u'平均每笔盈利:\t%s' %formatNumber(d['capital']/d['totalResult']))
self.output(u'平均每笔滑点成本:\t%s' %formatNumber(d['totalSlippage']/d['totalResult']))
@ -1128,11 +1226,12 @@ class TradingResult(object):
"""每笔交易的结果"""
#----------------------------------------------------------------------
def __init__(self, entry, exit, volume, rate, slippage, size):
def __init__(self, entry, exit, volume, rate, slippage, size, groupId):
"""Constructor"""
self.entry = entry # 开仓价格
self.exit = exit # 平仓价格
self.volume = volume # 交易数量(+/-代表方向)
self.groupId = groupId # 主交易ID针对多手平仓
self.turnover = (self.entry+self.exit)*size # 成交金额
self.commission =round(float(self.turnover*rate),4) # 手续费成本