Backtrader 量化回测实践(1)—— 架构理解和MACD/KDJ混合指标
按Backtrader的架构组织,整理了一个代码,包括了Backtrader所有的功能点,原来总是使用SMA最简单的指标,现在稍微增加了复杂性,用MACD和KDJ两个指标综合作为操作指标,因此买入卖出操作就比较少,还有就是买入的时候,采用了限价单,整个的交易频率不高,所以图示交易点比较少,也符合多看少动的交易理念。
通过代码结合架构图,可以充分去理解整个Backtrader的功能设计思路,前面一个功能一个功能学习理解,现在把所有的功能综合在一起进行展示,小有成就感。
回测的操作过程 :
- #1.实例初始化
- #2.加载数据 Data feeds
- #3.加载策略 Strategy
- #4.加载分析器 Analyzers
- #5.加载观察者 Observers
- #6.设置仓位管理 Sizers
- #7.设置佣金管理 Commission
- #8.设置初始资金
- #9.启动回测
- #10.回测结果
1. Backtrader的架构
2. 代码
import pandas as pd
import numpy as np
import common # get data
import datetime
import backtrader as bt
# 定义Observer
class OrderObserver(bt.observer.Observer):
lines = ('created', 'expired',)
# 做图参数设置
plotinfo = dict(plot=True, subplot=True, plotlinelabels=True)
# 创建工单 * 标识,过期工单 方块 标识
plotlines = dict(
created=dict(marker='*', markersize=8.0, color='lime', fillstyle='full'),
expired=dict(marker='s', markersize=8.0, color='red', fillstyle='full')
)
# 处理 Lines
def next(self):
for order in self._owner._orderspending:
if order.data is not self.data:
continue
if not order.isbuy():
continue
# Only interested in "buy" orders, because the sell orders
# in the strategy are Market orders and will be immediately
# executed
if order.status in [bt.Order.Accepted, bt.Order.Submitted]:
self.lines.created[0] = order.created.price
elif order.status in [bt.Order.Expired]:
self.lines.expired[0] = order.created.price
# 定义策略
class MACD_KDJStrategy(bt.Strategy):
# 策略参数
params = (
('highperiod', 9),
('lowperiod', 9),
('kperiod', 3),
('dperiod', 3),
('me1period', 12),
('me2period', 26),
('signalperiod', 9),
('limitperc', 1.0), # 限价比例 ,下跌1个百分点才买入,目的可以展示Observer的过期单
('valid', 7), # 限价周期
('print', False),
('counter', 0), # 计数器
)
def log(self, txt, dt=None):
""" Logging function fot this strategy"""
dt = dt or self.datas[0].datetime.date(0)
if self.params.print:
print("%s, %s" % (dt.isoformat(), txt))
def __init__(self):
# 初始化全局变量,备用
self.dataclose = self.datas[0].close
self.dataopen = self.datas[0].open
self.datahigh = self.datas[0].high
self.datalow = self.datas[0].low
self.volume = self.datas[0].volume
self.order = None
self.buyprice = None
self.buycomm = None
# N个交易日内最高价
self.highest = bt.indicators.Highest(self.data.high, period=self.p.highperiod)
# N个交易日内最低价
self.lowest = bt.indicators.Lowest(self.data.low, period=self.p.lowperiod)
# 计算rsv值 RSV=(CLOSE- LOW) / (HIGH-LOW) * 100
# 如果被除数0 ,为None
self.rsv = 100 * bt.DivByZero(
self.data_close - self.lowest, self.highest - self.lowest, zero=None
)
# 计算rsv的N个周期加权平均值,即K值
self.K = bt.indicators.EMA(self.rsv, period=self.p.kperiod, plot=False)
# D值=K值 的N个周期加权平均值
self.D = bt.indicators.EMA(self.K, period=self.p.dperiod, plot=False)
# J=3*K-2*D
self.J = 3 * self.K - 2 * self.D
# MACD策略参数
me1 = bt.indicators.EMA(self.data, period=self.p.me1period, plot=True)
me2 = bt.indicators.EMA(self.data, period=self.p.me2period, plot=True)
self.macd = me1 - me2
self.signal = bt.indicators.EMA(self.macd, period=self.p.signalperiod)
bt.indicators.MACDHisto(self.data)
# 订单通知处理
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
self.log(
"BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
% (order.executed.price, order.executed.value, order.executed.comm)
)
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
self.bar_executed_close = self.dataclose[0]
else:
self.log(
"SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
% (order.executed.price, order.executed.value, order.executed.comm)
)
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log("Order Canceled/Margin/Rejected")
self.order = None
# 交易通知处理
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log("OPERATION PROFIT, GROSS %.2f, NET %.2f" % (trade.pnl, trade.pnlcomm))
# 策略执行
def next(self):
self.log("Close, %.2f" % self.dataclose[0])
if self.order:
return
# 空仓中,开仓买入
if not self.position:
# 买入基于MACD策略
condition1 = self.macd[-1] - self.signal[-1] # 昨天低于signal
condition2 = self.macd[0] - self.signal[0] # 今天高于signal
# 买入基于KDJ策略 K值大于D值,K线向上突破D线时,为买进信号。下跌趋势中,K值小于D值,K线向下跌破D线时,为卖出信号。
condition3 = self.K[-1] - self.D[-1] # 昨天J低于D
condition4 = self.K[0] - self.D[0] # 今天J高于D
if condition1 < 0 and condition2 > 0 and condition3 < 0 and condition4 > 0 :
self.log('BUY CREATE, %.2f' % self.dataclose[0])
plimit = self.data.close[0] * (1.0 - self.p.limitperc / 100.0)
valid = self.data.datetime.date(0) + datetime.timedelta(days=self.p.valid)
self.log('BUY CREATE, %.2f' % plimit)
# 限价购买
self.buy(exectype=bt.Order.Limit, price=plimit, valid=valid)
else:
# 卖出基于MACD策略
condition1 = self.macd[-1] - self.signal[-1]
condition2 = self.macd[0] - self.signal[0]
# 卖出基于KDJ策略
condition3 = self.K[-1] - self.D[-1]
condition4 = self.D[0] - self.D[0]
if condition1 > 0 and condition2 < 0 and (condition3 > 0 or condition4 < 0):
self.log("SELL CREATE, %.2f" % self.dataclose[0])
self.order = self.sell()
def start(self):
# 从0 开始
# self.params.counter += 1
self.log('Strategy start %s' % self.params.counter)
def nextstart(self):
self.params.counter += 1
self.log('Strategy nextstart %s' % self.params.counter)
def prenext(self):
self.params.counter += 1
self.log('Strategy prenext %s' % self.params.counter)
def stop(self):
self.params.counter += 1
self.log('Strategy stop %s' % self.params.counter)
self.log('Ending Value %.2f' % ( self.broker.getvalue()))
if __name__ == "__main__":
tframes = dict(
days=bt.TimeFrame.Days,
weeks=bt.TimeFrame.Weeks,
months=bt.TimeFrame.Months,
years=bt.TimeFrame.Years)
#1.实例初始化
cerebro = bt.Cerebro()
# 2.加载数据 Data feeds
# 加载数据到模型中,由dataframe 到 Lines 数据类型,查询10年数据到dataframe
stock_df = common.get_data('000858.SZ','2010-01-01','2021-01-01')
# 加载5年数据进行分析
start_date = datetime.datetime(2016, 1, 1) # 回测开始时间
end_date = datetime.datetime(2020, 12, 31) # 回测结束时间
# bt数据转换
data = bt.feeds.PandasData(dataname=stock_df, fromdate=start_date, todate=end_date)
# bt加载数据
cerebro.adddata(data)
#3.加载策略 Strategy
cerebro.addstrategy(MACD_KDJStrategy)
#4.加载分析器 Analyzers
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='mysharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown,_name = 'mydrawdown')
cerebro.addanalyzer(bt.analyzers.AnnualReturn,_name = 'myannualreturn')
#5.加载观察者 Observers
cerebro.addobserver(OrderObserver)
#6.设置仓位管理 Sizers
cerebro.addsizer(bt.sizers.FixedSize, stake=100)
#7.设置佣金管理 Commission
cerebro.broker.setcommission(commission=0.002)
#8.设置初始资金
cerebro.broker.setcash(100000)
print("Starting Portfolio Value: %.2f" % cerebro.broker.getvalue())
#9.启动回测
checkstrats = cerebro.run()
#数据源0 返回值处理
checkstrat = checkstrats[0]
#10.回测结果
print("Final Portfolio Value: %.2f" % cerebro.broker.getvalue())
print('夏普率:')
for k, v in checkstrat.analyzers.mysharpe.get_analysis().items():
print(k, ':', v)
print('最大回测:')
for k, v in checkstrat.analyzers.mydrawdown.get_analysis()['max'].items():
print('max ', k, ':', v)
print('年化收益率:')
for year, ann_ret in checkstrat.analyzers.myannualreturn.get_analysis().items():
print(year, ':', ann_ret)
#11.回测图示
cerebro.plot()
3.输出
Starting Portfolio Value: 100000.00
Final Portfolio Value: 109320.46
夏普率:
sharperatio : 0.24167200140493122
最大回测:
max len : 323
max drawdown : 4.220391363516371
max moneydown : 4426.0
年化收益率:
2016 : 0.0
2017 : 0.03684790760000012
2018 : -0.027969386625977366
2019 : 0.07656254422728326
2020 : 0.007551367384477592
4.图示
做个有趣的猜测,如果对市场上所有的stock代码按程序的遍历一遍,不知道盈亏情况,比例如何?另外一个关心的就是消耗时间?
如果大家有兴趣知道结果,点赞收藏超过100 ,就做个Excel ,给大家看看效果。文章来源:https://www.toymoban.com/news/detail-832638.html
仅供学习参考,不做交易操作依据。文章来源地址https://www.toymoban.com/news/detail-832638.html
到了这里,关于Backtrader 量化回测实践(1)—— 架构理解和MACD/KDJ混合指标的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!