量化投资 - Backtrader实战 - 01 - 从0开始实现策略回测

用Backtrader回测策略基本有五个步骤:
摘要由CSDN通过智能技术生成

用Backtrader回测策略基本有五个步骤:

  1. 创建一个Cerebro引擎
  2. 加入一个Strategy
  3. 加载数据
  4. 执行:cerebor.run()
  5. 对执行结果可视化

从0开始实现策略回测

对于量化投资相关的学习,最好的方法就是自己跟着教程一步一步开始做,在不断迭代的过程中,从简单到复杂实现策略的回测。在实践中发现问题,并不断改进问题。

创建第一个程序

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import backtrader as bt
 
if __name__ == '__main__':
    cerebro = bt.Cerebro()
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    cerebro.run()
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

执行下,看看啥结果:

Starting Portfolio Value: 10000.00
Final Portfolio Value: 10000.0

这个程序的主要功能:

  • 将Backtrader引入到咱们程序中,命名为bt。

  • 创建了一个机器人大脑(Cerebro),同时隐含创建了一个borker(券商)。

  • 让机器人大脑开始运行。

  • 显示了机器人在券商那里存有多少钱。

给空白的大脑加载数据

import backtrader as bt
import pandas as pd
from datetime import datetime
if __name__ == '__main__':
    cerebro = bt.Cerebro()
    #获取数据
    stock_hfq_df = pd.read_excel("./data/sh600000.xlsx",index_col='date',parse_dates=True)
    start_date = datetime(2010, 9, 30)  # 回测开始时间
    end_date = datetime(2021, 9, 30)  # 回测结束时间
    data = bt.feeds.PandasData(dataname=stock_hfq_df, fromdate=start_date, todate=end_date)  # 加载数据
    cerebro.adddata(data)  # 将数据传入回测系统
    
    cerebro.broker.setcash(100000.0)
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    cerebro.run()
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

再执行看看:

Starting Portfolio Value: 100000.00
Final Portfolio Value: 100000.00

大脑开始发育:

  • 能接受外部数据,浦发银行的行情数据
  • 能对datetime进行处理,根据需要回测特定时期的数据

给大脑第一个策略

资金有了,行情数据也有了,下面就是进行投资,建立一个投资策略,针对数据(通常是收盘价)来决定投资策略。策略首先来输出收盘价测试下。

from __future__ import (absolute_import, division, print_function, unicode_literals)
import backtrader as bt
import pandas as pd
from utils.backtesting import *
from utils.utility import *



# Create a Strategy
class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        """ 提供记录功能 """
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # 引用到输入数据的close价格
        self.dataclose = self.datas[0].close

    def next(self):
        # 当前策略:显示收盘价
        self.log('Close, %.2f' % self.dataclose[0])


if __name__ == '__main__':
    cerebro = bt.Cerebro()

    # 增加一个策略
    cerebro.addstrategy(TestStrategy)

    # 获取数据
    stock_hfq_df = pd.read_excel("./data/600000.xlsx", index_col='date', parse_dates=True)
    start_date = datetime.datetime(2010, 9, 30)  # 回测开始时间
    end_date = datetime.datetime(2021, 9, 30)  # 回测结束时间
    data = bt.feeds.PandasData(dataname=stock_hfq_df, fromdate=start_date, todate=end_date)  # 加载数据
    cerebro.adddata(data)  # 将数据传入回测系统

    cerebro.broker.setcash(100000.0)
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    cerebro.run()

    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

执行后结果:

Starting Portfolio Value: 100000.00
2020-09-30, Close, 125.97
......

2021-09-27, Close, 127.11
2021-09-28, Close, 127.26
2021-09-29, Close, 127.11
2021-09-30, Close, 126.83
Final Portfolio Value: 100000.00

关于这个策略,需要重点关注:

  • Strategy初始化的时候,将大脑加载的数据更新到dataclose属性中(这是个列表,保存股票回测开始时间到结束时间所有close数据)。self.datas[0]指向的事大脑通过cerebro.adddata函数加载的第一个数据,本例中加载浦发银行的股票数据。
  • self.dataclose = self.datas[0].close指向的是close(收盘价)line。
  • strategy的next方法针对self.dataclose(也就是收盘价Line)的每一行(也就是Bar)进行处理。在本例中,只是打印了下close的值。next方法是Strategy最重要的的方法,具体策略的实现都在这个函数中,后续还会详细介绍。

