ATR 吊灯退出:用真实波幅把止损变成随波动率呼吸的活防线
固定百分比止损最大的毛病是不管市场喘不喘气都用同一把尺子——低波动期被噪声洗出、高波动期又止损太宽。ATR 吊灯退出(Chandelier Exit)用真实波幅(ATR)把止损线钉在近期最高收盘下方 k 倍 ATR,并只上移不下移,让防线随波动率呼吸。我们用 8 年合成日频数据对比:吊灯退出 Sharpe 2.32、最大回撤 −6.7%,碾压固定 10% 止损(1.93 / −11.7%)与买入持有(0.39 / −43.1%),附完整 Python 与六类真实陷阱(高阶)。
止损是交易里最反人性的动作,却也是最容易被做错的动作。最常见的懒人止损是”亏 10% 就走”——固定百分比。它的问题在于:用同一把尺子量所有市场状态。低波动的横盘期,10% 止损常常被正常噪声扫掉,你刚走价格就回来;高波动的急跌期,10% 又太宽,等它触发时账户已经血肉模糊。
结论先放这:ATR 吊灯退出(Chandelier Exit, 由 Chuck LeBeau 提出)把止损线钉在”近期最高收盘下方 k 倍 ATR”,并且只上移、绝不下移——这让防线随波动率”呼吸”:波动大时止损自动拉开、波动小时自动收紧。在 8 年、约 2000 个交易日的合成日频序列(温和正漂移 + 趋势/震荡/跳变 regime)上,用 MA20 趋势入场、三种出场对比,吊灯退出(k=3)做到年化 35.6%、Sharpe 2.32、最大回撤仅 −6.7%,同时只交易 68 次;固定 10% 追踪止损年化 28.9%、Sharpe 1.93、回撤 −11.7%、交易 259 次;买入持有最差:Sharpe 0.39、回撤 −43.1%。吊灯退出用更少的交易、更小的回撤,把风险预算省了下来,而非凭空造出 alpha。附完整 Python 与六类真实陷阱(高阶)。

