Skip to content

Latest commit

 

History

History
245 lines (195 loc) · 9.94 KB

File metadata and controls

245 lines (195 loc) · 9.94 KB

31、【backtrader 股票策略】《151 trading strategies》中的通道策略(Channel)(2022-10-31 更新回测结果)

原文:https://yunjinqi.blog.csdn.net/article/details/111879085

本节分享《151 trading strategies》中的通道策略,案例中给了一个唐奇安通道的例子,策略思想是均值回归。本节主要分为四个部分:策略逻辑描述,策略代码,策略绩效,以及策略分析。

在这里插入图片描述

策略逻辑

和前几个策略的资金、资金分配、交易手续费都是一样的,不一样的是开平仓信号。

我们使用全市场的 A 股日数据进行测试,只做多头。

资金管理方法和上个策略一样。

假设初始资金有 1 个亿。

假设手续费为万分之二。

首先,计算过去 60 日的最高价和最低价(不包含当前 bar),当收盘价小于 60 日最低价的时候,并且没有持多头仓位,开多;当收盘价大于 60 日最高价的时候,如果持有多头仓位,平多。

策略代码

import backtrader as bt
import datetime
import pandas as pd
import numpy as np
import os,sys
import copy
import talib
import math 
import warnings
warnings.filterwarnings("ignore")
import pyfolio as pf

