Skip to content

这个从0到100的指标,量的不是涨跌

作 者:老余捞鱼

原创不易,转载请标明出处及原作者。

文中内容仅用于技术讨论,不构成任何操作建议。
模型评测不等于投资建议。
不荐股、不卖课、不承诺收益。
市场有风险,请合法合规投资。

本文约 3100 字 | 预计阅读 10 分钟,后有完整代码,建议先收藏


同样 20 个交易日,同样涨 10%,你更愿意拿哪一只?

第一只,从 10 块一路走到 11 块,中途没怎么回头。

第二只,也从 10 块出发,冲到 12,回落到 9.5,再拉到 11.8,跌回 10.2,最后收在 11 块。

涨跌幅一模一样,可你拿着的感觉可能完全两回事。第一只能闭眼拿,第二只被来回抽三次,止损挂哪儿都不舒服。

涨跌幅一样,算出来的年化也一样,凭什么说它们不是一回事。我后来才想明白,涨跌幅只认起点和终点,中间怎么走的,它不管。

我今天要说的数,专管中间那一段。

这个数到底在量什么

指标叫 Choppiness Index,中文叫震荡指数,缩写 CHOP。名字挺唬人,你可能也会以为它是某种黑箱评分,我第一次看到就是这么想的。

后来才明白,它只回答一件事,这十几根 K 线到底在往前走,还是在原地打转。不猜方向,也不评强弱。

举个栗子:你家到公司,高德导航上直线 3 公里,结果你开了 10 公里才到(绕路、找车位、错过两个出口又掉头回来),这个放到 CHOP 里是会被打个高分。反过来,要是你上班全程走快速路,直着过去,路程 3.2 公里,净位移 3 公里,CHOP 就低。

算法我拆成一句话:过去 14 根 K 线,价格一共走了多少路,除以这 14 根跨过的净高度。

分子要看总路程。每根 K 线取三个数里最大的那个,今天的高低差、今天最高减昨天收盘、今天最低减昨天收盘,14 根加起来。这个量法跟 ATR 同源,只是求和不是求平均。

分母就不用查了,14 根里的最高点减最低点。

这个比值我起初没看懂。比值越接近 1,说明走的路全用在了位移上,一条道走到黑,CHOP 趋近 0。比值越大,路绕得越远,CHOP 越高。

最后一步归一化我卡了一会儿。14 根 K 线最多能绕到比值 14,log10(14) 是 1.146,拿它当分母除一下,刻度刚好落在 0 到 100。我一开始以为这个除数是随手定的,按了一遍计算器才明白,它取的就是最绕的那一种情况。

两条线起点终点一样,只是左边直、右边绕。路程是净位移的 1.02 倍对 3.52 倍

分界我用的是斐波那契。38.2 以下叫趋势,61.8 以上叫震荡,中间那段叫中性。

这里有个细节要注意,61.8 以上叫震荡,大部分人第一反应是这票没动静。恰恰相反,它的意思是价格动得不少,动得多但没走出去。我看这一档比 38.2 以下更热闹,不是更安静。真正安静的票通常落在中间,既没走出去,也没怎么动。

这一档我最初读反了。看见 61.8 以上就往「没行情」那边想,后来对着几个月的 K 线才想明白,读数高恰恰说明它天天在动,只是动完又回到原地。

同一把尺子,换个周期就不是同一件事

我头一遍只算日线,算完当时就觉得不对,才把周线和月线加上。

公式里那个 14 是固定参数,可我原来一直把它当成「两周」。

日线的 14 根是两周半,月线的 14 根是 14 个月。还是同一个 14,我一算才发现两边差着二十倍。

我拿跳空试过一次。差别不只停在长短上,日线里一根跳空高开就能把 14 天的高低差撑开,分母变大,读数被压回中段。月线里同一根大阴线被 14 个月的区间摊薄,影响小得多。

换句话说,日线读数容易被单根 K 线绑架,月线不会。这两件事我是后来才对着数据对上的。

我把这个猜想拿 A 股五只票验了一遍,样本起点 2015 年,一直算到最新一根 K 线。日线读数落在趋势区的时间占 13.2%,月线占 17.8%。

茅台最明显,我特意单独看了它。月线有 33% 的时间在 38.2 以下,日线只有 20%。

五只 A 股 2015 年至今,月线落在趋势区的时间占比 17.8%,日线 13.2%

