Backtrader 量化回测实践(1)—— 架构理解和MACD/KDJ混合指标

Backtrader 量化回测实践(1)—— 架构理解和MACD/KDJ混合指标

按Backtrader的架构组织,整理了一个代码,包括了Backtrader所有的功能点,原来总是使用SMA最简单的指标,现在稍微增加了复杂性,用MACD和KDJ两个指标综合作为操作指标,因此买入卖出操作就比较少,还有就是买入的时候,采用了限价单,整个的交易频率不高,所以图示交易点比较少,也符合多看少动的交易理念。
通过代码结合架构图,可以充分去理解整个Backtrader的功能设计思路,前面一个功能一个功能学习理解,现在把所有的功能综合在一起进行展示,小有成就感。

回测的操作过程 :

  • #1.实例初始化
  • #2.加载数据 Data feeds
  • #3.加载策略 Strategy
  • #4.加载分析器 Analyzers
  • #5.加载观察者 Observers
  • #6.设置仓位管理 Sizers
  • #7.设置佣金管理 Commission
  • #8.设置初始资金
  • #9.启动回测
  • #10.回测结果

1. Backtrader的架构

在这里插入图片描述

2. 代码

import pandas as pd
import numpy as np

import common # get data
import datetime
import backtrader as bt

# 定义Observer
class OrderObserver(bt.observer.Observer):
    lines = ('created', 'expired',)
    # 做图参数设置
    plotinfo = dict(plot=True, subplot=True, plotlinelabels=True)
    # 创建工单 * 标识,过期工单 方块 标识
    plotlines = dict(
        created=dict(marker='*', markersize=8.0, color='lime', fillstyle='full'),
        expired=dict(marker='s', markersize=8.0, color='red', fillstyle='full')
    )

    # 处理 Lines
    def next(self):
        for order in self._owner._orderspending:
            if order.data is not self.data:
                continue

            if not order.isbuy():
                continue

            # Only interested in "buy" orders, because the sell orders
            # in the strategy are Market orders and will be immediately
            # executed

            if order.status in [bt.Order.Accepted, bt.Order.Submitted]:
                self.lines.created[0] = order.created.price

            elif order.status in [bt.Order.Expired]:
                self.lines.expired[0] = order.created.price

