Backtrader 文档学习- 整体架构功能分析理解

王柳  金牌会员 | 2024-7-29 22:51:15 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 823|帖子 823|积分 2469

Backtrader 文档学习- 架构功能分析理解

1. 概述

backtrader是一个用于开发和实验交易策略的Python框架。它提供了一套完整的工具和功能,使得用户可以方便地举行策略回测、实盘交易以及数据分析。
backtrader的入口为Cerebro类,该类将全部输入(Data Feeds)、策略 (Strategy)、观察者(Observers)、策略评估(Analyzers) 、经纪人(Broker)、订单(Order)、交易(Trade)和日记记录(Writers)整合起来,实现回测以及交易,并返回结果和图表。
backtrader优缺点,先说缺点:
(1)缺点:



  • 在于使用元类编程技能,使用了大量的类,调试的时间会比较难。
  • 对于python的要求高,需要掌握pandas,matplotlib ,有基础上手更容易。
  • 对环境存在兼容性, 使用pandas ,numpy ,matplotlib 的版本不能太高。
  • 对回测结果分析,需定制化做大量工作,比如需要将各类策略的测试结果,除了能打印日记外,需要保存到数据库中,便于后期对比分析,比如周期参数,5天、10天、20天同一个策略不同的周期,实验的绩效记录处置惩罚。
(2)优点:



  • 功能非常美满,支持多品种、多策略、多周期的回测和交易,编写策略简单,上手容易。
  • 交易的品种非常丰富(股票、期货、期权、外汇等)。
  • 详细的文档,提供的用例比较多,可以学习和根据需求改进 。
  • 在量化框架上,收费的居多,优矿,米筐,聚宽,不能当地化部署。免费开源的少。
  • 支持内部方法的重写,可以方便定制化策略。
  • 内置多个指标参数,很丰富,包括Talib 。
(3)使用目的:



  • 以研究策略为主,验证策略效果,选择标的股票。
  • 不需要太多的参数因子,基于通用指标,可以灵活组合。
  • 不做实盘高频交易。
  • 适合个人学习使用,探索根本量化方向,避免盲目交易,有数据支撑。
2.架构理解

backtrader的整体架构可以分为以下紧张组件:

(1)数据源(Data Feeds):

backtrader支持多种数据源,包括CSV文件、Pandas DataFrame、及时数据源等。用户可以根据自己的需求选择适合的数据源,并通过数据加载器将数据加载到backtrader中举行处置惩罚。
backtrader回测的数据类型是由一系列的点构成的Lines,通常包括以下种别的数据:Open(开盘价)、High(最高价)、Low(最低价)、 Close(收盘价)、Volume(成交量)、OpenInterest(未平仓权益)。Data Feeds(数据加载)、Indicators(技能指标)和Strategies(策略)都会天生 Lines。代价数据中的全部的开盘价按时间构成一条 Line,因此一组含有以上6个种别的代价数据,共有6条 Lines。如果算上DateTime(时间,可以看作是一组数据的主键),一共有7条 Lines。Lines根据需要是可以扩展的。
焦点概念:
Line的概念和Python中的list索引不同,Python List中索引-1,表示最后一个数据。
一条Line数据的下标为0表示访问当前时刻数据,-1表示访问下一个数据。在回测过程中,无需知道已经处置惩罚了多少条/分钟/天/月,”0”不停指向当前值,下标 -1 来访问下一个值,下标 -2 访问下下一个值。
在backtrader中,-1指的是当前处置惩罚数据(索引为0)的上一个数据。
Lines是随时变革的,run的时间,next不断改变Lines的长度,在数据载入,策略,指示器应用中,需要丈量Lines的长度。
两个长度函数:len和buflen之间的区别:


  • len已处置惩罚了Lines
  • buflen为数据加载Lines的总数
(2)策略(Strategies):

