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

Pine Script(260):唐奇安通道突破与双通道策略

#Pine Script入门教学

在TradingView Pine中编写唐奇安通道突破交易策略

理查德·唐奇安(Richard Donchian)在他那个时代是一位成功的交易员。他提出了如今闻名遐迩的唐奇安通道(Donchian Channel),该指标根据一段时间内的最高高点和最低低点绘制出一条上轨和一条下轨。接下来,让我们看看一个基于该理念的TradingView策略是什么样的。

唐奇安通道突破策略:一种经典的趋势跟踪系统

在其著作《趋势跟踪》(Trend Following)中,迈克尔·柯威尔(Michael Covel)研究了一些全球顶尖的交易者。这些交易者无一例外都是趋势跟踪者。趋势跟踪的目标看似简单:为了盈利而捕捉到一轮上涨或下跌趋势的主体部分。

与其他试图预测趋势何时何地开始的交易风格不同,趋势跟踪者只简单地观察价格。他们预先定义何种价格行为构成了趋势,然后在他们看到新趋势出现时才建立仓位。这意味着他们永远抓不住趋势的开端,也总是在趋势结束后才离场。这听起来像是一个亏钱的法门?远非如此!大趋势的主体部分蕴含着大量的利润。

趋势跟踪策略通常有相同的特点:它们通过监控价格来识别主要趋势;让盈利的头寸持续奔跑,直到趋势结束;在预设的止损位果断了结亏损;并通过合理的头寸规模管理来控制亏损。

柯威尔在他的书中分享的趋势跟踪策略之一,便是唐奇安通道突破(Donchian Channel Breakout)策略。该策略基于由理查德·唐奇安创建的同名指标。该指标由一条上轨和一条下轨组成,上轨代表过去N日的最高高点,而下轨则标志着前N日的最低低点。

其背后的理念是,对这些具有心理重要性的界线的突破,是市场观点发生改变的结果。一旦这种情况发生,我们可以预期原始的移动方向将得以延续。唐奇安通道的另一个特点是,当价格波动性增加时,其通道宽度也会增加,这有助于避免因市场噪音而非真实趋势变化所导致的错误入场。让我们看看如何利用唐奇安通道进行交易。

唐奇安通道突破策略的交易规则

该策略包含以下交易规则。做多入场与空头离场:使用止损单(stop order)在价格突破100周期唐奇安通道上轨时做多(并平掉任何已有的空头仓位)。做空入场与多头离场:使用止损单在价格跌破100周期唐奇安通道下轨时做空(并平掉任何已有的多头仓位)。头寸规模的确定包含两条规则:每笔头寸的初始风险(入场价与四倍10周期平均真实波幅(ATR)之间的差异)是权益的2%;单个头寸的最大风险敞口(即保证金与权益的比率)是权益的10%。

该策略的头寸大小是基于每笔交易承担2%的权益风险来计算的。然而,原文并没有设置止损订单,所以交易的最终亏损可能超过2%。为了防止过度的亏损,该策略确实限制了每个头寸的规模:我们不会将超过10%的权益投资于单笔交易。

需要说明的是,此处原文的规则描述存在矛盾:头寸规模确定部分提到了一个基于4倍ATR的风险计算,这通常意味着存在一个相应的止损位,但紧随其后的段落又明确指出策略没有止损订单。在后续的代码实现中,我们将遵循没有明确ATR止损、仅通过反向信号离场的逻辑来编写。

柯威尔分享的、被证明盈利的该策略回测,是基于20个美国期货市场长达15年的日线数据完成的,包括货币(英镑、日元、欧元)、大宗商品(原油、黄金、白银、玉米、小麦)、软商品(咖啡、糖)以及金融产品(标普500、纳斯达克100、5年期美国国债)。

在TradingView中编写唐奇安通道突破策略

现在,让我们将上述交易规则转化为一个完整的TradingView策略脚本。一个有效的方法是使用模板。这样能为我们提供一个结构,并将编程项目分解成更小、更易于管理的工作区块。

对于唐奇安通道突破策略,我们将使用以下模板:

//@version=5
// 步骤1. 定义策略设置
// 步骤2. 计算策略所需指标值
// 步骤3. 在图表上输出数据
// 步骤4. 确定交易条件
// 步骤5. 提交入场订单
// 步骤6. 提交出场订单

如果你想跟随下方的代码讨论,请在TradingView的Pine编辑器中创建一个新的策略脚本,并将上述模板粘贴进去。(如果你只想看完整的策略代码,请参见本文末尾。)

为了让你对我们即将编写的代码有个直观的认识,以下是最终完成的唐奇安通道突破脚本在图表上的样子:

现在,让我们开始将上述交易规则,一步步地转化为一个规范的TradingView策略脚本。

步骤1:定义策略设置和输入选项

在第一步,我们需要配置策略的整体属性,并创建用户可调的参数设置。我们首先定义策略的基础设置:

//@version=5
// 步骤1. 定义策略设置
strategy(title="唐奇安通道突破策略", overlay=true,
     pyramiding=0, initial_capital=100000,
     commission_type=strategy.commission.cash_per_order,
     commission_value=4, slippage=2)

