量化交易backtrader实践(四)_评价统计篇(5)_自定义评价

2024-09-05 01:12

本文主要是介绍量化交易backtrader实践(四)_评价统计篇(5)_自定义评价,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Analyzer应用Step-by-step

01_直接使用

直接使用是开始学习的时候会接触比较多,通过把cerebro.addStrategy()写出来,然后在.run()之后从result[0]中再取评价的数据,我们可以对这个运行的流程加深理解。

cerebro = bt.Cerebro()
# ......
cerebro.addstrategy(run_strategy)  # 添加策略
cerebro.adddata(data, name=code_)  # 添加数据# 添加评价
cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn') # 年化收益率01
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio') # 夏普比率04
# 其他评价...# ......
result = cerebro.run()     strat = result[0]# 获取评价数据
sout01 = result.analyzers._AnnualReturn.get_analysis()
sout04 = result.analyzers._SharpeRatio.get_analysis()
# 其他评价...

02_制作函数应用

当需要添加的评价比较多,或者需要得到的评价指标数据比较多时,制作两个函数是比较方便有效的做法。第一个函数是 add_analyzer_all,在添加评价的位置上替换掉所有评价的添加;第二个函数是analyzer_output,在.run()得到result后,对result[0]的内容进行处理,提取需要的数据并创建一个字典来保存。

def add_analyzer_all(cerebro):cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn') # 年化收益率 01cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown') # 回撤 02# 03cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio') # 夏普比率 04cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='_TradeAnalyzer') # 交易分析 05cerebro.addanalyzer(bt.analyzers.SQN, _name='_SQN') # 交易系统性能得分 SQN 06# 07,08,09,10cerebro.addanalyzer(bt.analyzers.Returns, _name='_Returns', tann=252) # 计算日度收益11cerebro.addanalyzer(bt.analyzers.VWR, _name='_VWR')  # 可变加权回报率 VWR 12# 13,14cerebro.addanalyzer(bt.analyzers.PeriodStats, _name='_PeriodStats') # 基本数据统计 15# 需要通过数据记录进行计算    cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl')                 # 03cerebro.addanalyzer(bt.analyzers.PositionsValue, _name='_PositionsValue') # 08cerebro.addanalyzer(bt.analyzers.Transactions, _name='_Transactions')     # 09cerebro.addanalyzer(bt.analyzers.GrossLeverage, _name='_GrossLeverage')   # 07cerebro.addanalyzer(bt.analyzers.PyFolio, _name='_PyFolio')               # 10def analyzer_output(result):dic1 = {}sout01 = result.analyzers._AnnualReturn.get_analysis()  # 年化收益率 01for k,v in sout01.items():dic1[f'{k}年化']= v*100sout02 = result.analyzers._DrawDown.get_analysis()      # 回撤 02dic1['回撤'] = sout02['drawdown']dic1['最大回撤'] = sout02['max']['drawdown']sout04 = result.analyzers._SharpeRatio.get_analysis()       # 夏普比率 04dic1['夏普率'] = sout04['sharperatio']sout06 = result.analyzers._SQN.get_analysis()     # 交易系统性能得分 SQN 06dic1['系统性能SQN'] = sout06['sqn']sout12 = result.analyzers._VWR.get_analysis()     # # 可变加权回报率 VWR 12dic1['VWR'] = sout12['vwr']#......

在函数的应用过程中,我们也发现了一些问题,有的已经解决,有的还没解决。

解决的比如说如果是做参数优化,它就与常规的回测不一样,包括cerebro.run()在内,很多地方会发生变化,特别是analyzer_output内部。并且,参数优化得到的results是个list,有多少组参数参加就会有多少个result。在每一个result里,可以用.params获取参数,也可以用.analyzers获取评价结果;而评价结果在这里的顺序是与add_analyzer时一一对应的,不能错,而且似乎不能使用getbyname的方法等。这些看起来跟正常回测的评价还是不同的,因此评价输出的函数需要有2个,一个给常规回测用,另一个给参数优化使用。

