基于backtrader的三均线策略(多参数自动调优和回测)

##导入相关包 优化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(2021,6,1)
while True:
    new_df = get_binance_bars('ETHUSDT', '4h', last_datetime, dt.datetime(2022,7,15)) # 获取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()
(2361, 6)
                        open     high      low    close       volume  \
2022-07-14 08:00:00  1115.01  1127.19  1109.00  1112.71  200647.5134   
2022-07-14 12:00:00  1112.72  1114.90  1093.30  1097.58  161471.2202   
2022-07-14 16:00:00  1097.57  1100.71  1072.11  1081.27  196371.8355   
2022-07-14 20:00:00  1081.26  1147.97  1072.80  1140.95  403737.3239   
2022-07-15 00:00:00  1140.96  1214.65  1133.14  1194.04  490261.6674   

                     openinterest  
2022-07-14 08:00:00             0  
2022-07-14 12:00:00             0  
2022-07-14 16:00:00             0  
2022-07-14 20:00:00             0  
2022-07-15 00:00:00             0  
open high low close volume openinterest
2021-06-01 00:00:00 2637.47 2677.70 2572.65 2627.20 192289.89830 0
2021-06-01 04:00:00 2627.20 2720.00 2593.20 2706.15 142944.38327 0
2021-06-01 08:00:00 2706.15 2740.00 2612.63 2630.79 190770.24350 0
2021-06-01 12:00:00 2630.66 2716.33 2615.27 2652.53 166661.30183 0
2021-06-01 16:00:00 2652.37 2672.60 2547.12 2624.09 263021.10974 0
class three_moving_average(bt.Strategy):
    params = dict(
        short_period=5,
        median_period=20,
        long_period=60,
        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.close = self.datas[0].close
        self.s_ma = bt.ind.SMA(period=int(self.p.short_period))
        self.m_ma = bt.ind.SMA(period=int(self.p.median_period))
        self.l_ma = bt.ind.SMA(period=int(self.p.long_period))
        # 捕获做多信号
        # 短期均线在中期均线上方,且中期均取也在长期均线上方,三线多头排列,取值为1;反之,取值为0
        self.signal1 = bt.And(self.m_ma>self.l_ma, self.s_ma>self.m_ma)
        # 做多信号,求上面 self.signal1 的环比增量,可以判断得到第一次同时满足上述条件的时间,第一次满足条件为1,其余条件为0
        self.long_signal = bt.If((self.signal1-self.signal1(-1))>0, 1, 0)
        # 做多平仓信号,短期均线下穿长期均线时,取值为1;反之取值为0
        self.close_long_signal = bt.ind.CrossDown(self.s_ma, self.m_ma)
        # 捕获做空信号和平仓信号,与做多相反
        self.signal2 = bt.And(self.m_ma<self.l_ma, self.s_ma<self.m_ma)
        self.short_signal = bt.If((self.signal2-self.signal2(-1))>0, 1, 0)
        self.close_short_signal = bt.ind.CrossUp(self.s_ma, self.m_ma)

    def next(self): 
#         self.log(self.sell_signal[0],doprint=True)
#         self.log(type(self.position.size),doprint=True)
        # 如果还有订单在执行中,就不做新的仓位调整
#         if self.order:
#             return
                
        # 如果当前持有多单
        if self.position.size>0:
#             self.log(self.position.size,doprint=True)
            # 平仓设置,出现平仓信号进行平仓
            if self.close_long_signal ==1:
                self.order = self.sell(size=abs(self.position.size))
         # 如果当前持有空单
        elif self.position.size < 0 :
            # 平仓设置,出现平仓信号进行平仓
            if self.close_short_signal ==1:
                self.order = self.buy(size=abs(self.position.size))
        else: # 如果没有持仓,等待入场时机
            #入场: 出现做多信号,做多,开四分之一仓位
            if self.long_signal ==1 :
                self.buy_unit = int(self.broker.getvalue()/self.close[0]/4)
                self.order = self.buy(size=self.buy_unit)
            #入场: 出现做空信号,做空,开四分之一仓位
            elif self.short_signal==1:
                self.sell_unit = int(self.broker.getvalue()/self.close[0]/4)
                self.order = self.sell(size=self.sell_unit)
         
    # 打印订单日志
    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 stop(self):
        self.log(f'(组合线:{self.p.short_period},{self.p.median_period},{self.p.long_period}); 期末总资金: {self.broker.getvalue():.2f}', doprint=False)

#编写回测主函数
def main(short_period,median_period,long_period,para_opt=True,startcash=100000,com=0.0005,printlog=False):
    if para_opt==True:
        cerebro = bt.Cerebro()
        cerebro.addstrategy(three_moving_average,short_period=short_period,
                            median_period=median_period,long_period=long_period,printlog=printlog)
        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(three_moving_average,short_period=short_period,
                            median_period=median_period,long_period=long_period,printlog=printlog)        
        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)
        print('期末总资金: %.2f' % cerebro.broker.getvalue())
        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, # 回测100次 获取最优参数
    solver_name='particle swarm',
    short_period=[5,30],
    median_period=[30,60],
    long_period=[60,100]
    )
optimal_pars, details, _ = opt  # optimal_pars 最优参数组合
print(optimal_pars)
{'short_period': 11.27713202190711, 'median_period': 57.050084350679064, 'long_period': 72.96828600731862}
#最优参数回测和画图
main(short_period=optimal_pars['short_period'],median_period=optimal_pars['median_period'],long_period=optimal_pars['long_period'],
     para_opt=False,com=0.0005,printlog=False)
期初总资金: 100000.00
期末总资金: 137124.16

基于backtrader的三均线策略(多参数自动调优和回测)_第1张图片

你可能感兴趣的:(backtrader学习日志,backtrader,三均线策略,多参数自动调优和回测)