我们通过 strategy() 函数来配置脚本属性。title 参数为策略命名。overlay=true 使策略直接叠加在主图表上。根据交易规则,我们通过 pyramiding=0 禁止加仓。initial_capital 则将策略的初始资金设为100,000。在交易成本方面,我们设定每笔单边交易(strategy.commission.cash_per_order)收取4个货币单位的固定佣金(commission_value=4),并假设市价单和止损单会产生2个最小跳动点的滑点(slippage=2)。这里的成本设置对于期货交易来说略高,但高估交易成本总比低估要安全。

接下来,我们创建策略的输入选项,以便无需修改代码就能方便地调整参数。第一个用于设置唐奇安通道的周期:

dochLen = input.int(100, title="唐奇安通道周期")

我们使用 input.int() 函数创建了一个名为唐奇安通道周期的整数输入项,默认值为100。

然后,我们为策略的仓位管理功能创建一系列输入选项:

// 仓位管理输入项
usePosSize    = input.bool(true, title="启用仓位管理?")
atrLen        = input.int(10, title="ATR周期")
atrRiskOffset = input.float(4, title="ATR风险偏移倍数", step=0.25)

maxRisk = input.float(2, title="单笔最大风险 %", step=.25, 
     minval=0.25, maxval=15)
maxExposure = input.float(10, title="最大风险敞口 %", step=1, 
     minval=1, maxval=100)
marginPerc = input.int(10, title="保证金预估 %", minval=1, maxval=100)

第一个是布尔型的复选框,可以方便地一键开启或关闭仓位管理算法。接着是用于估算交易风险的两个输入:ATR周期(默认为10)和ATR风险偏移倍数(默认为4),后者用于设定我们以ATR的多少倍来作为风险的代理。单笔最大风险是一个浮点数输入,用于设定在单笔交易中我们愿意承担的权益风险比例,默认值为2%。最大风险敞口是另一个浮点数输入,用于限制总仓位规模相对于策略权益的比例,默认值为10%。保证金预估是一个整数输入,由于Pine Script无法直接获取经纪商提供的确切保证金率,我们在此创建一个输入项,以便用户根据实际情况进行估算,默认值为10%。

步骤2:计算交易策略所需的值

现在,我们进入第二步:计算策略运行所需的数据。首先,我们计算出唐奇安通道的上下轨:

// 步骤2. 计算策略所需值
upperband = ta.highest(high, dochLen)[1]
lowerband = ta.lowest(low, dochLen)[1]

我们使用 ta.highest()ta.lowest() 函数来计算指定周期内的最高高点和最低低点。至关重要的是,我们在函数后面加上了 [1],以确保我们计算的是不包含当前K线的前100根K线的极值,这样才能有效地捕捉到价格的突破。

然后,我们计算策略的头寸规模:

// 计算头寸规模
riskEquity = (maxRisk * 0.01) * strategy.equity
riskTrade  = (ta.atr(atrLen) * atrRiskOffset) * syminfo.pointvalue

maxPos = ((maxExposure * 0.01) * strategy.equity) /
     ((marginPerc * 0.01) * (close * syminfo.pointvalue))

posSize = usePosSize ? math.min(math.floor(riskEquity / riskTrade), maxPos) : 1

我们分步进行计算:首先,计算出本次交易愿意承担的风险金额 riskEquity。接着,计算出每手交易的初始风险 riskTrade(我们用4倍ATR的货币价值作为代理)。然后,计算出基于风险敞口和保证金预估的最大允许仓位 maxPos。最后,如果用户启用了仓位管理,我们就在基于单笔风险计算出的仓位和最大允许仓位之间取其较小者,作为最终的头寸规模 posSize。如果未启用,则固定为1。

步骤3:输出策略数据

下一步,我们在图表上绘制策略的数据,以便验证和跟踪脚本的行为。我们绘制唐奇安通道的上下轨:

// 步骤3. 输出策略数据
plot(upperband, color=color.green, linewidth=2, title="唐奇安上轨")
plot(lowerband, color=color.red, linewidth=2, title="唐奇安下轨")

我们用绿色和红色分别绘制了加粗的唐奇安通道上下轨。

步骤4:确定交易条件

接下来,我们定义策略何时可以进行交易:

// 步骤4. 定义交易条件
tradeWindow  = time <= timenow - (86400000 * 3)

tradeAllowed = tradeWindow and bar_index > dochLen

我们设置了一个交易窗口(tradeWindow),确保策略在回测结束前三天停止交易,以获得干净的报告。同时,我们定义了一个 tradeAllowed 变量,它要求不仅要处于交易窗口内,还要有足够的历史K线来计算唐奇安通道,以此来避免在数据不足时产生交易。

步骤5:提交开仓订单

在这一步,我们让策略根据规则开仓。与许多其他策略不同,唐奇安通道突破策略没有明确的入场条件,而是通过在通道上下轨挂止损单来实现开仓和反转。

第一个止损单用于开立多头仓位:

// 步骤5. 提交开仓订单
if tradeAllowed
    if strategy.position_size < 1
        strategy.entry("EL", strategy.long, qty=posSize,
             stop=upperband + syminfo.mintick)

