基于backtrader的唐奇安结合ADX策略实现(自动多参数调优和回测)

该博客介绍了如何使用backtrader库结合唐奇安通道和平均方向指数(ADX)策略进行数字货币的回测。通过Python的optunity库实现了参数优化,寻找最佳的N1、N2、N3和N4参数值。在回测过程中,策略根据价格突破唐奇安通道和ADX强度信号进行买卖操作。最后展示了优化后的策略回测结果,包括年化收益率、最大回撤等关键指标。
摘要由CSDN通过智能技术生成

基于backtrader的唐奇安结合ADX策略实现(自动多参数调优和回测)

##导入相关包 优化jupyter画图设置
from datetime import datetime,timedelta
import backtrader as bt
import tushare as ts
import pandas as pd
import talib as ta
import numpy as np
import matplotlib.pyplot as plt
import mplfinance as mpf
import pyfolio as pf
import optunity
import optunity.metrics
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
plt.rcParams['figure.figsize']=[10, 8]
plt.rcParams['figure.dpi']=200
plt.rcParams['figure.facecolor']='w'
plt.rcParams['figure.edgecolor']='k'
# K线数据获取
#数字货币回测
# 从Binance币安在线api下载k线,进行回测
import requests 
import json 
import pandas as pd
import datetime as dt
def get_binance_bars(symbol, interval, startTime, endTime):
    url = "https://api.binance.com/api/v3/klines"
    startTime = str(int(startTime.timestamp() * 1000))
    endTime = str(int(endTime.timestamp() * 1000))
    limit = '50'
    req_params = {"symbol" : symbol, 'interval' : interval, 'startTime' : startTime, 'endTime' : endTime, 'limit' : limit}
    df = pd.DataFrame(json.loads(requests.get(url, params = req_params).text))
    if (len(df.index) == 0):
        return None
    df = df.iloc[:, 0:6]
    df.columns = ['datetime', 'open', 'high', 'low', 'close', 'volume']
    df.open = df.open.astype("float")
    df.high = df.high.astype("float")
    df.low = df.low.astype("float")
    df.close = df.close.astype("float")
    df.volume = df.volume.astype("float")
    df.index = [dt.datetime.fromtimestamp(x / 1000.0) for x in df.datetime]
    return df
df_list = []
# 数据起点时间
last_datetime = dt.datetime(2022,1,1)
while True:
    new_df = get_binance_bars('ETHUSDT', '4h', last_datetime, dt.datetime(2022,7,10)) # 获取k线数据
    if new_df is None:
        break
    df_list.append(new_df)
    last_datetime = max(new_df.index) + dt.timedelta(0, 5)

dataframe=pd.concat(df_list)
dataframe['openinterest']=0
dataframe=dataframe[['open','high','low','close','volume','openinterest']]
print(dataframe.shape)
print(dataframe.tail())
dataframe.head()
(1099, 6)
                        open     high      low    close       volume  \
2022-07-09 08:00:00  1214.03  1221.79  1204.67  1217.01  147773.1735   
2022-07-09 12:00:00  1217.01  1227.40  1210.69  1225.02   79680.3095   
2022-07-09 16:00:00  1225.01  1228.25  1205.27  1210.28   80972.9123   
2022-07-09 20:00:00  1210.28  1222.61  1206.52  1215.24   83406.7132   
2022-07-10 00:00:00  1215.23  1235.40  1209.33  1218.61   89320.9760   

                     openinterest  
