ATR · 真实波幅

ATR(Average True Range,真实波幅均值)是量化里少数几个几乎不会有人用错定义、但经常有人用错场景的指标。

它只回答一个问题:这个品种最近一段时间,一根 K 线平均动多少? 它不告诉你方向,也不预测未来。正因为如此,它在仓位与止损这类「尺度」问题上非常好用,而在「该不该进」这类问题上几乎无用。

一、它在算什么

naive 的想法是「用最高价减最低价」当波动幅度。但这样会漏掉跳空 —— 今天的低点比昨天收盘还高,或者今天的低点远低于昨天的收盘,这两段的实际波动都没有被 High − Low 覆盖到。

所以 ATR 先定义了一个更完整的波动度量,叫 真实波幅 TR:取以下三个值的最大值。

TRt = max( Ht − Lt, |Ht − Ct−1|, |Lt − Ct−1| )
  • 前者是当根 K 线自身的振幅
  • 后两者分别覆盖向上跳空与向下跳空带来的额外波动
注意单位

TR 的单位是价格,不是百分比。所以 EURUSD 的 ATR 可能是 0.0050(50 点),而 XAUUSD 的 ATR 可能是 28(28 美元)。这两个数字不能直接比大小 —— 这是后面误用清单里的第 2 条。

ATR drawn as a volatility ruler laid over price bars

二、TR 怎么平均成 ATR

这里有个很多人忽略的细节:ATR 用的不是简单移动平均,而是 Wilder 平滑(Welles Wilder 本人提出的递推式)。它是一种指数平滑的变体,衰减更慢。

ATRt = ( ATRt−1 × (n − 1) + TRt ) / n

其中 n 是周期,默认 14。第一个 ATR 值没有前值可用,所以取前 n 根 TR 的简单算术平均作为种子。

这个递推式等价于平滑系数 α = 1/n 的 EMA,但写法不同。如果你用标准 EMA 公式(α = 2/(n+1))去复现 ATR,数值会对不上 —— 这也是很多「我手写的 ATR 和平台不一样」的来源。

三、MQL5 怎么拿

平台已经内置了,绝大多数情况直接用句柄就好:

iATR.mq5
//--- 建立句柄(只建一次,放 OnInit)
hATR = iATR(_Symbol, PERIOD_CURRENT, 14);

//--- 取最近 3 根的值
double atr[3];
if(CopyBuffer(hATR, 0, 0, 3, atr) <= 0)
{
   Print("ATR 取值失败: ", GetLastError());
   return;
}
Print("当前 ATR = ", atr[0]);

但如果你想自己算一遍(比如要在非标准数据上做统计,或者想确认平台的行为),Wilder 递推可以这样写:

CalcATR.mq5
double CalcATR(const int n, const int bar)
{
   // bar: 从 0(当前)开始往前数的位置
   double tr[];
   ArrayResize(tr, n);
   for(int i = 0; i < n; i++)
   {
      double h = iHigh(_Symbol, PERIOD_CURRENT, bar + i);
      double l = iLow (_Symbol, PERIOD_CURRENT, bar + i);
      double pc= iClose(_Symbol, PERIOD_CURRENT, bar + i + 1); // 前一根收盘
      tr[i] = MathMax(h - l, MathMax(MathAbs(h - pc), MathAbs(l - pc)));
   }
   //--- 种子:前 n 根 TR 的简单平均
   double seed = 0;
   for(int i = 0; i < n; i++) seed += tr[i];
   seed /= n;

   return seed; // 严格来说还要继续往前递推才能得到「当前」值
}
实际项目别这么写

上面这段代码只是为了把公式摊开看清楚。生产环境一律用 iATR 句柄 —— 它已经处理了数据不足、品种切换、时间帧变化这些边界情况。

四、参数改变的是什么

周期 n 改变的是「对近期波动的敏感度」,下面是最常见的几档取值:

周期 n反应速度典型用途
7–10快,紧跟最近波动短周期策略的止损距离
14(默认)中等,最通用多数情况的默认值
20–30慢,忽略短期毛刺判断「当前波动率是否处于高位」

注意这里没有任何一档是「更好」的。周期小,止损跟得紧、容易被扫;周期大,止损宽、单笔风险放大但不易被噪音打掉。这是一个需要和其他参数一起做敏感性检验的取舍,不是靠感觉挑的(敏感性检验的做法在 L5)。

五、五个常见误用

1. 拿 ATR 判断方向

ATR 里全是绝对值,上涨和下跌对它完全等价。看到 ATR 放大就说「要涨了」,是把「波动加大」误读成「方向确定」。波动放大只意味着不确定性上升。

2. 跨品种直接比较 ATR 数值

前面提过:单位是价格。要比,就除掉价格量纲,比如 ATR / Close,得到「波动占价格的百分比」,这才跨品种可比。

3. 用固定倍数止损,却以为风险是固定的

「止损 = 2 × ATR」听起来风险恒定,其实不是。真正的单笔亏损是 手数 × 止损距离 × 每点价值。止损距离随 ATR 变化,手数却固定的话,每单风险就在漂。正确做法是先定风险金额,再反推手数(L4 有完整实现)。

4. 把日线 ATR 当成小时 ATR × 24

波动不是线性累积的。日内有开盘/收盘的流动性差异,隔夜还有跳空。需要哪个周期的 ATR,就用哪个周期去取。

5. 在回测里用固定点数止损

这是最隐蔽的一个。固定 30 点止损在 2020 年可能很宽,在 2026 年可能很窄 —— 于是你的回测结果其实反映的是那一段历史的波动水平,而不是策略本身的逻辑。用 ATR 归一化之后,策略才能在不同时段之间比较。

小结

ATR 是一个「尺子」,不是「信号」。凡是需要把距离翻译成仓位的地方,它就很好用;凡是想知道往哪个方向走的地方,它一点忙都帮不上。