我们首先检查交易是否被允许(tradeAllowed)。然后,检查当前是否未持有多仓或持有空仓(strategy.position_size < 1)。如果条件满足,我们便调用 strategy.entry() 在唐奇安通道上轨加一个最小跳动点的位置,挂一个ID为”EL”的多头止损开仓单。

接下来,我们提交空头开仓的指令:

if tradeAllowed
    if strategy.position_size > -1
        strategy.entry("ES", strategy.short, qty=posSize,
             stop=lowerband - syminfo.mintick)

逻辑与多头类似。当交易被允许且当前未持有空仓或持有多仓时,我们在唐奇安通道下轨减一个最小跳动点的位置,挂一个ID为”ES”的空头止损开仓单。

步骤6:提交平仓订单

策略代码的最后一部分,是在回测的时间窗口结束时平掉所有仓位:

// 步骤6. 提交平仓订单
if not tradeWindow
    strategy.close_all()

我们通过 not tradeWindow 这个条件来判断回测窗口是否已经结束。一旦结束,便调用 strategy.close_all() 函数,以市价单清空所有剩余仓位。

唐奇安通道突破策略的表现

让我们先从一个积极的方面说起。当市场出现长期趋势时,唐奇安通道突破策略能捕捉到趋势的大部分,并创造出色的表现。

例如,在下图中,该策略成功捕捉了E-mini S&P 500期货的一波超过600点的上涨行情:

当然,市场并非总是如此慷慨。当市场进入震荡时,唐奇安通道突破策略的表现就会不佳。在那些时候,策略会因为入场太晚(或者说,趋势持续时间不够长)而亏损。有时价格在触及通道后甚至会立刻反向运动。

下方的E-mini S&P 500期货图表就展示了一个横盘市场。在这里,策略连续遭遇了三次亏损:

下表展示了唐奇安通道突破策略的回测结果。该策略在E-迷你标普500指数期货和原油期货上均取得了盈利。其45%左右的胜率对于趋势跟踪策略而言也相当高,这类策略的胜率通常在30-35%左右。此外,相对于净利润而言,其最大回撤也相对较小。

然而,一个令人担忧的问题是其极低的交易次数:该策略平均每年仅交易1.5次。如此稀少的交易样本,让我们无法充满信心地断定该策略是否真的有效。因此,进行进一步的测试,以更好地理解该策略的行为和表现,似乎是一个明智之举。

表现指标 E-迷你标普500指数期货 (ES) 原油期货 (CL)
首次交易 1998-02-03 1983-11-16
最后交易 2018-10-23 2018-10-23
时间框架 日线 日线
净利润 $47,696 $115,750
总利润 $124,021 $260,952
总亏损 -$76,324 -$145,202
最大回撤 $21,771 $54,784
盈利因子 1.625 1.797
总交易数 28 54
胜率 46.43% 44.44%
平均每笔交易 $1,703 $2,143
平均盈利交易 $9,540 $10,873
平均亏损交易 -$5,088 -$4,840
平均盈亏比 1.875 2.246
支付佣金 $116 $220
每笔订单滑点 2个跳动点 2个跳动点

改进思路与新策略方向

虽然上表中的结果还不算太差,但仍有进一步探索和改进的空间。以下是一些你可能会觉得有价值的思路。

与大多数趋势跟踪策略一样,唐奇安通道突破策略在市场横盘时表现不佳。如果我们能过滤掉这些不利的市场环境,策略的表现很可能会得到改善。或许,可以引入平均真实波幅(ATR)、ADX或成交量等过滤器,来剔除那些没有前景的交易机会。

另一个可以改善策略的地方是其离场机制。目前,交易会持续很长时间,等待价格创下新的100周期高点或低点。当然,一个过短的唐奇安通道周期只会增加噪音。但也许使用两个唐奇安通道——一个用于入场,一个用于出场——会有所帮助。你可以参考双唐奇安通道突破策略来了解使用此想法的脚本。

唐奇安通道突破策略在价格刚刚突破通道上下轨一个最小变动单位(tick)时便开仓。这确实能让我们尽快入场,但并非每一个初次探出通道的tick都是有效的突破。也许,在价格突破唐奇安通道一定距离后我们再开仓,策略的表现会变得更好。

该策略目前不使用止损订单。虽然一个策略在没有明确止损的情况下当然也可能表现良好,但一个在市场中实际存在的止损订单也能让人更加安心。这反过来又能使策略更容易被交易者信任和执行。或许一个以通道另一侧的轨线为起点的追踪止损会有所帮助,这既能为价格波动提供足够的空间,又能让止损在价格朝有利方向发展时跟进。

改善策略的其他方法包括风险管理。例如,我们可以限制策略的连续亏损天数,或者为其最大资金回撤设定一个上限。

与唐奇安通道突破策略相似的一个策略是双唐奇安通道突破策略。其他同样使用唐奇安通道的TradingView策略还有唐奇安趋势策略和带时间退出的唐奇安趋势策略。

完整代码: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)

// --- 唐奇安通道输入项 ---
dochLen = input.int(100, title="唐奇安通道周期")

