VeighNa增加按交易数统计回测结果

现在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,带着相关按次数分析的回测结果。


请使用浏览器的分享功能分享到微信等