Python技术迷

Python量化交易新手必学的2个波动率指标指标:ATR和布林带

回测曲线一拉出来,收益还凑合,最大回撤直接把人看醒。

这种策略我第一眼一般不看收益率,先看波动。新手做量化最容易犯的毛病,就是拿一根均线、一个突破信号就往里冲,好像价格穿过去就该涨。市场没这么客气。很多时候不是方向错了,是你没看清这段行情到底有多“抖”。

波动率指标里,ATR 和布林带算是最该先学的两个。

ATR 看的是价格每天能晃多大。

布林带看的是价格现在离自己的常态有多远。

这俩指标都不负责告诉你“明天一定涨”,它们更像风控台上的两个小灯。灯亮了,不一定有事故;灯不亮,别硬说安全。

先看 ATR。

ATR 全名 Average True Range,平均真实波幅。它不只看最高价减最低价,还会把昨天收盘价也算进去。这个设计挺关键,因为股票、期货、币都会跳空。只看当天高低点,很多隔夜风险就被你漏掉了。

真实波幅一般取这三个值里面最大的:

high - low
abs(high - prev_close)
abs(low - prev_close)

用 Python 写出来,不用搞得很玄:

import pandas as pd

defadd_atr(bar: pd.DataFrame, window: int = 14) -> pd.DataFrame:
    df = bar.copy()

    prev_close = df["close"].shift(1)

    range_in_day = df["high"] - df["low"]
    gap_up_down = (df["high"] - prev_close).abs()
    gap_down_up = (df["low"] - prev_close).abs()

    df["tr"] = pd.concat(
        [range_in_day, gap_up_down, gap_down_up],
        axis=1
    ).max(axis=1)

    df["atr"] = df["tr"].rolling(window=window, min_periods=window).mean()
return df

这里我没用什么花哨写法。新手阶段先把逻辑写清楚,别上来就压成一行。指标这东西,错一列数据,后面回测全是假的。

ATR 怎么用?

比如你做突破策略,同样是突破 20 日高点,有的标的 ATR 很小,价格慢慢蹭上去;有的标的 ATR 很大,一天能把你止损线来回打两遍。这两种行情,不能用同一套止损距离。

现场我一般会这么处理:

defmark_atr_risk(df: pd.DataFrame) -> pd.DataFrame:
    out = df.copy()

    out["atr_pct"] = out["atr"] / out["close"]

    out["risk_level"] = "normal"
    out.loc[out["atr_pct"] > 0.05, "risk_level"] = "hot"
    out.loc[out["atr_pct"] < 0.015, "risk_level"] = "quiet"

return out

atr_pct 比单独看 ATR 更顺眼。因为 10 块钱的股票 ATR 是 0.5,和 500 块钱的股票 ATR 是 0.5,完全不是一回事。前者波动 5%,后者才 0.1%。

我不太喜欢新手一开始就拿 ATR 做买卖点。更靠谱的用法是做仓位和止损。波动大,仓位降一点;波动小,止损别放得太窄。否则策略不是输给方向,是输给噪声。

再看布林带。

布林带由三条线组成:中轨、上轨、下轨。

中轨一般是 N 日均线,上下轨是均线加减若干倍标准差。最常见的是 20 日均线,2 倍标准差。

代码也不复杂:

defadd_bollinger(bar: pd.DataFrame, window: int = 20, k: float = 2.0) -> pd.DataFrame:
    df = bar.copy()

    mid = df["close"].rolling(window=window, min_periods=window).mean()
    vol = df["close"].rolling(window=window, min_periods=window).std(ddof=0)

    df["boll_mid"] = mid
    df["boll_up"] = mid + k * vol
    df["boll_low"] = mid - k * vol
    df["boll_width"] = (df["boll_up"] - df["boll_low"]) / df["boll_mid"]

return df

布林带容易被新手用歪。

很多人看到价格碰上轨就想空,碰下轨就想多。这个思路在震荡行情里有时能跑,在趋势行情里会被打得很难看。

上涨趋势里,价格可以贴着上轨走很久。你第一次碰上轨就空,后面每一天都像在提醒你:别和趋势硬拧。

我更愿意先看带宽。

defmark_boll_state(df: pd.DataFrame) -> pd.DataFrame:
    out = df.copy()

    base = out["boll_width"].rolling(60, min_periods=40).median()

    out["boll_state"] = "normal"
    out.loc[out["boll_width"] < base * 0.7, "boll_state"] = "squeeze"
    out.loc[out["boll_width"] > base * 1.4, "boll_state"] = "spread"

return out

squeeze 是收口,说明最近波动被压住了。压得久了,后面容易出方向,但方向不知道。

spread 是开口,说明波动已经打出来了。这个阶段再去追,得小心滑点和假突破。

把 ATR 和布林带放一起看,会比单独看舒服很多。

比如:

defbuild_vol_signal(bar: pd.DataFrame) -> pd.DataFrame:
    df = add_atr(bar, window=14)
    df = add_bollinger(df, window=20, k=2)
    df = mark_atr_risk(df)
    df = mark_boll_state(df)

    df["watch"] = False

    df.loc[
        (df["boll_state"] == "squeeze") &
        (df["risk_level"] != "hot"),
"watch"
    ] = True

return df

这段代码没有生成买入卖出信号,只打了一个 watch 标记。

这个地方很重要。

新手写策略,别一上来就让指标决定交易。先让指标帮你过滤环境。比如布林带收口,但 ATR 已经很高,我一般不会觉得这是“安静等待突破”,更像是盘口已经不稳,只是收盘价暂时没拉开距离。

ATR 和布林带的区别,可以粗暴记一下:

ATR 更像尺子,量这一段行情每天晃多大。

布林带更像边界,看价格有没有跑出近期常态。

一个看波动幅度,一个看价格位置。

但它们都有同一个毛病:只描述过去。

所以别迷信指标。指标算得再漂亮,也只是把 K 线重新加工了一遍。真正要命的是,你有没有用它控制仓位,有没有用它过滤行情,有没有在回测里把手续费、滑点、停牌、涨跌停这些脏东西算进去。

很多策略不是输在数学上,是输在太干净。代码里世界很平,实盘里全是坑。

ATR 和布林带先学到这个程度就够了。能算,能解释,能用来做过滤。后面再谈入场、止损、仓位,不然顺序反了,很容易写出一个回测很好看、实盘一碰就碎的玩具。