Python量化交易学习笔记(38)——backtrader多周期回测2

本文继续介绍backtrader多周期回测的第二种方法,使用resample来进行多周期数据的加载。

简述

如果需要做多周期的策略回测,但是只有单周期数据可用,那么就可以使用重采样(resampling)来解决多周期数据的生成问题。

这里的重采样(resampling)实际指的是上采样(upsampling),使用小周期的数据来合成大周期数据。例如,用日线数据合成周线数据。这里所说的上采样和信号处理的上采样效果是相反的,在信号处理中,上采样会获得比源数据更多的数据,而这里的上采样则是获得大周期的数据,较源数据而言,数据量变少。

backtrader的重采样

backtrader内置了重采样方法:

cerebro.resampledata(data, **kwargs)

其中data为源数据(小周期数据),通过该方法调用,重采样后的目标数据(大周期数据)就已经被添加到cerebro中。

该方法主要有两个参数来控制重采样的具体实现:

  • timeframe(默认值:bt.TimeFrame.Days)

该参数为目标数据的周期类型,目标数据周期应大于等于源数据,即如果源数据为日线数据,那么目标数据可以为日线、周线、月线或更大周期数据。

  • compression(默认值:1)

该参数为压缩比,取值为1到n,将目标数据的n根K线进一步压缩成1根。

使用resample时,同样也笔记(37)存在中提到的,大周期数据的使用会使得策略的最小周期变大的情况。

枢轴点(Pivot Point)

枢轴点本身就是一个多周期的技术指标,本文示例会用到Pivot Point指标,因此这里做一下简单介绍。

枢轴点指标考虑的是过去时间内更大周期的K线取值情况,例如,当对日线进行研判时,会使用上一个月的月线数据进行指标计算。当然也可以根据需要使用日线与其他大周期K线进行组合使用。

指标的计算公式为:

  • pivot = (high + low + close) / 3

  • support1 = 2.0 * pivot - high

  • support2 = pivot - (high - low)

  • resistance1 = 2.0 * pivot - low

  • resistance2 = pivot + (high - low)

仍以日线与月线的组合为例,这里的high、low、close分别对应于上月月线的最高价、最低价、收盘价。

在backtrader中,这5个指标对应的名称为p、s1、s2、r1、r2。其中p为轴心点,s1、s2对应第1、2级支撑,r1、r2对应于第1、2级阻力。

更多枢轴点的信息可参考:

https://school.stockcharts.com/doku.php?id=technical_indicators:pivot_points

示例

为了演示resample回测,本文使用以下方案:

  • 策略将先读入日线数据,然后通过resample生成并加载月线数据。回测股票为000001平安银行,回测周期为2018年1月1日至2019年12月31日。
# 加载数据
def load_data(stk_code, fromdate, todate):
    datapath = './stk_data/d/' + stk_code + '.csv'
    return bt.feeds.GenericCSVData(
        dataname = datapath,
        fromdate = fromdate,
        todate = todate + datetime.timedelta(days=1),
        nullvalue = 0.0,
        dtformat = ('%Y-%m-%d'),
        datetime = 0,
        open = 1,
        high = 2,
        low = 3,
        close = 4,
        volume = 5,
        openinterest = -1
        )

stk_list = ['sz.000001']
fromdate = datetime.datetime(2018, 1, 1)
todate = datetime.datetime(2019, 12, 31)
for stk_code in stk_list:
    data = load_data(stk_code, fromdate, todate)
    cerebro.adddata(data)
    cerebro.resampledata(data, timeframe = bt.TimeFrame.Months, compression = 1)

先加载日线数据,然后通过resample得到并加载月线数据。

  • 买入条件:价格回踩2级支撑或突破1级阻力;卖出条件:价格较最高收盘价回撤5%卖出。

