现在VeighNa交易是按日进行统计回测的,但是在之前的v1版本,是同时支持按日和按照交易数统计回测结果。
通过借用之前版本的代码,可以获得按交易数统计回测结果,下图示例,其中红框的都是按交易统计获得数据,比如交易胜率就是盈利交易次数比总交易次数:
代码改动,在cta_strategy.backtesting新增TradingResult类中,这里如果输入费率如果大于0.1,则费率按手数计算,小于按照保证金百分比计算。
class TradingResult:
"""每笔交易的结果"""
#----------------------------------------------------------------------
def __init__(self, entryPrice, entryDt, exitPrice,
exitDt, volume, rate, slippage, size):
"""Constructor"""
self.entryPrice = entryPrice # 开仓价格
self.exitPrice = exitPrice # 平仓价格
self.entryDt = entryDt # 开仓时间datetime
self.exitDt = exitDt # 平仓时间
self.volume = volume # 交易数量(+/-代表方向)
self.turnover = (self.entryPrice+self.exitPrice)*size*abs(volume) # 成交金额
if rate < 0.1:
self.commission = self.turnover*rate # 手续费成本
else:
self.commission = abs(volume)*rate*2
self.slippage = slippage*2*size*abs(volume) # 滑点成本
self.pnl = ((self.exitPrice - self.entryPrice) * volume * size
- self.commission - self.slippage) # 净盈亏
代码改动,在cta_strategy.backtesting的BacktestingEngine类中,其实这段代码基本就是1.92 copy过来,稍微改改。
#----------------------------------------------------------------------
def calculateBacktestingResult(self):
"""
计算回测结果
"""
self.output(u'计算回测结果')
# 检查成交记录
if not self.trades:
self.output(u'成交记录为空,无法计算回测结果')
noResult = {}
noResult['capital'] = 0
noResult['maxCapital'] = 0
noResult['drawdown'] = 0
noResult['totalResult'] = 0
noResult['totalTurnover'] = 0
noResult['totalCommission'] = 0
noResult['totalSlippage'] = 0
noResult['timeList'] = 0
noResult['pnlList'] = 0
noResult['capitalList'] = 0
noResult['drawdownList'] = 0
noResult['winningRate'] = 0
noResult['averageWinning'] = 0
noResult['averageLosing'] = 0
noResult['profitLossRatio'] = 0
noResult['posList'] = 0
noResult['tradeTimeList'] = 0
noResult['resultList'] = 0
noResult['maxDrawdown'] = 0
return noResult
# 首先基于回测后的成交记录,计算每笔交易的盈亏
resultList = [] # 交易结果列表
longTrade = [] # 未平仓的多头交易
shortTrade = [] # 未平仓的空头交易
tradeTimeList = [] # 每笔成交时间戳
posList = [0] # 每笔成交后的持仓情况
for trade in self.trades.values():
# 复制成交对象,因为下面的开平仓交易配对涉及到对成交数量的修改
# 若不进行复制直接操作,则计算完后所有成交的数量会变成0
trade = copy.copy(trade)
# 多头交易
if trade.direction == Direction.LONG:
# 如果尚无空头交易
if not shortTrade:
longTrade.append(trade)
# 当前多头交易为平空
else:
while True:
entryTrade = shortTrade[0]
exitTrade = trade
# 清算开平仓交易
closedVolume = min(exitTrade.volume, entryTrade.volume)
result = TradingResult(entryTrade.price, entryTrade.datetime,
exitTrade.price, exitTrade.datetime,
-closedVolume, self.rate, self.slippage, self.size)
resultList.append(result)
posList.extend([-1, 0])
tradeTimeList.extend([result.entryDt, result.exitDt])
# 计算未清算部分
entryTrade.volume -= closedVolume
exitTrade.volume -= closedVolume
# 如果开仓交易已经全部清算,则从列表中移除
if not entryTrade.volume:
shortTrade.pop(0)
# 如果平仓交易已经全部清算,则退出循环
if not exitTrade.volume:
break
# 如果平仓交易未全部清算,
if exitTrade.volume:
# 且开仓交易已经全部清算完,则平仓交易剩余的部分
# 等于新的反向开仓交易,添加到队列中
if not shortTrade:
longTrade.append(exitTrade)
break
# 如果开仓交易还有剩余,则进入下一轮循环
else:
pass
# 空头交易
else:
# 如果尚无多头交易
if not longTrade:
shortTrade.append(trade)
# 当前空头交易为平多
else:
while True:
entryTrade = longTrade[0]
exitTrade = trade
# 清算开平仓交易
closedVolume = min(exitTrade.volume, entryTrade.volume)
result = TradingResult(entryTrade.price, entryTrade.datetime,
exitTrade.price, exitTrade.datetime,
closedVolume, self.rate, self.slippage, self.size)
resultList.append(result)
posList.extend([1, 0])
tradeTimeList.extend([result.entryDt, result.exitDt])
# 计算未清算部分
entryTrade.volume -= closedVolume
exitTrade.volume -= closedVolume
# 如果开仓交易已经全部清算,则从列表中移除
if not entryTrade.volume:
longTrade.pop(0)
# 如果平仓交易已经全部清算,则退出循环
if not exitTrade.volume:
break
# 如果平仓交易未全部清算,
if exitTrade.volume:
# 且开仓交易已经全部清算完,则平仓交易剩余的部分
# 等于新的反向开仓交易,添加到队列中
if not longTrade:
shortTrade.append(exitTrade)
break
# 如果开仓交易还有剩余,则进入下一轮循环
else:
pass
# 到最后交易日尚未平仓的交易,则以最后价格平仓
if self.mode == BacktestingMode.BAR:
endPrice = self.bar.close_price
else:
endPrice = self.tick.last_price
for trade in longTrade:
result = TradingResult(trade.price, trade.datetime, endPrice, self.datetime,
trade.volume, self.rate, self.slippage, self.size)
resultList.append(result)
for trade in shortTrade:
result = TradingResult(trade.price, trade.datetime, endPrice, self.datetime,
-trade.volume, self.rate, self.slippage, self.size)
resultList.append(result)
# 检查是否有交易
if not resultList:
self.output(u'无交易结果')
return {}
# 然后基于每笔交易的结果,我们可以计算具体的盈亏曲线和最大回撤等
capital = 0 # 资金
maxCapital = 0 # 资金最高净值
drawdown = 0 # 回撤
totalResult = 0 # 总成交数量
totalTurnover = 0 # 总成交金额(合约面值)
totalCommission = 0 # 总手续费
totalSlippage = 0 # 总滑点
timeList = [] # 时间序列
pnlList = [] # 每笔盈亏序列
capitalList = [] # 盈亏汇总的时间序列
drawdownList = [] # 回撤的时间序列
winningResult = 0 # 盈利次数
losingResult = 0 # 亏损次数
totalWinning = 0 # 总盈利金额
totalLosing = 0 # 总亏损金额
maxDrawdown = 0 # 最大回撤
max_win_count = 0
win_count = 0
max_lose_count = 0
lose_count = 0
for result in resultList:
capital += result.pnl
maxCapital = max(capital, maxCapital)
drawdown = capital - maxCapital
maxDrawdown = min(drawdown, maxDrawdown)
pnlList.append(result.pnl)
timeList.append(result.exitDt) # 交易的时间戳使用平仓时间
capitalList.append(capital)
drawdownList.append(drawdown)
totalResult += 1
totalTurnover += result.turnover
totalCommission += result.commission
totalSlippage += result.slippage
if result.pnl >= 0:
winningResult += 1
totalWinning += result.pnl
max_lose_count = max(max_lose_count, lose_count)
lose_count = 0
win_count +=1
else:
losingResult += 1
totalLosing += result.pnl
max_win_count = max(max_win_count, win_count)
win_count = 0
lose_count +=1
# 计算盈亏相关数据
winningRate = winningResult / totalResult * 100 # 胜率
averageWinning = 0 # 这里把数据都初始化为0
averageLosing = 0
profitLossRatio = 0
if winningResult:
averageWinning = totalWinning / winningResult # 平均每笔盈利
if losingResult:
averageLosing = totalLosing / losingResult # 平均每笔亏损
if averageLosing:
profitLossRatio = -averageWinning / averageLosing # 盈亏比
#shaperadio
annualDays =240
dailyReturn = np.mean(pnlList) * 100
returnStd = np.std(pnlList) * 100
if returnStd:
sharpeRatio = dailyReturn / returnStd
else:
sharpeRatio = 0
# 返回回测结果
d = {}
d['capital'] = capital
d['maxCapital'] = maxCapital
d['drawdown'] = drawdown
d['totalResult'] = totalResult
d['totalTurnover'] = totalTurnover
d['totalCommission'] = totalCommission
d['totalSlippage'] = totalSlippage
d['timeList'] = timeList
d['pnlList'] = pnlList
d['capitalList'] = capitalList
d['drawdownList'] = drawdownList
d["averageProfit"] = capital/totalResult
d['winningRate'] = winningRate
d["totalWinning"] = totalWinning
d["winningResult"] = winningResult
d['averageWinning'] = averageWinning
d["totalLosing"] = totalLosing
d["losingResult"] = losingResult
d['averageLosing'] = averageLosing
d['profitLossRatio'] = profitLossRatio
d['posList'] = posList
d['tradeTimeList'] = tradeTimeList
d['resultList'] = resultList
d['maxDrawdown'] = maxDrawdown
d['sharpeRatio'] = sharpeRatio
d['returnStd'] = returnStd
d['max_win_count'] = max_win_count
d['max_lose_count'] = max_lose_count
return d
使用也很简单,BacktestingEngine在run_backtesting 后, 运行calculateBacktestingResult,可以返回一个Dict,带着相关按次数分析的回测结果。