基于backtrader和pyfolio的海龟策略

基于backtrader和pyfolio的海龟策略

策略思路

指标计算:
用 20 日的最高、最低、收盘价计算平均真实波幅 ATR;
计算出近 20 日的最高与 20 日最低价,构建唐奇安通道。
交易信号:
入场:价格突破 20 日价格高点时,入场;
加仓:价格继续上涨至 0.5 倍 ATR ,再次加仓,加仓次数不超过 3 次;
止损:价格回落 2 倍 ATR 时止损离场;
止盈:价格突破 10 日最低点时止盈离场;
做空与做多的逻辑相反。

##导入相关包 优化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
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
plt.rcParams['figure.figsize']=[6, 3]
plt.rcParams['figure.dpi']=100
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 = '1000'
 
    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['adj_close'] = df['close']
     
    df.index = [dt.datetime.fromtimestamp(x / 1000.0) for x in df.datetime]
 
    return df

df_list = []
# 数据起点时间
last_datetime = dt.datetime(2021,1,1)
while True:
    new_df = get_binance_bars('GMTUSDT', '30m', last_datetime, dt.datetime(2022,5,31)) # 获取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)
dataframe.tail()
(3897, 6)
open high low close volume openinterest
2022-05-30 22:00:00 1.17477 1.19300 1.17029 1.17549 5514608.4 0
2022-05-30 22:30:00 1.17549 1.17971 1.15300 1.17173 4878648.9 0
2022-05-30 23:00:00 1.17174 1.20500 1.16896 1.18477 6555892.4 0
2022-05-30 23:30:00 1.18509 1.19250 1.17491 1.18103 3482797.3 0
2022-05-31 00:00:00 1.18103 1.19662 1.17212 1.18941 5499311.7 0
#编写海龟策略
class TurtleTradingStrategy(bt.Strategy):
    params = dict(
        N1= 40, # 唐奇安通道上轨的t
        N2=30, # 唐奇安通道下轨的t
        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}')
#             dt = dt or self.datas[0].datetime.date(0)
#             print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self): 
        self.order = None                   
        self.buy_count = 0 # 记录买入次数
        self.last_price = 0 # 记录买入价格
        # 准备第一个标的沪深300主力合约的close、high、low 行情数据
        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=self.p.N1, subplot=True)
        # 计算唐奇安通道下轨:过去10日的最低价
        self.DonchianL = bt.ind.Lowest(self.low(-1), period=self.p.N2, subplot=True)
        # 生成唐奇安通道上轨突破: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)
        # 计算 ATR
#         self.TR = bt.ind.Max((self.high(0)-self.low(0)), # 当日最高价-当日最低价
#                                     abs(self.high(0)-self.close(-1)), # abs(当日最高价−前一日收盘价)
#                                     abs(self.low(0)-self.close(-1))) # abs(当日最低价-前一日收盘价)
#         self.ATR = bt.ind.SimpleMovingAverage(self.TR, period=self.p.N1, subplot=False)
        # 计算 ATR,直接调用 talib ,使用前需要安装 python3 -m pip install TA-Lib
        self.ATR = bt.talib.ATR(self.high, self.low, self.close, timeperiod=self.p.N1, subplot=True)
#         self.ADX = bt.talib.ADX()
    
    def next(self): 
        # 如果还有订单在执行中,就不做新的仓位调整
        if self.order:
            return  
                
        # 如果当前持有多单
        if self.position.size > 0 :
            # 多单加仓:价格上涨了买入价的0.5的ATR且加仓次数少于等于3次
            if self.datas[0].close >self.last_price + 0.5*self.ATR[0] and self.buy_count <= 4:
