量化交易backtrader实践(四)_评价统计篇(4)_多个回测的评价列表

news2024/12/23 0:31:40

本节目标

在第1节里,我们认识了backtrader内置评价指标,了解了每个指标的大概内容;在第2节里把内置评价指标中比较常用的指标进行了获取和输出;第3节里我们探索其他backtrader中没有的评价指标,并对pyfolio, empyrical和quantstat库进行了初步的认识,以及使用quantstat可以方便的进行评价指标的可视化实践。以上的动作,都是针对一支股票和一个特定的策略进行的,而在我们的实际中,需要对多个股票和各种不同的策略进行回测,也可能对同一策略的不同参数组合进行回测。

这就需要生成多个回测的评价列表,这一节的实践就是这个。

多回测评价列表

01_多股票x多策略的评价列表

001_大致实践的思路

  • 前面我们已经有了单支股票一个策略的函数:run_main_analyser()
  • 那么多个股票乘上多种策略就可以采用两重循环来进行(这里的demo采用3x2)
  • 每次函数运行返回一个字典或列表或DataFrame
  • 最后把这些数据放到一起以.csv的文件输出
  • 然后就可以通过EXCEL来观察这些评价列表了
a_股票和策略的循环调用

 首先,需要有一个自选股列表,比如拿之前的一个df_list,在demo里用2,3,6这三支股票。

代码名称
601168西部矿业
300058蓝色光标
000921海信家电
601086国芳集团
600794保税科技
002516旷达科技
300697电工合金
159633中证1000指数ETF
515220煤炭ETF
def run_analyzer_all():
    sel11 = [2,3,6]
    straList=[myStrategys["经典_KDJ"],myStrategys['经典布林线策略']]
    list_analyzer = []
    
    for stra1 in straList:    # 策略做大循环
        stra2 = eval(stra1)
        for isel1 in sel11:   # 股票做小循环
            run_main_analyser4(df_list,stra2,isel1,d1,d2,list_analyzer,False,1)

    return list_analyzer
['000921', '海信家电']
策略为 经典KD交叉 , 期末总资金 95522.90  盈利为 -4477.10 总共交易次数为 46 ,交易成功率为 28.3%
['601086', '国芳集团']
策略为 经典KD交叉 , 期末总资金 132906.24  盈利为 32906.24 总共交易次数为 39 ,交易成功率为 35.9%
['300697', '电工合金']
策略为 经典KD交叉 , 期末总资金 94248.60  盈利为 -5751.40 总共交易次数为 42 ,交易成功率为 33.3%
['000921', '海信家电']
策略为 经典BOLL策略 , 期末总资金 101298.06  盈利为 1298.06 总共交易次数为  3 ,交易成功率为 66.7%
['601086', '国芳集团']
策略为 经典BOLL策略 , 期末总资金 97992.30  盈利为 -2007.70 总共交易次数为  4 ,交易成功率为 75.0%
['300697', '电工合金']
策略为 经典BOLL策略 , 期末总资金 116375.33  盈利为 16375.33 总共交易次数为  5 ,交易成功率为 60.0%

 大致从盈利的情况上看,其实已经看出,有些股票适合KD策略,有的股票可能适合BOLL策略,没有最好的策略,关键看是不是跟股票匹配上了。

b_单策略cerebro与analyzer的应用

这个函数跟之前的基本没有变化,主要在 analyzer_output()时返回一个dic_analyzer,并添加到参数给的list_analyzer中。

def run_main_analyser4 (df_list,run_strategy,i, sdate1,sdate2, list_analyzer): 
    iSel = i
    code_ = df_list.iloc[iSel,0]
    df1 = get_bt_feed_data(code_)

    cerebro = bt.Cerebro()
    data = bt.feeds.PandasData(dataname=df1, fromdate=sdate, todate=edate) 

    add_analyzer_all(cerebro)            # 加入---------analyzer ------------

    cerebro.addstrategy(run_strategy, log_off= logoff)         

    cerebro.adddata(data, name=code_)   

    result = cerebro.run()     # 运行策略参数优化 maxcpus=1
    strat = result[0]
    dic_analyzer = analyzer_output(strat)  # 输出-------analyzer-------------

    list_analyzer.append(dic_analyzer)
        
    return list_analyzer
c_添加评价函数和输出评价函数

直接把第2节最后的评价函数拿过来用

def add_analyzer_all(cerebro):
    
    cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn') # 年化收益率 01
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown') # 回撤 02
    # 03
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio') # 夏普比率 04
    cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='_TradeAnalyzer') # 交易分析 05
    cerebro.addanalyzer(bt.analyzers.SQN, _name='_SQN') # 交易系统性能得分 SQN 06
    # 07,08,09,10
    cerebro.addanalyzer(bt.analyzers.Returns, _name='_Returns', tann=252) # 计算252日度收益 11
    cerebro.addanalyzer(bt.analyzers.VWR, _name='_VWR')  # 可变加权回报率 VWR 12
    # 13,14
    cerebro.addanalyzer(bt.analyzers.PeriodStats, _name='_PeriodStats') # 基本数据统计 15
    
    
    # 需要通过数据记录进行计算    
    cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl') # 返回收益率时序数据    # 03
    cerebro.addanalyzer(bt.analyzers.PositionsValue, _name='_PositionsValue')  # position 08
    cerebro.addanalyzer(bt.analyzers.Transactions, _name='_Transactions')  # Transactions 09
    cerebro.addanalyzer(bt.analyzers.GrossLeverage, _name='_GrossLeverage')  # GrossLeverage 07
    cerebro.addanalyzer(bt.analyzers.PyFolio, _name='_PyFolio')   # 10

