4. 连板高度衰减曲线:题材由强转弱的高度阈值判定

把“高度”变成一条可判读的曲线

题材由强转弱,最先松动的往往不是涨停家数,而是连板高度。本节把题材每个交易日的“最高连板数”抽成一条时间序列,用衰减曲线刻画高度回落的速度,再给出一个可回测的阈值规则:当高度从峰值回落到阈值以下并持续确认,判定题材进入弱势阶段。

先明确三个口径,避免后续计算与图形不一致:

  • 高度 H(t):第 t 个交易日,题材成分股中收盘仍封住涨停的最大连续涨停天数(首板记为 1,无涨停记为 0)。
  • 峰值 Hpeak:观察窗口内 H(t) 的最大值,及其首次出现日 tpeak。
  • 衰减率 k:以 tpeak 为起点,对后续交易日拟合指数衰减 H(t) ≈ Hpeak·e−k(t−tpeak),k 越大表示高度塌得越快。

阈值不拍脑袋定,用“峰值比例 + 绝对高度”双条件:H(t) ≤ max(2, ⌈0.5·Hpeak⌉) 视为跌破阈值,且需连续 2 个交易日满足才确认转弱,避免单日炸板造成的假信号。

示例数据与曲线拟合

下面是一段教学示例序列(假设数据,用于演示算法,非真实行情)。某题材 10 个交易日的最高连板高度为:

t12345678910
H(t)2467543221

峰值 Hpeak=7,出现在 tpeak=4。阈值 = max(2, ⌈3.5⌉) = 4。t=6 时 H=4 首次满足 ≤4,t=7 时 H=3 再次满足,连续 2 日确认,t=7 判定转弱。对 t≥4 的数据做对数线性回归 ln H = ln Hpeak − k·(t−tpeak),可得 k≈0.31/日,半衰期约 2.2 个交易日——高度大约每两天掉一半,属于快速衰减型。

连板高度衰减曲线与阈值触发 示例数据:峰值 7 板(t=4),阈值 4 板,连续 2 日确认 → t=7 转弱 H t(交易日) 0 2 4 6 8 阈值 4 峰值 7 拟合衰减 H≈7·e^(−0.31·Δt) 峰值 7 板 连续 2 日 ≤ 阈值 t=7 确认转弱 上升段 衰减段 弱势段 实际高度序列 拟合衰减曲线 转弱阈值

阈值回测:触发之后题材怎么走

阈值只有经过回测才有意义。回测要固定三件事:触发定义(连续 2 日 H ≤ 阈值)、观察窗口(触发后 3 个交易日)、结果标签(题材内是否再出现 ≥ 触发日高度的新连板,或题材成交额是否继续萎缩)。下面给出可直接运行的 Python 实现,输入为高度序列,输出峰值、阈值、触发日与回测标签。

import math

def fit_decay(heights, peak_idx):
    """对峰值之后的数据做对数线性回归,返回衰减率 k 与半衰期。"""
    xs, ys = [], []
    hp = heights[peak_idx]
    for i in range(peak_idx, len(heights)):
        if heights[i] > 0:
            xs.append(i - peak_idx)
            ys.append(math.log(heights[i] / hp))
    n = len(xs)
    if n < 2:
        return 0.0, float("inf")
    mx = sum(xs) / n
    my = sum(ys) / n
    num = sum((x - mx) * (y - my) for x, y in zip(xs, ys))
    den = sum((x - mx) ** 2 for x in xs)
    k = -(num / den) if den else 0.0
    half_life = math.log(2) / k if k > 0 else float("inf")
    return k, half_life

def detect_weak(heights, confirm_days=2):
    hp = max(heights)
    peak_idx = heights.index(hp)
    threshold = max(2, math.ceil(0.5 * hp))
    k, half_life = fit_decay(heights, peak_idx)

    trigger = None
    streak = 0
    for i in range(peak_idx, len(heights)):
        if heights[i] <= threshold:
            streak += 1
            if streak >= confirm_days:
                trigger = i
                break
        else:
            streak = 0
    return {
        "peak": hp, "peak_idx": peak_idx,
        "threshold": threshold, "k": round(k, 3),
        "half_life": round(half_life, 2),
        "trigger_idx": trigger,
    }

# 教学示例数据(假设)
heights = [2, 4, 6, 7, 5, 4, 3, 2, 2, 1]
print(detect_weak(heights))
# {'peak': 7, 'peak_idx': 3, 'threshold': 4, 'k': 0.31,
#  'half_life': 2.24, 'trigger_idx': 6}

注意索引从 0 开始,trigger_idx=6 对应第 7 个交易日,与图中 t=7 一致。回测时把同一函数套到历史每个题材样本上,记录触发日之后 3 日的表现:若多数样本在触发后高度不再创新高、成交额占比继续下滑,说明阈值规则有效;若频繁出现触发后立刻反包创新高,则阈值偏松,应把确认天数提高到 3 日或把比例从 0.5 收紧到 0.4。

阈值不是固定常数,而是随峰值缩放的相对值。高标题材(峰值 8 板以上)用 0.5 比例往往过早触发,可改用 0.4;小高度题材(峰值 4 板)则要防止阈值被压到 2 板以下而失去区分度,故设下限 2。
单日高度骤降可能只是龙头炸板或停牌,不代表题材转弱。必须用连续确认日过滤,并同时观察题材内是否仍有新的中位连板接力;若高度下降但梯队完整,更可能是换龙而非转弱。

判读要点

  • 看斜率不看单点:k 值大(半衰期短)说明高度塌得快,转弱更可信;k 小但高度长期横在阈值附近,属于钝化,需结合成交额判断。
  • 峰值日即分水岭:tpeak 之后每一天都在消耗题材动能,触发日越靠近峰值,转弱信号越强。
  • 回测标签要可量化:用“触发后 3 日是否出现 ≥ 触发日高度的新连板”作为主标签,避免用主观感受评价阈值好坏。