2022-07-09 08:00:00             0  
2022-07-09 12:00:00             0  
2022-07-09 16:00:00             0  
2022-07-09 20:00:00             0  
2022-07-10 00:00:00             0  
openhighlowclosevolumeopeninterest
2022-01-01 00:00:003784.643788.453623.003626.2754604.90270
2022-01-01 04:00:003626.213712.503622.293676.2339496.59930
2022-01-01 08:00:003676.223748.453676.223723.9626592.76930
2022-01-01 12:00:003723.963765.273701.003715.3127251.14010
2022-01-01 16:00:003715.323733.843673.463693.3723727.99490
#编写海龟策略
class D_ADX_TradingStrategy(bt.Strategy):
    params = dict(
        N1= 40, # 唐奇安通道上轨的t
        N2=30, # 唐奇安通道下轨的t
        N3=30,# ADX的强度值
        N4=20, # ADX的时间周期参数
        printlog=False, 
        )
    
    def log(self, txt, dt=None,doprint=False):
        if self.params.printlog or doprint:
            dt = dt or self.datas[0].datetime.date(0)
            print(f'{dt.isoformat()},{txt}')
    def __init__(self): 
        self.order = None                   
        self.buy_count = 0 # 记录买入次数
        self.last_price = 0 # 记录买入价格
        self.close = self.datas[0].close
        self.high = self.datas[0].high
        self.low = self.datas[0].low
        # 计算唐奇安通道上轨:过去20日的最高价
        self.DonchianH = bt.ind.Highest(self.high(-1), period=int(self.p.N1), subplot=True)
        # 计算唐奇安通道下轨:过去10日的最低价
        self.DonchianL = bt.ind.Lowest(self.low(-1), period=int(self.p.N2), subplot=True)
        # 计算唐奇安通道中轨
        self.DonchianM= (self.DonchianH+self.DonchianL)/2
        
        # 生成唐奇安通道上轨突破:close>DonchianH,取值为1.0;反之为 -1.0
        self.CrossoverH = bt.ind.CrossOver(self.close(0), self.DonchianH, subplot=False)
        # 生成唐奇安通道下轨突破:
        self.CrossoverL = bt.ind.CrossOver(self.close(0), self.DonchianL, subplot=False)
        # 生成唐奇安通道中轨突破:
        self.CrossoverM = bt.ind.CrossOver(self.close(0), self.DonchianM, subplot=False)

        # 计算 ADX,直接调用 talib 
        self.ADX = bt.talib.ADX(self.high, self.low, self.close,timeperiod=int(self.p.N4),subplot=True)
#         self.log(self.ADX[-1],doprint=True)
    def next(self): 
#         self.log(self.ADX[0],doprint=True)
        # 如果还有订单在执行中,就不做新的仓位调整
        if self.order:
            return  
                
        # 如果当前持有多单
        if self.position.size > 0 :
            # 平仓设置
            if self.CrossoverM<0 or self.ADX[0]<self.ADX[-1]:
                self.order = self.sell(size=abs(self.position.size))
                self.buy_count = 0 
                
        # 如果当前持有空单
        elif self.position.size < 0 :
            # 平仓设置
            if self.CrossoverM>0 or self.ADX[0]<self.ADX[-1]:
                self.order = self.buy(size=abs(self.position.size))
                self.buy_count = 0 
                
        else: # 如果没有持仓,等待入场时机
            #入场: 价格突破上轨线且空仓时,做多
            if self.CrossoverH > 0 and self.buy_count == 0 and self.ADX[0]>self.ADX[-1] and self.ADX[-1]>int(self.p.N3):
                self.buy_unit = int(self.broker.getvalue()/self.close[0]/4)
#                 self.buy_unit=500
                self.order = self.buy(size=self.buy_unit)
                self.last_price = self.position.price # 记录买入价格
                self.buy_count = 1  # 记录本次交易价格
            #入场: 价格跌破下轨线且空仓时,做空
            elif self.CrossoverL < 0 and self.buy_count == 0 and self.ADX[0]>self.ADX[-1] and self.ADX[-1]>int(self.p.N3):
                self.buy_unit = int(self.broker.getvalue()/self.close[0]/4)
                self.buy_unit=500
                self.order = self.sell(size=self.buy_unit)
                self.last_price = self.position.price # 记录买入价格
                self.buy_count = 1  # 记录本次交易价格
        
    # 打印订单日志
    def notify_order(self, order):
        order_status = ['Created','Submitted','Accepted','Partial',
                        'Completed','Canceled','Expired','Margin','Rejected']
        # 未被处理的订单
        if order.status in [order.Submitted, order.Accepted]:
            self.log('ref:%.0f, name: %s, Order: %s'% (order.ref,
                                                   order.data._name,
                                                   order_status[order.status]))
            return
        # 已经处理的订单
        if order.status in [order.Partial, order.Completed]:
            if order.isbuy():
                self.log(
                        'BUY EXECUTED, status: %s, ref:%.0f, name: %s, Size: %.2f, Price: %.2f, Cost: %.2f, Comm %.2f' %
                        (order_status[order.status], # 订单状态
                         order.ref, # 订单编号
                         order.data._name, # 股票名称
                         order.executed.size, # 成交量
                         order.executed.price, # 成交价
                         order.executed.value, # 成交额
                         order.executed.comm)) # 佣金
            else: # Sell
                self.log('SELL EXECUTED, status: %s, ref:%.0f, name: %s, Size: %.2f, Price: %.2f, Cost: %.2f, Comm %.2f' %
                            (order_status[order.status],
                             order.ref,
                             order.data._name,
                             order.executed.size,
                             order.executed.price,
                             order.executed.value,
                             order.executed.comm))
                    
        elif order.status in [order.Canceled, order.Margin, order.Rejected, order.Expired]:
            # 订单未完成
            self.log('ref:%.0f, name: %s, status: %s'% (
                order.ref, order.data._name, order_status[order.status]))
            
        self.order = None
        
    def notify_trade(self, trade):
        # 交易刚打开时
        if trade.justopened:
            self.log('Trade Opened, name: %s, Size: %.2f,Price: %.2f' % (
                    trade.getdataname(), trade.size, trade.price))
        # 交易结束
        elif trade.isclosed:
            self.log('Trade Closed, name: %s, GROSS %.2f, NET %.2f, Comm %.2f' %(
            trade.getdataname(), trade.pnl, trade.pnlcomm, trade.commission))
        # 更新交易状态
        else:
            self.log('Trade Updated, name: %s, Size: %.2f,Price: %.2f' % (
                    trade.getdataname(), trade.size, trade.price))
    def stop(self):
        
        self.log(f'(组合线:{self.p.N1},{self.p.N2},{self.p.N3},{self.p.N4}); 期末总资金: {self.broker.getvalue():.2f}', doprint=False)