def analyzer_output(result):
    dic1 = {}
    
    sout01 = result.analyzers._AnnualReturn.get_analysis()  # 年化收益率 01
    for k,v in sout01.items():
        dic1[f'{k}年化']= v*100
        
    sout02 = result.analyzers._DrawDown.get_analysis()      # 回撤 02
    dic1['回撤'] = sout02['drawdown']
    dic1['最大回撤'] = sout02['max']['drawdown']

    sout04 = result.analyzers._SharpeRatio.get_analysis()       # 夏普比率 04
    dic1['夏普率'] = sout04['sharperatio']
    
    sout06 = result.analyzers._SQN.get_analysis()     # 交易系统性能得分 SQN 06
    dic1['系统性能SQN'] = sout06['sqn']
    
    sout12 = result.analyzers._VWR.get_analysis()     # # 可变加权回报率 VWR 12
    dic1['VWR'] = sout12['vwr']
    
    sout15 = result.analyzers._PeriodStats.get_analysis()  # # 基本数据统计 15
    dic1['平均'] = sout15['average']
    dic1['标准差'] = sout15['stddev']
    dic1['最佳收益'] = sout15['best']
    dic1['最大亏损'] = sout15['worst']
    
    sout11 = result.analyzers._Returns.get_analysis()    # 计算252日度收益 11
    dic1['年化回报率'] = sout11['rnorm100']
    
    sout05 = result.analyzers._TradeAnalyzer.get_analysis()   # 交易分析 05
    dic1['关闭交易'] = sout05['total']['closed']          
    dic1['连胜次数'] = sout05['streak']['won']['current']
    dic1['最大连胜'] = sout05['streak']['won']['longest']
    dic1['连负次数'] = sout05['streak']['lost']['current']
    dic1['最大连负'] = sout05['streak']['lost']['longest']
    dic1['毛利润'] = sout05['pnl']['gross']['total']
    dic1['净利润'] = sout05['pnl']['net']['total']
    dic1['总胜次数'] = sout05['won']['total']
    dic1['总盈利'] = sout05['won']['pnl']['total']
    dic1['最大盈利'] = sout05['won']['pnl']['max']
    dic1['总亏次数'] = sout05['lost']['total']
    dic1['总亏损'] = sout05['lost']['pnl']['total']
    dic1['最大亏损'] = sout05['lost']['pnl']['max']
    
    dic1['胜率'] = dic1['总胜次数']/dic1['关闭交易'] * 100
    # dic1['盈亏比'] = abs(dic1['总盈利']/dic1['总亏损'] )  # 会出现总亏损为0,除数不能为0

    # 盈亏时间比 盈利周期数/亏损周期数
    # dic1['盈亏时间比'] = sout05['len']['won']['total'] / sout05['len']['lost']['total']

    # 通过data.close的差值来取区间涨幅
    len1 = len(result.data.close)
    cend = result.data.close[0]
    cstart = result.data.close[-len1+1]

    qjzf2 = (cend-cstart)/cstart
    qjzf2_pct = (cend-cstart)/cstart * 100
    dic1['区间涨跌'] = qjzf2
    dic1['区间涨幅'] = qjzf2_pct

    # 持仓周期数 - 更简单用 level来统计,不需要取list第一项
    sout07 = result.analyzers._GrossLeverage.get_analysis()
    s2 = pd.Series(sout07)
    cnt_zero = s2.eq(0).sum()
    cnt_all = s2.count()
    cnt_position = cnt_all - cnt_zero
    dic1['持仓周期数'] = cnt_position
    dic1['持仓占比'] = cnt_position/cnt_all * 100    
    
    sout03 = result.analyzers.pnl.get_analysis()  
    a2 = pd.Series(sout03)

    recent_1m = a2[-21:].sum() *100             # 近1月
    recent_3m = a2[-64:].sum() *100
    recent_6m = a2[-126:].sum() *100
    recent_1y = a2[-252:].sum() *100
    
    dic1['近1月'] = recent_1m
    dic1['近3月'] = recent_3m 
    dic1['近6月'] = recent_6m 
    dic1['近1年'] = recent_1y 
    
    return dic1

002_数据结构与添加区分

从analyzer_output 函数返回的是字典结构,可以用来记录每个评价指标(key)的数值(value),回到run_main_analyzer4 函数后,把字典加到列表list_analyzer 中去,每1支股票对应的1个策略都可以把评价指标dict加进去。

然后,打印列表或转成DataFrame进行显示,这个时候问题来了,面对很多行的数据,我们不知道某一行是哪支股票用了哪个策略得到的!于是,我们采用了一些做法,在run_main_analyzer4中,先把股票的代号,名称和使用的策略先添加到字典中去,这样在输出后就能区分是哪支股票应用哪个策略了。

    dic_analyzer['code'] = data._name  # 代号

    cn_name = df_list.iloc[iSel,1]
    dic_analyzer['名称'] = cn_name     # 股票名称

    strategy_params = result[0].params.stra_name
    dic_analyzer['策略'] = strategy_params    # 策略名称
    

 代号例如“600397”在data._name里,名称我们去查自己的自选股列表可以得到,而策略可以简单的写在策略类的params里,就可以在result[0].params.stra_name去获取了。

class shortMA(BaseSt):
    params = (
        ('stra_name','经典短均线'),   # 设定策略名称 
        ('ma1',5), )            
              
    def __init__(self): 
        self.order = None
        
        self.sma1 = bt.indicators.SMA(self.data.close, period=self.params.ma1)
        self.crs= myCross2(self.data.close, self.sma1)
    
    def next(self):
        pass

003_把代号名称放到最前面