# 定义策略
class MACD_KDJStrategy(bt.Strategy):
    # 策略参数
    params = (
        ('highperiod', 9),
        ('lowperiod', 9),
        ('kperiod', 3),
        ('dperiod', 3),
        ('me1period', 12),
        ('me2period', 26),
        ('signalperiod', 9),
        ('limitperc', 1.0), # 限价比例 ,下跌1个百分点才买入,目的可以展示Observer的过期单
        ('valid', 7), # 限价周期
        ('print', False),
        ('counter', 0),  # 计数器
    )

    def log(self, txt, dt=None):
        """ Logging function fot this strategy"""
        dt = dt or self.datas[0].datetime.date(0)
        if self.params.print:
            print("%s, %s" % (dt.isoformat(), txt))

    def __init__(self):
        # 初始化全局变量,备用
        self.dataclose = self.datas[0].close
        self.dataopen = self.datas[0].open
        self.datahigh = self.datas[0].high
        self.datalow = self.datas[0].low
        self.volume = self.datas[0].volume

        self.order = None
        self.buyprice = None
        self.buycomm = None

        # N个交易日内最高价
        self.highest = bt.indicators.Highest(self.data.high, period=self.p.highperiod)
        # N个交易日内最低价
        self.lowest = bt.indicators.Lowest(self.data.low, period=self.p.lowperiod)

        # 计算rsv值 RSV=(CLOSE- LOW) / (HIGH-LOW) * 100
        # 如果被除数0 ,为None
        self.rsv = 100 * bt.DivByZero(
            self.data_close - self.lowest, self.highest - self.lowest, zero=None
        )

        # 计算rsv的N个周期加权平均值,即K值
        self.K = bt.indicators.EMA(self.rsv, period=self.p.kperiod, plot=False)
        # D值=K值 的N个周期加权平均值
        self.D = bt.indicators.EMA(self.K, period=self.p.dperiod, plot=False)
        # J=3*K-2*D
        self.J = 3 * self.K - 2 * self.D

        # MACD策略参数
        me1 = bt.indicators.EMA(self.data, period=self.p.me1period, plot=True)
        me2 = bt.indicators.EMA(self.data, period=self.p.me2period, plot=True)

        self.macd = me1 - me2
        self.signal = bt.indicators.EMA(self.macd, period=self.p.signalperiod)
        bt.indicators.MACDHisto(self.data)

    # 订单通知处理
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            return
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    "BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
                    % (order.executed.price, order.executed.value, order.executed.comm)
                )

                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
                self.bar_executed_close = self.dataclose[0]
            else:
                self.log(
                    "SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
                    % (order.executed.price, order.executed.value, order.executed.comm)
                )
            self.bar_executed = len(self)

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log("Order Canceled/Margin/Rejected")

        self.order = None

    # 交易通知处理
    def notify_trade(self, trade):
        if not trade.isclosed:
            return

        self.log("OPERATION PROFIT, GROSS %.2f, NET %.2f" % (trade.pnl, trade.pnlcomm))

    # 策略执行
    def next(self):
        self.log("Close, %.2f" % self.dataclose[0])
        if self.order:
            return

        # 空仓中,开仓买入
        if not self.position:
            # 买入基于MACD策略
            condition1 = self.macd[-1] - self.signal[-1] # 昨天低于signal
            condition2 = self.macd[0] - self.signal[0] # 今天高于signal
            # 买入基于KDJ策略 K值大于D值,K线向上突破D线时,为买进信号。下跌趋势中,K值小于D值,K线向下跌破D线时,为卖出信号。
            condition3 = self.K[-1] - self.D[-1] # 昨天J低于D
            condition4 = self.K[0] - self.D[0]   # 今天J高于D

            if condition1 < 0 and condition2 > 0 and condition3 < 0 and condition4 > 0 :
                self.log('BUY CREATE, %.2f' % self.dataclose[0])

                plimit = self.data.close[0] * (1.0 - self.p.limitperc / 100.0)
                valid = self.data.datetime.date(0) + datetime.timedelta(days=self.p.valid)
                self.log('BUY CREATE, %.2f' % plimit)
                # 限价购买
                self.buy(exectype=bt.Order.Limit, price=plimit, valid=valid)


        else:
            # 卖出基于MACD策略
            condition1 = self.macd[-1] - self.signal[-1]
            condition2 = self.macd[0] - self.signal[0]
            # 卖出基于KDJ策略
            condition3 = self.K[-1] - self.D[-1]
            condition4 = self.D[0] - self.D[0]

            if condition1 > 0 and condition2 < 0 and (condition3 > 0 or condition4 < 0):
                self.log("SELL CREATE, %.2f" % self.dataclose[0])
                self.order = self.sell()

    def start(self):
        # 从0 开始
        # self.params.counter += 1
        self.log('Strategy start %s' % self.params.counter)

    def nextstart(self):
        self.params.counter += 1
        self.log('Strategy nextstart %s' % self.params.counter)

    def prenext(self):
        self.params.counter += 1
        self.log('Strategy prenext  %s' % self.params.counter)

    def stop(self):
        self.params.counter += 1
        self.log('Strategy stop  %s' % self.params.counter)
        self.log('Ending Value %.2f' % ( self.broker.getvalue()))