// --- 头寸规模管理输入项 ---
usePosSize    = input.bool(true, title="是否启用头寸规模管理?")
atrLen        = input.int(10, title="ATR周期")
atrRiskOffset = input.float(4, title="ATR风险偏移倍数", step=0.25)

maxRisk = input.float(2, title="最大单笔风险%", step=.25, 
     minval=0.25, maxval=15)
maxExposure = input.float(10, title="最大头寸敞口%", step=1, 
     minval=1, maxval=100)
marginPerc = input.int(10, title="保证金%", minval=1, maxval=100)

// 步骤2. 计算策略所需指标值
// 计算通道上下轨
upperband = ta.highest(high, dochLen)[1]
lowerband = ta.lowest(low, dochLen)[1]

// 计算头寸规模 (双重风控:风险百分比 vs 最大敞口)
riskEquity = (maxRisk * 0.01) * strategy.equity
riskTrade  = (ta.atr(atrLen) * atrRiskOffset) * syminfo.pointvalue

maxPos = ((maxExposure * 0.01) * strategy.equity) /
     ((marginPerc * 0.01) * (close * syminfo.pointvalue))

posSize = usePosSize ? math.min(math.floor(riskEquity / riskTrade), maxPos) : 1

// 步骤3. 在图表上输出数据
plot(upperband, color=color.green, linewidth=2, title="唐奇安上轨")
plot(lowerband, color=color.red, linewidth=2, title="唐奇安下轨")

// 步骤4. 定义交易条件
// 设置回测时间窗口,在当前日期的3天前停止交易
tradeWindow  = time <= timenow - (86400000 * 3)

// 结合时间窗口和K线数量检查,确保有足够数据计算通道
tradeAllowed = tradeWindow and bar_index > dochLen

// 步骤5. 提交入场订单 (反转信号系统)
if tradeAllowed
    // 当无仓或持空仓时,在通道上轨之上挂多头止损单入场
    if strategy.position_size < 1
        strategy.entry("EL", strategy.long, qty=posSize,
             stop=upperband + syminfo.mintick)

    // 当无仓或持多仓时,在通道下轨之下挂空头止损单入场
    if strategy.position_size > -1
        strategy.entry("ES", strategy.short, qty=posSize,
             stop=lowerband - syminfo.mintick)

// 步骤6. 提交出场订单 (仅在回测窗口结束时)
if not tradeWindow
    strategy.close_all()

TradingView编写的双唐奇安通道突破策略

唐奇安通道是趋势跟踪策略中的一个常用工具。但这个基于最高高点和最低低点的通道有一个缺点:它可能需要很长时间才能触及相反的轨线并产生离场信号。本文将探讨如何通过加倍唐奇安通道来解决这个问题。

双唐奇安通道突破策略:一种更灵活的趋势跟踪系统

在《趋势跟踪》(Trend Following)一书中,迈克尔·柯威尔(Michael Covel)分享了他对一些全球顶尖交易者的研究和见解。这些顶级交易员的一个共同点便是他们的交易方法——他们都是趋势跟踪者。这种交易方法有一个看似简单的目标:为了盈利而捕捉到一轮上涨或下跌趋势的主体部分。

趋势跟踪者与其他交易风格的不同之处在于,他们不预测趋势何时何地开始,而是简单地观察价格。他们预先定义何种价格行为构成了趋势,并只在趋势开始后才建立仓位。这意味着他们总是会错过趋势的开端,也无法在顶部附近离场。但这并无大碍:大趋势的主体部分蕴含着大量的利润。

大多数趋势跟踪策略有几个共同的特点:它们通过监控价格变化来识别主要趋势;让盈利的头寸持续奔跑,直到趋势结束;在预设的止损位果断了结亏损;并通过头寸规模管理来平衡每个头寸的风险。

其中一个趋势跟踪策略是双唐奇安通道突破(Double Donchian Channel Breakout)策略。该策略是对我们之前讨论的唐奇安通道突破策略的一种改进尝试。原版策略的一个问题是,在等待价格穿越通道的另一侧轨线时,亏损的头寸会持有过长的时间。这不仅会加剧亏损,也会在离场信号出现前,大幅侵蚀浮动利润。

为了解决这个问题,双唐奇安通道突破策略并不是使用一个,而是两个唐奇安通道。一个使用较长的回看周期(因此通道更宽),我们用它来入场。另一个则使用较短的回看周期(因此能捕捉到更小的价格波动范围),我们用它来出场。接下来,让我们看看该策略的交易规则。

双唐奇安通道突破策略的交易规则

虽然柯威尔讨论了几个趋势跟踪策略,但他只是简短地提及了双唐奇安通道突破策略。因此,为了构建以下交易规则,我结合了前一篇文章的唐奇安通道突破策略以及柯威尔书中常见的风险管理原则。

这便得出了以下交易规则。做多入场:当价格突破100周期唐奇安通道上轨时,使用止损单(stop order)做多。做多离场:当价格跌破40周期唐奇安通道下轨时,平掉所有多头仓位。做空入场:当价格跌破100周期唐奇安通道下轨时,使用止损单做空。做空离场:当价格上涨并突破40周期唐奇安通道上轨时,平掉所有空头仓位。头寸规模的确定是一个双重风控机制:每笔头寸的初始风险(入场价与40周期唐奇安通道之间的差异)被设定为权益的2%;单个头寸的最大风险敞口(即保证金与权益的比率)被限制在权益的10%以内。