#                 print('if self.datas[0].close >self.last_price + 0.5*self.ATR[0] and self.buy_count <= 4:')
#                 print('self.buy_count',self.buy_count)
                # 计算建仓单位:self.ATR*期货合约乘数300*保证金比例0.1
                self.buy_unit = max((self.broker.getvalue()*0.005)/(self.ATR*300*0.1),1)
                self.buy_unit = int(self.buy_unit) # 交易单位为手
                # self.sizer.p.stake = self.buy_unit
                self.order = self.buy(size=self.buy_unit)
                self.last_price = self.position.price # 获取买入价格
                self.buy_count = self.buy_count + 1
            #多单止损:当价格回落2倍ATR时止损平仓
            elif self.datas[0].close < (self.last_price - 2*self.ATR[0]):
#                 print('elif self.datas[0].close < (self.last_price - 2*self.ATR[0]):')
                self.order = self.sell(size=abs(self.position.size))
                self.buy_count = 0
            #多单止盈:当价格突破10日最低点时止盈离场 平仓
            elif self.CrossoverL < 0:
#                 print('self.CrossoverL < 0')
                self.order = self.sell(size=abs(self.position.size))
                self.buy_count = 0 
                
        # 如果当前持有空单
        elif self.position.size < 0 :
            # 空单加仓:价格小于买入价的0.5的ATR且加仓次数少于等于3次
            if self.datas[0].close<self.last_price-0.5*self.ATR[0] and self.buy_count <= 4:
#                 print('self.datas[0].close
                # 计算建仓单位:self.ATR*期货合约乘数300*保证金比例0.1
                self.buy_unit = max((self.broker.getvalue()*0.005)/(self.ATR*300*0.1),1)
                self.buy_unit = int(self.buy_unit) # 交易单位为手
                # self.sizer.p.stake = self.buy_unit
                self.order = self.sell(size=self.buy_unit)
                self.last_price = self.position.price # 获取买入价格
                self.buy_count = self.buy_count + 1              
            #空单止损:当价格上涨至2倍ATR时止损平仓
            elif self.datas[0].close < (self.last_price+2*self.ATR[0]):
#                 print('self.datas[0].close < (self.last_price+2*self.ATR[0])')
                self.order = self.buy(size=abs(self.position.size))
                self.buy_count = 0
            #多单止盈:当价格突破20日最高点时止盈平仓
            elif self.CrossoverH>0:
#                 print('self.CrossoverH>0')
                self.order = self.buy(size=abs(self.position.size))
                self.buy_count = 0
                
        else: # 如果没有持仓,等待入场时机
            #入场: 价格突破上轨线且空仓时,做多
            if self.CrossoverH > 0 and self.buy_count == 0:
#                 print('if self.CrossoverH > 0 and self.buy_count == 0:')
                # 计算建仓单位:self.ATR*期货合约乘数300*保证金比例0.1
                self.buy_unit = max((self.broker.getvalue()*0.005)/(self.ATR*300*0.1),1)
                self.buy_unit = int(self.buy_unit) # 交易单位为手
                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:
#                 print('self.CrossoverL < 0 and self.buy_count == 0')
                # 计算建仓单位:self.ATR*期货合约乘数300*保证金比例0.1
                self.buy_unit = max((self.broker.getvalue()*0.005)/(self.ATR*300*0.1),1)
                self.buy_unit = int(self.buy_unit) # 交易单位为手
                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.broker.getvalue():.2f}', doprint=True)

#编写回测主函数
def main(df,long_list,short_list,best_long,best_short,startcash=10000,com=0.001):
    #创建主控制器
    cerebro = bt.Cerebro()      
    #导入策略参数寻优
    if long_list:
        cerebro.optstrategy(TurtleTradingStrategy,N1=long_list,N2=short_list)    
        #将数据加载至回测系统
        data = bt.feeds.PandasData(dataname=df)    
        cerebro.adddata(data)
        #broker设置资金、手续费
        cerebro.broker.setcash(startcash)
        cerebro.broker.setcommission(commission=com)
        print('期初总资金: %.2f' % cerebro.broker.getvalue())
        cerebro.run(maxcpus=1)
    #回测最优参数并画图
    else:
        cerebro.addstrategy(TurtleTradingStrategy,N1=best_long,N2=best_short)
        data = bt.feeds.PandasData(dataname=df)    
        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()
        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)