得到最后的list后,进行简单处理,会发现code,名称和策略在最后面,这样在评价指标比较多的时候,就会不方便,我们需要把光标移到最后面才能看到。

这里又偷懒再次请教了AI,给出如下代码,运行后就能把code,名称和策略放到最前面了

df_ana = pd.DataFrame(list1)

# 获取最后三列的名称
original_columns = df_ana.columns.tolist()
last_three_columns = original_columns[-3:]

# 将最后三列添加到列名列表的开头
reordered_columns = last_three_columns + original_columns[:-3]

# 使用reordered_columns重新排列DataFrame
df_ana = df_ana.reindex(columns=reordered_columns)
print(df_ana.columns)
df_ana.iloc[:,[0,1,2,3,-5,-4,-3,-2,-1]]

--------------------------------
Index(['code', '名称', '策略', '2023年化', '2024年化', '回撤', 
'最大回撤', '夏普率', '系统性能SQN', 'VWR', '平均', '标准差', 
'最佳收益', '最大亏损', '年化回报率', '关闭交易', '连胜次数', '最大连胜',
'连负次数', '最大连负', '毛利润', '净利润', '总胜次数', '总盈利', 
'最大盈利', '总亏次数', '总亏损', '胜率', '区间涨跌', '区间涨幅', 
'持仓周期数', '持仓占比', '近1月', '近3月', '近6月', '近1年'],
 dtype='object')

 004_to_csv和数据后处理

当前,我们还不太会根据评价指标来进行分析,这个不属于backtrader的实践范畴,所以直接输出成.csv格式。然后就是数据后处理,可以用excel打开来,也可以用pandas对这些数据进行分析和处理。

df_ana.to_csv('analyzer_list_01.csv',encoding='utf-8-sig')
print('to_csv OK!')

 比如我们对“夏普率”进行降序排序,那我们就得到电工合金的经典BOLL策略夏普率得分最高......

名称策略2023年化2024年化最大回撤夏普率
电工合金经典BOLL策略6.0773307079.70801302917.074508763.79690166
国芳集团经典KD交叉32.643109730.19837411713.721363580.950585149
海信家电经典KD交叉-6.7167658232.40092671112.57285827-0.69270148
国芳集团经典BOLL策略1.811742713-3.75147422410.89152999-0.708175064
电工合金经典KD交叉-5.419165717-0.35126956515.71722809-1.533266478
海信家电经典BOLL策略0.8009658480.4931475894.735670387-2.293192627

02_参数优化的评价列表

某股票软件有“探索最佳专家系统”这种功能,它的操作是你选中一支股票后,打开这个功能,就会显示从MACD,KDJ到xxx大概十多个专家交易系统给你选择,以KDJ为例,你点击开始评测后,它会将KDJ的2个参数开始双重循环遍历,最终给出所有参数组合的评价指标,并且按你选择的(胜率最高,净利润最大......)的选项,得出成绩最好的参数组合。

参数优化其实就是这样一个功能,对于1个策略的参数进行循环回测,并且这里的参数取值范围是可以自己设置的,不是探索系统那种帮你预设好的例如KDJ的period参数从1开始测起,某些股票居然是1的净利润最高......

001_参数优化Demo

在做参数优化的评价列表之前,先完成一个参数优化的demo程序,回顾一下参数优化的注意点。

a_策略类params添加

对于参数的优化,首先要有参数,参数的值有一个范围,这个参数在策略类里面进行定义(p1,p2,p3),并且指标indicators调用时需要填写参数,而不能使用默认值。

如果对指标的参数定义不清楚,可以直接在官方文档里查看 Indicators - Reference - Backtrader

例如KD的指标(在backtrader中为Stochastic),那么它的Params就是下面这个样子,标红的是我们在股票软件里KDJ的常规参数设置(9,3,3),在backtrader中,默认是(14,3,3)

Params:

  • period (14)

  • period_dfast (3)

  • movav (MovingAverageSimple)

  • upperband (80.0)

  • lowerband (20.0)

  • safediv (False)

  • safezero (0.0)

  • period_dslow (3)

class St_KDJ_class1(BaseOptSt1):
     params = (
         ('stra_name','经典KD交叉'),
         ('p1',9), ('p2',3), ('p3',3),     # KDJ的3个参数
         ('tradeCnt',1), ('sucessCnt',0),)            
               
     def __init__(self): 
         self.order = None
         self.kd = bt.indicators.Stochastic(
                                            self.data,
                                            period=self.p.p1,      # 填写参数,不用默认
                                            period_dfast=self.p.p2,
                                            period_dslow=self.p.p3)

         self.crs = myCross2(self.kd.percK,self.kd.percD)
            
     def next(self):
         if self.order:  # 检查是否有指令等待执行
             return
      
         if not self.position:  # 没有持仓 才会进入 
             if self.crs.l.crsup:  
                 self.order = self.buy()  # 执行买入
         else:
             if self.crs.l.crsdn:  
                 self.order = self.sell()  # 执行卖出
                 
     def stop(self):
         sucessPct = self.params.sucessCnt/self.params.tradeCnt
         print("当参数为 %2d,%2d,%2d  期末总资金 %.2f  盈利为 %.2f" % 
              (self.params.p1,self.params.p2,self.p.p3,self.broker.getvalue(),self.broker.getvalue()-100000) , 
              "总共交易次数为 %2d ,交易成功率为 %.2f" % (self.params.tradeCnt,sucessPct))
b_在.run()过程中代码变化
正常回测参数优化
cerebro = bt.Cerebro()cerebro = bt.Cerebro()
cerebro.addstrategy(run_strategy)