if __name__ == "__main__":
    tframes = dict(
        days=bt.TimeFrame.Days,
        weeks=bt.TimeFrame.Weeks,
        months=bt.TimeFrame.Months,
        years=bt.TimeFrame.Years)

    #1.实例初始化
    cerebro = bt.Cerebro()

    # 2.加载数据 Data feeds
    # 加载数据到模型中,由dataframe 到 Lines 数据类型,查询10年数据到dataframe
    stock_df = common.get_data('000858.SZ','2010-01-01','2021-01-01')
    # 加载5年数据进行分析
    start_date = datetime.datetime(2016, 1, 1)  # 回测开始时间
    end_date = datetime.datetime(2020, 12, 31)  # 回测结束时间
    # bt数据转换
    data = bt.feeds.PandasData(dataname=stock_df, fromdate=start_date, todate=end_date)
    # bt加载数据
    cerebro.adddata(data)

    #3.加载策略 Strategy
    cerebro.addstrategy(MACD_KDJStrategy)

    #4.加载分析器 Analyzers
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='mysharpe')
    cerebro.addanalyzer(bt.analyzers.DrawDown,_name = 'mydrawdown')
    cerebro.addanalyzer(bt.analyzers.AnnualReturn,_name = 'myannualreturn')

    #5.加载观察者 Observers
    cerebro.addobserver(OrderObserver)

    #6.设置仓位管理 Sizers
    cerebro.addsizer(bt.sizers.FixedSize, stake=100)

    #7.设置佣金管理 Commission
    cerebro.broker.setcommission(commission=0.002)

    #8.设置初始资金
    cerebro.broker.setcash(100000)
    print("Starting Portfolio Value: %.2f" % cerebro.broker.getvalue())

    #9.启动回测
    checkstrats = cerebro.run()
    #数据源0 返回值处理
    checkstrat = checkstrats[0]

    #10.回测结果
    print("Final Portfolio Value: %.2f" % cerebro.broker.getvalue())

    print('夏普率:')
    for k, v in checkstrat.analyzers.mysharpe.get_analysis().items():
        print(k, ':', v)

    print('最大回测:')
    for k, v in checkstrat.analyzers.mydrawdown.get_analysis()['max'].items():
        print('max ', k, ':', v)

    print('年化收益率:')
    for year, ann_ret in checkstrat.analyzers.myannualreturn.get_analysis().items():
        print(year, ':', ann_ret)

    #11.回测图示
    cerebro.plot()

3.输出

Starting Portfolio Value: 100000.00
Final Portfolio Value: 109320.46
夏普率:
sharperatio : 0.24167200140493122
最大回测:
max  len : 323
max  drawdown : 4.220391363516371
max  moneydown : 4426.0
年化收益率:
2016 : 0.0
2017 : 0.03684790760000012
2018 : -0.027969386625977366
2019 : 0.07656254422728326
2020 : 0.007551367384477592

4.图示

在这里插入图片描述
做个有趣的猜测,如果对市场上所有的stock代码按程序的遍历一遍,不知道盈亏情况,比例如何?另外一个关心的就是消耗时间?

如果大家有兴趣知道结果,点赞收藏超过100 ,就做个Excel ,给大家看看效果。

仅供学习参考,不做交易操作依据。

  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尽力回答你的问题。首先,需要安装tushare和backtrader库。可以在Python中使用以下命令进行安装: ``` pip install tushare pip install backtrader ``` 接下来,我们需要从tushare获取数据。以下是代码示例: ```python import tushare as ts # 设置tushare pro的token ts.set_token('your_token') # 初始化pro接口 pro = ts.pro_api() # 获取股票代码为000001.SH,时间从20180101到20211231的日线数据 df = pro.daily(ts_code='000001.SH', start_date='20180101', end_date='20211231') ``` 接下来,我们需要编写策略。这里我们使用RSI指标和均线指标来实现策略。以下是代码示例: ```python import backtrader as bt class MyStrategy(bt.Strategy): params = ( ('rsi_period', 14), ('ema_period', 20), ) def __init__(self): self.rsi = bt.indicators.RSI(period=self.params.rsi_period) self.ema = bt.indicators.ExponentialMovingAverage(period=self.params.ema_period) def next(self): if self.rsi < 30 and self.data.close[0] > self.ema: self.buy() if self.rsi > 70: self.sell() ``` 最后,我们需要将数据加载到backtrader引擎中,并运行回测。以下是代码示例: ```python cerebro = bt.Cerebro() # 将数据加载到引擎中 data = bt.feeds.PandasData(dataname=df) cerebro.adddata(data) # 将策略加载到引擎中 cerebro.addstrategy(MyStrategy) # 设置初始资金 cerebro.broker.setcash(1000000) # 运行回测 cerebro.run() # 打印最终结果 print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) ``` 这样就完成了量化回测的编写。当然,这只是一个简单的示例,实际应用中还需要考虑许多因素,例如手续费、滑点、止损等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值