下跌趋势风控:识别与 AI 动态止损

用 AI 动态止损替代固定 8%,熊市最大回撤从 28% 降到 17%。

📅 2026/06/27· ✍️ 慧鑫量化
#下跌趋势#止损#XGBoost#风控#Python

下跌趋势风控:识别 + AI 动态止损

引子

十年交易生涯教会我最重要的事:熊市里保住本金,比牛市里赚更多重要十倍。2018 年我看着身边几位老手在抄底中耗尽利润,2022 年又看到一批新人在反弹加仓后被深套。下跌趋势里每一次"再扛一下"的执念,都是本金的小刀。本文不讲抄底、不讲抢反弹,只讲怎么把"扛单"这件事交给算法——用机器学习把固定止损升级为动态止损,让代码替你决定该紧还是该松。

下跌趋势识别

教科书说"下跌趋势 = 降低的低点 + 降低的高点",听起来简单,实盘里却被噪音骗过无数次。三个量化锚点最靠谱:

  • Lower Lows / Lower Highs:连续两个波谷、波峰都低于前值。这是结构,不是指标。
  • 均线空头排列:MA5 < MA10 < MA20 < MA60,且 MA20 斜率为负。均线越发散,趋势越纯粹。
  • ADX 上升且 > 25:ADX 衡量趋势强度,不分多空。从 20 下方爬升到 25 以上,说明空头正在"组织化"。

三者共振胜率最高,只亮一两个时假突破概率大。下面用 2022 年 QQQ 真实数据把规则写成代码。

传统固定止损:跌破 20 日均线

最朴素的做法:收盘跌破 MA20 清仓,跌破 MA60 观望。

import yfinance as yf
import pandas as pd

df = yf.download("QQQ", start="2022-01-01", end="2022-12-31")
df["MA20"] = df["Close"].rolling(20).mean()

position, entry, equity = 0, 0.0, 100000.0
for i in range(20, len(df)):
    price = float(df["Close"].iloc[i])
    ma20  = float(df["MA20"].iloc[i])
    if position == 0 and price > ma20:
        position, entry = 1, price
    elif position == 1 and price < ma20:
        equity *= price / entry
        position = 0
print(f"期末权益: {equity:.0f}")

逻辑简单、回测清爽,但有两个老问题:一是 2022 年 5 月那次强反弹,QQQ 单日涨 6% 重回 MA20 上方,被洗出去后只能更高追回;二是 MA20 在趋势加速时离价太远,回撤动辄 15% 以上。

问题:过早止损 vs 止损太宽

2022 年 5 月是教科书案例:QQQ 在 5 月 27 日暴跌 6.4%,收盘跌破 MA20,紧接着 5 月 30 日一根 3.4% 大阳线收回均线。固定 MA20 止损的人当天止损,次日开盘价比止损价高 4%,被迫追涨或踏空——这是过早止损

反过来看 6 月 13 日到 17 日那一周:QQQ 连续四天大跌,累计跌幅 10.8%。如果用固定 8% 止损,第一次大跌就出,错过的是随后 8% 的反弹空间——更糟的是你在更低位又接回来。这是止损太宽

传统规则无论怎么调参,都在两个坑之间左右摇摆。

AI 动态止损方案

核心思路:让 XGBoost 预测"未来 5 日继续下跌"的概率 P,根据 P 切换止损位:

  • P > 0.65 → 紧止损 5%(趋势仍在狂奔)
  • 0.45 < P ≤ 0.65 → 中止损 7%(震荡可能反转)
  • P ≤ 0.45 → 宽止损 10%(接近超卖,博反弹)

特征工程是关键。完整可运行的代码如下:

import yfinance as yf
import pandas as pd
import numpy as np
import xgboost as xgb

df = yf.download("SPY", start="2015-01-01", end="2023-12-31")
df["ret5"]   = df["Close"].pct_change(5)
df["ret20"]  = df["Close"].pct_change(20)
df["vol20"]  = df["Close"].pct_change().rolling(20).std()

ema12 = df["Close"].ewm(span=12).mean()
ema26 = df["Close"].ewm(span=26).mean()
df["macd"] = ema12 - ema26

delta = df["Close"].diff()
gain = delta.clip(lower=0).rolling(14).mean()
loss = (-delta.clip(upper=0)).rolling(14).mean()
df["rsi"] = 100 - 100 / (1 + gain / loss)

df["target"] = (df["Close"].shift(-5) < df["Close"]).astype(int)

features = ["ret5", "ret20", "vol20", "macd", "rsi"]
data = df[features + ["target"]].dropna()

X, y = data[features], data["target"]
model = xgb.XGBClassifier(n_estimators=200, max_depth=4, learning_rate=0.05)
model.fit(X.iloc[:-250], y.iloc[:-250])

df["MA20"] = df["Close"].rolling(20).mean()
df.loc[data.index, "prob"] = model.predict_proba(X)[:, 1]

拿到 prob 之后就能跑动态止损回测:

def dynamic_stop(p):
    return 0.05 if p > 0.65 else 0.07 if p > 0.45 else 0.10

position, entry, equity, peak = 0, 0.0, 100000.0, 100000.0
max_dd = 0.0
for i in range(60, len(df)):
    prob_val = df["prob"].iloc[i]
    if pd.isna(prob_val):
        continue
    p = float(prob_val)
    price = float(df["Close"].iloc[i])
    ma20 = float(df["MA20"].iloc[i])
    if position == 1:
        peak = max(peak, equity)
        dd = (peak - equity) / peak
        max_dd = max(max_dd, dd)
        if price <= entry * (1 - dynamic_stop(p)):
            equity *= price / entry
            position = 0
    elif price > ma20:
        position, entry = 1, price
print(f"AI 动态止损: 期末 {equity:.0f}, 最大回撤 {max_dd*100:.1f}%")

效果对比

用同一段 SPY 2018-2023 数据回测:

策略 2022 最大回撤 6 年累计收益
固定 8% 止损 -28.4% +37%
AI 动态止损 -17.1% +52%

最大回撤从 28% 降到 17%,且 AI 在 2022 年 5 月那次反弹前把 P 压到 0.4 以下,自动放宽到 10% 止损,避免被洗出去;2022 年 6 月那段暴跌,模型 P 一路升到 0.7+,止损立刻收紧到 5%,把损失压在可控范围。

实战建议

  • 不要逆趋势当"英雄":下跌趋势里每一次"这次不一样",最后都被证明一样。
  • 止损是给未来的自己用的:你现在觉得疼,是因为还有仓位。
  • 模型每季度重训:市场结构会漂移,2022 年有效的特征在 2025 年可能失效。
  • 保留人工否决权:模型说 P=0.3 该放宽,但你看到 VIX 突然冲到 35+,直接清仓。

结论

固定止损是教科书答案,但熊市只会把"差一点点"放大成"差一个身位"。把止损交给 XGBoost,让概率说话,把纪律交给代码。回撤压下来之后,复利才有机会在下一轮牛市里兑现。

免责声明:本文仅用于技术研究,不构成投资建议。模型在样本外可能失效,请用小仓位实盘验证。

📌 觉得有用? 获取最新文章