cerebro.optstrategy(run_strategy,

                                p1 = range(8,15),

                                p2=range(2,5),

                                p3=3)   

results = cerebro.run()

results = cerebro.run(maxcpus=1)    

# 运行策略参数优化 maxcpus=1

c_简单实践
run_opt_analyser7(df_list,St_KDJ_class1,3,d1,d2,False,1)



# 参数范围设置
  p1 = range(8,15),  # 8,9,10,11,12,13,14 
  p2=range(2,5),     # 2,3,4
  p3=3               # 3
参数  8, 2, 3 期末权益 156594.96  盈利 56594.96 总交易次数 50 ,成功率 0.50
参数  8, 3, 3 期末权益 141741.28  盈利 41741.28 总交易次数 40 ,成功率 0.42
参数  8, 4, 3 期末权益 133225.11  盈利 33225.11 总交易次数 36 ,成功率 0.50
参数  9, 2, 3 期末权益 151308.83  盈利 51308.83 总交易次数 48 ,成功率 0.46
参数  9, 3, 3 期末权益 142014.08  盈利 42014.08 总交易次数 38 ,成功率 0.45
参数  9, 4, 3 期末权益 131445.63  盈利 31445.63 总交易次数 36 ,成功率 0.44
参数 10, 2, 3 期末权益 146093.29  盈利 46093.29 总交易次数 50 ,成功率 0.46
参数 10, 3, 3 期末权益 123681.14  盈利 23681.14 总交易次数 40 ,成功率 0.45
参数 10, 4, 3 期末权益 137941.24  盈利 37941.24 总交易次数 33 ,成功率 0.48
参数 11, 2, 3 期末权益 142015.91  盈利 42015.91 总交易次数 50 ,成功率 0.42
参数 11, 3, 3 期末权益 131808.30  盈利 31808.30 总交易次数 37 ,成功率 0.46
参数 11, 4, 3 期末权益 139241.66  盈利 39241.66 总交易次数 33 ,成功率 0.45
参数 12, 2, 3 期末权益 150707.36  盈利 50707.36 总交易次数 45 ,成功率 0.44
参数 12, 3, 3 期末权益 137273.11  盈利 37273.11 总交易次数 37 ,成功率 0.43
参数 12, 4, 3 期末权益 144680.72  盈利 44680.72 总交易次数 32 ,成功率 0.47
参数 13, 2, 3 期末权益 133561.35  盈利 33561.35 总交易次数 49 ,成功率 0.37
参数 13, 3, 3 期末权益 130616.21  盈利 30616.21 总交易次数 38 ,成功率 0.37
参数 13, 4, 3 期末权益 146422.77  盈利 46422.77 总交易次数 33 ,成功率 0.45
参数 14, 2, 3 期末权益 139921.73  盈利 39921.73 总交易次数 51 ,成功率 0.39
参数 14, 3, 3 期末权益 132906.24  盈利 32906.24 总交易次数 39 ,成功率 0.36
参数 14, 4, 3 期末权益 141057.22  盈利 41057.22 总交易次数 32 ,成功率 0.50

002_参数优化的Analyzer

添加analyzer的函数如下

def add_analyzer_all_opt(cerebro):

    cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn') #年化收益01
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown') # 回撤02
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio') #夏普比率04

    cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl') # 返回收益率 03
a_直接使用原来的result报错
cerebro = bt.Cerebro()

data = bt.feeds.PandasData(dataname=df1, fromdate=sdate1, todate=sdate2) 
cerebro.adddata(data, name=code_)  

cerebro.optstrategy(run_strategy, p1 = range(8,15),p2=range(2,5),p3=3)  

add_analyzer_all_opt(cerebro)               # 加入----------analyzer ------

results= cerebro.run(maxcpus=1)  

strat =  results[0]

sout04 = strat.analyzers._SharpeRatio.get_analysis()
print(sout04)

--------------------------
---> 25 sout04 = strat.analyzers._SharpeRatio.get_analysis()
AttributeError: 'list' object has no attribute 'analyzers'
b_检查results的内容
    result = cerebro.run(maxcpus=1)     # 运行策略参数优化 maxcpus=1
    print(result,len(result))
    strat = result[0]
    print(strat)

---------------
[[<backtrader.cerebro.OptReturn object at 0x000001EDE5C8B4D0>], 
[<backtrader.cerebro.OptReturn object at 0x000001EDE5A8AED0>], 
[<backtrader.cerebro.OptReturn object at 0x000001EDE5AC8D10>], 
[<backtrader.cerebro.OptReturn object at 0x000001EDE60B34D0>], 
......
[<backtrader.cerebro.OptReturn object at 0x000001EDE6C83E10>], 
[<backtrader.cerebro.OptReturn object at 0x000001EDE6103E10>], 
[<backtrader.cerebro.OptReturn object at 0x000001EDE2D73E10>], 
[<backtrader.cerebro.OptReturn object at 0x000001EDE5E57050>]] 21

[<backtrader.cerebro.OptReturn object at 0x000001EDE5C8B4D0>]

通过打印result内容发现,就result是个列表,有21个元素,也就是刚才7x3的参数优化的数量,通过result[x] (x在0~20之间)就能得到其中一组参数的优化结果(OptReturn)。

c_OptReturn对象

这里还是使用问AI的方式来进行下一步,

backtrader.cerebro.OptReturn object 是什么对象,怎么查看里面的数据?

backtrader.cerebro.OptReturn 对象是在使用 Backtrader 进行策略优化时,由 cerebro.optstrategy 方法返回的一个对象。这个对象包含了策略优化过程中的关键数据,如策略参数、分析器对象等。OptReturn 对象不直接包含数据,而是包含了优化过程中的统计和分析结果。