backtrader提供了一个基类Strategy,用户可以继承该类并实现自己的交易策略。策略类中界说了一系列的回调函数,用户可以在回测函数中编写详细的交易逻辑。
该模块是回测体系最焦点的部门,需要设计交易决策,得出买入/卖出信号。策略类代码包罗紧张的参数和用于实验策略的功能,要界说的参数或函数名如下:
(1)params:全局参数,可选,用于更改交易策略中变量/参数的值,可用于参数调优。
(2)log:日记,可选,用于记录策略的实验日记,可以打印出该函数提供的日期时间和txt变量。
(3)init:用于初始化交易策略,类中使用的全局变量界说,在此中声明的任何指标都会在next()方法调用之进步行计算。部门python操纵符不支持,需要使用bt内置函数来处置惩罚,比方bt.And, bt.Or, bt.All, bt.Any等。
(4)notify_order,可选,用于跟踪交易订单(order)的状态。order具有提交,担当,买入/卖出实验和代价,已取消/拒绝等状态。
(5)notify_trade,可选,用于跟踪交易的状态,任何已平仓的交易都将报告毛利和净利润。
(6)next,必选,用于订定交易策略的函数,策略模块最焦点的部门。
当满足全部data/indicators的最小周期后,实验将对全部剩余数据点调用。
如period是10 ,最小周期达到后,开始实验next方法。
(7)nextstart(),可选,方法实验一次,在最小周期的data/indicators满足时,默认实验next方法。
如period是10 ,在到达9实验最小周期达到后,实验一次nextstart,之后实验next方法。
(8)prenext(),可选,方法将在全部数据/指标的最小周期满足策略开始实验之前调用实验。
如period是10 ,在1到9实验最小周期之间,实验prenext。
(9)start(),可选,在回测即将开始之前调用,实验一次。
(10)stop(),可选,在回测即将结束之前调用,实验一次。
(11)notify_cashvalue(),可选,吸收当前使用的资金余额
(12)notify_fund(),可选,策略的资金和代价发生变革时被调用。用于跟踪和记录策略的现金、代价、基金代价和持仓股票的数目。
(3)指标(Indicators):

backtrader内置了许多常用的技能指标,如移动平均线、MACD、RSI等。用户可以通过指标类来计算这些指标,并在策略中使用。
backtrader在指标中也集成了TA-Lib库
(4)订单(Order):

backtrader提供了一套美满的订单管理体系,用户可以通过创建订单对象来实验买入和卖出操纵。订单管理体系还支持订单状态的跟踪和管理。
将策略中逻辑做出的决策转换为适合经纪人实验操纵的消息,通常在交易策略中调用。
Order将strategy的逻辑做出的决策转换为适合broker实验操纵的消息。通过以下方式完成:
创建 ,通过strategy 的方法:buy sell 和close 都可以返回Order的实例
取消,通过strategy的方法:cancel 也可以产生Order实例
通知,notify_order 的方法,也返回Order 实例
订单还作为反馈给用户的通信方法,通知代理中的运行环境
(5)经纪人(Broker):

通过设置回测的初始自己、佣金费率、税收费率、滑点率等交易条件,模仿不同的订单类型,限价订单,控制订单的有效期,并根据现金检查订单,计算每次交易的现金和权益,保存交易数据。
Trade :backtrader会自动记录每笔交易的详细信息,包括买入代价、卖出代价、手续费等。用户可以通过这些交易记录举行后续的分析和统计。
Position :仓位管理 。
(6)策略评估(Analyzers):

用于分析交易策略的利润和风险,分析交易体系的绩效。
内置各类评价指标,可以参加到评测对象中,对结果举行绩效分析,分析指标:年度回报率,卡尔玛比率,最大回撤,资金杠杆,仓位资金,组合投资值等,是对策略的效果评估参数。
(7)观察者(Observers):

用于记录交易过程,包括现金、权益、费用以及交易动作、买卖订单等数据。
(8)回测引擎(Backtesting Engine):

cerebro.run()
backtrader提供了一个强盛的回测引擎,用户可以通过指定回测时间段和初始资金等参数来举行策略回测。回测引擎会模仿真实的交易环境,并根据用户界说的策略举行交易。
(9)画图(Plot):