#编写回测主函数
def main(N1,N2,N3,N4,para_opt=True,startcash=100000,com=0.02):
    if para_opt==True:
        cerebro = bt.Cerebro()
        cerebro.addstrategy(D_ADX_TradingStrategy,N1=N1,N2=N2,N3=N3,N4=N4)
        data = bt.feeds.PandasData(dataname=dataframe)    
        cerebro.adddata(data)
        #broker设置资金、手续费
        cerebro.broker.setcash(startcash)
        cerebro.broker.setcommission(commission=com)
        cerebro.run(maxcpus=2)
        value = cerebro.broker.getvalue()
        return value
    else:
        cerebro = bt.Cerebro()
        cerebro.addstrategy(D_ADX_TradingStrategy,N1=N1,N2=N2,N3=N3,N4=N4)
        data = bt.feeds.PandasData(dataname=dataframe)    
        cerebro.adddata(data)
        #broker设置资金、手续费
        cerebro.broker.setcash(startcash)
        cerebro.broker.setcommission(commission=com)
        #设置指标观察
        cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
        print('期初总资金: %.2f' % cerebro.broker.getvalue())
        results=cerebro.run(maxcpus=2)
        cerebro.plot(iplot=False)
        result = results[0]
        pyfolio = result.analyzers.pyfolio # 注意:后面不要调用 .get_analysis() 方法
        # 或者是 result[0].analyzers.getbyname('pyfolio')
        returns, positions, transactions, gross_lev = pyfolio.get_pf_items()
        pf.create_full_tear_sheet(returns)

'''backtrader内置的策略参数优化方法是权利搜索方法,也就是遍历每个参数组合值。在参数很多,每个参数取值变化范围大的情况下,优化效率是很低的。
可以采用智能优化算法,比如粒子群优化等进行大规模参数优化。下面,我们用python开源算法库optunity来对backtrader策略参数进行优化。 '''
# 执行优化,第一个参数是评估函数
'''
执行10次回测,设置两个参数sma1、sma2的取值范围
num_evals: 执行次数
Available solvers: particle swarm, tpe, sobol, nelder-mead, random search, cma-es, grid search
sma1、sma2 确定参数的取值范围
'''
opt = optunity.maximize(
    f=main,
    num_evals=100,
    solver_name='particle swarm',
    N1=[40,100], #上轨参数调优取值范围
    N2=[40,100], #下轨参数调优取值范围
    N3=[20,50], #ADX强度参数调优取值范围
    N4=[8,20] ##ADX时间周期参数调优取值范围
    )

optimal_pars, details, _ = opt  # optimal_pars 最优参数组合
print(optimal_pars)
{'N1': 76.64719168590987, 'N2': 83.65052762407552, 'N3': 25.247355935345496, 'N4': 15.344248021285086}
#最优参数回测并画图
main(N1=optimal_pars['N1'],N2=optimal_pars['N2'],N3=optimal_pars['N3'],N4=optimal_pars['N4'],para_opt=False)
期初总资金: 100000.00

在这里插入图片描述

Start date2022-01-01
End date2022-07-10
Total months9
Backtest
Annual return276.335%
Cumulative returns173.054%
Annual volatility3386.631%
Sharpe ratio-0.12
Calmar ratio2.35
StabilityNaN
Max drawdown-117.486%
Omega ratio0.90
Sortino ratio-0.15
Skew-3.82
Kurtosis92.83
Tail ratio0.60
Daily value at risk-428.304%
Worst drawdown periodsNet drawdown in %Peak dateValley dateRecovery dateDuration
0117.492022-01-222022-05-042022-06-13101
125.222022-01-202022-01-212022-01-222
223.402022-06-152022-06-19NaTNaN
33.432022-06-132022-06-142022-06-153
4NaNNaTNaTNaTNaN