要查看 OptReturn 对象中的数据,您可以通过访问该对象的属性来获取。例如,您可以通过 params 属性来查看优化过程中使用的策略参数,通过 analyzers 属性来获取策略的分析结果。这些属性通常是列表或字典类型,包含了优化过程中的多组数据。

# 假设 cerebro 是您的 Cerebro 实例,并且已经运行了优化
opt_returns = cerebro.runopt()

# 遍历优化结果,查看每个优化参数集及其对应的分析结果
for opt_return in opt_returns:
    print("Parameters:", opt_return.params)
    print("Analyzers:", opt_return.analyzers)

在上述代码中,runopt 方法执行了优化,并返回了一个包含 OptReturn 对象的列表。通过遍历这个列表,您可以查看每个优化迭代的详细信息。

我不知道它是从哪些网页上搜索得到的信息,只能说部分有用,还会有些坑在里

  • 首先,参数优化不是 cerebro.runopt(),或许是某人自己封装的,前面也说到了,参数优化用到的语句是cerebro.run(maxcpus=1)  
  • 其实,opt_returns是参数优化返回的对象,我们刚刚看到它是一个OptReturn对象的列表,所以可以使用for in语句来遍历,但这里层次关系还是不对,所以是会报错的
错误 1
AttributeError: 'Cerebro' object has no attribute 'runopt'

修正上面错误后:

     38 for opt_return in opt_returns:
---> 39     print("Parameters:", opt_return.params)
     40     print("Analyzers:", opt_return.analyzers)
AttributeError: 'list' object has no attribute 'params'

接着我们去查看了源码,然后把源码喂给了AI,然后AI的回复如下:

class OptReturn(object):
    def __init__(self, params, **kwargs):
        self.p = self.params = params
        for k, v in kwargs.items():
            setattr(self, k, v)

错误信息 'list' object has no attribute 'params' 暗示您可能在一个列表上调用了 .params,而不是在 OptReturn 对象上调用。这通常发生在您期望得到的是 OptReturn 对象,但实际上得到的是一个包含这些对象的列表

为了解决这个问题,您需要检查导致错误的代码行,确保在尝试访问 .params 属性之前,您已经从优化结果列表中提取了 OptReturn 对象。如果您在遍历优化结果列表时遇到这个错误,您应该使用索引或迭代来访问每个 OptReturn 对象,然后再访问其 .params 属性。

于是,我们回过来再检查一下result的类型,

    result = cerebro.run(maxcpus=1)     # 运行策略参数优化 maxcpus=1
    print(type(result))
    strat = result[0]
    print(strat,type(strat))

----------------------
<class 'list'>
[<backtrader.cerebro.OptReturn object at 0x000001EDE2F66110>] <class 'list'>

由上面的结果可知,result是list类型,而result[0]仍然是list类型,这里又出现了之前我们在做持仓周期时遇到的positions的值是list,而这个list里只有一项这样的情况,因此要取到OptReturn其实需要 result[x][0]来得到。

    opt_results = cerebro.run(maxcpus=1)
    for result in opt_results:
    # # 访问优化结果中的参数
        print(result,type(result))
        print(result[0],type(result[0]))

---------------
[<backtrader.cerebro.OptReturn object at 0x000001EDE5FD0D10>] <class 'list'>
<backtrader.cerebro.OptReturn object at 0x000001EDE5FD0D10> <class 'backtrader.cerebro.OptReturn'>
[<backtrader.cerebro.OptReturn object at 0x000001EDE616BE10>] <class 'list'>
<backtrader.cerebro.OptReturn object at 0x000001EDE616BE10> <class 'backtrader.cerebro.OptReturn'>
.......
d_OptReturn的内容

在正确获取到OptReturn的对象后,我们就可以继续找到其内容,根据前面AI提供的代码,把层级关系搞清楚后,也可以进行print(),但是得到的都是对象,而且无法显示其内容

    opt_results = cerebro.run(maxcpus=1)
    for result in opt_results:
    # # 访问优化结果中的参数
        # print(result,type(result))
        # print(result[0],type(result[0]))

        strat1 = result[0]
        print(strat1, type(strat1))
        print("Parameters:", strat1.params)
        print("Analyzers:", strat1.analyzers)

---------------------
<backtrader.cerebro.OptReturn object at 0x000001EDE6A09C90> 
<class 'backtrader.cerebro.OptReturn'>
Parameters: <backtrader.metabase.AutoInfoClass_LineRoot_LineMultiple_LineSeries_LineIterator_DataAccessor_StrategyBase_Strategy_BaseOptSt1_St_KDJ_class11 object at 0x000001EDE67D0810>
Analyzers: <backtrader.metabase.ItemCollection object at 0x000001EDE67B4F90>

这个时候,我们再回顾第2节补充的美化打印功能,backtrader的数据可以使用.pprint()以及.print()进行美化打印,这个可以试一下~

    opt_results = cerebro.run(maxcpus=1)
    for result in opt_results:

        strat1 = result[0]

        params1 = strat1.params
        print(params1.p1)
        print(params1.p2)
        print(params1.p3)

        analyzers = strat1.analyzers
        analyzers[0].pprint()
        analyzers[1].pprint()
        analyzers[2].pprint()
-----------------
8 2 3    # params的p1,p2,p3
OrderedDict([(2023, 0.40010978535734054), (2024, 0.11844770219796641)]) # 年化收益
AutoOrderedDict([('len', 63),
                 ('drawdown', 8.33814918815141),                      # Drawdown
                 ('moneydown', 14244.88054622573),
                 ('max',
                  AutoOrderedDict([('len', 99),
                                   ('drawdown', 9.694768681778413),
                                   ('moneydown', 16562.527088321513)]))])