我们用这个策略交易时,头寸的风险是基于40周期的高低点来计算的。当市场波动性较低时,这个风险度量值就会很小,可能导致计算出的头寸规模过大。当价格发生对我们不利的跳空时,这就成了一个问题,可能导致远超预期的亏损。

为了防止策略交易过大的头寸,我们将每个头寸的风险敞口限制在权益的10%以内。这样,用于保证金的资金就不会超过策略权益的十分之一。

柯威尔分享的其他策略都使用了各种美国期货的日线数据,包括货币(英镑、日元、欧元)、大宗商品(原油、黄金、白银、玉米、小麦)、软商品(咖啡、糖)以及金融产品(标普500、纳斯达克100、5年期美国国债)。因此,为了测试双唐奇安通道突破策略,我们也将使用日线级别的期货数据。

在TradingView中编写双唐奇安通道突破策略

现在,让我们将上述策略规则转化为一个功能完备的TradingView策略。一个有效的方法是使用模板,它能提供一个结构,并将编程任务分解成更小、更易于管理的部分。

这是我们将用于双唐奇安通道突破策略的模板:

//@version=5
// 步骤1. 定义策略设置
// 步骤2. 计算策略所需指标值
// 步骤3. 在图表上输出数据
// 步骤4. 定义多头交易条件
// 步骤5. 定义空头交易条件
// 步骤6. 提交入场订单
// 步骤7. 提交出场订单

如果你想跟随下方的代码讨论,请在TradingView的Pine编辑器中创建一个新的策略脚本,然后粘贴上述模板。(如果你只想看完成的代码,请跳转至本文末尾的完整策略。)

为了让你对我们即将编写的代码有个直观的认识,以下是最终完成的双唐奇安通道突破策略在图表上的样子:

现在,让我们开始动手,将一个基于双唐奇安通道的交易策略,转化为规范的TradingView脚本。

步骤1:定义策略设置和输入选项

在第一步,我们定义策略的整体属性及其可由用户配置的输入参数。我们首先使用 strategy() 函数来配置策略的基础设置:

// 步骤1. 定义策略设置
strategy(title="双唐奇安通道突破策略", overlay=true,
     pyramiding=0, initial_capital=100000,
     commission_type=strategy.commission.cash_per_order,
     commission_value=4, slippage=2)

我们通过 title 参数为策略命名。overlay=true 使策略直接叠加在主图表上。根据交易规则,我们通过 pyramiding=0 禁止加仓。initial_capital 则将策略的初始资金设为100,000。在交易成本方面,我们设定每笔单边交易(strategy.commission.cash_per_order)收取4个货币单位的固定佣金(commission_value=4),并假设市价单和止损单会产生2个最小跳动点的滑点(slippage=2)。这里的成本设置相对悲观,但高估交易成本总比低估要安全。

接下来,我们创建策略的输入选项,以便无需修改代码就能方便地调整参数。首先是唐奇安通道的周期设置:

// 唐奇安通道输入项
fastLen = input.int(40, title="快线唐奇安通道周期")
slowLen = input.int(100, title="慢线唐奇安通道周期")

我们使用 input.int() 函数创建了两个整数输入项,分别用于设置快、慢两条唐奇安通道的计算周期,默认值分别为40和100。

然后,我们为策略的仓位管理功能创建一系列输入选项:

// 仓位管理输入项
usePosSize = input.bool(true, title="启用仓位管理?")

maxRisk = input.float(2, title="单笔最大风险 %", step=.25, 
     minval=0.25, maxval=15) * 0.01
maxExposure = input.float(10, title="最大风险敞口 %", step=1, 
     minval=1, maxval=100) * 0.01

marginPerc = input.int(10, title="保证金预估 %", minval=1, maxval=100) * 0.01

第一个是布尔型的复选框,可以方便地一键开启或关闭我们自定义的仓位管理算法。中间两个浮点数输入分别用于设定单笔交易的最大风险和总仓位的最大风险敞口,默认值分别为2%和10%。最后一个是整数输入,由于Pine Script无法直接获取经纪商提供的确切保证金率,我们在此创建一个输入项,以便用户根据实际情况进行估算,默认值为10%。请注意,我们直接将所有基于百分比的输入项乘以0.01,将其转换为小数形式,以便于后续的计算。

步骤2:计算交易策略所需的值

接下来,我们计算策略运行所需的数据,包括唐奇安通道值、头寸规模和交易窗口。首先,我们编写计算唐奇安通道的代码:

// 步骤2. 计算策略所需值
// 计算唐奇安通道的值
ubSlow = ta.highest(high, slowLen)[1]
lbSlow = ta.lowest(low, slowLen)[1]

ubFast = ta.highest(high, fastLen)[1]
lbFast = ta.lowest(low, fastLen)[1]

我们使用 ta.highest()ta.lowest() 函数来计算快慢两条唐奇安通道的上下轨。至关重要的是,我们在函数后面都加上了 [1],以确保我们计算的是不包含当前K线的历史极值,这样才能有效地捕捉到价格的突破。