加入买的逻辑到Strategy中

没有买卖就没有收入,开始买卖,东西就要买便宜的,那就用价格连续降2天我们就买入的策略。

# 创建测试策略
class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        """ 记录策略信息 """
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # 引用第一个数据源的收盘价
        self.dataclose = self.datas[0].close

    def next(self):
        # 当前策略:当价格连续下降两天便买入

        # 简单记录收盘价序列
        self.log('Close, %.2f' % self.dataclose[0])

        if self.dataclose[0] < self.dataclose[-1]:
            # 当前价格比上一交易日价格低
            if self.dataclose[-1] < self.dataclose[-2]:
                # 上一交易日价格比上上一交易日价格低,则开始买入
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.buy()

执行结果如下:

Starting Portfolio Value: 100000.00
2010-09-30, Close, 65.55
2010-09-30, BUY CREATE, 65.55
2010-10-08, Close, 67.58
2010-10-11, Close, 70.26
2010-10-12, Close, 70.61

......

2021-09-17, BUY CREATE, 128.29
2021-09-22, Close, 127.16
2021-09-22, BUY CREATE, 127.16
2021-09-23, Close, 127.16
2021-09-24, Close, 127.02
2021-09-27, Close, 127.02
2021-09-28, Close, 127.16
2021-09-29, Close, 127.02
2021-09-30, Close, 126.74
2021-09-30, BUY CREATE, 126.74
Final Portfolio Value: 110309.45

策略居然真钱了,具体买卖过程:

  • self.datas[0] 就是我们购买了的股票。本例中没有输入其他数据,如果输入了其他数据,购买的股票就不一定是啥了,这个要看具体的策略执行情况。
  • 买了多少股本(stake)的股票?这个通过机器人大脑的position sizer属性来记录,缺省值为1,就是缺省咱们每一次操作只买卖1股。
  • 当前order执行的时候,采用的价格是第二天的开盘价。
  • 当前order执行的时候,没有收佣金。佣金如何设置后续还会说明。

不仅要买,还要卖出

一次完整的交易,不仅要买入,还要卖出。为了简单起见,我们处理5个bar数据(注意这个bar没有包含任何时间的概念,可以是1分钟、1小时、1天、一个月,是基于你输入的数据来决定的),本例中代买使用len函数与Python中不同,返回的事已经处理过得数据行(也就是bar)。注意,如果不在市场内,就不能卖出,不在市场内就是你不拥有任何股票头寸,也就是没有买入资产,在策略中通过position属性来记录。

本次代码中,将要增加:

  • 访问position获取是否在市场内
  • 创建买和卖的订单(order)
  • 订单状态的改变会通过notify方法通知strategy
class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        """ 记录策略信息 """
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # 引用第一个数据源的收盘价
        self.dataclose = self.datas[0].close

        # 跟踪记录未执行交易
        self.order = None

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log('BUY EXECUTED, %.2f' % order.executed.price)
            elif order.issell():
                self.log('SELL EXECUTED, %.2f' % order.executed.price)

            self.bar_executed = len(self)

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

        # Write down: no pending order
        self.order = None

    def next(self):
        # 当前策略:当价格连续下降两天便买入

        # 简单记录收盘价序列
        self.log('Close, %.2f' % self.dataclose[0])

        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # 检查是否在市场
        if not self.position:
            # 不在,那么连续3天价格下跌就买点
            if self.dataclose[0] < self.dataclose[-1]:
                # 当前价格比上一次低

                if self.dataclose[-1] < self.dataclose[-2]:
                    # 上一次的价格比上上次低

                    # 买入!!!
                    self.log('BUY CREATE, %.2f' % self.dataclose[0])

                    # Keep track of the created order to avoid a 2nd order
                    self.order = self.buy()

        else:

            # 已经在市场,5天后就卖掉。
            if len(self) >= (self.bar_executed + 5):
                # 这里注意,Len(self)返回的是当前执行的bar数量,
                # 每次next会加1.而Self.bar_executed记录的最后一次交易执行时的bar位置。
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()

执行结果:

Starting Portfolio Value: 100000.00
2010-09-30, Close, 65.55
2010-09-30, BUY CREATE, 65.55
2010-10-08, BUY EXECUTED, 66.21
2010-10-08, Close, 67.58
2010-10-11, Close, 70.26