我把日线和月线都算了一遍,反过来也成立。日线读数更爱在中间带打转,因为它被噪音稀释了。

我按一年两百多个交易日粗估,这个差别就是两种完全不同的过滤器。用它筛票,日线筛出来的东西会比月线多,但里面混着更多只是短期没方向的票。

五只 A 股跑出来的结果

我挑的是贵州茅台、中国平安、招商银行、长江电力、紫金矿业,跟上面同一批。口径也一样,都取前复权日线,算到 2026-10-20。

标的日线周线月线
贵州茅台38.26 中性66.47 震荡54.21 中性
中国平安44.23 中性52.98 中性50.86 中性
招商银行48.56 中性47.09 中性56.77 中性
长江电力42.10 中性64.34 震荡53.74 中性
紫金矿业53.14 中性59.82 中性52.22 中性
同一个横轴上看,日线五根挤在窄带里,周线才拉开距离

先看日线那一栏,五只全部中性,38.26 到 53.14。挤在一条 15 个点的窄带里,看着像五只票都没故事。

看到这张表,多半会先想自己手上那只落在哪一格。建议大家可以先自己翻翻,把三个数摆出来,这一步本身就有信息。

茅台 38.26,离 38.2 只差 0.06。我盯着这个数看了好几遍,最后还是把它归进中性那一档。这种擦边数别当信号,四舍五入的运气不算结构。

再看周线那一栏,分水岭在这儿。茅台 66.47、长电 64.34,两只都过了 61.8,落在震荡区。剩下三只在中间,紫金 59.82 离震荡线只有两个点,也没过去。

最后看月线那一栏,五只挤在 50.86 到 56.77 之间,没有一只进趋势区,也没有一只到震荡区。

我心里更看重的其实是纵向,不是横向。同一只票,茅台日线 38.26 像要起趋势,周线 66.47 明明白白在震荡。长电日线 42.10 也不难看,周线 64.34 同样在箱体里。

把这两个读数摞一起看,说的是同一句话,它们现在涨价的方式是来回磨,不是单边走。我以前就按日线那层去追,很容易追在箱体的上沿。

月线的位置我想单独说一句。月线的极端值我最信,因为最难做出来。我知道要让它掉到 38.2 以下,得让 14 个月的 K 线大体朝一个方向走。14 天做到不难,14 个月做到,那是真的在走。

原本指望月线能给出一个方向,结果五只全卡在中间带,说明它们眼下都不在多年的单边里。说不好这五只里哪只会先走出 61.8,但至少现在,它们都还没有那层兜底。这个结果不算好看,但它比一个漂亮的结果有用,因为它说明这套阈值在这批标的上不常给信号。

日线为什么会骗人

上面那张表里,日线五只全是中性,看着最没用。真出事,往往就出在这一层。

先说假突破是怎么来的。区间上沿被反复测试,多头在等一个确认,卖单挂在上面,两边的成本都堆在这一条线附近。价格一旦真的穿过去,触发的是止损和追涨单,不是新资金进场。推力用完,价格自然掉回来。

你有没有过这种经历?进场那天什么都对,第二天一根低开,第三天价格已经回到箱体里,止损刚好被扫掉。我自己就有过,还不止一次。

把自己被抓的那几次翻了一遍,共同点很一致。回头看那 14 天,净位移几乎是零,读数在 60 以上。当时我只看 K 线的形状,压根没看这个数。

我想了想,最难受的地方在这儿。进场的时候每一步都合规矩,错了以后回头看,K 线也没有哪一根长得不对,要怪只能怪自己少看了一层。

CHOP 抓的正是这个阶段,14 天里价格反复摩擦同一个高度,路程跑了很多,净位移几乎为零,读数当然高。

所以现在应该只看周线在不在震荡区。周线读数在 61.8 以上,日线每一次突破都发生在同一个箱体里,失败的概率会被抬得很高。我试过把信号画得更严格,没用,它脚下的地板本来就在震。

反过来还有个坑。我一开始也把 38.2 以下当成可以进的信号,后来才发现不是。日线读数在 38.2 以下,容易让人兴奋过头,以为趋势来了。可我算过,14 天窗口撑出来的趋势只有三五天的寿命,等我在日线上确认完,它往往已经走完一半。

代码

我把它压成一个函数。注意真实波幅那一步得用 shift 取前一根收盘,否则第一根 K 线会算出空值。