然后,我们计算策略的头寸规模。为此,我们首先计算愿意承担的风险金额和估计的交易风险:

// 计算头寸规模
riskEquity = maxRisk * strategy.equity
riskLong   = (close - lbFast) * syminfo.pointvalue
riskShort  = (ubFast - close) * syminfo.pointvalue

我们用 maxRisk 乘以策略总权益 strategy.equity,得出本次交易可以承担的风险金额。对于交易风险,我们以快线唐奇安通道作为初始止损位,计算出多头和空头交易的初始风险金额 riskLongriskShort

接下来,我们计算策略允许交易的最大头寸规模:

maxPos = math.floor((maxExposure * strategy.equity) /
     (marginPerc * (close * syminfo.pointvalue)))

我们用最大风险敞口比例 maxExposure 乘以总权益,得出最大可投资金额,然后除以每手合约所需的预估保证金,得出最大允许的合约数 maxPos,并向下取整。

最后,我们结合以上计算,确定最终的订单规模:

longPosSize  = usePosSize ? math.min(math.floor(riskEquity / riskLong), maxPos) : 1
shortPosSize = usePosSize ? math.min(math.floor(riskEquity / riskShort), maxPos) : 1

如果用户启用了仓位管理,我们就在基于单笔风险计算出的仓位和最大允许仓位 maxPos 之间取其较小者,作为最终的头寸规模。如果未启用,则固定为1。我们分别为多头和空头计算其规模。

在这一步中我们需要计算的最后一件事是策略的交易窗口:

// 确定交易窗口
tradeAllowed = time <= timenow - (86400000 * 5) and 
     bar_index > slowLen and 
     longPosSize > 0 and 
     shortPosSize > 0

我们设置了一个交易窗口,确保策略在回测结束前五天停止交易,以获得干净的报告。同时,tradeAllowed 变量还要求有足够的历史K线来计算唐奇安通道,并且计算出的头寸规模必须大于0,以避免无效交易。

步骤3:输出数据并可视化信号

在第三步,我们在图表上输出策略的数据,以便跟踪其行为并验证交易设置。首先,我们绘制用于入场的慢线唐奇安通道:

// 步骤3. 输出策略数据
// 绘制用于入场的唐奇安通道
plot(ubSlow, color=color.green, linewidth=2, title="慢线唐奇安 - 上轨")
plot(lbSlow, color=color.red, linewidth=2, title="慢线唐奇安 - 下轨")

我们用绿色和红色分别绘制了加粗的慢线唐奇安通道上下轨。

然后,我们显示用于出场的快线唐奇安通道:

// 绘制用于出场的唐奇安通道
plot(strategy.position_size < 0 ? ubFast : na, color=color.teal, 
     style=plot.style_linebr, linewidth=2, title="快线唐奇安 - 上轨")
plot(strategy.position_size > 0 ? lbFast : na, color=color.orange, 
     style=plot.style_linebr, linewidth=2, title="快线唐奇安 - 下轨")

为了保持图表整洁,我们只在持有多仓时绘制快线下轨(作为移动止损),在持空仓时绘制快线上轨。我们通过条件运算符(?:)来实现这一功能。

步骤4:编写多头交易规则

接下来,我们将策略的多头交易规则转化为TradingView代码。由于该策略使用止损单(stop order)来入场,因此我们不需要像之前那样定义一个明确的做多入场条件。相反,我们的逻辑是当策略处于空仓状态时,简单地在通道上下轨挂上止损单。

不过,我们确实需要为策略的多头离场编写明确的规则。规则是:当价格跌破40周期唐奇安通道的下轨时,平掉所有多头仓位。代码实现如下:

// 步骤4. 定义多头交易条件
exitLong = ta.crossunder(close, lbFast) and
     strategy.position_size > 0

我们在这里创建的 exitLong 布尔变量,其值由两个通过 and 运算符连接的表达式决定。第一个表达式使用 ta.crossunder() 函数,判断收盘价(close)是否下穿了40周期的最低低点(lbFast)。第二个要求是,策略当前必须持有多头仓位(strategy.position_size > 0)。增加这个检查,是为了确保我们只在确实持有多仓时才发送平多订单,避免在空仓或持空仓时错误地下达卖出指令。

步骤5:编写空头交易条件

双唐奇安通道突破策略在价格上涨并突破40周期唐奇安通道上轨时,平掉所有空头仓位。我们将其编写如下:

// 步骤5. 定义空头交易条件
exitShort = ta.crossover(close, ubFast) and
     strategy.position_size < 0

exitShort 变量的逻辑与 exitLong 类似。它判断收盘价是否上穿了40周期的最高高点(ubFast),并且策略当前正持有空头仓位(strategy.position_size < 0)。

步骤6:通过入场订单建立交易头寸

第六步,我们编写打开仓位的策略代码。以下是我们如何提交脚本的入场止损订单:

// 步骤6. 提交入场订单
if strategy.position_size == 0 and tradeAllowed
    strategy.entry("EL", strategy.long, qty=longPosSize,
         stop=ubSlow + syminfo.mintick,
         oca_type=strategy.oca.cancel, oca_name="EntryOrders")

    strategy.entry("ES", strategy.short, qty=shortPosSize,
         stop=lbSlow - syminfo.mintick,
         oca_type=strategy.oca.cancel, oca_name="EntryOrders")