分析和可视化(Analysis and Visualization):
backtrader提供了多种分析工具和可视化功能,用户可以对回测结果举行详细的分析和可视化展示,可以增长图示内容,包括收益曲线、风险指标、交易位置等。
通过图形的方式显示交易丈量回测的结果,画图显示的结果包括三部门类型:现金及权益、交易损益、买卖动作。
画图设置通过plotinfo来设置,其参数紧张有:plot(是否画图,默认为True),subplot(是否单独窗口画图,默认为True,MA类指标该参数为False),plotname(指标图名,默认为指标类名),plotabove(画图位置在数据上方,默认为False),plotlinelabels, plotymargin, plotyticks,plothlines, plotyhlines, plotforce。
3.代码示例

做一个最简单的SMA测试示例。
取100个股票,举行测试,测试的时间范围从2016年到2020年,一共5年的数据。
在策略中的init()初始化界说了Indicator ,在next()中买卖操纵。
增长了Observer和Analyzer ,把大多数的监控绩效的指标都参加了。
  1. #!/usr/bin/env python
  2. import datetime
  3. import pandas as pd
  4. import numpy as np
  5. import pymysql
  6. import backtrader as bt
  7. import backtrader.feeds as btfeeds
  8. import backtrader.indicators as btind
  9. from sqlalchemy import create_engine
  10. engine_ts = create_engine(
  11.     'mysql+pymysql://user:pwd@ip:3306/dbname?charset=utf8&use_unicode=1')
  12. def get_stock_code():
  13.     sql = "select t.ts_code from ts_stock_basic t where t.list_status='L' limit 100;"
  14.     stock_data = pd.read_sql(sql, con=engine_ts)
  15.     return stock_data
  16. def get_code(stock_code):
  17.     sql = "select t.trade_date as date,t.`open`,t.high,t.low,t.`close`,t.vol,t.amount from ts_stock_daily t where  \
  18.           t.ts_code='" + stock_code + "' and t.trade_date > '2016-01-01' and t.trade_date < '2020-12-31' order by date ;"
  19.     # print(sql)
  20.     # stock_data = pd.read_sql(sql, con=engine_ts,index_col="date")
  21.     # 因为BackTrader日期类型必须是datetime ,从数据库中读取的日期类型是date 。
  22.     # 读数据,先不设置索引
  23.     stock_data = pd.read_sql(sql, con=engine_ts)  # ,index_col="date"
  24.     # 增加一列,select 字段名是date,赋值到trade_date,同时转datetime类型
  25.     stock_data['trade_date'] = pd.to_datetime(stock_data['date'], format='%Y%m%d %H:%M:%S')
  26.     # 删除原来的date列
  27.     stock_data.drop(columns=['date'])
  28.     # 新datetime列作为索引列
  29.     stock_data.set_index(['trade_date'], inplace=True)
  30.     # 索引列改名
  31.     stock_data.index.name = 'date'
  32.     # 按backtrader 格式要求,第7列openinterest ,也可以不用
  33.     # stock_data['openinterest'] = 0
  34.     data = stock_data.sort_index(ascending=True)
  35.     #engine_ts.dispose()
  36.     return data
  37. class OrderObserver(bt.observer.Observer):
  38.     lines = ('created', 'expired',)
  39.     plotinfo = dict(plot=True, subplot=True, plotlinelabels=True)
  40.     plotlines = dict(
  41.         created=dict(marker='*', markersize=8.0, color='lime', fillstyle='full'),
  42.         expired=dict(marker='s', markersize=8.0, color='red', fillstyle='full')
  43.     )
  44.     def next(self):
  45.         for order in self._owner._orderspending:
  46.             if order.data is not self.data:
  47.                 continue
  48.             if not order.isbuy():
  49.                 continue
  50.             # Only interested in "buy" orders, because the sell orders
  51.             # in the strategy are Market orders and will be immediately
  52.             # executed
  53.             if order.status in [bt.Order.Accepted, bt.Order.Submitted]:
  54.                 self.lines.created[0] = order.created.price
  55.             elif order.status in [bt.Order.Expired]:
  56.                 self.lines.expired[0] = order.created.price
  57. # CrossOver Strategy
  58. class St_CrossOver(bt.Strategy):
  59.     params = (
  60.         ('smaperiod', 15),
  61.         ('limitperc', 1.0),
  62.         ('valid', 7),
  63.         ('print', False),
  64.     )
  65.     def log(self, txt, dt=None):
  66.         ''' Logging function fot this strategy'''
  67.         dt = dt or self.data.datetime[0]
  68.         if isinstance(dt, float):
  69.             dt = bt.num2date(dt).date() # no Hour mintue second
  70.         if self.params.print :
  71.             print('%s, %s' % (dt.isoformat(), txt))
  72.     def notify_order(self, order):
  73.         if order.status in [order.Submitted, order.Accepted]:
  74.             # Buy/Sell order submitted/accepted to/by broker - Nothing to do
  75.             self.log('ORDER ACCEPTED/SUBMITTED', dt=order.created.dt)
  76.             self.order = order
  77.             return
  78.         if order.status in [order.Expired]:
  79.             self.log('BUY EXPIRED')
  80.         elif order.status in [order.Completed]:
  81.             if order.isbuy():
  82.                 self.log(
  83.                     'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
  84.                     (order.executed.price,
  85.                      order.executed.value,
  86.                      order.executed.comm))
  87.             else:  # Sell
  88.                 self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
  89.                          (order.executed.price,
  90.                           order.executed.value,
  91.                           order.executed.comm))
  92.         # Sentinel to None: new orders allowed
  93.         self.order = None
  94.     def __init__(self):
  95.         # SimpleMovingAverage on main data
  96.         # Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)
  97.         sma = btind.SMA(period=self.p.smaperiod)
  98.         # CrossOver (1: up, -1: down) close / sma
  99.         self.buysell = btind.CrossOver(self.data.close, sma, plot=True)
  100.         # Sentinel to None: new ordersa allowed
  101.         self.order = None
  102.     def next(self):
  103.         if self.order:
  104.             # pending order ... do nothing
  105.             return
  106.         # Check if we are in the market
  107.         if self.position:
  108.             if self.buysell < 0:
  109.                 self.log('SELL CREATE, %.2f' % self.data.close[0])
  110.                 self.sell()
  111.         elif self.buysell > 0:
  112.             plimit = self.data.close[0] * (1.0 - self.p.limitperc / 100.0)
  113.             valid = self.data.datetime.date(0) + \
  114.                 datetime.timedelta(days=self.p.valid)
  115.             self.log('BUY CREATE, %.2f' % plimit)
  116.             self.buy(exectype=bt.Order.Limit, price=plimit, valid=valid)
  117. def runstrat(stock_code):
  118.     cerebro = bt.Cerebro()
  119.     # 初始资金 100,000,000
  120.     cerebro.broker.setcash(100000.0)
  121.     # 佣金,双边各 0.0003
  122.     cerebro.broker.setcommission(commission=0.0003)
  123.     # 滑点:双边各 0.0001
  124.     cerebro.broker.set_slippage_perc(perc=0.0001)
  125.     # 获取数据
  126.     stock_df = get_code(stock_code)
  127.     start_date = datetime.datetime(2016, 1, 1)  # 回测开始时间
  128.     end_date = datetime.datetime(2020, 12, 31)  # 回测结束时间
  129.     data = bt.feeds.PandasData(dataname=stock_df, fromdate=start_date, todate=end_date)  # 加载数据
  130.     cerebro.adddata(data)
  131.     cerebro.addobserver(OrderObserver)
  132.     cerebro.addstrategy(St_CrossOver)
  133.     tframes = dict(
  134.         days=bt.TimeFrame.Days,
  135.         weeks=bt.TimeFrame.Weeks,
  136.         months=bt.TimeFrame.Months,
  137.         years=bt.TimeFrame.Years)
  138.     cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='mysharpe')
  139.     cerebro.addanalyzer(bt.analyzers.DrawDown,_name = 'mydrown')
  140.     cerebro.addanalyzer(bt.analyzers.AnnualReturn,_name = 'myannualreturn')
  141.     cerebro.addanalyzer(bt.analyzers.SQN,_name = 'mysqn')
  142.     cerebro.addanalyzer(bt.analyzers.TradeAnalyzer,_name = 'mytradeanalyzer')
  143.     cerebro.addanalyzer(bt.analyzers.PositionsValue,_name = 'mypositionvalue')
  144.     cerebro.addanalyzer(bt.analyzers.Returns,_name = 'myreturns')
  145.     cerebro.addanalyzer(bt.analyzers.LogReturnsRolling,timeframe=tframes['years'],_name = 'mylogreturnsrolling')
  146.     cerebro.addanalyzer(bt.analyzers.Transactions, _name='mytransactions')
  147.     thestrats = cerebro.run()
  148.     thestrat = thestrats[0]
  149.     print('Sharpe Ratio:', thestrat.analyzers.mysharpe.get_analysis())
  150.     print('DrawDown:', thestrat.analyzers.mydrown.get_analysis())
  151.     print('AnnualReturn:', thestrat.analyzers.myannualreturn.get_analysis())
  152.     print('SQN:', thestrat.analyzers.mysqn.get_analysis())
  153.     print('TradeAnalyzer:', thestrat.analyzers.mytradeanalyzer.get_analysis())
  154.     print('PositionsValue:', thestrat.analyzers.mypositionvalue.get_analysis())
  155.     print('Returns:', thestrat.analyzers.myreturns.get_analysis())
  156.     print('LogReturnsRolling:', thestrat.analyzers.mylogreturnsrolling.get_analysis())
  157.     print('Transactions:', thestrat.analyzers.mytransactions.get_analysis())
  158.     #cerebro.plot()
  159. if __name__ == '__main__':
  160.    
  161.     t1 = datetime.datetime.now()
  162.     stock_data = get_stock_code()
  163.    
  164.     for i in range(len(stock_data)):
  165.         print(stock_data.loc[i, 'ts_code'])
  166.         runstrat(stock_data.loc[i, 'ts_code'])
  167.    
  168.     t2 = datetime.datetime.now()
  169.     print('Spend time:', t2 - t1)
  170.     engine_ts.dispose()
  171.     print('ok!')