OrderedDict([('sharperatio', 1.7700553868062032)])                    # sharp率

8 3 3
OrderedDict([(2023, 0.33635769361659196), (2024, 0.06065375497412018)])
......
e_params和analyzers的内容

到上面为止,我们已经能够看到params部分内容(如果知道有哪些的话),以及analyzers的数值。但是analyzers是AutoOrderedDict,暂时取不出来,这个就只能回顾前面所学习过的知识点以及查源码。

两个对象分别为

Parameters: <backtrader.metabase.AutoInfoClass_......>
Analyzers: <backtrader.metabase.ItemCollection object >

class AutoInfoClass(object):
    #........
    @classmethod
    def _getitems(cls):
        return cls._getpairs().items()


class ItemCollection(object):
    #...............
    def getitems(self):
        return zip(self._names, self._items)

class Analyzer(with_metaclass(MetaAnalyzer, object)):
    #..............
    def get_analysis(self):
        '''Returns a *dict-like* object with the results of the analysis

        The keys and format of analysis results in the dictionary is
        implementation dependent.

        It is not even enforced that the result is a *dict-like object*, just
        the convention

        The default implementation returns the default OrderedDict ``rets``
        created by the default ``create_analysis`` method

        '''
        return self.rets

对params和analyzers分别进行测试后,总结内容如下:

    opt_results = cerebro.run(maxcpus=1)
    for result in opt_results:

        strat1 = result[0]
 
        params1 = strat1.params
        # print(type(params1))
        pa2 = params1._getitems()  # odict_items([('p1', 9), ('p2', 3), ('tradeCnt', 1), 
                              # ('sucessCnt', 0), ('stra_name', '经典KD交叉'), ('p3', 3)])
        print(pa2)
        print(params1.p1,params1.p2,params1.p3)  # 8 2 3
        print(params1.stra_name)                 # 经典KD交叉

--------------------------
odict_items([('p1', 9), ('p2', 3), ('tradeCnt', 1), 
('sucessCnt', 0), ('stra_name', '经典KD交叉'), ('p3', 3)])
8 2 3
经典KD交叉
odict_items([('p1', 9), ('p2', 3), ('tradeCnt', 1), 
'sucessCnt', 0), ('stra_name', '经典KD交叉'), ('p3', 3)])
8 3 3
经典KD交叉

 对于params而言

  • 它是metabase.AutoInfoClass的对象,
  • 可以利用类方法_getitems()获取内容,是odict_items一种字典结构
  • 取值可以直接使用.p1这样的方式
    opt_results = cerebro.run(maxcpus=1)
    for result in opt_results:

        analyzers = strat1.analyzers
        ana2 = analyzers.getitems()  # getitems <zip object at 0x000001EDE6763F40>
        print('getitems',ana2)
        for x in ana2:
            print(x)

        analyzers[0].pprint()   
        sout01 = analyzers[0].get_analysis()
        print('get_analysis',sout01)
        
        analyzers[1].pprint()
        sout02 = analyzers[1].get_analysis()
        print('get_analysis', sout02)

------------
getitems <zip object at 0x000001EDE6763F40>
('_AnnualReturn', <backtrader.analyzers.annualreturn.AnnualReturn object at 0x000001EDE68DEA90>)
('_DrawDown', <backtrader.analyzers.drawdown.DrawDown object at 0x000001EDE68DDDD0>)
('_SharpeRatio', <backtrader.analyzers.sharpe.SharpeRatio object at 0x000001EDE68DFC50>)
('pnl', <backtrader.analyzers.timereturn.TimeReturn object at 0x000001EDE2F78B10>)

OrderedDict([(2023, 0.40010978535734054), (2024, 0.11844770219796641)])
get_analysis OrderedDict([(2023, 0.40010978535734054), (2024, 0.11844770219796641)])

AutoOrderedDict([('len', 63),
                 ('drawdown', 8.33814918815141),
                 ('moneydown', 14244.88054622573),
                 ('max',
                  AutoOrderedDict([('len', 99),
                                   ('drawdown', 9.694768681778413),
                                   ('moneydown', 16562.527088321513)]))])
get_analysis AutoOrderedDict([('len', 63), 
('drawdown', 8.33814918815141), ('moneydown', 14244.88054622573), 
('max', AutoOrderedDict([('len', 99), 
('drawdown', 9.694768681778413), ('moneydown', 16562.527088321513)]))])

getitems <zip object at 0x000001EDE6F74E00>
......

对于analyzers而言

  • 它是metabase.ItemCollection object的对象
  • 可以利用.getitems()来获取其内容,以当前add_analyzer为例,添加了年化,回撤,夏普和日收益率共4个评价,则获取到的内容用 for in遍历出来为('_AnnualReturn','_DrawDown','_SharpeRatio', 'pnl')
  • 其中的每一项,都是 backtrader.analyzers.下面的类,例如年化收益01 - <class 'backtrader.analyzers.annualreturn.AnnualReturn'> ,前面正常回测的时候也是使用.get_analysis()来获取它的内容的
  • 原来analyzer_output中 类似于 sout02 = xxx. get_analysis()之后的语句可以复用
f_参数优化加评价的初步集成

前面在参数优化Demo的时候,大部分与正常回测不一样的地方都已经修改,目前需要把添加评价和输出评价两个函数进行更新,其实添加评价函数是不用改的,但是参数优化后的result它里面的analyzer都是按添加评价时的顺序生成的,所以千万要做到一一对应、对齐。