import quantstats as qs
from backtrader import Analyzer, TimeFrame
def add_analyzer_all_opt(cerebro):cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn') # analyzers[0]cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown')  # analyzers[1]cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio') # analyzers[2]cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_Sharpe4',timeframe=TimeFrame.Days,riskfreerate=0,daysfactor=252,convertrate=False,factor=252) # analyzers[3]cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl') # analyzers[4]def analyzer_output_opt(result):list_analyzer = []for strat in result:dic1 = {}strat1 = strat[0]params1 = strat1.params    # 取paramsstr_params = '(%2d,%2d,%2d)'%(params1.p1,params1.p2,params1.p3)# print(str_params)dic1['策略'] = params1.stra_namedic1['参数'] = str_paramsanalyzers = strat1.analyzers  # 取analyzers结果sout01 = analyzers[0].get_analysis()       for k,v in sout01.items():dic1[f'{k}年化']= v*100                 sout02 = analyzers[1].get_analysis()       dic1['回撤'] = sout02['drawdown']dic1['最大回撤'] = sout02['max']['drawdown'] sout04 = analyzers[2].get_analysis()       dic1['夏普率_default'] = sout04['sharperatio']sout042 = analyzers[3].get_analysis() dic1['夏普率_2'] = sout042['sharperatio']sout03 = analyzers[4].get_analysis()       a2 = pd.Series(sout03)recent_1y = a2[-252:].sum() *100dic1['近1年'] = recent_1y list_analyzer.append(dic1)return list_analyzer

评价指标的参数

另外一个解决的问题就是改变评价指标参数的默认值。

以夏普比率为例,之前全部都是默认值,有一个计算结果出来我们就觉得OK了,后来在使用empyrical和quantstats的过程中,发现它们计算得到的夏普率数据并不相等,某一些数值相近,另外有些数值差异比较大。夏普率的计算公式都是一样的,那么差别出在哪里?有可能在参数设置。