#回测和参数调试,寻找最优参数
long_list=range(20,70,5)
short_list=range(5,20,5)
main(dataframe,long_list=long_list,short_list=short_list,best_long=None,best_short=None)
期初总资金: 10000.00
2022-05-31,(组合线:20,5);         期末总资金: 10113.13
2022-05-31,(组合线:20,10);         期末总资金: 10319.87
2022-05-31,(组合线:20,15);         期末总资金: 10806.86
2022-05-31,(组合线:25,5);         期末总资金: 10116.70
2022-05-31,(组合线:25,10);         期末总资金: 10289.09
2022-05-31,(组合线:25,15);         期末总资金: 10862.80
2022-05-31,(组合线:30,5);         期末总资金: 10054.22
2022-05-31,(组合线:30,10);         期末总资金: 10276.60
2022-05-31,(组合线:30,15);         期末总资金: 10766.95
2022-05-31,(组合线:35,5);         期末总资金: 10101.55
2022-05-31,(组合线:35,10);         期末总资金: 10311.73
2022-05-31,(组合线:35,15);         期末总资金: 10791.96
2022-05-31,(组合线:40,5);         期末总资金: 10098.15
2022-05-31,(组合线:40,10);         期末总资金: 10311.38
2022-05-31,(组合线:40,15);         期末总资金: 10806.02
2022-05-31,(组合线:45,5);         期末总资金: 10094.97
2022-05-31,(组合线:45,10);         期末总资金: 10260.57
2022-05-31,(组合线:45,15);         期末总资金: 10748.87
2022-05-31,(组合线:50,5);         期末总资金: 10028.98
2022-05-31,(组合线:50,10);         期末总资金: 10247.39
2022-05-31,(组合线:50,15);         期末总资金: 10716.91
2022-05-31,(组合线:55,5);         期末总资金: 10025.94
2022-05-31,(组合线:55,10);         期末总资金: 10249.30
2022-05-31,(组合线:55,15);         期末总资金: 10731.42
2022-05-31,(组合线:60,5);         期末总资金: 10053.92
2022-05-31,(组合线:60,10);         期末总资金: 10205.10
2022-05-31,(组合线:60,15);         期末总资金: 10653.57
2022-05-31,(组合线:65,5);         期末总资金: 10067.04
2022-05-31,(组合线:65,10);         期末总资金: 10237.22
2022-05-31,(组合线:65,15);         期末总资金: 10685.12
#最优参数回测并画图
main(dataframe,long_list=None,short_list=None,best_long=25,best_short=15)
期初总资金: 10000.00
2022-05-31,(组合线:25,15);         期末总资金: 10862.80
Start date 2022-03-09
End date 2022-05-31
Total months 4
Backtest
Annual return 28.181%
Cumulative returns 8.628%
Annual volatility 10.107%
Sharpe ratio 2.51
Calmar ratio 15.58
Stability 0.50
Max drawdown -1.809%
Omega ratio 2.31
Sortino ratio 9.88
Skew 4.76
Kurtosis 27.47
Tail ratio 2.77
Daily value at risk -1.173%
Worst drawdown periods Net drawdown in % Peak date Valley date Recovery date Duration
0 1.81 2022-03-30 2022-05-25 NaT NaN
1 0.75 2022-03-13 2022-03-14 2022-03-17 4
2 0.65 2022-03-21 2022-03-27 2022-03-28 6
3 0.01 2022-03-18 2022-03-19 2022-03-20 1
4 NaN NaT NaT NaT NaN

基于backtrader和pyfolio的海龟策略_第1张图片
基于backtrader和pyfolio的海龟策略_第2张图片

你可能感兴趣的:(backtrader学习日志)