由于双唐奇安通道突破策略使用止损单来入场,因此每当策略处于空仓状态时(strategy.position_size == 0),我们就同时提交一个做多止损单和一个做空止损单,形成一个括号订单。我们将使用一取消多(One-Cancels-All, OCA)订单组,以确保一旦其中一笔订单成交,TradingView便会自动取消另一笔待处理的止损订单。

为了实现这一点,上述 if 语句首先检查策略是否空仓,并且当前K线是否在允许的交易窗口内(tradeAllowed)。

当条件满足时,if 代码块内的两个 strategy.entry() 语句便会执行。第一个 strategy.entry() 函数提交一个名为”EL”的多头入场单,其止损价格(stop)设在100周期最高高点(ubSlow)之上一个最小变动单位(tick)的位置。第二个 strategy.entry() 函数则提交一个名为”ES”的空头入场单,其止损价格设在100周期最低低点(lbSlow)之下一个最小变动单位的位置。

为了避免在一笔订单成交后,另一笔订单仍然有效,我们为这两笔订单都设置了 oca_type=strategy.oca.cancel,并赋予它们相同的组名 oca_name="EntryOrders"。这样,TradingView就知道它们同属一个OCA组,一旦其中一笔订单成交,另一笔就会被自动取消。

步骤7:通过出场订单平掉市场头寸

在最后一步,我们来处理平仓逻辑。策略有两种平仓方式:一种是当多头或空头的离场信号出现时;另一种是当回测结束时,我们强制平掉所有仓位。

首先,我们提交策略的离场交易:

// 步骤7. 提交出场订单
if exitLong
    strategy.close("EL", comment="XL")

if exitShort
    strategy.close("ES", comment="XS")

第一个 if 语句检查 exitLong。之前我们已将其定义为:当策略持多仓且收盘价下穿40周期最低低点时为 true。此时,我们调用 strategy.close() 函数,平掉所有名为”EL”的入场单,从而平掉多头仓位。第二个 if 语句则以同样的方式处理空头离场。

策略的最后一行代码,负责在回测结束时平掉所有仓位:

if not tradeAllowed
    strategy.close_all()

这个 if 语句在 not tradeAllowedtrue 时,通过 strategy.close_all() 函数平掉所有持仓。

之前,我们将 tradeAllowed 变量定义为,在第一个100根K线之后、且在当前日期的5天之前的时间窗口内为 true。在该窗口之外,该变量为 false。现在,在回测的末尾,我们希望 strategy.close_all() 生效,因此我们需要一个 true 的条件。通过在 tradeAllowed 前加上 not 逻辑运算符,我们便能在 tradeAllowedfalse 时获得 true,从而触发平仓操作。

请注意,由于我们在开仓之前也检查了 tradeAllowed 的值,因此当该变量为 false 时,策略会完全停止交易。这样,脚本就只会按照我们定义的时间窗口进行交易和回测。

TradingView的双唐奇安通道突破策略的表现

让我们先从双唐奇安通道突破策略表现出色的方面,来开始我们对策略表现的审视。作为一种趋势跟踪策略,该脚本在价格经历长期趋势时表现非常出色。

下图的E-迷你标普500指数期货便是一个例子。在这里,策略在2,200点附近做多,并在价格上涨了整整400点之后,才平掉了这个盈利的多头交易:

当然,双唐奇安通道突破策略也有其固有的弱点。当价格横盘移动时,该策略会遭受损失。这种市场环境之所以会带来亏损,是因为趋势未能有效启动,价格走向错误的方向,并且趋势的持续时间不足以让交易盈利出场。

下方图表便是这种表现不佳的一个例子。在这里,由于E-迷你标普500指数期货在数月的时间里上下波动,双唐奇安通道突破策略连续遭遇了数笔亏损交易:

下表展示了双唐奇安通道突破策略的回测表现。E-mini S&P 500期货和欧洲斯托克50指数期货的结果均为正向。此外,对于一个基础的趋势跟踪策略来说,胜率也还算不错。

然而,一个突出的问题是交易次数过低。年均约2笔的交易频率,使得我们没有足够的数据来确信这个策略是否真的有效。值得注意的是,下表中的结果甚至是在未启用仓位管理(即执行了每一个信号)的情况下得到的,但这依然没能带来足够多的交易样本,因此还需要进一步的测试。

绩效指标 E-mini S&P 500 期货 (ES) 欧洲斯托克50指数期货 (FESX)
首次交易 1998-02-03 1999-01-04
最后交易 2018-11-04 2018-11-01
时间周期 日线 日线
净利润 $2,716 €38,172
总盈利 $97,840 €62,248
总亏损 -$95,124 -€24,076
最大回撤 $25,755 €9,944
盈利因子 1.029 2.585
总交易数 48 36
胜率 41.67% 52.78%
平均每笔交易 $56.58 €1,060
平均盈利 $4,892 €3,276
平均亏损 -$3,397 -€1,416
盈亏比 1.44 2.313
支付佣金 $384 €288
滑点 2 跳 2 跳

