Backtrader解决多股回测时跳过日期的问题

股票的上市日期各不相同,有些也退市了。在回测时,Backtrader会遍历所有的数据,选择有效期的交集开始执行next()

这时我们的选股策略就会因为数据的问题出现一段时间的空窗期,所以我们不要用next()来执行,而是用prenext()来执行,Backtrader会循环所有的数据,选择最小的那个日期作为开始日,执行prenext(),但是此时衍生出两个问题:

  1. 这只股票的开始日,未必是另一支股票的开始日,这回导致另一只股票没数据
  2. 一直股票可能在某一天退市了就没有数据了,但是有持仓,这就很尴尬了

为了解决以上问题,我们首先学习需要用到的方法:

  • 策略中使用self.datetime.date(),可以知道当前模拟的日期
  • 使用self.getdatabyname(股票name).datetime.date(0),可知给定name的股票数据的起始日期
  • 使用len(this_bond_data) >= this_bond_data.buflen(),得到Backtrader是否已经遍历全部数据点,即终止条件

策略中的函数调用顺序与生命周期可以参考官方文档:https://www.backtrader.com/docu/strategy/

策略中使用next()

如上所述,Backtrader会遍历所有的数据,选择有效期的交集开始执行next()

from datetime import datetime
import backtrader as bt
import pandas as pd
import efinance


def get_k_data(stock_code, begin: datetime, end: datetime) -> pd.DataFrame:
    """
    根据efinance工具包获取股票数据
    :param stock_code:股票代码
    :param begin: 开始日期
    :param end: 结束日期
    :return:
    """
    # stock_code = '600519'  # 股票代码,茅台
    k_dataframe: pd.DataFrame = efinance.stock.get_quote_history(
        stock_code, beg=begin.strftime("%Y%m%d"), end=end.strftime("%Y%m%d"))
    k_dataframe = k_dataframe.iloc[:, :9]
    k_dataframe.columns = ['name', 'code', 'date', 'open', 'close', 'high', 'low', 'volume', 'turnover']
    k_dataframe.index = pd.to_datetime(k_dataframe.date)
    k_dataframe.drop(['name', 'code', 'date'], axis=1, inplace=True)
    return k_dataframe


class MyStrategy1(bt.Strategy):  # 策略
    def __init__(self):
        # 初始化交易指令、买卖价格和手续费
        pass

    def next(self):  # 固定的函数,框架执行过程中会不断循环next(),过一个K线,执行一次next()
        print("今天是:", self.datetime.date())


if __name__ == '__main__':
    # 获取数据
    start_time = datetime(2020, 1, 1)
    end_time = datetime(2020, 3, 1)
    maotai_df = get_k_data('600519', begin=start_time, end=end_time)  # 茅台,从1月1日开始
    ningde_df = get_k_data("300750", begin=datetime(2020, 2, 1), end=end_time)  # 宁德时代,从5月1日开始
    # =============== 为系统注入数据 =================
    # 加载数据
    maotai_data = bt.feeds.PandasData(dataname=maotai_df, fromdate=start_time, todate=end_time)
    ningde_data = bt.feeds.PandasData(dataname=ningde_df, fromdate=start_time, todate=end_time)
    # 初始化cerebro回测系统
    cerebral_system = bt.Cerebro()  # Cerebro引擎在后台创建了broker(经纪人)实例,系统默认每个broker的初始资金量为10000
    # 将数据传入回测系统
    cerebral_system.adddata(maotai_data, name="茅台")  # 导入数据,在策略中使用 self.datas 来获取数据源
    cerebral_system.adddata(ningde_data, name="宁德")
    # 将交易策略加载到回测系统中
    cerebral_system.addstrategy(MyStrategy1)
    # =============== 系统设置 ==================
    # 设置启动资金为 100000
    start_cash = 1000000
    cerebral_system.broker.setcash(start_cash)
    # 设置手续费 万2.5
    cerebral_system.broker.setcommission(commission=0.00025)
    # 运行回测系统
    cerebral_system.run()

我们可以看到:

今天是: 2020-02-03
...
...
今天是: 2020-02-27
今天是: 2020-02-28

是从2月3日开始,修改如下:

使用prenext从第一个模拟交易日开始指定策略

