Python量化交易学习笔记(17)——多只股票同时策略回测

  • 假设我们现在有策略A,在股票a的历史数据上进行回测后,发现能够取得稳定收益。但是我们有很长时间要等待股票a达到买入条件后,才能进行买入。这是对时间成本的严重浪费。
  • 策略A可以在股票a上获得良好的收益,但是可能无法在股票b,c……上取得良好表现。
  • 我们可以尝试做这样的改进:在股票a,b,c……的历史数据上分别进行策略回测,找到一个能够稳定收益策略B,来避免时间成本浪费的问题。但是这样仍然存在问题,在等待股票a出现买点的时候,股票b,c……的买点可能也没有出现。因此对所有股票依次做单独的策略回测,不足以验证策略的优劣。

鉴于以上问题,我们在验证策略时,需要对多只甚至全部的股票同时进行回测。本文基于backtrader,编写了多股票同时回测程序。

同样,本文旨在验证回测功能,策略依然选择简单的长短期均线金叉买入死叉卖出策略。核心代码位于策略类的init及next方法,先来看init方法:

    def __init__(self):
        self.inds = dict()
        for i, d in enumerate(self.datas):
            self.inds[d] = dict()
            self.inds[d]['sma1'] = bt.ind.SMA(d.close, period=self.p.pfast)  # 短期均线
            self.inds[d]['sma2'] = bt.ind.SMA(d.close, period=self.p.pslow)  # 长期均线
            self.inds[d]['cross'] = bt.ind.CrossOver(self.inds[d]['sma1'], self.inds[d]['sma2'], plot = False)  # 交叉信

这里定义了一个python字典类型变量self.inds,用于存储不同股票数据的技术指标,该字典的key为单支股票的数据,即代码中的d,value对应的该股票对应的技术指标,这些技术指标也存在一个字典内,字典内包含短期均线、长期均线、交叉信号3个指标。

再来看next方法:

    def next(self):
        for i, d in enumerate(self.datas):
            dt, dn = self.datetime.date(), d._name           # 获取时间及股票代码
            pos = self.getposition(d).size
            if not pos:                                      # 不在场内,则可以买入
                if self.inds[d]['cross'] > 0:                # 如果金叉
                    self.buy(data = d, size = self.p.pstake) # 买买买
            elif self.inds[d]['cross'] < 0:                  # 在场内,且死叉
                self.close(data = d)                         # 卖卖

next方法中,循环遍历所有待测的股票,对每只股票,获取时间及股票名称,这样便于后续打印输出、日志留存或者调试。然后通过判断当前股票position的size,判断是否已经买入该股票,如果没有买入,判断短期均线金叉长期均线后,即可买入。如果已经持有了该股票,那么判断长期均线死叉短期均线后即可卖出。

最后要注意的是,向cerebro添加不同股票数据时,补充添加股票名称,以便后续调试及分析使用:

	# 在Cerebro中添加股票数据
    cerebro.adddata(data, name = stk_code)

我们依然选择5日线作为短期均线,60日线作为长期均线,回测初始资金100000,单笔操作单位1000股,佣金千分之一,回测时间自2018年1月1日至2020年3月20日,按股票代码的升序排列依次添加回测股票,即先回测000001, 再加入000002,再加入000004……(000003停牌还是退市了。。。)

当1只股票进行回测时,回测最终资产103355.34:
在这里插入图片描述

当2只股票进行回测时,回测最终资产93691.44:
在这里插入图片描述

当3只股票进行回测时,回测最终资产108217.40:

在这里插入图片描述

此外,还测试了5只股票回测最终资产为106339.30,10只股票最终资产为102132.84,随着回测股票数目的增加,程序运行的时间也越长。

友情提示:本系列学习笔记只做数据分析,记录个人学习过程,不作为交易依据,盈亏自负。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import datetime  # 用于datetime对象操作
import os.path  # 用于管理路径
import sys  # 用于在argvTo[0]中找到脚本名称
import backtrader as bt # 引入backtrader框架
import pandas as pd

stk_num = 10  # 回测股票数目
# 创建策略
class SmaCross(bt.Strategy):
    # 可配置策略参数
    params = dict(
        pfast=5,  # 短期均线周期
        pslow=60,   # 长期均线周期
        poneplot = False,  # 是否打印到同一张图
        pstake = 1000 # 单笔交易股票数目
    )
    def __init__(self):
        self.inds = dict()
        for i, d in enumerate(self.datas):
            self.inds[d] = dict()
            self.inds[d]['sma1'] = bt.ind.SMA(d.close, period=self.p.pfast)  # 短期均线
            self.inds[d]['sma2'] = bt.ind.SMA(d.close, period=self.p.pslow)  # 长期均线
            self.inds[d]['cross'] = bt.ind.CrossOver(self.inds[d]['sma1'], self.inds[d]['sma2'], plot = False)  # 交叉信号
            # 跳过第一只股票data,第一只股票data作为主图数据
            if i > 0:
                if self.p.poneplot:
                    d.plotinfo.plotmaster = self.datas[0]
    def next(self):
        for i, d in enumerate(self.datas):
            dt, dn = self.datetime.date(), d._name           # 获取时间及股票代码
            pos = self.getposition(d).size
            if not pos:                                      # 不在场内,则可以买入
                if self.inds[d]['cross'] > 0:                # 如果金叉
                    self.buy(data = d, size = self.p.pstake) # 买买买
            elif self.inds[d]['cross'] < 0:                  # 在场内,且死叉
                self.close(data = d)                         # 卖卖卖
                
cerebro = bt.Cerebro()  # 创建cerebro
# 读入股票代码
stk_code_file = '../TQDat/TQDown2020v1/data/stock_code_update.csv'
stk_pools = pd.read_csv(stk_code_file, encoding = 'gbk')
if stk_num > stk_pools.shape[0]:
    print('股票数目不能大于%d' % stk_pools.shape[0])
    exit()
for i in range(stk_num):
    stk_code = stk_pools['code'][stk_pools.index[i]]
    stk_code = '%06d' % stk_code
    # 读入数据
    datapath = '../TQDat/day/stk/' + stk_code + '.csv'
    # 创建价格数据
    data = bt.feeds.GenericCSVData(
            dataname = datapath,
            fromdate = datetime.datetime(2018, 1, 1),
            todate = datetime.datetime(2020, 3, 20),
            nullvalue = 0.0,
            dtformat = ('%Y-%m-%d'),
            datetime = 0,
            open = 1,
            high = 2,
            low = 3,
            close = 4,
            volume = 5,
            openinterest = -1
            )
    # 在Cerebro中添加股票数据
    cerebro.adddata(data, name = stk_code)
# 设置启动资金
cerebro.broker.setcash(100000.0)
# 设置交易单位大小
#cerebro.addsizer(bt.sizers.FixedSize, stake = 5000)
# 设置佣金为千分之一
cerebro.broker.setcommission(commission=0.001)
cerebro.addstrategy(SmaCross, poneplot = False)  # 添加策略
cerebro.run()  # 遍历所有数据
# 打印最后结果
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.plot(style = "candlestick")  # 绘图

博客内容只用于交流学习,不构成投资建议,盈亏自负!

个人博客:http://coderx.com.cn/(优先更新)
项目最新代码:https://gitee.com/sl/quant_from_scratch
欢迎大家转发、留言。有微信群用于学习交流,感兴趣的读者请扫码加微信!
如果认为博客对您有帮助,可以扫码进行捐赠,感谢!

微信二维码微信捐赠二维码
在这里插入图片描述在这里插入图片描述
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值