复制代码
详细结果内容多,就不展示了。
结果是用了32.59秒完成计算,100个股票,5年的数据,速度还可以。
   Spend time: 0:00:32.591037
ok!
  4.小结

backtrader的整体架构是基于事件驱动的,用户通过编写策略类来界说交易逻辑,并通过回测引擎举行策略回测和分析。
通过上面的比较全面的功能测试,根本上覆盖前期学习的backtrader紧张功能,完成了一个自界说的策略回测验证。
在不考虑数据收罗的环境下,假设数据全部在数据库中,需要后期完成的工作很多:


  • 组合策略的界说,肯定不能是简单的SMA,可以结合MACD /RSI/KDJ/ADX 等等指标。
  • 时间周期的界说,触发不同指标的效果。
  • 策略评估结果保存入库后,举行横向比较。
  • 参数调优,最简单的如前篇文章,测试胜率和盈亏比组合,组合参数的递增或递减,绩效结果对比。
  • 黑天鹅事件,比如千股跌停的环境,异常极端环境的风险控制,2008年6月次贷危机天下经济危机,2015年6月到8月去杠杆,2016年1月熔断机制上线,尚有本年2月5日 。
  • 加仓机制实现
  • 止损机制实现
  • 止盈机制实现
  • 仓位管理实现
  • 对于不同走势界说不同的策略,如短期打板策略,需要看成交量、换手率,严格的止盈止损。
  • 策略模块化,可以是内置类
  • 日记,绩效结果模块化,到数据库
  • 绩效参考基准,比如沪深500
对于自界说的回测功能或控制内容,接待各人可以留言探究。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王柳

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表