使用prenext()可以从所有数据集的最小的日期开始制定策略,如下代码:

from datetime import datetime
import backtrader as bt
import pandas as pd
import efinance


def get_k_data(stock_code, begin: datetime, end: datetime) -> pd.DataFrame:
    """
    根据efinance工具包获取股票数据
    :param stock_code:股票代码
    :param begin: 开始日期
    :param end: 结束日期
    :return:
    """
    # stock_code = '600519'  # 股票代码,茅台
    k_dataframe: pd.DataFrame = efinance.stock.get_quote_history(
        stock_code, beg=begin.strftime("%Y%m%d"), end=end.strftime("%Y%m%d"))
    k_dataframe = k_dataframe.iloc[:, :9]
    k_dataframe.columns = ['name', 'code', 'date', 'open', 'close', 'high', 'low', 'volume', 'turnover']
    k_dataframe.index = pd.to_datetime(k_dataframe.date)
    k_dataframe.drop(['name', 'code', 'date'], axis=1, inplace=True)
    return k_dataframe


class MyStrategy1(bt.Strategy):  # 策略
    def __init__(self):
        # 初始化交易指令、买卖价格和手续费
        pass

    def prenext(self):
        self.next()

    def next(self):  # 固定的函数,框架执行过程中会不断循环next(),过一个K线,执行一次next()
        print("今天是:", self.datetime.date())


if __name__ == '__main__':
    # 获取数据
    start_time = datetime(2020, 1, 1)
    end_time = datetime(2020, 3, 1)
    maotai_df = get_k_data('600519', begin=start_time, end=end_time)  # 茅台,从1月1日开始
    ningde_df = get_k_data("300750", begin=datetime(2020, 2, 1), end=end_time)  # 宁德时代,从5月1日开始
    # =============== 为系统注入数据 =================
    # 加载数据
    maotai_data = bt.feeds.PandasData(dataname=maotai_df, fromdate=start_time, todate=end_time)
    ningde_data = bt.feeds.PandasData(dataname=ningde_df, fromdate=start_time, todate=end_time)
    # 初始化cerebro回测系统
    cerebral_system = bt.Cerebro()  # Cerebro引擎在后台创建了broker(经纪人)实例,系统默认每个broker的初始资金量为10000
    # 将数据传入回测系统
    cerebral_system.adddata(maotai_data, name="茅台")  # 导入数据,在策略中使用 self.datas 来获取数据源
    cerebral_system.adddata(ningde_data, name="宁德")
    # 将交易策略加载到回测系统中
    cerebral_system.addstrategy(MyStrategy1)
    # =============== 系统设置 ==================
    # 设置启动资金为 100000
    start_cash = 1000000
    cerebral_system.broker.setcash(start_cash)
    # 设置手续费 万2.5
    cerebral_system.broker.setcommission(commission=0.00025)
    # 运行回测系统
    cerebral_system.run()

只需要在策略中添加一个:

    def prenext(self):
        self.next()

就可以从第一天开始执行了

注意:在切换prenext()next()时,会触发一次nextstart(),此时可以执行一些操作

巧妙运用prenext与next

如果我们修改策略为:

class MyStrategy1(bt.Strategy):  # 策略
    def __init__(self):
        # 初始化交易指令、买卖价格和手续费
        pass

    def prenext(self):
        print('PRENEXT 今天是', self.datetime.date())
        self.next()

    def next(self):  # 固定的函数,框架执行过程中会不断循环next(),过一个K线,执行一次next()
        print("今天是:", self.datetime.date())

就可以看到:

PRENEXT 今天是 2020-01-02
今天是: 2020-01-02
PRENEXT 今天是 2020-01-03
今天是: 2020-01-03
....
....
PRENEXT 今天是 2020-01-22
今天是: 2020-01-22
PRENEXT 今天是 2020-01-23
今天是: 2020-01-23
今天是: 2020-02-03
....
....
今天是: 2020-02-26
今天是: 2020-02-27
今天是: 2020-02-28

prenext()会从第一个交易日开始执行,知道所有的数据都存在交集的时候,切换到next(),当然可以在prenext()中主动调用next()

灵活的使用这两种代码可以组合完成非常复杂的逻辑

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呆萌的代Ma

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值