基于backtrader的唐奇安结合ADX策略实现(自动多参数调优和回测)
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'
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))
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
openhighlowclosevolumeopeninterest2022-01-01 00:00:003784.643788.453623.003626.2754604.902702022-01-01 04:00:003626.213712.503622.293676.2339496.599302022-01-01 08:00:003676.223748.453676.223723.9626592.769302022-01-01 12:00:003723.963765.273701.003715.3127251.140102022-01-01 16:00:003715.323733.843673.463693.3723727.99490
class D_ADX_TradingStrategy(bt.Strategy):
params = dict(
N1= 40,
N2=30,
N3=30,
N4=20,
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=int(self.p.N1), subplot=True)
self.DonchianL = bt.ind.Lowest(self.low(-1), period=int(self.p.N2), subplot=True)
self.DonchianM= (self.DonchianH+self.DonchianL)/2
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)
self.ADX = bt.talib.ADX(self.high, self.low, self.close,timeperiod=int(self.p.N4),subplot=True)
def next(self):
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.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:
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)
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)
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
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],
N4=[8,20]
)
optimal_pars, details, _ = opt
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-01End date2022-07-10Total months9BacktestAnnual return276.335%Cumulative returns173.054%Annual volatility3386.631%Sharpe ratio-0.12Calmar ratio2.35StabilityNaNMax drawdown-117.486%Omega ratio0.90Sortino ratio-0.15Skew-3.82Kurtosis92.83Tail ratio0.60Daily value at risk-428.304% Worst drawdown periodsNet drawdown in %Peak dateValley dateRecovery dateDuration0117.492022-01-222022-05-042022-06-13101125.222022-01-202022-01-212022-01-222223.402022-06-152022-06-19NaTNaN33.432022-06-132022-06-142022-06-1534NaNNaTNaTNaTNaN
Original: https://blog.csdn.net/jsyzliuyu/article/details/125717295
Author: 飘逸高铁侠
Title: 基于backtrader的唐奇安结合ADX策略实现(自动多参数调优和回测)
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/678341/
转载文章受原作者版权保护。转载请注明原作者出处!