保持敬畏之心
交易是一场持久战

Pine Script(255):ATR通道与布林带突破策略实战

#Pine Script入门教学

为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值。结果分别存入 smaValueatrValue 变量。

接着,我们计算ATR通道的上下轨。上轨的计算方式是SMA加上ATR乘以 ubOffset(上轨偏移量,默认为7)。下轨则是SMA减去 lbOffset 乘以ATR。

然后,我们开始计算头寸规模。该算法主要基于两个要素:一部分权益和ATR的货币价值。首先,我们将输入的风险百分比 riskPerc 除以100,将其转换为小数形式(例如0.5%变为0.005),然后乘以 strategy.equity(策略的当前总权益),得出本次交易可以承担的风险金额。

为了确定ATR的货币价值,我们将 atrValue 乘以 syminfo.pointvaluesyminfo.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)。如果发生上穿,enterLongtrue,否则为 false

exitLong 则使用 ta.crossunder() 函数判断收盘价是否向下跌破了SMA均线(smaValue)。如果发生下穿,exitLongtrue,否则为 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 语句同理,当 enterShorttrue 时,开立一个ID为”ES”的空头仓位。

步骤7:提交平仓订单。最后,我们需要指定策略何时平仓。代码如下:

// 7. 提交平仓订单
if exitLong
    strategy.close("EL")
if exitShort
    strategy.close("ES")

第一个 if 语句判断 exitLong 是否为 true(即价格下穿SMA)。如果是,就调用 strategy.close() 函数,通过指定订单ID”EL”来平掉对应的多头仓位。

第二个 if 语句则在 exitShorttrue 时,平掉ID为”ES”的空头仓位。

顺带一提,strategy.close() 函数只有在存在指定ID的未平仓订单时才会生效。例如,如果我们当前持有空仓,而此时由于某种原因 exitLong 变成了 truestrategy.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")
赞(0)
未经允许不得转载:图道交易 » Pine Script(255):ATR通道与布林带突破策略实战
分享到

评论 抢沙发

登录

找回密码

注册