在策略类的init方法中,定义所需的技术指标:

    def __init__(self):
        # 存储不同数据的技术指标
        self.inds = dict()
        # 存储特定股票的订单,key为股票的代码
        self.orders = dict()
        # 遍历所有数据
        for i, d in enumerate(self.datas):
            self.orders[d._name] = None
            # 为每个数据定义字典,存储技术指标
            self.inds[d] = dict()
            # 判断d是否为日线数据
            if 0 == i % 2:
                self.inds[d]['lowest'] = btind.Lowest(d, period = self.p.lowestperiod)
            # 判断d是否为月线数据
            else:
                # 定义pivot point指标
                self.inds[d]['pp'] = btind.PivotPoint(d)

定义字典self.inds,来存储不同数据的技术指标。

定义self.orders,来存储特定股票的订单,key为股票的代码。

然后遍历所有的数据,将数据的订单先置为空,并且为每个数据创建字典,来存储技术指标。由于系统是先添加日线数据,再添加月线数据,因此当i % 2等于0时,d为日线数据,那么就计算最小值指标;当i % 2不等于0时,d为月线数据,那么计算Pivot Point指标。

在策略类的next方法中,定义买入卖出条件:

    def next(self):
        for i, d in enumerate(self.datas):
            # 如果处理月线数据则跳过买卖条件,因为已在日线数据判断处理过
            if 1 == i % 2:
                continue
            pos = self.getposition(d)
            # 不在场内,则可以买入
            if not len(pos):
                # 达到买入条件
                month_pp = self.inds[self.datas[i + 1]]['pp']
                if (self.inds[d]['lowest'] <= month_pp.s2 and d.close > month_pp.s2) or (
                    self.inds[d]['lowest'] <= month_pp.r1 and d.close > month_pp.r1) :
                    # 买入手数
                    stake = int(self.broker.cash / len(stk_list) // (d.close[0] * 100)) * 100
                    # 买买买
                    self.buy(data = d, size = stake)
            elif not self.orders[d._name]:
                # 下保护点卖单
                self.orders[d._name] = self.close(data = d, exectype= bt.Order.StopTrail,
                            trailamount=self.p.trailamount,
                            trailpercent=self.p.trailpercent)

这里依然只对i % 2等于0的数据(即日线数据)进行条件判断,然后使用self.inds[self.datas[i + 1]][‘pp’]的方式对月线技术指标进行访问。

当达到买入条件后下买单,订单将在第二天以开盘价成交。在计算买入仓位大小时,保证资金得到最大程度的使用。

买单成交当天,下StopTrail卖单,当股价较最高收盘价回撤5%卖出(具体参加笔记(20)笔记(31))。这里使用close方法而不是sell方法,如果使用sell方法,股票将以1股1股的卖出,使用close则是全部卖出。

输出结果为:

2018-08-27 BUY sz.000001 EXECUTED, Price: 10.02
2018-09-10 SELL sz.000001 EXECUTED, Price: 9.91
2018-10-08 BUY sz.000001 EXECUTED, Price: 10.70
2018-10-11 SELL sz.000001 EXECUTED, Price: 10.03
2019-01-21 BUY sz.000001 EXECUTED, Price: 10.34
2019-03-08 SELL sz.000001 EXECUTED, Price: 12.43
2019-04-09 BUY sz.000001 EXECUTED, Price: 13.87
2019-04-23 SELL sz.000001 EXECUTED, Price: 13.99
2019-06-21 BUY sz.000001 EXECUTED, Price: 13.76
2019-07-08 SELL sz.000001 EXECUTED, Price: 13.47
2019-08-13 BUY sz.000001 EXECUTED, Price: 15.00
2019-08-22 SELL sz.000001 EXECUTED, Price: 14.24
2019-08-26 BUY sz.000001 EXECUTED, Price: 14.42
2019-10-22 SELL sz.000001 EXECUTED, Price: 16.36
Final Portfolio Value: 1184108.05

在这里插入图片描述
两年间共有7笔交易,收益率为18.4%

总结

  • backtrader可以通过resample来实现基于单周期数据的多周期数据生成与加载。

  • 使用源数据进行resample时,目标数据周期应大于等于源数据周期

  • 在策略实现时,通过数据索引的取余运算来区分源数据、目标数据。

多周期策略回测程序v2代码:

# 多周期
# 买入条件:价格回踩2级支撑或突破1级阻力
# 卖出条件:价格较最高收盘价回撤5%卖出
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import datetime
import pandas as pd


stk_list = ['sz.000001']

class PivotMultiTF(bt.Strategy):
    params = (
        ('lowestperiod', 5),
        ('trailamount', 0.0),
        ('trailpercent', 0.05),
    )

    def __init__(self):
        # 存储不同数据的技术指标
        self.inds = dict()
        # 存储特定股票的订单,key为股票的代码
        self.orders = dict()
        # 遍历所有数据
        for i, d in enumerate(self.datas):
            self.orders[d._name] = None
            # 为每个数据定义字典,存储技术指标
            self.inds[d] = dict()
            # 判断d是否为日线数据
            if 0 == i % 2:
                self.inds[d]['lowest'] = btind.Lowest(d, period = self.p.lowestperiod)
            # 判断d是否为月线数据
            else:
                # 定义pivot point指标
                self.inds[d]['pp'] = btind.PivotPoint(d)
            
    def next(self):
        for i, d in enumerate(self.datas):
            # 如果处理月线数据则跳过买卖条件,因为已在日线数据判断处理过
            if 1 == i % 2:
                continue
            pos = self.getposition(d)
            # 不在场内,则可以买入
            if not len(pos):
                # 达到买入条件
                month_pp = self.inds[self.datas[i + 1]]['pp']
                if (self.inds[d]['lowest'] <= month_pp.s2 and d.close > month_pp.s2) or (
                    self.inds[d]['lowest'] <= month_pp.r1 and d.close > month_pp.r1) :
                    # 买入手数
                    stake = int(self.broker.cash / len(stk_list) // (d.close[0] * 100)) * 100
                    # 买买买
                    self.buy(data = d, size = stake)
            elif not self.orders[d._name]:
                # 下保护点卖单
                self.orders[d._name] = self.close(data = d, exectype= bt.Order.StopTrail,
                            trailamount=self.p.trailamount,
                            trailpercent=self.p.trailpercent)

    def notify_order(self, order):
        if order.status in [order.Completed]:
            if order.isbuy():
                print('{} BUY {} EXECUTED, Price: {:.2f}'.format(self.datetime.date(), order.data._name, order.executed.price))
            else:  # Sell
                self.orders[order.data._name] = None
                print('{} SELL {} EXECUTED, Price: {:.2f}'.format(self.datetime.date(), order.data._name, order.executed.price))

# 加载数据
def load_data(stk_code, fromdate, todate):
    datapath = './stk_data/d/' + stk_code + '.csv'
    return bt.feeds.GenericCSVData(
        dataname = datapath,
        fromdate = fromdate,
        todate = todate + datetime.timedelta(days=1),
        nullvalue = 0.0,
        dtformat = ('%Y-%m-%d'),
        datetime = 0,
        open = 1,
        high = 2,
        low = 3,
        close = 4,
        volume = 5,
        openinterest = -1
        )

def runstrat():
    cerebro = bt.Cerebro()
    cerebro.broker.setcash(1000000.0)
    cerebro.addstrategy(PivotMultiTF)
    fromdate = datetime.datetime(2018, 1, 1)
    todate = datetime.datetime(2019, 12, 31)
    for stk_code in stk_list:
        data = load_data(stk_code, fromdate, todate)
        cerebro.adddata(data)
        cerebro.resampledata(data, timeframe = bt.TimeFrame.Months, compression = 1)
    cerebro.addwriter(bt.WriterFile, out = 'log.csv', csv = True)
    cerebro.run()
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
    # Plot the result绘制结果
    cerebro.plot(start=datetime.date(2018, 1, 1), end=datetime.date(2019, 12, 31),
            volume = False, style = 'candle',
            barup = 'red', bardown = 'green')

if __name__ == '__main__':
    runstrat()

欢迎大家关注、点赞、转发、留言,感谢支持!
为了便于相互交流学习,已建微信群,感兴趣的读者请加微信。

在这里插入图片描述

Guess you like

Origin blog.csdn.net/m0_46603114/article/details/107140358