ATR · 真实波幅
ATR(Average True Range,真实波幅均值)是量化里少数几个几乎不会有人用错定义、但经常有人用错场景的指标。
它只回答一个问题:这个品种最近一段时间,一根 K 线平均动多少? 它不告诉你方向,也不预测未来。正因为如此,它在仓位与止损这类「尺度」问题上非常好用,而在「该不该进」这类问题上几乎无用。
一、它在算什么
naive 的想法是「用最高价减最低价」当波动幅度。但这样会漏掉跳空 —— 今天的低点比昨天收盘还高,或者今天的低点远低于昨天的收盘,这两段的实际波动都没有被 High − Low 覆盖到。
所以 ATR 先定义了一个更完整的波动度量,叫 真实波幅 TR:取以下三个值的最大值。
- 前者是当根 K 线自身的振幅
- 后两者分别覆盖向上跳空与向下跳空带来的额外波动
TR 的单位是价格,不是百分比。所以 EURUSD 的 ATR 可能是 0.0050(50 点),而 XAUUSD 的 ATR 可能是 28(28 美元)。这两个数字不能直接比大小 —— 这是后面误用清单里的第 2 条。
二、TR 怎么平均成 ATR
这里有个很多人忽略的细节:ATR 用的不是简单移动平均,而是 Wilder 平滑(Welles Wilder 本人提出的递推式)。它是一种指数平滑的变体,衰减更慢。
其中 n 是周期,默认 14。第一个 ATR 值没有前值可用,所以取前 n 根 TR 的简单算术平均作为种子。
这个递推式等价于平滑系数 α = 1/n 的 EMA,但写法不同。如果你用标准 EMA 公式(α = 2/(n+1))去复现 ATR,数值会对不上 —— 这也是很多「我手写的 ATR 和平台不一样」的来源。
三、MQL5 怎么拿
平台已经内置了,绝大多数情况直接用句柄就好:
//--- 建立句柄(只建一次,放 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 递推可以这样写:
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 是一个「尺子」,不是「信号」。凡是需要把距离翻译成仓位的地方,它就很好用;凡是想知道往哪个方向走的地方,它一点忙都帮不上。