双指数移动平均线指标(DEMA)
移动平均线是一种广受欢迎的交易指标,它能帮助交易者过滤市场噪音并确认价格趋势。但它们也存在滞后于市场的问题。双指数移动平均线(Double Exponential Moving Average, DEMA)正是为了尽可能地减少这种滞后而设计的。下面,让我们来仔细了解一下。
使用双指数移动平均线(DEMA)减少价格滞后
移动平均线是一种趋势跟踪工具,它通过发出新趋势开始或旧趋势结束的信号来发挥作用。这使得它们是市场的“追随者”而非“预测者”。它们在趋势明显的市场中表现更佳,而在价格横盘整理时则表现不佳。
由于移动平均线是对历史数值进行平均,因此它们不可避免地会滞后于最新的价格行为。所以,当价格突然飙升或新趋势启动时,移动平均线的反应总会慢半拍。多年来,人们开发了多种旨在减少价格滞后的移动平均线。
双指数移动平均线(DEMA)就是其中一种经过改良的移动平均线。该指标由帕特里克·马洛伊(Patrick Mulloy)在他1994年发表的一篇名为《用更快的移动平均线平滑数据》(Smoothing Data With Faster Moving Averages)的文章中提出。
DEMA的特别之处在于它利用了常规的指数移动平均线 (EMA)来减少滞后。这使得DEMA的响应速度比EMA更快,而EMA本身的速度就已经快于最常见的简单移动平均线 (SMA)。让我们来看看如何计算DEMA。
计算双指数移动平均线(DEMA)
我们借助常规的指数移动平均线来计算双指数移动平均线(DEMA),其公式如下:
这里,N是计算DEMA所用的周期(即其长度)。
用通俗的话来说,我们首先将一条常规的指数移动平均线(EMA)乘以2,然后再计算出另一条平滑均线(即对EMA再进行一次EMA计算)。最后,将这两个值相减,就得到了DEMA。
如何使用双指数移动平均线进行交易
我们交易双指数移动平均线(DEMA)的方法与其他移动平均线类似,这为我们提供了多种使用方式。
我们可以在图表上绘制一条DEMA来确认价格趋势。当K线位于均线上方时,市场处于上升趋势;而当价格位于均线下方时,则为下降趋势。
另一种方法是交易价格穿越。即,当价格向上穿越均线时,产生看涨信号;当价格向下跌破均线时,则是看跌的信号。
第三种方法是在图表上绘制多条DEMA,然后观察均线之间的交叉。当短周期均线向上穿越长周期均线时,产生买入信号;反之,当快线向下跌破慢线时,则为卖出信号。
第四种用法是将移动平均线视为动态的支撑和阻力区域。也就是说,在上涨市场中,价格回调通常会在移动平均线区域找到支撑;而在下跌市场中,短期反弹则会在均线附近遇到阻力。
注意
尽管DEMA减少了滞后性,但价格滞后并不一定是一件坏事。当一个指标对多根K线的数据进行平均时,它也过滤掉了市场的噪音。而且,当我们必须等待几根K线才能看到均线改变方向时,我们也减少了假信号的数量。
一个滞后性很小的移动平均线会过于敏感,反而会对微小的价格变化做出反应。使用任何移动平均线的诀窍在于,找到一个既足够敏感能及早产生信号,又足够迟钝能避免大部分市场随机噪音的平衡点。
将DEMA指标编写为TradingView Pine脚本
在开始编写DEMA指标之前,让我们先规划一下脚本需要具备哪些功能:
- 由于TradingView限制了图表上可以加载的指标数量,我们的指标最好能同时绘制多条DEMA线。但如果它绘制了比我们实际需要更多的线条,也会很烦人。因此,我们将使用输入选项来设置线条的周期,或者在不需要时禁用它。
- 我们将通过彩色背景来高亮显示移动平均线的信号。如何确定这些信号取决于图表上有多少条DEMA线。(我们也会创建一个输入选项,在需要时禁用这些信号。)
- 我们还会根据这些信号生成警报。这样,即使我们没有盯着市场,也能收到通知。
用于编写DEMA指标的模板
为了将上述功能转化为TradingView代码,最简单的方法是使用一个模板。这能将一个大任务(编写一个完整的指标)分解成若干个更小的部分,同时也提供了一些结构,让我们不必从一个空白的代码编辑器开始。
这是我们将用于DEMA指标的模板:
//@version=5
// 步骤一:脚本设置
// 步骤二:计算指标数值
// 步骤三:确定指标信号
// 步骤四:输出指标数据
// 步骤五:创建指标警报
如果你想跟着本文一起操作,请在TradingView的Pine编辑器中创建一个新的指标脚本,并粘贴以上模板。(如果你只想要完整的代码,可以直接滚动到本文末尾。)
为了让你对我们即将编写的代码有个直观的印象,下图是最终完成的双指数移动平均线在图表上的样子:
现在,让我们开始动手编写代码吧!
步骤一:设置指标参数与输入选项
我们首先来定义指标的各项设置。为此,我们调用 indicator() 函数:
//@version=5
// 步骤一:脚本设置
indicator(title="Double Exponential Moving Average", shorttitle="DEMA",
overlay=true)
我们使用 title 参数来为脚本命名。shorttitle 参数为指标提供了一个缩写名称(会显示在图表上)。通过 overlay 参数,我们让指标能够直接叠加显示在主图表的交易品种上。
接着,我们创建几个输入选项,以便能够方便地配置指标的参数。第一个输入用于指定脚本所用的价格数据:
// 输入选项
srcData = input.source(close, title="Source Data")
我们创建了一个数据源输入选项(input.source())。该输入会创建一个下拉菜单,我们可以从中选择价格数据(如收盘价、最高价或最低价)。我们将其默认值设为收盘价(close)。稍后,我们将基于这类数据来计算DEMA线。
然后,我们创建用于设置移动平均线周期的输入项:
// 周期设置
len1 = input.int(21, title="Length DEMA #1", minval=1)
len2 = input.int(0, title="Length DEMA #2", minval=0)
len3 = input.int(0, title="Length DEMA #3", minval=0)
我们在这里使用 input.int() 函数创建了三个整数输入选项。第一个,名为“Length DEMA #1”,默认值为21,最小值为1 (minval=1)。这个输入用于设置图表上第一条双指数移动平均线的周期。
另外两个 input.int() 调用创建了两个整数输入,默认值都为0。我们稍后编写的代码将只在这些均线的周期大于0时才进行绘制。(因此,以其默认值0来看,该指标默认只绘制一条DEMA线。)
接下来,我们为指标的信号显示创建一个输入项:
// 外观选项
showSignals = input.bool(true, title="Show Signals?")
这里我们让 input.bool() 创建一个布尔型(真/假)输入选项。当此设置被启用时——默认即为启用(true)——我们的指标会高亮显示移动平均线的信号。如果关闭此选项,图表上则不显示任何信号。
步骤二:计算指标数值
接下来,我们计算指标的数值。由于我们的脚本最多可以绘制3条DEMA线,因此使用一个自定义函数来计算移动平均线会更方便,这能避免我们编写重复的代码。
该函数如下所示:
// 步骤二:计算指标数值
// 用于计算DEMA的函数
DEMA(series, length) =>
if length > 0
emaValue = ta.ema(series, length)
2 * emaValue - ta.ema(emaValue, length)
else
na
这个 DEMA() 自定义函数有两个参数:series 是用于计算的价格数据,length 则是均线的周期(K线数量)。
在函数内部,我们有一个 if/else 语句。if 部分会检查 length 是否大于0。如果是,我们首先使用TradingView的 ta.ema() 函数计算一条指数移动平均线。该函数会基于 DEMA() 函数传入的 series 和 length 参数进行计算。
然后,我们计算双指数移动平均线的值:将刚才计算出的EMA乘以2,再减去对该EMA再次进行EMA计算的结果。这个最终值就是DEMA,也是该函数返回的值。
函数的 else 部分则返回 na 值。当传入的 length 参数为0或更小时,就会执行这一部分。
返回 na 看起来可能有点奇怪,但原因如下:我们之前创建的输入选项,在被设为0时,会禁用对应的DEMA线。如果我们用0作为周期来调用 DEMA() 函数,那么它就不需要进行任何计算。
在这些情况下,返回0并不是一个理想的选择,因为如果我们绘制0值,图表会变得一团糟。因此,我们的做法是返回 na。当我们在绘图时使用 na 值,图表上就不会显示任何东西,这实际上关闭了绘图。这正是我们希望在某个DEMA线周期被设为0时所发生的效果。
创建好函数后,我们就可以用它来计算三条DEMA的值了:
// 计算DEMA值
demaVal1 = DEMA(srcData, len1)
demaVal2 = DEMA(srcData, len2)
demaVal3 = DEMA(srcData, len3)
这里,DEMA() 函数被调用三次来计算三条双指数移动平均线。每次调用都使用了 srcData 输入变量(默认设为收盘价)。
对于均线的周期,我们分别使用了 len1、len2 和 len3 这三个输入变量。第一个的默认值为21,其余的则从0开始。我们将 DEMA() 返回的值分别存储在 demaVal1、demaVal2 和 demaVal3 变量中。
步骤三:确定指标的买卖交易信号
在计算出DEMA值之后,我们就可以在下一步输出它们之前,先判断是否发生了交易信号。
由于我们的脚本可以在图表上绘制多条DEMA线,因此产生买卖信号的方式也有多种。例如,如果图表上只有一条DEMA线,我们会关注价格何时穿越该均线;但如果有两条DEMA线,我们则会监控它们之间的交叉。
下面是该指标寻找看涨信号的逻辑:
// 步骤三:确定指标信号
// 判断是否有DEMA均线金叉
demaCrossover = if len2 > 0 and len3 > 0
ta.crossover(demaVal1, demaVal2) and demaVal3 > demaVal3[1]
else if len2 > 0 and len3 == 0
ta.crossover(demaVal1, demaVal2)
else if len3 > 0 and len2 == 0
ta.crossover(demaVal1, demaVal3)
else
ta.crossover(close, demaVal1)
我们在这里创建的 demaCrossover 变量,在当前K线上发生买入信号时其值为 true,否则为 false。我们通过一个级联 if 语句(即一系列 if 语句)来评估多种情况,从而得到这个值。这看起来可能很复杂,但让我们一步步来分析。
第一个 if 分支检查 len2 和 len3 这两个输入变量是否都大于0。如果是,我们就假定三条DEMA线都被启用并显示在图表上。此时,买入信号需要满足两个条件:
- 第一条DEMA线(
demaVal1) 上穿 (ta.crossover())了第二条线(demaVal2)。 - 并且,第三条DEMA线(
demaVal3)相比其前一根K线是上涨的(demaVal3[1])。
如果 len2 和 len3 不都大于0,那么代码会执行 else 部分。 这部分包含另一个 if 语句,它检查 len2 是否大于0而 len3 等于0。这种情况对应于第二条DEMA线被启用,而第三条没有。此时,买入信号的条件是:
- 第一条DEMA线(
demaVal1) 上穿 (ta.crossover())了第二条线(demaVal2)。
再往下的 else 部分包含了另一个 if/else 语句。这里我们判断 len3 是否大于0而 len2 等于0。这种情况对应于第三条DEMA线被启用,而第二条没有。因此,我们的买入信号变为:
- 第一条DEMA线(
demaVal1) 上穿 (ta.crossover())了第三条线(demaVal3)。
最后,当以上所有条件都不为 true 时,最后的 else 分支会执行。在这种情况下(即只有一条DEMA线被启用),我们只寻找:
- K线的收盘价(
close) 上穿 (ta.crossover())了第一条DEMA线(demaVal1)。
同样,我们也让指标寻找看跌信号:
// 检查是否有DEMA均线死叉
demaCrossunder = if len2 > 0 and len3 > 0
ta.crossunder(demaVal1, demaVal2) and demaVal3 < demaVal3[1]
else if len2 > 0 and len3 == 0
ta.crossunder(demaVal1, demaVal2)
else if len3 > 0 and len2 == 0
ta.crossunder(demaVal1, demaVal3)
else
ta.crossunder(close, demaVal1)
这段代码的逻辑与我们刚刚讨论的非常相似。我们在这里创建的 demaCrossunder 变量在发生看跌信号时值为 true,否则为 false。
它通过TradingView的 ta.crossunder() 函数来获取这些值,该函数在这里判断一条DEMA线(或收盘价)是否下穿了另一条DEMA线。
步骤四:绘制或输出指标的数据与信号
在第四步中,我们将在图表上显示双指数移动平均线的数据。首先,我们绘制移动平均线本身:
// 步骤四:输出指标数据
// 在图表上绘制DEMA
plot(demaVal1, color=color.orange, linewidth=2, title="DEMA #1")
plot(demaVal2, color=color.blue, linewidth=2, title="DEMA #2")
plot(demaVal3, color=color.fuchsia, linewidth=2, title="DEMA #3")
为了显示DEMA,我们调用了TradingView的 plot() 函数。第一个调用以橙色(color.orange)绘制 demaVal1 变量。通过将 linewidth 参数设为2,这条常规线图会比默认的更粗一些。title 参数则为该绘图命名,以帮助我们区分不同的线条。
另外两个 plot() 语句则负责在图表上显示 demaVal2 和 demaVal3 变量。
在脚本的前面,我们的 DEMA() 自定义函数在周期为零时会返回 na 值。现在,plot() 函数的特性是它不会在图表上显示 na 值。因此,当某条DEMA线的周期被设为0时,上述代码就不会在图表上绘制它。
接下来,每当有买卖信号发生时,我们高亮显示图表的背景:
// 高亮DEMA信号
bgColour = demaCrossover ? color.new(color.green, 80) :
demaCrossunder ? color.new(color.red, 80) :
na
bgcolor(showSignals ? bgColour : na)
我们在这里创建的 bgColour 变量通过TradingView的条件运算符(?:)来获取三个可能的值之一。当有买入信号时(demaCrossover),该变量被设为绿色(color.green)。我们使用 color.new() 函数为这个绿色设置80%的透明度。
对于卖出信号(demaCrossunder),我们使用红色(color.red),同样设置80%的透明度。在没有信号的情况下,我们使用 na 来禁用背景色。
为了将颜色应用到图表上,我们使用了 bgcolor() 函数。但在直接应用之前,我们还用了另一个条件运算符。这次我们评估的是 showSignals 输入变量,它存储了“Show Signals?”输入选项的值。
只有当该选项被启用时,showSignals 才返回 true,此时条件运算符会返回 bgColour 变量,其值随即被 bgcolor() 函数应用到背景上。如果该输入选项被关闭,条件运算符则返回 na 来禁用背景色。通过这种方式,买卖信号只有在用户允许的情况下才会显示。
步骤五:为TradingView指标添加警报条件
在最后一步中,我们来创建一些警报。为此,我们调用 alertcondition() 函数:
// 步骤五:创建指标警报
alertcondition(condition=demaCrossover,
title="Crossover",
message="DEMA generated a buy (crossover) signal")
alertcondition(condition=demaCrossunder,
title="Crossunder",
message="DEMA generated a sell (crossunder) signal")
第一个 alertcondition() 函数调用的 condition 参数被设为 demaCrossover 变量。当收盘价或一条DEMA线上穿另一条(更慢的)DEMA线时,该变量的值为 true。届时触发的警报被命名为Crossover(金叉)。并通过 alertcondition() 的 message 参数,我们定义了警报触发时显示的默认文本(“DEMA产生了买入(金叉)信号”)。
另一个 alertcondition() 调用则基于 demaCrossunder 变量来触发。该变量在收盘价或一条DEMA线下穿另一条(更慢的)DEMA线时发出信号。我们将这个警报命名为Crossunder(死叉),并同样通过 message 参数设置了默认的警报文本。
尽管我们的脚本执行了 alertcondition() 函数,但这些警报并不会自动触发。在我们将指标添加到图表后,还需要手动启用并配置警报。没有这个额外的步骤,警报是无法触发的。
在图表上绘制DEMA指标
现在我们已经完成了指标的编码,让我们看看它在TradingView图表上的样子。当我们只绘制一条双指数移动平均线时,可以看到它紧密地跟随着价格,并能迅速产生信号:
这张欧元/美元图表上出现了一些假信号,但DEMA很好地捕捉到了这波上涨行情。
下图是带有21周期和55周期DEMA的图表示例:
在这张比特币(BTC/USD)的多日图表上,DEMA只产生了两个信号。但这两个买入和卖出信号都恰到好处,完美地捕捉了这轮加密货币的上升趋势。
当出现一个清晰的下跌趋势时,两条DEMA线也能成功地发出下降趋势信号,就像它们在这张英镑/美元图表上所做的那样:
与DEMA类似,另一个试图减少价格滞后的移动平均线是三重指数移动平均线(TEMA) 指标。
完整代码:适用于TradingView的双指数移动平均线
双指数移动平均线指标的完整代码如下。更多细节和信息请参考上文的讨论。
//@version=5
// 步骤一:脚本设置
indicator(title="Double Exponential Moving Average", shorttitle="DEMA",
overlay=true)
// 输入选项
srcData = input.source(close, title="Source Data")
// 周期设置
len1 = input.int(21, title="Length DEMA #1", minval=1)
len2 = input.int(0, title="Length DEMA #2", minval=0)
len3 = input.int(0, title="Length DEMA #3", minval=0)
// 外观选项
showSignals = input.bool(true, title="Show Signals?")
// 步骤二:计算指标数值
// 用于计算DEMA的函数
DEMA(series, length) =>
if length > 0
emaValue = ta.ema(series, length)
2 * emaValue - ta.ema(emaValue, length)
else
na
// 计算DEMA值
demaVal1 = DEMA(srcData, len1)
demaVal2 = DEMA(srcData, len2)
demaVal3 = DEMA(srcData, len3)
// 步骤三:确定指标信号
// 判断是否有DEMA均线金叉
demaCrossover = if len2 > 0 and len3 > 0
ta.crossover(demaVal1, demaVal2) and demaVal3 > demaVal3[1]
else if len2 > 0 and len3 == 0
ta.crossover(demaVal1, demaVal2)
else if len3 > 0 and len2 == 0
ta.crossover(demaVal1, demaVal3)
else
ta.crossover(close, demaVal1)
// 检查是否有DEMA均线死叉
demaCrossunder = if len2 > 0 and len3 > 0
ta.crossunder(demaVal1, demaVal2) and demaVal3 < demaVal3[1]
else if len2 > 0 and len3 == 0
ta.crossunder(demaVal1, demaVal2)
else if len3 > 0 and len2 == 0
ta.crossunder(demaVal1, demaVal3)
else
ta.crossunder(close, demaVal1)
// 步骤四:输出指标数据
// 在图表上绘制DEMA
plot(demaVal1, color=color.orange, linewidth=2, title="DEMA #1")
plot(demaVal2, color=color.blue, linewidth=2, title="DEMA #2")
plot(demaVal3, color=color.fuchsia, linewidth=2, title="DEMA #3")
// 高亮DEMA信号
bgColour = demaCrossover ? color.new(color.green, 80) :
demaCrossunder ? color.new(color.red, 80) :
na
bgcolor(showSignals ? bgColour : na)
// 步骤五:创建指标警报
alertcondition(condition=demaCrossover,
title="Crossover",
message="DEMA generated a buy (crossover) signal")
alertcondition(condition=demaCrossunder,
title="Crossunder",
message="DEMA generated a sell (crossunder) signal")







