为TradingView编写的ATR通道突破策略
趋势跟踪可以是一种盈利能力极强的交易方法,但这种方法的执行难度也相当高:其胜率通常很低,且回撤幅度巨大。接下来,让我们一同了解并衡量一种经典的趋势跟踪策略在TradingView平台上的具体表现。
ATR通道突破策略:源自海龟交易法则的趋势跟踪
在《海龟交易法则》(Way of the Turtle)一书中,作者柯蒂斯·费思(Curtis Faith)分享了他作为第一代海龟成员所学到的宝贵经验。在二十世纪八十年代,名噪一时的海龟们是一群由传奇交易员理查德·丹尼斯亲自挑选并训练的普通人。丹尼斯向他们传授了两种盈利丰厚的趋势跟踪策略,并在他们完成训练后,给予每人高达200万美元的真实账户进行交易。在作为海龟的几年里,费思本人为丹尼斯创造了超过3000万美元的利润。
海龟们是坚定的趋势跟踪者。趋势跟踪的核心,是试图从持续数月的大级别价格波段中获利。他们在市场创下历史新高时买入,在价格暴跌时做空。这种方法的成功,有赖于价格动能的持续。然而,趋势跟踪的挑战在于,它常常伴随着巨大的资金回撤和极低的胜率,错过几笔关键的大幅盈利交易,就可能毁掉一整年的努力。
费思在其书中分享的趋势跟踪策略之一,便是ATR通道突破策略。这是一个基于波动率的系统,它使用平均真实波幅(Average True Range, ATR)作为衡量价格波动的标尺。我们利用ATR构建一个动态的通道上轨和下轨,其核心思想是,当价格突破这个根据近期正常波动范围计算出的通道时,便可能标志着一轮新趋势的开始。
让我们来具体了解该策略的交易规则,以及如何将其编写为TradingView策略脚本。
ATR通道突破策略的交易规则
该策略的交易规则如下。做多入场:当收盘价上穿通道上轨时,建立多头仓位,通道上轨的计算公式为350日移动平均线 + 7倍的20周期ATR。做多离场:当价格下穿350日移动平均线时,平掉多头仓位。做空入场:当收盘价下穿通道下轨时,建立空头仓位,通道下轨的计算公式为350日移动平均线 – 3倍的ATR。做空离场:当价格上穿350日移动平均线时,回补空头仓位。头寸规模:对于多头和空头头寸,将权益的0.5%除以该市场20周期ATR所代表的美元价值,来确定头寸大小。
值得注意的是,该策略缺少明确的止损订单。然而,它包含一个隐性的止损机制:当价格朝持仓不利的方向移动时,在某个点位终将触及并穿越350日移动平均线,从而触发策略的离场信号。这种方式虽然能限制亏损,但我们无法预先知道一个确切的止损价格。
另一点需要说明的是,费思并未指明应使用何种类型的移动平均线。根据对原书的解读,这里假定他使用的是简单移动平均线(Simple Moving Average, SMA)。
费思进行的那次回测,其盈利结果是建立在日线数据以及广泛的期货市场之上的。回测的品种包罗万象,涵盖了外汇(澳元、英镑、欧元)、大宗商品(黄金、原油、铜、天然气)、软商品(棉花、咖啡、活牛、大豆)以及固定收益期货(美国国库券和国债)。
为TradingView编写ATR通道突破策略
了解了交易规则后,我们来着手编写策略脚本。编写策略时,一个行之有效的方法是使用编码模板。这能将编写策略这项大任务,分解成更小、更易于管理的工作区块,不仅可以减少我们忽略某些环节的可能性,还能让我们的思路更加结构化。
我们将使用以下模板来编写ATR通道突破策略:
//@version=5
// 1. 定义策略设置
// 2. 计算策略所需指标值
// 3. 在图表上输出数据
// 4. 定义多头交易条件
// 5. 定义空头交易条件
// 6. 提交入场订单
// 7. 提交出场订单
如果你想跟随本文一起操作,请在TradingView的Pine编辑器中创建一个新的策略脚本,并将上述模板粘贴进去。
为了让你对我们即将编写的代码有个直观的认识,以下是最终完成的策略在图表上的样子:
现在,让我们开始为TradingView编写这个策略的代码。
步骤1:定义策略设置和输入选项。首先,我们来定义策略的各项参数以及用户输入选项。第一步的代码如下:
//@version=5
// 1. 定义策略设置
strategy(title="ATR Channel Breakout", overlay=true,
pyramiding=0, initial_capital=100000,
commission_type=strategy.commission.cash_per_order,
commission_value=4, slippage=2)
smaLength = input.int(350, title="SMA周期")
atrLength = input.int(20, title="ATR周期")
ubOffset = input.float(7, title="上轨偏移量", step=0.50)
lbOffset = input.float(3, title="下轨偏移量", step=0.50)
usePosSize = input.bool(true, title="启用仓位管理?")
riskPerc = input.float(0.5, title="风险百分比 %", step=0.25)
我们使用 strategy() 函数来配置脚本的基本属性。除了常规选项外,我们通过 pyramiding=0 来禁止金字塔式加仓。initial_capital 将策略的初始资金设为100,000。我们设定每笔交易(commission_type)收取4个单位的固定佣金(commission_value)。slippage 参数设为2,意味着止损单和市价单的成交价比预期要差2个最小跳动点。(通过这种相对悲观的佣金和滑点设置,策略必须证明其具备真正的优势。)
接下来,我们添加策略的输入选项。前两个输入项,SMA周期和ATR周期,是通过 input.int() 创建的整数输入,用于设置ATR通道的参数,默认值分别为350和20。
后两个是 input.float() 创建的浮点数输入,名为上轨偏移量和下轨偏移量。它们决定了通道的上轨和下轨距离均线多少个ATR的倍数,默认值分别为7和3。
接着是一个布尔型(真/假)输入选项:启用仓位管理。它由 input.bool() 创建,可以方便地一键开启或关闭策略的仓位管理算法。我们默认启用该算法(true),但如果关闭它,策略将为每笔交易固定交易1个合约。
最后一个是风险百分比输入项。这个浮点数输入(input.float())定义了仓位管理算法在每笔交易中愿意承担的权益风险比例,默认值为0.5%。
我们将每个输入项的当前值都保存在一个变量中,这样在后续代码中就可以方便地通过变量名来引用它们。
步骤2:计算策略所需的核心指标。第二步,我们来计算策略运行所需的核心指标值:
// 2. 计算策略所需值
smaValue = ta.sma(close, smaLength)
atrValue = ta.atr(atrLength)
upperBand = smaValue + (ubOffset * atrValue)
lowerBand = smaValue - (lbOffset * atrValue)
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = atrValue * syminfo.pointvalue
posSize = usePosSize ? math.floor(riskEquity / atrCurrency) : 1
首先,我们使用 ta.sma() 函数计算SMA均线,其周期由输入变量 smaLength 决定。然后用 ta.atr() 和 atrLength 计算ATR值。结果分别存入 smaValue 和 atrValue 变量。
接着,我们计算ATR通道的上下轨。上轨的计算方式是SMA加上ATR乘以 ubOffset(上轨偏移量,默认为7)。下轨则是SMA减去 lbOffset 乘以ATR。
然后,我们开始计算头寸规模。该算法主要基于两个要素:一部分权益和ATR的货币价值。首先,我们将输入的风险百分比 riskPerc 除以100,将其转换为小数形式(例如0.5%变为0.005),然后乘以 strategy.equity(策略的当前总权益),得出本次交易可以承担的风险金额。
为了确定ATR的货币价值,我们将 atrValue 乘以 syminfo.pointvalue。syminfo.pointvalue 是一个内置变量,返回当前交易品种每波动一个点所代表的货币价值。例如,对于E-mini S&P 500(ES)期货,该值为50,因为ES价格每波动1点,价值变化为$50。
最后,我们确定最终的头寸规模 posSize。我们使用条件运算符(?:)检查用户是否启用了仓位管理(usePosSize)。如果启用,我们将风险金额(riskEquity)除以ATR的货币价值(atrCurrency),然后使用 math.floor() 向下取整,得到最终的头寸规模。如果用户关闭了仓位管理,posSize 就简单地设为1。
步骤3:输出数据并可视化信号。为了方便地验证策略逻辑,我们将计算出的指标绘制在图表上:
// 3. 输出策略数据
plot(smaValue, title="SMA", color=color.orange)
plot(upperBand, title="UB", color=color.green, linewidth=2)
plot(lowerBand, title="LB", color=color.red, linewidth=2)
我们用 plot() 函数来显示这些数据。第一行代码绘制了橙色的350周期SMA。后两行则分别以绿色和红色绘制了ATR通道的上下轨,并加粗了线条以便观察。
步骤4:编写多头交易规则。接下来,我们定义开立和关闭多头仓位的条件。根据交易规则,当收盘价向上突破通道上轨时做多,当价格向下跌破SMA均线时平掉多仓。代码实现如下:
// 4. 定义多头交易条件
enterLong = ta.crossover(close, upperBand)
exitLong = ta.crossunder(close, smaValue)
我们创建了两个布尔变量。enterLong 使用 ta.crossover() 函数判断收盘价(close)是否向上穿越了通道上轨(upperBand)。如果发生上穿,enterLong 为 true,否则为 false。
exitLong 则使用 ta.crossunder() 函数判断收盘价是否向下跌破了SMA均线(smaValue)。如果发生下穿,exitLong 为 true,否则为 false。
步骤5:编写空头交易条件。然后,我们计算空头交易的条件。规则是:当价格收盘向下跌破ATR通道下轨时做空,当价格向上穿越SMA均线时平掉空仓。代码如下:
// 5. 定义空头交易条件
enterShort = ta.crossunder(close, lowerBand)
exitShort = ta.crossover(close, smaValue)
逻辑与多头类似。enterShort 使用 ta.crossunder() 判断是否向下跌破下轨。exitShort 则使用 ta.crossover() 判断是否向上穿越SMA均线。
步骤6:提交开仓订单。现在,我们利用前几步创建的布尔变量来实际提交开仓订单:
// 6. 提交开仓订单
if enterLong
strategy.entry("EL", strategy.long, qty=posSize)
if enterShort
strategy.entry("ES", strategy.short, qty=posSize)
第一个 if 语句检查 enterLong 是否为 true。如果是,就调用 strategy.entry() 函数开立一个多头仓位(strategy.long)。订单的ID设为”EL”,数量则使用我们之前计算好的 posSize 变量。
第二个 if 语句同理,当 enterShort 为 true 时,开立一个ID为”ES”的空头仓位。
步骤7:提交平仓订单。最后,我们需要指定策略何时平仓。代码如下:
// 7. 提交平仓订单
if exitLong
strategy.close("EL")
if exitShort
strategy.close("ES")
第一个 if 语句判断 exitLong 是否为 true(即价格下穿SMA)。如果是,就调用 strategy.close() 函数,通过指定订单ID”EL”来平掉对应的多头仓位。
第二个 if 语句则在 exitShort 为 true 时,平掉ID为”ES”的空头仓位。
顺带一提,strategy.close() 函数只有在存在指定ID的未平仓订单时才会生效。例如,如果我们当前持有空仓,而此时由于某种原因 exitLong 变成了 true,strategy.close("EL") 不会平掉我们的空仓,因为它只针对ID为”EL”的多头仓位。
现在,我们已经完成了策略的所有部分,让我们快速看下它的表现。
ATR通道突破策略的表现
先说优点:ATR通道突破策略在长期的趋势行情中表现非常出色。例如,在下图原油期货数年的下跌趋势中,当油价腰斩不止时,该策略成功捕捉了大部分行情:
该策略也有其弱点。当价格进入横盘震荡时,ATR通道突破策略会被反复打脸,产生一连串的亏损交易。不过,这是许多趋势跟踪策略的通病。
在下面这个横盘市场中,策略连续数年都没有一笔盈利的交易:
下表展示了该策略在两种不同品种上的回测表现。需要说明的是,这些结果是在禁用头寸规模管理功能的情况下得出的。因为当策略严格按照费思的算法来确定头寸大小时,在某些品种上,交易会因资金不足而在回测结束前提前终止。而回测中的交易笔数越多,我们对策略表现的评估就越可靠。因此,为了获得更具参考价值的数据,这里关闭了头寸规模管理功能来进行本次测试。
| 表现指标 | WTI原油 (CL) | 大豆 (ZS) |
|---|---|---|
| 首次交易 | 1984-08-30 | 1971-08-11 |
| 最后交易 | 2017-06-07 | 2018-01-22 |
| 时间框架 | 日线 | 日线 |
| 净利润 | -$8,278 | -$62,731 |
| 总利润 | $119,264 | $91,564 |
| 总亏损 | -$127,542 | -$154,295 |
| 最大回撤 | $64,000 | $72,269 |
| 盈利因子 | 0.935 | 0.593 |
| 总交易数 | 41 | 68 |
| 盈利交易数 | 12 | 17 |
| 胜率 | 29% | 25% |
| 平均每笔交易 | -$201 | -$922 |
| 平均盈利交易 | $9,938 | $5,386 |
| 平均亏损交易 | -$4,398 | -$3,025 |
| 平均盈亏比 | 2.26 | 1.78 |
| 支付佣金 | $332 | $548 |
| 每笔订单滑点 | 2个跳动点 | 2个跳动点 |
改进ATR通道突破策略的思路
从以上结果可以看出,ATR通道突破策略的表现无疑有很大的提升空间。以下是一些你可能会觉得有价值去尝试和探索的改进方向。
该策略的权益曲线经历了剧烈波动,部分原因是在日线图上使用了长达350周期的移动平均线。或许,通过使用更低的时间框架和/或周期更短的移动平均线,策略可以对趋势的(失败)做出更快的反应。
费思并未详细解释他选择这些特定参数的原因,也不清楚为何上轨基于7倍ATR,而下轨使用3倍ATR。策略在使用其他参数时可能会表现更佳,特别是当这些参数能更好地适应特定品种和图表周期时。
引入趋势过滤器无疑会改善策略表现。这有助于避免在市场横盘时建立仓位,从而可以规避大量的亏损交易。
该策略没有使用明确的止损订单,而只有一个隐性的止损机制:当价格穿越350周期均线时平仓。但策略或许不必等到价格穿越均线时,才知道这笔交易已经难有作为。此外,在市场中设置一个明确的止损订单,也能让交易者在持仓时更加安心。因此,进行止损订单的实验似乎是十分必要的。
也许,通过加入更多的风险管理规则,策略也能变得更好。例如,我们可以限制策略的最大资金回撤,或者为持仓规模设置上限。
TradingView的ATR通道突破策略完整代码
以下是ATR通道突破策略的完整代码。关于特定部分的详细解释,请参阅上文的讨论。
//@version=5
// 1. 定义策略设置
strategy(title="ATR通道突破", overlay=true,
pyramiding=0, initial_capital=100000,
commission_type=strategy.commission.cash_per_order,
commission_value=4, slippage=2)
// --- 输入项 ---
smaLength = input.int(350, title="SMA周期")
atrLength = input.int(20, title="ATR周期")
ubOffset = input.float(7, title="上轨偏移倍数", step=0.50)
lbOffset = input.float(3, title="下轨偏移倍数", step=0.50)
usePosSize = input.bool(true, title="是否启用头寸规模管理?")
riskPerc = input.float(0.5, title="风险百分比%", step=0.25)
// 2. 计算策略所需指标值
smaValue = ta.sma(close, smaLength)
atrValue = ta.atr(atrLength)
// 计算通道轨线
upperBand = smaValue + (ubOffset * atrValue)
lowerBand = smaValue - (lbOffset * atrValue)
// 计算头寸规模
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = atrValue * syminfo.pointvalue
posSize = usePosSize ? math.floor(riskEquity / atrCurrency) : 1
// 3. 在图表上输出数据
plot(smaValue, title="SMA", color=color.orange)
plot(upperBand, title="上轨", color=color.green, linewidth=2)
plot(lowerBand, title="下轨", color=color.red, linewidth=2)
// 4. 定义多头交易条件
enterLong = ta.crossover(close, upperBand)
exitLong = ta.crossunder(close, smaValue)
// 5. 定义空头交易条件
enterShort = ta.crossunder(close, lowerBand)
exitShort = ta.crossover(close, smaValue)
// 6. 提交入场订单
if enterLong
strategy.entry("EL", strategy.long, qty=posSize)
if enterShort
strategy.entry("ES", strategy.short, qty=posSize)
// 7. 提交出场订单
if exitLong
strategy.close("EL")
if exitShort
strategy.close("ES")
为TradingView编写布林带突破策略
布林带(Bollinger band)围绕着价格,将绝大部分的价格行为都框定在其上下轨之内。但是,一旦价格强势突破并完全运行于通道之外,我们便知道市场发生了某些重要事件。本文将探讨如何利用一个趋势跟踪策略,来捕捉这些由突破所引发的交易机会。
布林带突破策略:源自海龟交易法则的趋势跟踪
柯蒂斯·费思(Curtis Faith)是第一代海龟交易员之一,他在其著作《海龟交易法则》(Way of the Turtle)中,详细描述了自己的交易经历。二十世纪八十年代,传奇交易员理查德·丹尼斯发起了一项著名的实验,亲自挑选并训练了一群背景各异的普通人,他们后来被称为海龟。丹尼斯向他们传授了两种盈利丰厚的策略,并为他们提供了高达200万美元的真实账户进行交易。最终,费思本人为丹尼斯创造了超过3000万美元的利润。
海龟们是坚定的趋势跟踪者。这类交易者致力于从持续数月的大级别价格波段中获利。他们在市场创下历史新高时买入,在价格暴跌时做空,其核心假设是价格在突破后,其动能仍会持续。趋势跟踪可以带来极为丰厚的回报,但执行起来也异常困难。其胜率通常徘徊在30-35%之间,而资金回撤的幅度往往与系统的年化回报率相当。
费思分享的趋势跟踪策略之一,便是布林带突破(Bollinger Breakout)策略。该策略最初由查克·勒博(Chuck LeBeau)与大卫·卢卡斯(David Lucas)在他们1992年出版的书籍《技术交易者期货市场计算机分析指南》中描述。而布林带指标本身,正如你所熟知的,是由约翰·布林格(John Bollinger)推广开来的。
布林带背后的理念是,它们能够捕获绝大多数(90%至95%)的价格行为,从而将大部分市场噪音隔离在通道内部。因此,当价格移动到通道之外时,就意味着发生了重要的、具备统计意义的事件。这些时刻,正是趋势跟踪者所寻求的交易良机。接下来,让我们看看该策略具体的交易规则。
布林带突破策略的交易规则
该策略的交易规则如下。做多入场:当收盘价高于布林带上轨时,建立多头仓位,上轨的计算方式为350日移动平均线 + 2.5倍标准差。做多离场:当价格下穿350日移动平均线时,平掉多头仓位。做空入场:当收盘价低于布林带下轨时,建立空头仓位,下轨的计算方式为350日移动平均线 – 2.5倍标准差。做空离场:当价格上穿350日移动平均线时,回补空头仓位。头寸规模:对于多头和空头头寸,将权益的0.5%除以该市场20周期平均真实波幅(ATR)所代表的美元价值,来确定头寸大小。
值得注意的是,该策略虽然没有定义明确的止损订单,但其中包含了一个隐性的止损机制。一旦价格朝持仓的不利方向移动,在某个点位终将触及并穿越作为中轨的移动平均线,从而触发离场信号。这种方式虽然能限制亏损,但其方式与我们预先设定一个确切止损价位不同。
费思进行的、并被证明盈利的回测,是建立在日线数据以及广泛的期货市场之上的。回测的品种包罗万象,涵盖了外汇(澳元、英镑、欧元)、大宗商品(黄金、原油、铜、天然气)、软商品(棉花、咖啡、活牛、大豆)以及固定收益期货(美国国库券和国债)。
为TradingView编写布林带突破策略
现在,让我们来着手编写这个策略。编写TradingView策略时,一个行之有效的方法是使用模板。这能将一项大任务分解成更小、更易于管理的工作区块,不仅可以减少我们忽略某些环节的可能性,还能让我们的思路更加结构化。
我们将使用以下模板来编写布林带突破策略:
//@version=5
// 1. 定义策略设置
// 2. 计算策略所需指标值
// 3. 在图表上输出数据
// 4. 定义多头交易条件
// 5. 定义空头交易条件
// 6. 提交入场订单
// 7. 提交出场订单
如果你想跟随本文一起操作,请在TradingView的Pine编辑器中创建一个新的策略脚本,并将上述模板粘贴进去。
为了让你对我们即将编写的代码有个直观的认识,以下是最终完成的策略在图表上的样子:
现在,让我们开始将这个布林带突破策略的交易规则,一步步转化为规范的TradingView代码。
步骤1:定义策略设置和输入选项。首先,我们来定义策略的各项参数以及用户可配置的输入选项。实现这一步的代码如下:
// 1. 定义策略设置
strategy(title="布林带突破策略", overlay=true,
pyramiding=0, initial_capital=100000,
commission_type=strategy.commission.cash_per_order,
commission_value=4, slippage=2)
smaLength = input.int(350, title="SMA周期")
stdLength = input.int(350, title="标准差周期")
ubOffset = input.float(2.5, title="上轨偏移倍数", step=0.5)
lbOffset = input.float(2.5, title="下轨偏移倍数", step=0.5)
usePosSize = input.bool(true, title="启用仓位管理?")
riskPerc = input.float(0.5, title="风险百分比 %", step=0.25)
我们首先使用 strategy() 函数来配置策略的基础信息。我们设定初始资金为100,000(initial_capital),并且不允许金字塔式加仓(pyramiding=0)。每笔单边交易(strategy.commission.cash_per_order)收取4个单位的固定佣金(commission_value)。市价单和止损单的滑点设为2个最小跳动点(slippage=2)。这里的佣金和滑点设置都比真实交易环境要高,这是一种保守的做法,旨在让策略在更严苛的条件下证明其有效性。
接着,我们创建策略的输入选项。前两个是整数输入项,通过 input.int() 函数创建,分别用于设置移动平均线(SMA周期)和标准差(标准差周期)的计算周期,默认值均为350。
随后的两个是浮点数输入项,由 input.float() 函数创建。上轨偏移倍数定义了上轨相对于均线的标准差倍数,而下轨偏移倍数则定义了下轨的倍数,二者默认值均为2.5。
然后是一个布尔型的复选框输入,通过 input.bool() 创建,名为启用仓位管理,我们默认开启它(true)。最后一个输入项也与仓位管理相关,它是一个浮点数输入(input.float()),用于设定风险百分比,默认值为0.5%。
每个输入函数都会返回其当前值,我们将其分别存入变量中,以便在后续代码中方便地引用用户的当前设置。
步骤2:计算策略所需的核心指标。接下来,我们计算策略运行所需的核心指标值:
// 2. 计算策略所需值
smaValue = ta.sma(close, smaLength)
stdDev = ta.stdev(close, stdLength)
upperBand = smaValue + (stdDev * ubOffset)
lowerBand = smaValue - (stdDev * lbOffset)
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = ta.atr(20) * syminfo.pointvalue
posSize = usePosSize ? math.floor(riskEquity / atrCurrency) : 1
首先,我们用 ta.sma() 计算作为布林带中轨的SMA均线。然后,用 ta.stdev() 计算同一周期的收盘价标准差。
接着,我们计算布林带的上下轨。上轨等于均线加上标准差乘以 ubOffset(上轨偏移倍数)。下轨则等于均线减去标准差乘以 lbOffset(下轨偏移倍数)。
为了计算头寸规模,我们先进行两项独立的计算:一个是本次交易可承担的风险金额,另一个是ATR指标的货币价值。对于前者,我们将输入的风险百分比 riskPerc 转换为小数(如1%变为0.01),再乘以 strategy.equity(策略当前总权益)。
对于后者,我们用 ta.atr(20) 获取20周期的ATR值,并乘以 syminfo.pointvalue(当前品种每波动一点所代表的货币价值)。
最后,我们确定实际的头寸规模 posSize。如果用户启用了仓位管理(usePosSize),我们就将风险金额(riskEquity)除以ATR的货币价值(atrCurrency),并用 math.floor() 向下取整。如果未启用,则 posSize 固定为1。
步骤3:输出数据并可视化信号。为了方便地验证策略逻辑,我们将计算出的指标绘制在图表上:
// 3. 输出策略数据
plot(smaValue, title="SMA", color=color.teal)
plot(upperBand, title="UB", color=color.green, linewidth=2)
plot(lowerBand, title="LB", color=color.red, linewidth=2)
我们用 plot() 函数来展示数据。第一行绘制了青色的SMA中轨。后两行则分别以绿色和红色绘制了布林带的上下轨,并加粗了线条以便观察。
步骤4:编写多头交易规则。接下来,我们定义开立和关闭多头仓位的条件。当价格向上突破布林带上轨时做多,当价格向下跌破SMA中轨时平仓。我们将这些规则转换为布尔变量:
// 4. 定义多头交易条件
enterLong = ta.crossover(close, upperBand)
exitLong = ta.crossunder(close, smaValue)
enterLong 变量使用 ta.crossover() 函数判断收盘价是否向上穿越了上轨。exitLong 则使用 ta.crossunder() 函数判断收盘价是否向下跌破了SMA中轨。
步骤5:编写空头交易条件。然后,我们定义空头交易的条件。当价格向下跌破布林带下轨时做空,当价格向上穿越SMA中轨时平仓。代码如下:
// 5. 定义空头交易条件
enterShort = ta.crossunder(close, lowerBand)
exitShort = ta.crossover(close, smaValue)
enterShort 使用 ta.crossunder() 判断是否向下跌破下轨。exitShort 则使用 ta.crossover() 判断是否向上穿越SMA中轨。
步骤6:提交开仓订单。有了明确的交易条件后,我们就可以提交开仓订单了:
// 6. 提交开仓订单
if enterLong
strategy.entry("EL", strategy.long, qty=posSize)
if enterShort
strategy.entry("ES", strategy.short, qty=posSize)
第一个 if 语句检查 enterLong 是否为真,若是,则调用 strategy.entry() 开立一个ID为”EL”的多头仓位,数量为我们计算出的 posSize。第二个 if 语句同理,用于开立ID为”ES”的空头仓位。
步骤7:提交平仓订单。策略的最后一部分代码负责在出现平仓信号时退出市场:
// 7. 提交平仓订单
if exitLong
strategy.close("EL")
if exitShort
strategy.close("ES")
当 exitLong 为真时(价格下穿中轨),我们调用 strategy.close() 函数,通过指定订单ID”EL”来平掉对应的多头仓位。同理,当 exitShort 为真时,平掉ID为”ES”的空头仓位。
至此,整个TradingView策略就完成了。现在,让我们快速看下它的表现。
布林带突破策略的表现
让我们先从积极的方面看起。布林带突破策略在长期的趋势行情中能够获得可观的利润,例如,在下图原油期货大约50%的上涨行情中,它表现良好:
然而,不幸的是,布林带突破策略也是一个典型的趋势跟踪策略。这类策略通常在两种情况下亏损:一是趋势反转时(导致浮动盈利回吐甚至亏损),二是市场陷入区间震荡时。在第一种情况下,该策略平仓不够迅速。而在市场横盘时,策略则会饱受假信号(whipsaw)的折磨。
例如,在下面这个长达数年的震荡市中,策略就因上述两个原因而持续亏损:
下表展示了布林带突破策略在两种不同品种上的回测结果:原油期货和10年期美国国债期货。以下结果是在禁用头寸规模管理功能的情况下得出的。这样做是为了让策略能执行每一次交易信号,从而增加回测的交易笔数。(尽管即便如此,交易样本量依然不足以让我们从这次小规模回测中得出确切的结论。)
| 表现指标 | WTI原油 (CL) | 10年期美国国债期货 (ZN) |
|---|---|---|
| 首次交易 | 1984-10-17 | 2001-10-31 |
| 最后交易 | 2016-05-11 | 2016-11-09 |
| 时间框架 | 日线 | 日线 |
| 净利润 | -$4,182 | -$21,283 |
| 总利润 | $92,862 | $5,741 |
| 总亏损 | -$97,044 | -$27,024 |
| 最大回撤 | $46,826 | $21,287 |
| 盈利因子 | 0.957 | 0.212 |
| 总交易数 | 19 | 10 |
| 盈利交易数 | 6 | 3 |
| 胜率 | 31% | 30% |
| 平均每笔交易 | -$220 | -$2,128 |
| 平均盈利交易 | $15,477 | $1,913 |
| 平均亏损交易 | -$7,464 | -$3,860 |
| 平均盈亏比 | 2.073 | 0.496 |
| 支付佣金 | $156 | $84 |
| 每笔订单滑点 | 2个跳动点 | 2个跳动点 |
改进布林带突破策略的思路
从上表可以看出,布林带突破策略的表现无疑有很大的提升空间。以下是一些你可能会觉得有价值去探索的改进方向。
策略的权益曲线出现了剧烈波动,部分原因是在日线图上结合了长达350周期的移动平均线。如果切换到更低的时间框架,策略或许能对入场和出场信号做出更快的反应。至少,这样做能增加交易次数,为我们提供更多可供分析的数据。
即便不改变时间框架,350周期的移动平均线本身也可能需要调整。这个超长周期的均线有两个主要缺点:首先,它使得布林带的通道异常宽阔,导致策略在趋势启动时反应迟缓;其次,350周期均线作为出场依据,其位置离入场点位过远,导致入场点位与(隐性)止损点位之间的距离过大,从而放大了单笔交易的风险。
费思并未详细阐述他选择这些参数的理由。策略在使用其他参数时可能会表现更佳,特别是当这些参数能更好地适应我们所回测的品种和时间框架时。
与所有趋势跟踪策略一样,布林带突破策略也会从引入趋势过滤器中受益。这样可以有效减少在横盘市场中的交易次数,从而避免大量不必要的亏损。
该策略没有使用明确的止损订单,尽管它会基于350日均线来平仓(这可视为一种隐性止损)。然而,这条均线离入场价相去甚远。在很多情况下,我们或许能更早地判断出这笔交易已无以为继。也许,一个更贴近入场价的追踪止损会有所帮助。即便止损订单本身不能提升策略的整体表现,一个明确的止损订单至少能让交易者在持仓时更加安心,因为它能有效防范重大亏损。
布林带突破策略遭受了相当多的假突破信号的困扰。如果我们能有效过滤掉这些情况,策略的亏损交易和交易成本都将随之下降。
风险管理是改善策略的另一条途径。例如,我们可以限制策略的最大持仓规模,或者在触及最大资金回撤后停止交易。
完整代码:TradingView的布林带突破策略
以下是布林带突破策略的完整代码。关于每个策略部分的更多信息,请参阅上文的讨论。
//@version=5
// 1. 定义策略设置
strategy(title="布林带突破", overlay=true,
pyramiding=0, initial_capital=100000,
commission_type=strategy.commission.cash_per_order,
commission_value=4, slippage=2)
// --- 输入项 ---
smaLength = input.int(350, title="SMA周期")
stdLength = input.int(350, title="标准差周期")
ubOffset = input.float(2.5, title="上轨偏移倍数", step=0.5)
lbOffset = input.float(2.5, title="下轨偏移倍数", step=0.5)
usePosSize = input.bool(true, title="是否启用头寸规模管理?")
riskPerc = input.float(0.5, title="风险百分比%", step=0.25)
// 2. 计算策略所需指标值
smaValue = ta.sma(close, smaLength)
stdDev = ta.stdev(close, stdLength)
// 计算通道轨线
upperBand = smaValue + (stdDev * ubOffset)
lowerBand = smaValue - (stdDev * lbOffset)
// 计算头寸规模
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = ta.atr(20) * syminfo.pointvalue
posSize = usePosSize ? math.floor(riskEquity / atrCurrency) : 1
// 3. 在图表上输出数据
plot(smaValue, title="SMA", color=color.teal)
plot(upperBand, title="上轨", color=color.green, linewidth=2)
plot(lowerBand, title="下轨", color=color.red, linewidth=2)
// 4. 定义多头交易条件
enterLong = ta.crossover(close, upperBand)
exitLong = ta.crossunder(close, smaValue)
// 5. 定义空头交易条件
enterShort = ta.crossunder(close, lowerBand)
exitShort = ta.crossover(close, smaValue)
// 6. 提交入场订单
if enterLong
strategy.entry("EL", strategy.long, qty=posSize)
if enterShort
strategy.entry("ES", strategy.short, qty=posSize)
// 7. 提交出场订单
if exitLong
strategy.close("EL")
if exitShort
strategy.close("ES")