import quantstats as qs
def add_analyzer_all_opt(cerebro):

    cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn') # 年化收益率 01
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown') # 回撤 02
    # 03
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio') # 夏普比率 04
    cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl') # 返回收益率时序数据    # 03

def analyzer_output_opt(result):
    list_analyzer = []
    for strat in result:
        dic1 = {}

        strat1 = strat[0]
        params1 = strat1.params
        str_params = '(%2d,%2d,%2d)'%(params1.p1,params1.p2,params1.p3)
        # print(str_params)
        dic1['策略'] = params1.stra_name
        dic1['参数'] = str_params
        
        analyzers = strat1.analyzers

        sout01 = analyzers[0].get_analysis()       # 年化收益率 01
        for k,v in sout01.items():
            dic1[f'{k}年化']= v*100                 

        sout02 = analyzers[1].get_analysis()       # 回撤 02
        dic1['回撤'] = sout02['drawdown']
        dic1['最大回撤'] = sout02['max']['drawdown'] 

        sout04 = analyzers[2].get_analysis()       # 夏普比率 04
        dic1['夏普率'] = sout04['sharperatio']

        sout03 = analyzers[3].get_analysis()       # 返回收益率时序数据    # 03
        a2 = pd.Series(sout03)
        
        # sharp
        sharpe_ratio = qs.stats.sharpe(a2, rf=0.02)  # 
        dic1['qs_sharp'] = sharpe_ratio
        
        recent_1m = a2[-21:].sum() *100             # 近1月
        recent_3m = a2[-64:].sum() *100
        recent_6m = a2[-126:].sum() *100
        recent_1y = a2[-252:].sum() *100

        dic1['近1月'] = recent_1m
        dic1['近3月'] = recent_3m 
        dic1['近6月'] = recent_6m 
        dic1['近1年'] = recent_1y 
    
        list_analyzer.append(dic1)
    return list_analyzer

最后再把list_analyzer转换成DataFrame类型后,它的输出结果:

到这里,即使在参数优化里,我们也能够把策略,参数对应的评价得分获取出来,或者to_csv()后放到excel中去进行排序等,寻找对应每支股票最佳的参数配置。这里我还是觉得,不同的股票有其自己的特性,它可能适合于某种策略,或不适合某种策略;而且,对于某种策略,它可能适合某种奇怪的参数配置,这种配置对其他股票不起作用,但就是对这支股票出奇的好用。

自定义Analyzer类

原本是计划写在这一节里的,结果实践过程有点曲折,导致前面的内容也增加了这么多,自定义Analyzer类的实践内容也挺多,就放到下一节吧。

本节小结

本节通过实践,我们制作了一个相对完整的程序,把多支股票乘以多种策略进行回测的许多评价指标做成了列表,以方便后续分析和选择分数高的股票或与之相匹配的策略。

接着,考虑到参数优化时单支股票和单策略但参数配置可以多组合的情况,在参数优化demo的基础上,尝试添加评价指标并输出,这期间其实遇到几次感觉很棘手的问题,例如怎么取opt运行后的结果里的analyzer等,问AI以及自己查找都没得到准备的答案,很多时候,还是需要查源码才能解决问题。

随着不断的实践,之前许多不清楚不明白的地方也逐渐明亮了起来,在这个过程中,也不断发现前面学习发生的错误,有错误并不可怕,更正了错误才能更好的成长。

实践是检验真理的唯一标准!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2093226.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图形化编程系统学习10

项目需求&#xff1a; 点击绿旗&#xff0c;可以使用键盘控制小兔子在地图上移动&#xff0c;收集食物&#xff0c;但只能在黄色道路上移动。 食物碰到小兔子会变大后隐藏&#xff0c;并发出声音。 收集完所有食物&#xff0c;回到温暖的小窝 。 思路解析 1、添加背景和角色…

【书生大模型实战营】进阶岛 第1关 探索 InternLM 模型能力边界

文章目录 【书生大模型实战营】进阶岛 第1关 探索 InternLM 模型能力边界学习任务Bad Case 1&#xff1a;Bad Case 2&#xff1a;Bad Case 3&#xff1a;Bad Case 4&#xff1a;Bad Case 5&#xff1a;Good Case 1&#xff1a;Good Case 2&#xff1a;Good Case 3&#xff1a;G…

requests:一个强大的HTTP请求Python库

我是东哥&#xff0c;一个热衷于用Python简化日常工作的技术爱好者。今天&#xff0c;我要和大家分享一个网络请求的Python利器——requests库。你是否曾经在编写代码时&#xff0c;需要从网络上获取数据&#xff0c;却对复杂的HTTP协议感到头疼&#xff1f;requests库将帮你轻…

LMDeploy 量化部署进阶实践

1 配置LMDeploy环境 1.1 InternStudio开发机创建与环境搭建 打开InternStudio平台&#xff0c;进入如下界面并按箭头指示顺序点击创建开发机。 点选开发机&#xff0c;自拟一个开发机名称&#xff0c;选择Cuda12.2-conda镜像。 我们要运行参数量为7B的InternLM2.5&#xff0c;…

AI 内容创作:思考与实践

文章目录 LLM 与 RAGLLMRAGRAG 定制策略AI 写作助手演示 内容层次结构与内容深度优化有效的主题与段落结构内容深度的多样性与独特性提高文本逻辑性与连贯性模拟实践 内容评测与优化迭代机制内容评测自动迭代优化机制评估指标模拟实践 个性化写作与 AI 协同写作用户画像与需求分…

【C++ 第十八章】C++11 新增语法(3)

前情回顾&#xff1a; 【C11 新增语法&#xff08;1&#xff09;&#xff1a;1~6 点】 C11出现与历史、花括号统一初始化、initializer_list初始化列表、 auto、decltype、nullptr、STL的一些新变化 【C11 新增语法&#xff08;2&#xff09;&#xff1a;7~8 点】 右值引用和…