......

2021-09-23, Close, 127.16
2021-09-24, Close, 127.02
2021-09-24, SELL CREATE, 127.02
2021-09-27, SELL EXECUTED, 127.02
2021-09-27, Close, 127.02
2021-09-28, Close, 127.16
2021-09-29, Close, 127.02
2021-09-30, Close, 126.74
2021-09-30, BUY CREATE, 126.74
Final Portfolio Value: 100039.20

从结果看,买入股票5天就卖出,结果只赚了40元,看来会卖的才是师傅。

券商的佣金呢?

券商说,你这里又买又卖的,我的佣金在哪儿?好的,咱们加上佣金,就0.1%吧,一行代码的事情:

# 0.1% ... 除以100去掉%号。
cerebro.broker.setcommission(commission=0.001)

下面我们看看佣金对收益的影响。

if __name__ == '__main__':
    cerebro = bt.Cerebro()

    # 增加一个策略
    cerebro.addstrategy(TestStrategy)

    # 获取数据
    stock_hfq_df = pd.read_excel("./data/600000.xlsx", index_col='date', parse_dates=True)
    start_date = datetime.datetime(2010, 9, 30)  # 回测开始时间
    end_date = datetime.datetime(2021, 9, 30)  # 回测结束时间
    data = bt.feeds.PandasData(dataname=stock_hfq_df, fromdate=start_date, todate=end_date)  # 加载数据
    cerebro.adddata(data)  # 将数据传入回测系统

    cerebro.broker.setcash(100000.0)
    # 设置佣金0.1% ... 除以100去掉%号。
    cerebro.broker.setcommission(commission=0.001)
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    cerebro.run()

    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

结果如下:

2021-09-23, Close, 127.16
2021-09-24, Close, 127.02
2021-09-24, SELL CREATE, 127.02
2021-09-27, SELL EXECUTED, 127.02
2021-09-27, Close, 127.02
2021-09-28, Close, 127.16
2021-09-29, Close, 127.02
2021-09-30, Close, 126.74
2021-09-30, BUY CREATE, 126.74
Final Portfolio Value: 99988.58

挣的钱都给了券商,所以一定不要频繁交易。

同时,我们注意到,每次订单执行,都有一个操作盈利记录(Gross:毛利;Net:净利):

2021-09-27, OPERATION PROFIT, GROSS -2.68, NET -2.94

如何给策略Strategy传递参数?

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
股票回测是量化交易中非常重要的一环,它可以通过历史数据对交易策略进行模拟和评估,从而评估策略的可行性和优劣性。在Python中,有很多开源的量化交易框架可以用来进行股票回测,如zipline、backtrader等。 下面是一个使用zipline框架进行简单交易策略回测的例子: 1. 安装zipline ```python pip install zipline ``` 2. 编写交易策略代码 ```python from zipline.api import order_target_percent, record, symbol def initialize(context): context.asset = symbol('AAPL') def handle_data(context, data): # 获取过去10天的收盘价 prices = data.history(context.asset, 'price', 10, '1d') # 计算平均价 mean_price = prices.mean() # 如果当前价格低于平均价,则买入 if data.current(context.asset, 'price') < mean_price: # 调整持仓比例至100% order_target_percent(context.asset, 1.0) # 否则卖出 else: # 调整持仓比例至0% order_target_percent(context.asset, 0.0) # 记录当前持仓比例 record(position=context.portfolio.positions[context.asset].amount) ``` 3. 运行回测 ```python from zipline import run_algorithm from zipline.api import symbol from datetime import datetime start = datetime(2016, 1, 1) end = datetime(2017, 1, 1) result = run_algorithm( start=start, end=end, initialize=initialize, capital_base=10000, handle_data=handle_data, bundle='quandl' ) ``` 在上述代码中,我们定义了一个简单的交易策略,即如果当前价格低于过去10天的平均价,则买入,否则卖出。然后我们使用zipline框架进行回测,设定回测开始和结束时间、初始资本、数据来源等参数,最终得到回测结果。 需要注意的是,这只是一个简单的例子,实际的交易策略可能会更加复杂,需要考虑更多的因素。另外,在进行股票回测时,也需要注意避免过度拟合或过度优化,以免出现回测虚高的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值