基于backtrader和pyfolio的海龟策略
策略思路
指标计算: 用 20 日的最高、最低、收盘价计算平均真实波幅 ATR; 计算出近 20 日的最高与 20 日最低价,构建唐奇安通道。 交易信号: 入场:价格突破 20 日价格高点时,入场; 加仓:价格继续上涨至 0.5 倍 ATR ,再次加仓,加仓次数不超过 3 次; 止损:价格回落 2 倍 ATR 时止损离场; 止盈:价格突破 10 日最低点时止盈离场; 做空与做多的逻辑相反。
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'
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 ) )
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 ,
N2= 30 ,
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
self. DonchianH = bt. ind. Highest( self. high( - 1 ) , period= self. p. N1, subplot= True )
self. DonchianL = bt. ind. Lowest( self. low( - 1 ) , period= self. p. N2, subplot= True )
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. ATR = bt. talib. ATR( self. high, self. low, self. close, timeperiod= self. p. N1, subplot= True )
def next ( self) :
if self. order:
return
if self. position. size > 0 :
if self. datas[ 0 ] . close > self. last_price + 0.5 * self. ATR[ 0 ] and self. buy_count <= 4 :
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 = self. buy_count + 1
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
elif self. CrossoverL < 0 :
self. order = self. sell( size= abs ( self. position. size) )
self. buy_count = 0
elif self. position. size < 0 :
if self. datas[ 0 ] . close< self. last_price- 0.5 * self. ATR[ 0 ] and self. buy_count <= 4 :
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 = self. buy_count + 1
elif self. datas[ 0 ] . close < ( self. last_price+ 2 * self. ATR[ 0 ] ) :
self. order = self. buy( size= abs ( self. position. size) )
self. buy_count = 0
elif 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 :
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 :
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 :
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)
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)
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
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