香橙派入手第一天

一、开箱 拿到快递回来以后&#xff0c;兴冲冲的把快递拆开&#xff0c;里面一共有一下几样东西&#xff0c;一个板卡&#xff0c;一个充电器&#xff0c;一个小风扇&#xff0c;还有一些安装用的零件和一把小螺丝刀。 值得一提的是这个充电器是最高支持65w的typec-typec的充电…

回溯法-0/1背包问题

什么是回溯法&#xff1f; 回溯法是一种搜索算法&#xff0c;它通过深度优先搜索的方式来解决决策问题。它从根节点开始&#xff0c;逐步扩展节点&#xff0c;直到找到所有可能的解。 回溯法的基本思想 开始节点&#xff1a;从根节点出发&#xff0c;这个节点是解空间的起点…

LLM 教程——如何为特定任务定制微调 BERT

通过本文&#xff0c;您将学会如何为特定的自然语言处理任务&#xff08;如分类、问答等&#xff09;微调BERT。 1、引言 BERT 是一个强大的预训练语言模型&#xff0c;可以用于多种下游任务&#xff0c;只需进行极小的修改。通过微调 BERT&#xff0c;您可以利用它的大规模知…

系统设计:一致性哈希的概念

目录 一、介绍 二、问题提出 三、朴素实施 四、一致性哈希 4.1 关闭服务器 4.2 添加新服务器 五、分布不均 5.1 虚拟节点 5.2 应用 六、结论 资源 一、介绍 我们生活在一个每天都会生成大量数据的世界里。在大公司中&#xff0c;几乎不可能将所有数据存储在单个服务器…

【区间dp、前缀和】 P1220 关路灯 题解

关路灯 题目描述 某一村庄在一条路线上安装了 n n n 盏路灯&#xff0c;每盏灯的功率有大有小&#xff08;即同一段时间内消耗的电量有多有少&#xff09;。老张就住在这条路中间某一路灯旁&#xff0c;他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。 为了给村里…

APACHE NIFI—wait、notify组件报拒绝连接访问的报错

报错文字&#xff1a; Wait[idele44704-6fb6-1b60-ffff-ffffdcofbba2]Failed to process session due to Failed to get signal for c84c4aec-1287-4216-b1a2-f5c6fod4a3b7 due to java.net.ConnectException:Connection refused: org.apache.nifi.processor.exception.Proces…

jmeter响应断言、json断言、断言持续时间操作

一、响应断言 Apply to&#xff1a;断言应用的范围&#xff0c;这里默认&#xff0c;通常发出一个请求只触发一个服务器测试字段 响应文本&#xff0c;response响应体内的信息响应代码&#xff1a; 响应码&#xff0c;一般是200响应信息&#xff1a;响应码后面的返回的信息&am…

zdppy+vue3+onlyoffice文档管理系统实战 20240831上课笔记 继续完善登录功能

遗留的问题 1、整合验证码的接口2、渲染验证码3、实现验证码校验的功能4、验证码校验通过之后&#xff0c;再校验登录功能 验证码框架怎么使用 安装&#xff1a; pip install zdppy_captcha使用示例&#xff1a; import zdppy_api as api import zdppy_captcha import zdp…

Docker compose 安装 ELK

1. 简介 方案概述 我们使用 Filebeat 作为日志收集器&#xff0c;接入到 Redis 队列&#xff0c;然后消费队列中的日志数据流转到 Logstash 中进行解析处理&#xff0c;最后输出到 Elasticsearch 中&#xff0c;再由 Kibana 展示到页面上。我们采用 Elasticsearch 3 节点集群…

hello树先生——AVL树

AVL树 一.什么是AVL树二.AVL树的结构1.AVL树的节点结构2.插入函数3.旋转调整 三.平衡测试 一.什么是AVL树 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。…

【计组 | Cache原理】讲透Cache的所有概念与题型方法

Cache 写在前面&#xff1a;高速缓存Cache一直408中的重点以及绝对的难点&#xff0c;前几天我在复习计组第三章的知识&#xff0c;Cache这一节把我困住了&#xff0c;我发现很多概念我都不记得了&#xff0c;一些综合性强的计算题根本无从下手&#xff0c;我深知Cache对于每个…

1分钟把高质量AI知识库站点嵌入小程序

许多企业都有把 AI 知识库装进小程序、网站、企业微信、钉钉等的需求&#xff0c;让用户能够在小程序上访问到高品质的内容。奈何有太多限制&#xff0c;往往会遇到IP地址不被信任或技术对接接口配置等困难。HelpLook能帮你节省这些繁琐的程序&#xff0c;0代码快速将AI知识库站…

工程师们都爱看的Docker容器技术,一看就会!保姆级教程(上)

文章目录 Docker简介Docker在企业中的应用场景Docker与虚拟化的对比Docker的优势 部署Docker部署DockerDocker的基本操作Docker镜像管理容器的常用操作 Docker镜像构建Docker镜像结构镜像运行的基本原理镜像获得方式镜像构建Docker镜像构建企业实例 镜像优化方案镜像优化策略镜…

一款免费强大的快速启动工具,快速打开程序,软件,网站,工具等

Lucy是一款由个人开发者针对个人需求开发的快速启动工具&#xff0c;其最大的特点在于简洁和快速。它允许用户通过简单的拖拽操作将文件、文件夹、网址等添加到启动列表中&#xff0c;实现快速访问常用程序和文件的目的。Lucy不依赖于网络连接&#xff0c;避免了隐私泄露的风险…