# 我们使用的时候,直接用我们新的类读取数据就可以了。
class test_two_ma_strategy(bt.Strategy):

    params = (('period',60),

             )

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('{}, {}'.format(dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.bar_num=0
        # 保存最高价最低价数据
        self.stock_highest_dict={data._name:bt.indicators.Highest(data,period = self.p.period) for data in self.datas}
        self.stock_lowest_dict={data._name:bt.indicators.Lowest(data,period = self.p.period) for data in self.datas}

        # 保存现有持仓的股票
        self.position_dict={}
        # 当前有交易的股票
        self.stock_dict={}

    def prenext(self):

        self.next()

    def next(self):
        # 假设有 100 万资金,每次成份股调整,每个股票使用 1 万元
        self.bar_num+=1

        # 前一交易日和当前的交易日
        pre_date = self.datas[0].datetime.date(-1).strftime("%Y-%m-%d")
        current_date = self.datas[0].datetime.date(0).strftime("%Y-%m-%d")
        # 总的价值
        total_value = self.broker.get_value()
        total_cash  = self.broker.get_cash()
        self.log(f"total_value : {total_value}")
        # 第一个数据是指数,校正时间使用,不能用于交易
        # 循环所有的股票,计算股票的数目
        for data in self.datas[1:]:
            data_date = data.datetime.date(0).strftime("%Y-%m-%d")
            # 如果两个日期相等,说明股票在交易
            if current_date == data_date:
                stock_name = data._name
                if stock_name not in self.stock_dict:
                    self.stock_dict[stock_name]=1
        total_target_stock_num = len(self.stock_dict)
        # 现在持仓的股票数目
        total_holding_stock_num = len(self.position_dict)
        # 计算理论上的手数
        if total_holding_stock_num<total_target_stock_num:
            now_value = total_cash/(total_target_stock_num-total_holding_stock_num)
            stock_value = total_value/total_target_stock_num
            now_value =min(now_value,stock_value)
        else:
            now_value = total_value/total_target_stock_num

        # 循环股票,开始交易
        for data in self.datas[1:]:
            data_date = data.datetime.date(0).strftime("%Y-%m-%d")
            # 如果两个日期相等,说明股票在交易
            if current_date == data_date:
                # 最高价和最低价数据,保存成 line 的格式
                highest_info = self.stock_highest_dict[data._name]
                lowest_info = self.stock_lowest_dict[data._name]
                size = self.getposition(data).size
                # 开多信号
                con_buy =  data.close[0]<lowest_info[-1] and data._name not in self.position_dict
                con_sell = data.close[0]>highest_info[-1]
                # 平多信号
                if con_sell:
                    # 已经下单并且成交了
                    if self.getposition(data).size>0:
                        self.close(data)
                        if data._name in self.position_dict:
                            self.position_dict.pop(data._name)
                        # self.buy_list.remove(stock)
                    # 已经下单,但是订单没有成交
                    if data._name in self.position_dict and size==0:
                        order = self.position_dict[data._name]
                        self.cancel(order)
                        self.position_dict.pop(data._name) 
                # 开多信号,价格站到均线上方,并且持仓量为 0 
                if con_buy  :
                    lots = now_value/data.close[0]
                    lots = int(lots/100)*100 # 计算能下的手数,取整数
                    order = self.buy(data,size = lots)
                    self.position_dict[data._name] = order

    def notify_order(self, order):

        if order.status in [order.Submitted, order.Accepted]:
            return

        if order.status == order.Rejected:
            self.log(f"Rejected : order_ref:{order.ref} data_name:{order.p.data._name}")

        if order.status == order.Margin:
            self.log(f"Margin : order_ref:{order.ref} data_name:{order.p.data._name}")

        if order.status == order.Cancelled:
            self.log(f"Concelled : order_ref:{order.ref} data_name:{order.p.data._name}")

        if order.status == order.Partial:
            self.log(f"Partial : order_ref:{order.ref} data_name:{order.p.data._name}")

        if order.status == order.Completed:
            if order.isbuy():
                self.log(f" BUY : data_name:{order.p.data._name} price : {order.executed.price} , cost : {order.executed.value} , commission : {order.executed.comm}")

            else:  # Sell
                self.log(f" SELL : data_name:{order.p.data._name} price : {order.executed.price} , cost : {order.executed.value} , commission : {order.executed.comm}")

    def notify_trade(self, trade):
        # 一个 trade 结束的时候输出信息
        if trade.isclosed:
            self.log('closed symbol is : {} , total_profit : {} , net_profit : {}' .format(
                            trade.getdataname(),trade.pnl, trade.pnlcomm))
            # self.trade_list.append([self.datas[0].datetime.date(0),trade.getdataname(),trade.pnl,trade.pnlcomm])

        if trade.isopen:
            self.log('open symbol is : {} , price : {} ' .format(
                            trade.getdataname(),trade.price))
    def stop(self):

        pass 

# 初始化 cerebro,获得一个实例
cerebro = bt.Cerebro()
# cerebro.broker = bt.brokers.BackBroker(shortcash=True)  # 0.5%
data_root = "/home/yun/data/stock/day/"
file_list =sorted(os.listdir(data_root))
params=dict(

    fromdate = datetime.datetime(2005,1,4),
    todate = datetime.datetime(2020,7,31),
    timeframe = bt.TimeFrame.Days,
    dtformat = ("%Y-%m-%d"),
    compression = 1,
    datetime = 0,
    open = 1,
    high = 2,
    low =3,
    close =4,
    volume =5,
    openinterest=-1)

# 加载指数数据
feed = bt.feeds.GenericCSVData(dataname = "/home/yun/data/stock/index.csv",**params)
# 添加数据到 cerebro
cerebro.adddata(feed, name = 'index')

# 读取数据
for file in file_list:
    #剔除不满一年的股票
    if len(pd.read_csv(data_root+file))<252:
        continue
    feed = bt.feeds.GenericCSVData(dataname = data_root+file,**params)
    # 添加数据到 cerebro
    cerebro.adddata(feed, name = file[:-4])
print("加载数据完毕")
# 添加手续费,按照万分之二收取
cerebro.broker.setcommission(commission=0.0002,stocklike=True)
# 设置初始资金为 1 亿
cerebro.broker.setcash(1_0000_0000)
# 添加策略
cerebro.addstrategy(test_two_ma_strategy)
cerebro.addanalyzer(bt.analyzers.TotalValue, _name='_TotalValue')
cerebro.addanalyzer(bt.analyzers.PyFolio)
# 运行回测
results = cerebro.run()
# 打印相关信息
pyfoliozer = results[0].analyzers.getbyname('pyfolio')
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
pf.create_full_tear_sheet(
    returns,
    positions=positions,
    transactions=transactions,
    # gross_lev=gross_lev,
    live_start_date='2019-01-01',
    ) 

策略回测结果

在这里插入图片描述

在这里插入图片描述

策略分析

虽然这个策略,看起来已经不错,但是相对来看,还是太过于简单。书上提了一个建议,那就是出现信号的时候使用成交量结合判断,把这个策略从价格策略进化成量价策略。

这个策略本质上算是均值回归类的策略,可以考虑同时使用趋势类策略和均值回归类策略,比如,选出来具有长期上涨趋势的股票,低于 60 天最低价的时候买入,高于 60 天最高价的时候卖出。


智慧、心灵、财富,总要有一个在路上,愿我们能在人生的道路上,不断成长、不断成熟~~~

感兴趣可以关注我的专栏:

my_quant_study_note:分享一些关于量化投资、量化交易相关的思考

backtrader 量化投资回测与交易:本专栏免费,分享 backtrader 相关的内容。

量化投资神器-backtrader 源码解析-从入门到精通:本专栏目前收费 299 元,预计更新 100 篇策略+20 篇 backtrader 讲解+80 篇源代码分析。