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 ,给大家看看效果。

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

相关推荐
1037号森林里一段干木头23 天前
量化之一:均值回归策略
算法·均值算法·回归·量化
量化祛魅师1 个月前
1.量化第一步,搭建属于自己的金融数据库!
数据库·python·mysql·金融·量化
Fulin_Gao2 个月前
【量化部署】AWQ in MLSys 2024
部署·量化·awq
愤怒的可乐2 个月前
[论文笔记]LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale
论文阅读·量化
Aousdu2 个月前
量化交易backtrader实践(四)_评价统计篇(2)_评价输出
python·backtrader
jnpher3 个月前
开源一套金融大模型插件(ChatGPT)
ide·人工智能·vscode·ai·chatgpt·金融·量化
我爱学Python!3 个月前
量化方法怎么选?如何评估量化后的大模型LLM?
人工智能·自然语言处理·llm·大语言模型·量化·ai大模型·rag
engchina3 个月前
LLaMA模型量化方法优化:提高性能与减小模型大小
llama·量化
苏慕白的博客3 个月前
Rust调用sui合约
后端·金融·rust·量化·高频量化
伊织code4 个月前
llama.cpp
大模型·llama·量化·llama.cpp