在这里插入图片描述

首先需要导入需要使用的库,其中tushare和pandas为数据处理库,talib为技术指标库,openpyxl为导出excel所需的库。 ```python import tushare as ts import pandas as pd import talib as ta from openpyxl import Workbook ``` 接下来需要通过tushare的pro版本获取股票数据,这里以获取某只股票的日线数据为例。 ```python # 获取日线数据 pro = ts.pro_api('输入你的token') df = pro.daily(ts_code='股票代码', start_date='开始日期', end_date='结束日期') df = df.sort_values('trade_date', ascending=True) df = df.reset_index(drop=True) ``` 接下来需要计算出唐奇安通道的上轨和下轨。 ```python # 计算唐奇安通道上轨和下轨 high_list = df['high'].rolling(window=20).max() low_list = df['low'].rolling(window=20).min() df['upperband'] = high_list.shift(1) df['lowerband'] = low_list.shift(1) ``` 同时也需要计算出MACD指标的值。 ```python # 计算MACD指标 df['dif'], df['dea'], df['hist'] = ta.MACD(df['close'].values, fastperiod=12, slowperiod=26, signalperiod=9) ``` 接下来需要编写一个函数来根据唐奇安通道和MACD指标来判断买入和卖出信号。 ```python # 根据唐奇安通道和MACD指标来判断买入和卖出信号 def get_signal(df): signal_list = [] for i in range(len(df)): if df.loc[i, 'close'] > df.loc[i, 'upperband'] and df.loc[i, 'hist'] > 0: signal = 'sell' elif df.loc[i, 'close'] < df.loc[i, 'lowerband'] and df.loc[i, 'hist'] < 0: signal = 'buy' else: signal = '' signal_list.append(signal) return signal_list df['signal'] = get_signal(df) ``` 最后需要将结果导出到excel文件中。 ```python # 导出结果到excel文件 wb = Workbook() ws = wb.active headers = ['trade_date', 'open', 'high', 'low', 'close', 'upperband', 'lowerband', 'dif', 'dea', 'hist', 'signal'] ws.append(headers) for i in range(len(df)): row = [df.loc[i, 'trade_date'], df.loc[i, 'open'], df.loc[i, 'high'], df.loc[i, 'low'], df.loc[i, 'close'], df.loc[i, 'upperband'], df.loc[i, 'lowerband'], df.loc[i, 'dif'], df.loc[i, 'dea'], df.loc[i, 'hist'], df.loc[i, 'signal']] ws.append(row) wb.save('result.xlsx') ``` 整合以上代码,得到完整的策略代码如下: ```python import tushare as ts import pandas as pd import talib as ta from openpyxl import Workbook # 获取日线数据 pro = ts.pro_api('输入你的token') df = pro.daily(ts_code='股票代码', start_date='开始日期', end_date='结束日期') df = df.sort_values('trade_date', ascending=True) df = df.reset_index(drop=True) # 计算唐奇安通道上轨和下轨 high_list = df['high'].rolling(window=20).max() low_list = df['low'].rolling(window=20).min() df['upperband'] = high_list.shift(1) df['lowerband'] = low_list.shift(1) # 计算MACD指标 df['dif'], df['dea'], df['hist'] = ta.MACD(df['close'].values, fastperiod=12, slowperiod=26, signalperiod=9) # 根据唐奇安通道和MACD指标来判断买入和卖出信号 def get_signal(df): signal_list = [] for i in range(len(df)): if df.loc[i, 'close'] > df.loc[i, 'upperband'] and df.loc[i, 'hist'] > 0: signal = 'sell' elif df.loc[i, 'close'] < df.loc[i, 'lowerband'] and df.loc[i, 'hist'] < 0: signal = 'buy' else: signal = '' signal_list.append(signal) return signal_list df['signal'] = get_signal(df) # 导出结果到excel文件 wb = Workbook() ws = wb.active headers = ['trade_date', 'open', 'high', 'low', 'close', 'upperband', 'lowerband', 'dif', 'dea', 'hist', 'signal'] ws.append(headers) for i in range(len(df)): row = [df.loc[i, 'trade_date'], df.loc[i, 'open'], df.loc[i, 'high'], df.loc[i, 'low'], df.loc[i, 'close'], df.loc[i, 'upperband'], df.loc[i, 'lowerband'], df.loc[i, 'dif'], df.loc[i, 'dea'], df.loc[i, 'hist'], df.loc[i, 'signal']] ws.append(row) wb.save('result.xlsx') ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

飘逸高铁侠

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

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

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

打赏作者

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

抵扣说明:

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

余额充值