搭建过程
每个交易者都应该形成一套自己的交易系统。
很多交易者也清楚知道,搭建自己交易系统的重要性。现实中,从0到1往往是最难跨越的一步。
授人鱼不如授人以渔,为了帮助大家跨出搭建量化系统的第一步,我们决定推出这个主题系列。
这个系列中,我们用Python从0开始一步步搭建出一套ETF量化交易系统(选择ETF标的是因为对于普通交易者来说,ETF相对于选强势股难度要小,而且没有退市风险)。大家可以跟随着我们的实现路径来一起学习,从过程中掌握方法。
掌握了方法之后,你可以换成期货系统、比特币系统、美股系统,然后在实战中不断去完善自己的系统了。
搭建一套ETF量化交易系统涉及多个模块和组件的协同工作,包括数据源模块、量化策略模块、可视化模块、数据库模块、回测评估模块、自动交易模块等等。
DAY1链接如下:15天搭建ETF量化交易系统Day1—数据源模块
DAY2链接如下:15天搭建ETF量化交易系统Day2—图形显示模块
DAY3链接如下:15天搭建ETF量化交易系统Day3—上手经典回测框架
海龟交易策略是众多趋势型策略中非常经典的一款,它涵盖了从入场交易、仓位管理、离场的整个过程,因此它的策略思想一直被大家所学习和借鉴。
海龟交易策略采用了唐奇安通道突破作为入场和离场的信号,同时基于ATR指标动态地管理仓位,包括止盈止损以及加减仓。
DAY4我们就基于BackTrader框架来实现海龟交易策略。
指标实现
海龟交易策略的主要组成部分是“唐奇安通道指标”和“ATR指标”。
“唐奇安通道指标”的核心思想:通过计算一定时间范围内的最高价和最低价,形成价格的上下边界通道,用于识别价格图表上的价格趋势和波动性。
上轨(上通道线):计算最近n个周期的最高价,并绘制为通道的上边界。
下轨(下通道线):计算最近n个周期的最低价,并绘制为通道的下边界。
做多信号:当价格向上突破上轨时,视为做多信号,交易者可以考虑买入。
做空信号:当价格向下跌破下轨时,视为做空信号,交易者可以考虑卖出。
“ATR指标”通过计算一定周期内的平均真实波幅来反映市场波动的程度。它通过比较每日最高价与最低价之间的波动范围以及前一天的收盘价来计算得出的。
首先计算每日价格范围(TR)
TR = Max(High - Low,
High - Close_pre, Close_pre - Low)
其中,High表示当日的最高价,Low表示当日的最低价,Close_pre表示前一日的收盘价。TR反映了一天内价格的波动范围。
然后再计算ATR
ATR = MA(TR, n)
其中,MA表示简单移动平均数,n表示ATR的计算周期,通常为14天。ATR反映了一段时间内价格的波动性,它越高表示市场波动越大,反之则越小。
在backtrader框架中可以直接使用相应的接口来实现。
# 海龟交易法则中的唐奇安通道和平均波幅ATR
self.H_line = bt.indicators.Highest(self.data.high(-1), period=self.p.long_period)
self.L_line = bt.indicators.Lowest(self.data.low(-1), period=self.p.short_period)
self.TR = bt.indicators.Max((self.data.high(0) - self.data.low(0)), \
abs(self.data.close(-1) - self.data.high(0)), \
abs(self.data.close(-1) - self.data.low(0)))
self.ATR = bt.indicators.SimpleMovingAverage(self.TR, period=14)
# 价格与上下轨线的交叉
self.buy_signal = bt.ind.CrossOver(self.data.close(0), self.H_line)
self.sell_signal = bt.ind.CrossOver(self.data.close(0), self.L_line)
制定策略
了解了关键的指标后,我们来制定海龟交易策略。
入场条件:当收盘价突破N1日价格高点时,买入一单位股票;
离场条件:当价格跌破N2日价格低点时清仓。
注意:这里的N1日价格高点和N2日价格低点构成唐奇安通道,所以海龟交易法则也可以理解成通道突破的趋势跟踪。N1日和N2日参数是根据自己的风格而定的。不同标的参数选取对策略的效果存在一定差异。当然也可以将时间周期作为可变参数,通过参数优化选取合适的时间周期进行回测。
加仓条件:当价格大于上一次买入价格的0.5个ATR(平均波幅),买入一单位股票,加仓次数不超过3次;
止损条件:当价格小于上一次买入价格的2个ATR时清仓;
我们设计TurtleStrategy类来实现策略。TurtleStrategy 类中的 next 方法实现海龟交易逻辑,在next方法中,self.buy_signal、self.data.close、self.atr获取到的是当前最新的数值,我们可以根据它们来计算入场、出场、加仓和止损的条件。如下所示:
def next(self):
if self.order:
return
# 入场:价格突破上轨线且空仓时
if self.buy_signal > 0 and self.buy_count == 0:
self.buy_size = self.broker.getvalue() * 0.01 / (self.ATR * 1000)
self.buy_size = int(self.buy_size / 100) * 100
print(f"入场买入{self.buy_size}")
self.sizer.p.stake = self.buy_size
self.buy_count = 1
self.order = self.buy()
# 加仓:价格上涨了买入价的0.5的ATR且加仓次数少于3次(含)
elif self.data.close > self.buyprice + 0.5 * self.ATR and self.buy_count > 0 and self.buy_count <= 4:
self.buy_size = self.broker.getvalue() * 0.01 / (self.ATR * 1000)
self.buy_size = int(self.buy_size / 100) * 100
print(f"加仓买入{self.buy_size}")
self.sizer.p.stake = self.buy_size
self.order = self.buy()
self.buy_count += 1
# 离场:价格跌破下轨线且持仓时
elif self.sell_signal < 0 and self.buy_count > 0:
self.order = self.sell()
print("离场卖出")
self.buy_count = 0
# 止损:价格跌破买入价的2个ATR且持仓时
elif self.data.close < (self.buyprice - 2 * self.ATR) and self.buy_count > 0:
print("止损卖出")
self.order = self.sell()
self.buy_count = 0
较高的ATR值意味着市场波动性较大,因此交易者可能会设置更宽松的止损和止盈水平,并可能需要较小的头寸规模来限制风险。相反,较低的ATR值意味着市场波动性较小,交易者可以考虑设置更紧密的止损和止盈水平,并可能选择较大的头寸规模。
海龟交易策略使用ATR来确定每个头寸单位的规模,以便在止损时每个头寸单位损失的最大金额都是一样的,从而限制了风险。
头寸单位的计算公式为:每单位头寸手数 = 账户余额 * 1% / ATR(N)。这意味着,基于N日ATR的值,交易者可以确定他们应该为每个头寸单位投入的资金量。
self.buy_size = self.broker.getvalue() * 0.01 / (self.ATR * 1000)
由于ETF的波动非常小,此处人为把它放大1000倍。
由于需要对仓位进行动态调整,根据ATR每次交易一单位股票,因此交易头寸需要重新设定。由TradeSizer类实现。
if isbuy:
# 如果是买入,返回 stake
return self.p.stake
else:
# 尝试获取当前持仓
position = self.broker.getposition(data)
# 如果持仓为空(即没有持仓),返回0
if not position or position.size == 0:
return 0
# 否则,返回当前持仓的大小(对于卖出)
return position.size
策略回测
关于BackTrader回测用法相关的介绍,大家可以参考之前的文章。
我们才用的是ETF的60分钟级别数据,直接看回测的结果,如下所示:
以上就是量化系统中经典的海龟交易策略的思想,以及在回测框架Backtrader的实现方法。随着搭建量化系统的深入,我们要继续学习经典的策略思路,站在巨人的肩膀上学习。
由于篇幅有限,此处提供策略的关键代码:
# 基于BackTrader海龟交易回测
if True:
class TurtleStrategy(bt.Strategy):
# 默认参数
params = (('long_period', 40),
('short_period', 10),
('printlog', True),)
def __init__(self):
self.order = None
self.buyprice = 0
self.buycomm = 0
self.buy_size = 0
self.buy_count = 0
# 海龟交易法则中的唐奇安通道和平均波幅ATR
self.H_line = bt.indicators.Highest(self.data.high(-1), period=self.p.long_period)
self.L_line = bt.indicators.Lowest(self.data.low(-1), period=self.p.short_period)
self.TR = bt.indicators.Max((self.data.high(0) - self.data.low(0)), \
abs(self.data.close(-1) - self.data.high(0)), \
abs(self.data.close(-1) - self.data.low(0)))
self.ATR = bt.indicators.SimpleMovingAverage(self.TR, period=14)
# 价格与上下轨线的交叉
self.buy_signal = bt.ind.CrossOver(self.data.close(0), self.H_line)
self.sell_signal = bt.ind.CrossOver(self.data.close(0), self.L_line)
def next(self):
if self.order:
return
# 入场:价格突破上轨线且空仓时
if self.buy_signal > 0 and self.buy_count == 0:
print(f"入场买入{self.buy_size}")
# 加仓:价格上涨了买入价的0.5的ATR且加仓次数少于3次(含)
elif self.data.close > self.buyprice + 0.5 * self.ATR and self.buy_count > 0 and self.buy_count <= 4:
print(f"加仓买入{self.buy_size}")
# 离场:价格跌破下轨线且持仓时
elif self.sell_signal < 0 and self.buy_count > 0:
print("离场卖出")
# 止损:价格跌破买入价的2个ATR且持仓时
elif self.data.close < (self.buyprice - 2 * self.ATR) and self.buy_count > 0:
print("止损卖出")
# 交易记录日志(默认不打印结果)
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 notify_order(self, order):
pass
# 记录交易收益情况(可省略,默认不输出结果)
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log(f'策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}')
def stop(self):
self.log(f'(组合线:{self.p.long_period},{self.p.short_period});\
期末总资金: {self.broker.getvalue():.2f}', doprint=True)
class TradeSizer(bt.Sizer):
params = (('stake', 1.0),) # stake 通常是一个浮点数,代表想要投入的资金比例
def _getsizing(self, comminfo, cash, data, isbuy):
if isbuy:
# 如果是买入,返回 stake
return self.p.stake
else:
# 尝试获取当前持仓
position = self.broker.getposition(data)
# 如果持仓为空(即没有持仓),返回0
if not position or position.size == 0:
return 0
# 否则,返回当前持仓的大小(对于卖出)
return position.size
def turtle_backtrader_example(code, long_list, short_list, start='', end='', startcash=1000000, com=0.001):
# 创建主控制器
cerebro = bt.Cerebro()
# 加载回测期间的数据
data = bt.feeds.PandasData(dataname=dat_feed_pandas(code, start, end),fromdate=start, todate=end)
cerebro = bt.Cerebro() # 初始化cerebro回测系统设置 默认 stdstats=True
cerebro.adddata(data) # 将数据传入回测系统
cerebro.addstrategy(TurtleStrategy) # 将交易策略加载到回测系统中
# broker设置资金、手续费
cerebro.broker.setcash(startcash)
cerebro.broker.setcommission(commission=com)
# 设置买入设置,策略,数量
cerebro.addsizer(TradeSizer)
print('期初总资金: %.2f' % cerebro.broker.getvalue())
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name = 'SharpeRatio')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DW')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='TradeAnalyzer')
results = cerebro.run(maxcpus=1) #运行回测系统
strat = results[0]
print(f'最终资金: {round(cerebro.broker.getvalue(),2)}') # 获取回测结束后的总资金
print('夏普比率:', strat.analyzers.SharpeRatio.get_analysis())
print('回撤指标:', strat.analyzers.DW.get_analysis())
print('交易信息:', strat.analyzers.TradeAnalyzer.get_analysis())
for k, v in strat.analyzers.TradeAnalyzer.get_analysis().items():
print(k, '->', v)
cerebro.plot(style='candlestick')
说明
此系列为连载专栏,完整代码会上传知识星球《玩转股票量化交易》!作为会员们的学习资料。
想要加入知识星球《玩转股票量化交易》的小伙伴记得先微信call我获取福利!
非星球会员需要的话,需要单独联系我购买!
知识星球介绍点击:知识星球《玩转股票量化交易》精华内容概览