欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python量化交易开发(backtrader多周期数据加载回测)

程序员文章站 2022-05-21 11:57:14
本文继续介绍backtrader多周期回测的第二种方法,使用resample来进行多周期数据的加载。简述如果需要做多周期的策略回测,但是只有单周期数据可用,那么就可以使用重采样(resampling)来解决多周期数据的生成问题。这里的重采样(resampling)实际指的是上采样(upsampling),使用小周期的数据来合成大周期数据。例如,用日线数据合成周线数据。这里所说的上采样和信号处理的上采样效果是相反的,在信号处理中,上采样会获得比源数据更多的数据,而这里的上采样则是获得大周期的数据,较源数...

本文继续介绍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

Python量化交易开发(backtrader多周期数据加载回测)
两年间共有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()

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

Python量化交易开发(backtrader多周期数据加载回测)

本文地址:https://blog.csdn.net/m0_46603114/article/details/107140358