改进思路与新策略方向

尽管回测结果不算糟糕,但我们很可能可以让这个双唐奇安通道突破策略表现得更好。以下是一些你可能会觉得有价值的探索方向。

增加市场过滤器:和大多数趋势跟踪策略一样,当市场进入横盘震荡时,该策略表现不佳。如果我们能增加一个过滤器(例如ADX或ATR指标)来识别并避开这种不利的市场环境,策略的表现会好很多。我们也可以像唐奇安趋势策略那样,使用移动平均线作为趋势过滤器。

参数优化:该策略的参数设置是基于柯威尔分享的策略,它们很可能不是最优的。通过对不同参数组合的探索和优化,策略的表现可能会更好。

优化入场触发:当前的入场触发(价格超过通道一个最小跳动点)可能过于灵敏。并非每一次对唐奇安通道的轻微刺探都是有效的突破。一个改进思路是要求K线收盘价必须有效突破通道一定距离,以确认突破的有效性。

引入明确的止损:该策略没有明确的止损机制,尽管它在价格反向运动时确实会平仓。但是,在市场中设置一个明确的止损单可以防范重大损失,也能让交易者在执行时更有安全感。此外,引入时间止损(time stop)可以帮助我们剔除那些毫无进展的交易。

整合其他风险管理:更多改进策略的方法包括引入其他风险管理维度。例如,我们可以根据连续亏损日来停止交易,或者限制策略的最大持仓规模。

关于流行的唐奇安通道指标的另一种实现,可以参考唐奇安通道突破策略。其他同样交易高低点突破的TradingView策略包括唐奇安趋势策略和带时间退出的唐奇安趋势策略。

完整代码: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)

// 唐奇安通道输入项
fastLen = input.int(40, title="快线唐奇安通道周期")
slowLen = input.int(100, title="慢线唐奇安通道周期")

// 仓位管理输入项
usePosSize = input.bool(true, title="启用仓位管理?")

maxRisk = input.float(2, title="单笔最大风险 %", step=.25, 
     minval=0.25, maxval=15) * 0.01
maxExposure = input.float(10, title="最大风险敞口 %", step=1, 
     minval=1, maxval=100) * 0.01

marginPerc = input.int(10, title="保证金预估 %", minval=1, maxval=100) * 0.01

// 步骤2. 计算策略所需值
// 计算唐奇安通道的值
ubSlow = ta.highest(high, slowLen)[1]
lbSlow = ta.lowest(low, slowLen)[1]

ubFast = ta.highest(high, fastLen)[1]
lbFast = ta.lowest(low, fastLen)[1]

// 计算仓位规模
riskEquity = maxRisk * strategy.equity
riskLong   = (close - lbFast) * syminfo.pointvalue
riskShort  = (ubFast - close) * syminfo.pointvalue

maxPos = math.floor((maxExposure * strategy.equity) /
     (marginPerc * (close * syminfo.pointvalue)))

longPosSize  = usePosSize ? math.min(math.floor(riskEquity / riskLong), maxPos) : 1
shortPosSize = usePosSize ? math.min(math.floor(riskEquity / riskShort), maxPos) : 1

// 确定交易窗口
tradeAllowed = time <= timenow - (86400000 * 5) and 
     bar_index > slowLen and 
     longPosSize > 0 and 
     shortPosSize > 0

// 步骤3. 输出策略数据并可视化
// 绘制用于入场的唐奇安通道
plot(ubSlow, color=color.green, linewidth=2, title="慢线唐奇安 - 上轨")
plot(lbSlow, color=color.red, linewidth=2, title="慢线唐奇安 - 下轨")

// 绘制用于出场的唐奇安通道
plot(strategy.position_size < 0 ? ubFast : na, color=color.teal, 
     style=plot.style_linebr, linewidth=2, title="快线唐奇安 - 上轨")
plot(strategy.position_size > 0 ? lbFast : na, color=color.orange, 
     style=plot.style_linebr, linewidth=2, title="快线唐奇安 - 下轨")

// 步骤4. 定义多头平仓条件
exitLong = ta.crossunder(close, lbFast) and
     strategy.position_size > 0

// 步骤5. 定义空头平仓条件
exitShort = ta.crossover(close, ubFast) and
     strategy.position_size < 0

// 步骤6. 提交开仓订单 (使用OCA订单组)
if strategy.position_size == 0 and tradeAllowed
    strategy.entry("EL", strategy.long, qty=longPosSize,
         stop=ubSlow + syminfo.mintick,
         oca_type=strategy.oca.cancel, oca_name="EntryOrders")

    strategy.entry("ES", strategy.short, qty=shortPosSize,
         stop=lbSlow - syminfo.mintick,
         oca_type=strategy.oca.cancel, oca_name="EntryOrders")

// 步骤7. 提交平仓订单
if exitLong
    strategy.close("EL", comment="XL")
if exitShort
    strategy.close("ES", comment="XS")

if not tradeAllowed
    strategy.close_all()
赞(0)
未经允许不得转载:图道交易 » Pine Script(260):唐奇安通道突破与双通道策略
分享到

评论 抢沙发

登录

找回密码

注册