策略参数2023年化2024年化夏普率qs_sharp近6月近1年
经典KD交叉( 8, 2, 3)40.01097911.8447701.7700551.77243510.54820327.697481
经典KD交叉( 8, 3, 3)33.6357696.0653751.3674501.3674495.13649721.761362
经典KD交叉( 8, 4, 3)28.9504233.3149831.1806081.1247282.57326413.866847
经典KD交叉( 9, 2, 3)43.5853185.3790401.2292311.6116064.57158425.490329
经典KD交叉( 9, 3, 3)35.8520674.5358261.2258141.3803903.67702721.301610
经典KD交叉( 9, 4, 3)22.8904256.9616501.7485381.0488586.15269012.667302

 我们打开源码“\backtrader\analyzers\sharpe.py”文件,找到它的参数设置的部分

    params = (('timeframe', TimeFrame.Years),('compression', 1),('riskfreerate', 0.01),('factor', None),('convertrate', True),('annualize', False),('stddev_sample', False),# old behavior('daysfactor', None),('legacyannual', False),('fund', None),)RATEFACTORS = {TimeFrame.Days: 252,TimeFrame.Weeks: 52,TimeFrame.Months: 12,TimeFrame.Years: 1,}

 再把它的内容喂到AI里要中文解析,得到的部分解析说明如下

  • timeframe: 表示分析的时间范围,默认为年(Years)。
  • compression: 用于子日(sub-day)时间范围的压缩因子,默认为1。
  • riskfreerate: 无风险利率,默认为1%(以年度表示)。
  • convertrate: 如果为真,则将无风险利率从年度转换为月度、周度或日度利率。
  • factor: 转换因子,用于从年度无风险利率转换到选定的时间范围。
  • annualize: 如果为真,并且convertrate也为真,则最终计算出的夏普比率将以年度化形式给出。
  • stddev_sample: 如果为真,在计算标准差时会应用贝塞尔修正(Bessel's correction),即在计算平均值时分母减一。
  • daysfactor: 这是factor的旧命名方式,如果设置且时间范围为天(Days),则认为是旧代码。
  • legacyannual: 如果为真,使用AnnualReturn分析器,这仅适用于年度数据。
  • fund: 如果为None,则自动检测经纪人的基金模式;可以显式设置为真或假。

 而在quantstats中的sharp,我们看到默认是periods=252,应该是相当于上面的TimeFrame.Days,而不是默认的TimeFrame.Years

def sharpe(returns, rf=0.0, periods=252, annualize=True, smart=False):pass

 从默认的参数似乎得到一个有点矛盾的参数设定,因为convertrate默认是True,按解析它会把无风险利率转换为月、周或日度,反正不是年度;但是Rp似乎又是以年度来计算的......所以我们可以尝试着更改它的参数设置来一探究竟。

​改变评价指标的参数

一开始自己把问题想复杂了,也受到第一次问AI回答的影响,一度在错误的道路上越行越远,其实backtrader已经把它做了简化处理,直接写上即可,这里把实践过程中的2个错误示例也列了出来,其中第1个其实是问AI得到的。

# 错误示例1
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio', params=dict(riskfreerate=0.02))# 错误示例2
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio', params['riskfreerate']=0.02))# 正确用法
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio', riskfreerate=0.01)

再知道了怎么更改评价指标的参数后,我们测试了几组不同更改的夏普率值,同时也跟Quantstats计算的夏普率做了一个对比,从结果上看,backtrader的sharpe计算某些参数单独更改不起效果,例如convertrate,legacyannual等,factor更改为252后计算值会变化,另外riskfreerate越大即无风险收益越高则夏普率数值会减小。

另外,根据quantstats计算的sharpe值,与backtrader不太匹配,我们把近1年的日收益率数值列出来后发现,quantstats明显的sharpe值与近1年的日收益率正相关,而backtrader的sharpe值与2024年化的值似乎正相关,所以backtrader的似乎是用年化收益来计算的。

参数2024年化夏普率_default夏普率_rf2夏普率_252qs_sharp近1年
( 8, 2, 3)11.8447701.7700551.6990481.8410621.82976027.697481
( 8, 3, 3)6.0653751.3674501.2949091.4399921.42457221.761362
( 8, 4, 3)3.3149831.1806081.1025911.2586251.18217313.866847
( 9, 2, 3)5.3790401.2292311.1768841.2815791.66804225.490329
( 9, 3, 3)4.5358261.2258141.1619501.2896791.43776121.301610
( 9, 4, 3)6.9616501.7485381.6229791.8740971.10492112.667302
(10, 2, 3)4.2108471.1784831.1228961.2340711.46142321.82432

03_制作类应用_自定义评价

001_直接继承自评价指标

类的应用,首先我们可以继承内置评价的类,然后可以简单的参数进行预设,也可以制作自己的方法将数据直接以字典的方式输出等。我们在源码中看到有很多评价指标,这些在第1节里我们一个个都进行了实践,也大体上明白它们的用法和得到的数据以及输出。

from .annualreturn import *
from .drawdown import *
from .timereturn import *
from .sharpe import *
from .tradeanalyzer import *
from .sqn import *
from .leverage import *
from .positions import *
from .transactions import *
from .pyfolio import *
from .returns import *
from .vwr import *from .logreturnsrolling import *from .calmar import *
from .periodstats import *

我们就以夏普率这个常用评价指标为例,新建的类继承bt.analyzers.SharpeRatio,所以类的成员及方法都是直接可以使用的,例如可以把它的无风险收益设为0.03,又例如可以重写get_analysis()函数或者新建一个get_my_dict()的方法等。

# 创建一个继承自backtrader.analyzers.SharpeRatio的自定义分析器类
class CustomSharpeRatio(bt.analyzers.SharpeRatio):params = (('riskfreerate', 0.03),  # 设置无风险利率为0.03)def get_my_dict(self):output = self.get_analysis()return dict(output)# .............
cerebro = bt.Cerebro(cerebro.addanalyzer(mySharpeRatio,_name='mySharpe')result = cerebro.run()    
strat = result[0]output = strat.analyzers.mySharpe.get_analysis()  # 两种方式
# output = strat.analyzers.getbyname('mySharpe').get_analysis()
print("output",output)op = strat.analyzers.mySharpe.get_my_dict()  # 自定义方法
print(op)-----------------------------------
output OrderedDict([('sharperatio', 0.8272985845005197)])
{'sharperatio': 0.8272985845005197}

002_自定义评价

参考文档: Analyzers - Backtrader

将官方文档的内容(现在都是偷懒的直接送到AI去解析)解析后得到的知识点如下:

  • 类名backtrader.Analyzer

  • 描述:所有分析器的基类,为策略提供分析

  • 自动设置成员属性

    • self.strategy:提供对策略及其所有可访问属性的访问
    • self.datas[x]:提供对系统中数据流数组的访问
    • self.data:等同于self.datas[0]
    • self.dataX:等同于self.datas[X]
    • self.dataX_Y:等同于self.datas[X].lines[Y]
    • self.dataX_name:等同于self.datas[X].name
    • self.data_name:等同于self.datas[0].name
    • self.data_Y:等同于self.datas[0].lines[Y]
  • 方法

    • __init__:实例化和初始设置
    • start():指示操作开始,用于设置所需的事项
    • stop():指示操作结束,用于关闭所需的事项
    • prenext():策略达到最小周期前,每次prenext调用时调用
    • nextstart():当策略首次达到最小周期时调用一次
    • next():策略达到最小周期后,每次next调用时调用
    • notify_cashvalue(cash, value):每次next循环前,接收现金/价值通知
    • notify_fund(cash, value, fundvalue, shares):接收当前现金、价值、基金价值和基金股份
    • notify_order(order):每次next循环前,接收订单通知
    • notify_trade(trade):每次next循环前,接收交易通知
    • get_analysis():返回包含分析结果的字典样对象
    • create_analysis():由子类重写,用于创建保存分析的结构
    • print(*args, **kwargs):通过标准Writerfile对象打印分析结果,默认写入标准输出
    • pprint(*args, **kwargs):使用Python的pprint模组打印分析结果
    • len():返回分析器所操作策略的当前长度
  • 操作模式:开放模式,无偏好模式

  • 分析生成:可在next调用中生成分析,或在stop中结束时生成,甚至在notify_trade中生成

  • get_analysis重要性:必须重写以返回分析结果

  • 默认行为get_analysis返回由create_analysis方法创建的默认OrderedDict对象rets

 并且我们还可以直接把官方文档中的示例代码拿出来进行研究,在这里看到夏普率的评价(04)是会使用到AnnualReturn(01)这个评价的,在init里,self.anret = AnnualReturn(),最后在stop方法中,通过了self.anret进行了计算。从这里,我们是不是可以解答上面为什么backtrader计算的夏普率和quantstats计算出来的有差别了,qs计算时用到的数据是日收益率,而backtrader用的数据是年收益率。

from backtrader.analyzers import AnnualReturnclass SharpeRatio(Analyzer):params = (('timeframe', TimeFrame.Years), ('riskfreerate', 0.01),)def __init__(self):super(SharpeRatio, self).__init__()self.anret = AnnualReturn()def start(self):# Not needed ... but could be usedpassdef next(self):# Not needed ... but could be usedpassdef stop(self):retfree = [self.p.riskfreerate] * len(self.anret.rets)retavg = average(list(map(operator.sub, self.anret.rets, retfree)))retdev = standarddev(self.anret.rets)self.ratio = retavg / retdevdef get_analysis(self):return dict(sharperatio=self.ratio)

 多示例代码我们看到,它跟策略类的写法很类似,都有__init__,start, next, stop等,看起来评价指标的计算往往都是在stop里进行的,最后它会有一个get_analysis()方法得到评价指标的输出。

并且在一个评价指标类里面,是可以很轻松的调用其他评价指标类中的数据和方法,比如上面的self.anret = AnnualReturn() 。

003_自定义类添加多个内置评价

于是我们设想做一个自定义类,把需要的评价指标都加进去的那种。

先来做一个放了三个评价指标的简单实践

from backtrader.analyzers import TimeReturn, SharpeRatio, VWRclass MyAnalyzer(bt.Analyzer):def __init__(self, data):self.data = data# 创建内置Analyzer的实例self.timereturn = TimeReturn()self.sharperatio = SharpeRatio()self.vwr = VWR()def start(self):# 启动内置Analyzerself.timereturn.start()self.sharperatio.start()self.vwr.start()def stop(self):# 停止内置Analyzerself.timereturn.stop()self.sharperatio.stop()self.vwr.stop()def get_analysis(self):# 获取内置Analyzer的分析结果return {"timereturn": self.timereturn.get_analysis(),"sharperatio": self.sharperatio.get_analysis(),"vwr": self.vwr.get_analysis()}

运行后结果:

out1 {'timereturn': OrderedDict([(datetime.datetime(2023, 1, 11, 0, 0), 0.0), ......(datetime.datetime(2024, 7, 16, 0, 0), -0.002108766373780102)]), 'sharperatio': OrderedDict([('sharperatio', 0.4870776964306147)]), 'vwr': OrderedDict([('vwr', 4.415307366076662)])}

这里,在自定义评价类 MyAnalyzer中,添加了包括 timereturn, sharperatio, vwr在内的三个内置评价指标,在__init__()中创建了内置评价指标的实例,在start中手动将每个内置评价start(),在stop中又手动将每个内置评价stop(),最后在get_analysis中返回3个内置评价的get_analysis()的结果。

其实,我们去看sharperatio的源码,它也没有start方法,也没有去给AnnualReturn做start()或者stop(),所以,中间的两段def start() 和 def stop()都不需要。

00_不使用Analyzer

这里的不使用,一方面是可能初学的时候还不知道有哪些内置的评价指标,但就是着急做一些常见的评价出来,比如说胜率、盈亏比等,这些计算都很简单,因此不使用Analyzer也能做的出来;另一方面是不会使用results,特别是遇到参数优化就不知道评价分数放在哪里,还不会用。

于是,可以把评价放到了策略类的stop()里,并且根据notify_trade的信息,进行简单的评价计算。

另外,当我们实践学习了借用第三方库例如empyrical或quantstats进行评价计算,通常只需要一项数据即日收益率就可以了,这项数据计算也很简单,第3节也实践过,用pandas一个函数就搞定。

df['pct_change'] = df['close'].pct_change()

在这样的基础上,我们还可以获取基准的日收益数据,这个基准可以是大盘,沪深300,创业板指数等指数,其实也可以是当前这支股票买入不动它自身的基础日收益率,有了基准就可以计算各种其他评价例如阿尔法-贝塔值等。

本篇结束

由上,需不需要使用backtrader的Analyzer,看自己的需求。

在进行了一轮评价统计篇的实践之后,我们加深了对backtrader以及评价指标的理解,也学会了使用其他的库来计算指标以及可视化操作,我们可以创建自定义的评价类来应用内置或者借用甚至自己写计算公式来制作评价。

得到了一堆评价分数后,那我们对当前股票 vs. 策略 vs. 参数就有了一个数据分布,前面提到过或许某些股票就适合某些特殊的策略,某些股票就适合某些特殊的参数设定,所以我认为应该先找到各自的良配。

这篇关于量化交易backtrader实践(四)_评价统计篇(5)_自定义评价的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1137544

相关文章

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

在C#中获取端口号与系统信息的高效实践

《在C#中获取端口号与系统信息的高效实践》在现代软件开发中,尤其是系统管理、运维、监控和性能优化等场景中,了解计算机硬件和网络的状态至关重要,C#作为一种广泛应用的编程语言,提供了丰富的API来帮助开... 目录引言1. 获取端口号信息1.1 获取活动的 TCP 和 UDP 连接说明:应用场景:2. 获取硬

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

Docker集成CI/CD的项目实践

《Docker集成CI/CD的项目实践》本文主要介绍了Docker集成CI/CD的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、引言1.1 什么是 CI/CD?1.2 docker 在 CI/CD 中的作用二、Docke

opencv实现像素统计的示例代码

《opencv实现像素统计的示例代码》本文介绍了OpenCV中统计图像像素信息的常用方法和函数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 统计像素值的基本信息2. 统计像素值的直方图3. 统计像素值的总和4. 统计非零像素的数量

如何评价Ubuntu 24.04 LTS? Ubuntu 24.04 LTS新功能亮点和重要变化

《如何评价Ubuntu24.04LTS?Ubuntu24.04LTS新功能亮点和重要变化》Ubuntu24.04LTS即将发布,带来一系列提升用户体验的显著功能,本文深入探讨了该版本的亮... Ubuntu 24.04 LTS,代号 Noble NumBAT,正式发布下载!如果你在使用 Ubuntu 23.

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

如何使用 Bash 脚本中的time命令来统计命令执行时间(中英双语)

《如何使用Bash脚本中的time命令来统计命令执行时间(中英双语)》本文介绍了如何在Bash脚本中使用`time`命令来测量命令执行时间,包括`real`、`user`和`sys`三个时间指标,... 使用 Bash 脚本中的 time 命令来统计命令执行时间在日常的开发和运维过程中,性能监控和优化是不

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M