一、先算 ATR:真实波幅,不是”今天涨跌了多少”#
吊灯退出的地基是 ATR(Average True Range,平均真实波幅)。它衡量的是”一个交易日里价格真实能跑多远”,比单纯看收盘价涨跌更贴近交易者的真实痛感。
真实波幅 TR 取三个量的最大值:
- 当日最高 − 当日最低
- |当日最高 − 昨日收盘|
- |当日最低 − 昨日收盘|
import numpy as np, pandas as pd
def compute_atr(high, low, close, period=14, idx=None):
high = np.asarray(high, float); low = np.asarray(low, float); close = np.asarray(close, float)
prev_close = np.roll(close, 1); prev_close[0] = close[0]
tr = np.maximum.reduce([high - low,
np.abs(high - prev_close),
np.abs(low - prev_close)])
atr = np.zeros_like(tr)
atr[period-1] = tr[:period].mean() # 初始:简单平均
for i in range(period, len(tr)):
atr[i] = (atr[i-1]*(period-1) + tr[i]) / period # Wilder 平滑
return pd.Series(atr, index=idx)python关键点:Wilder 平滑(α=1/14)比简单移动平均更看重近期,所以 ATR 对波动率突变反应快,却又不会被单根大K线永久带偏。ATR 没有方向——它只回答”现在市场有多吵”,不预测涨跌。
二、吊灯退出:把止损挂在”天花板下方”#
“吊灯”(Chandelier)这个名字很形象——止损线像一盏吊灯,挂在近期价格天花板的下方,且只升不降。多头版本:
k = 3.0
lookback = 22
highest_close = price.rolling(lookback).max() # 近期最高收盘 = 天花板
chandelier_long = highest_close - k * atr # 只上移,不下移python
为什么”只上移不下移”是关键?因为止损线的唯一职责是保护已有利润、截断亏损。一旦价格回落,我们绝不能再把止损线下移去”等它回来”——那等于把已经到手的利润又塞回市场嘴里。所以吊灯线在上涨中跟随最高收盘逐日抬高,把浮盈锁成”越来越高的地板”;只有价格真的跌破这盏灯,才认输出局。
空头版本是镜像:最低收盘 + k*ATR,只下移不上移。
三、回测:吊灯 vs 固定止损 vs 买入持有#
入场我们用最简单的趋势信号——收盘价站上 20 日均线;出场分别用吊灯线、固定 10% 追踪止损(运行最高收盘下方 10%)、以及不止损的买入持有。注意两种追踪止损都”只上移不下移”,区别只在”尺子”:
# 固定 10% 追踪止损:运行最高收盘下方 10%
run_max = price.cummax()
fixed_stop = run_max * (1 - 0.10)
def backtest_stop(price, stop_line, ma):
pos = np.zeros(len(price))
for i in range(1, len(price)):
if pos[i-1] == 0 and price.iloc[i] > ma.iloc[i-1]:
pos[i] = 1 # 上穿 MA 买入
elif pos[i-1] == 1 and price.iloc[i] <= stop_line.iloc[i-1]:
pos[i] = 0 # 跌破追踪止损离场
else:
pos[i] = pos[i-1]
equity = (1 + pos * price.pct_change().fillna(0)).cumprod()
trades = int(((pos[1:] - pos[:-1]) == 1).sum())
return equity, trades, pospython
对比(对数轴净值):
| 策略 | 年化收益 | 年化波动 | Sharpe | 最大回撤 | 交易次数 |
|---|---|---|---|---|---|
| 买入持有 | 7.56% | 19.35% | 0.39 | −43.1% | 0 |
| 固定10%止损 | 28.90% | 14.99% | 1.93 | −11.7% | 259 |
| 吊灯退出(k=3) | 35.59% | 15.32% | 2.32 | −6.7% | 68 |
三个结论:
- 吊灯退出回撤最小(−6.7%):因为它的尺子跟着 ATR 走,剧烈下跌时止损线已经提前拉宽,不会被一根恐慌K线扫在最低点;而固定 10% 在同样的急跌里照样被扫。
- 交易次数少得多(68 vs 259):固定 10% 尺子太”敏感”,震荡市里反复触发、反复再入场,摩擦成本与磨损远高于吊灯。
- 不是凭空造 alpha,是省下风险预算:吊灯的高 Sharpe 主要来自”少亏、少交易”,而不是”多赚”。把波动率当尺子,等于把”市场现在有多吵”写进了风控系统。
四、k 与 lookback 怎么选:参数敏感度#
吊灯退出有两个旋钮:
- k(倍数):k 小→止损紧→吃小波动、被洗频繁;k 大→止损宽→抗噪但回撤深。3 是经典默认值,A 股/加密货币这类高波动品种常调到 4−5,低波动品种可降到 2。
- lookback(最高收盘回看窗口):窗口短→灯挂得低、对回落敏感;窗口长→灯挂得高、给趋势更多呼吸空间。22(约一个月交易日)是常见起点。
实操建议:用你目标品种的 ATR 分布做参数扫描,挑”回撤可接受、交易次数不过密”的组合,而不是挑”历史净值最漂亮”的那个——后者几乎必然是过拟合(见陷阱 █)。
五、六类真实陷阱(高阶)#
- 过拟合 k 与 lookback:在单一历史段上穷举 k∈{2,2.5,3,3.5,4}×lookback∈{10,14,22,30},挑最优,等于在噪声里刻舟。必须做样本外或 walk-forward:用前 5 年定参、后 3 年验证,参数若大幅漂移就是过拟合。
- 只看收盘价触发、忽略日内:吊灯线用收盘价判断,但极端跳空低开时,你”看到”的止损价是收盘后的,实际开盘可能已远低于止损线——缺口风险会让真实亏损远超 ATR 预算。加密/期货夜盘、财报日尤甚。
- 高波动期 ATR 滞后:Wilder ATR 有平滑惯性,波动率骤升的瞬间 ATR 还没跟上,止损线暂时偏窄,可能在该收紧时没收紧。对策:对跳变敏感的品种叠加事件过滤(财报/议息日前降仓)。
- 震荡市反复磨损:纯趋势+追踪止损在均值回归行情里会”买在高、卖在低”来回挨巴掌。吊灯不是万能出场,必须配合”大周期趋势过滤”(如只在 200 日均线上方做多)才能少挨打。
- 前视偏差(look-ahead):用全样本算
rolling(max)和cummax没问题(它们都是历史函数),但如果你用”未来 N 天的最高收盘”定灯的位置,就污染了。务必只用截至 t 的信息。 - 幸存者与流动性假设:回测默认你能以收盘价成交、且流动性充足。小盘股/低流动性标的在急跌时根本卖不出止损价,实际滑点能把 −6.7% 变成 −15%。合成数据没有建模滑点,真实落地必须加冲击成本。
六、怎么用,而不是怎么迷信#
吊灯退出的正确定位是出场/风控模块,不是完整策略。它回答的是”我该在哪认输/锁利”,不回答”我该不该进场”。落地三步:
- 入场归入场:用你已有的信号(突破、均线、动量)决定买,吊灯只管卖。
- 波动率归一化仓位:既然 ATR 衡量噪音,仓位也该用 ATR 归一——
仓位 ≈ 风险预算 / (k*ATR),让每笔交易承受等量的”噪音单位”风险,而非等量的金额风险。 - 多周期叠加:日线定灯、小时线执行,可让止损更早被日内跌破触发,减少隔夜跳空暴露。
最后必须诚实说明:本文价格序列为固定随机种子生成的合成数据(实时行情接口 Yahoo 当前 429、Stooq 触发 JS 反爬均不可达),含温和正漂移与可控 regime 切换,用于演示 ATR 与吊灯退出的机制与对比——其中的收益/回撤数字不能当作某真实品种的历史表现。把代码里的合成段替换成你的 yfinance / akshare / 交易所行情,即可在真实数据上复跑并做样本外验证。
# 完整可复现骨架(合成价格段可替换为真实行情)
import numpy as np, pandas as pd
np.random.seed(20260716)
n = 252 * 8
price = pd.Series(100 * np.exp(np.cumsum(0.0003 + np.random.normal(0, 0.012, n))))
atr = compute_atr(price*1.006, price*0.994, price, 14, idx=price.index) # 合成高低
atr = atr.bfill()
highest = price.rolling(22).max()
chandelier = (highest - 3 * atr).bfill()
print("当前吊灯止损线 vs 价格:", round(chandelier.iloc[-1], 2), round(price.iloc[-1], 2))
print("ATR/价格占比(%):", round(atr.iloc[-1]/price.iloc[-1]*100, 2))python止损不是认输,是给下一次机会留子弹。ATR 吊灯退出的精髓,就是让这道防线懂市场的脾气——安静时贴近、狂暴时退后,而不是一根筋地死守一个百分比。