import math
import numpy as np
import pandas as pd


def chop(df, window=14):
    """df 需要 Open/High/Low/Close 四列,按时间升序。""
    high_low = df["High"] - df["Low"]
    high_close = (df["High"] - df["Close"].shift(1)).abs()
    low_close = (df["Low"] - df["Close"].shift(1)).abs()
    tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)

    atr_sum = tr.rolling(window).sum()
    hh = df["High"].rolling(window).max()
    ll = df["Low"].rolling(window).min()
    rng = (hh - ll).replace(0, np.nan)          # 一字板会除零,先挡掉

    return 100 * np.log10(atr_sum / rng) / math.log10(window)

def regime(v):
    if v < 38.2:
        return "趋势"
    if v > 61.8:
        return "震荡"
    return "中性"

三周期我先取了三次数据,麻烦还容易对不上。后来改成拉一份日线重采样两次,三个周期的 OHLC 天然同源。

import akshare as ak

def resample(d, rule, start):
    g = d.resample(rule).agg(
        {"Open": "first", "High": "max", "Low": "min", "Close": "last"}
    ).dropna()
    return g[g.index >= pd.Timestamp(start)]

raw = ak.stock_zh_a_daily(
    symbol="sh600519", start_date="20150101", end_date="20261020", adjust="qfq"
)
d = raw.rename(columns={"date": "Date", "open": "Open", "high": "High",
                        "low": "Low", "close": "Close"})
d["Date"] = pd.to_datetime(d["Date"])
d = d.set_index("Date").sort_index().astype(float)

frames = {
    "日线": resample(d, "B", "2024-01-01"),
    "周线": resample(d, "W-FRI", "2022-01-01"),
    "月线": resample(d, "ME", "2015-01-01"),
}

for name, f in frames.items():
    s = chop(f).dropna()
    print(name, round(s.iloc[-1], 2), regime(s.iloc[-1]), s.index[-1].date())

跑出来后就是上面那张表。周线用 W-FRI,月线用月末,两个锚点固定,同样一份日线在谁的机器上都重采样成一样的结果。

三条能用的规则

第一条我留给周线。日线信号必须先过周线这一关,周线在 61.8 以上,同一个日线突破的成功率会被吃掉一大截。不是说不能做,是仓位要压小、止损要贴近,因为它随时会回到箱体中间。

第二条我自己看反过。月线在 38.2 以下的票,日线和周线的中性回调是位置,不是风险。这一层兜底很难造假,14 个月的 K 线要凑成一个方向,中间得躲过好几轮折腾。我跑的这五只眼下都没有这个条件,月线全在中间带,所以它们的中性回调只能当回调看。

第三条需要反复提醒自己,别把日线 CHOP 当进出场开关。日线读数一天一个样,它只告诉你现在是不是在打转,不告诉你该不该进。

还有个边界我想说清楚。CHOP 不给方向。我只把它当过滤器。它只说有没有方向,不说往哪走。所以它永远只能当第二层过滤,单独拿它出信号,等于闭着眼睛猜。

这套东西最实用的地方,是它给了我一个不做的理由。周线读数在 61.8 以上的时候,日线上再漂亮的开局都可以先放着。

建议大家一般先念月线,再念周线。月线在哪一档,周线在哪一档,大致就知道这票现在该不该盯。所以它该放在流程的最前面。我更倾向的顺序是,先看月线定值不值得看,再看周线定这一段时间能不能做趋势,日线只负责挑一天。一层管一层,14 根日线撑不起那个判断。

回到开头那两段行情

同样从 10 块到 11 块,一段涨得干净,一段抽了三次。我把这两段对过一遍。

涨跌幅把它们算成同一件事,CHOP 不。我按收盘价粗算,第一段的路程就是净位移本身,第二段是 9.2 倍,读数一个在天上一个在地下。

这也是我现在看票的习惯,先翻三个周期的读数,再翻 K 线。顺序反了,K 线会把你想看的东西画给你。

你手上有没有哪只票,日线一直中性、月线却很低的。把三个周期的读数摆在一起对一下,评论区见。

觉得有用的话,把这篇转给同样在搞量化的朋友。


#震荡指数 #CHOP #多周期分析 #市场状态 #趋势跟踪 #量化指标 #A股实测 #Python #akshare #突破陷阱 #技术分析

Published inAI&Invest专栏

Be First to Comment

    发表回复