3. 尾段回落时间窗口定位:回落起点与低点的时间差统计

任务拆解:从分时序列中提取两个时刻

本节要解决的是一个“时间定位”问题:在某个竞价交易日的分时数据里,先找到尾段回落的起点时刻,再找到回落区间的最低点时刻,然后计算两者之间的时间差与期间价格斜率。时间差回答“跌得快不快”,斜率回答“跌得陡不陡”,两者结合才能区分急跌与缓跌。

为了让定位过程可复现,先明确三个约定:

  • 尾段范围:以 14:30 为尾段起点,收盘 15:00 为终点,共 30 分钟。若当日有集合竞价收盘,14:57–15:00 单独标记,不参与斜率拟合。
  • 起点时刻 t_start:尾段内瞬时价首次跌破“尾段起始价 × (1 − 0.3%)”的那一分钟,且此后不再回到该阈值上方,视为回落确认起点。
  • 最低点时刻 t_low:从 t_start 到 15:00 之间瞬时价的最小值所在分钟;若最小值出现多次,取最早一次。

时间差 Δt = t_low − t_start(单位:分钟),斜率 k = (P_low − P_start) / Δt(单位:元/分钟,负值表示下行)。

示例数据与定位过程

下表是一段教学示例分时数据(1 分钟粒度,仅列尾段关键点),价格为假设值,用于演示定位与计算逻辑,不代表任何真实标的。

时刻瞬时价(元)相对尾段起始价状态标记
14:3020.000.00%尾段起始
14:3319.96−0.20%未破阈值
14:3619.93−0.35%t_start 候选
14:3819.95−0.25%回抽,未确认
14:4119.90−0.50%t_start 确认
14:4719.78−1.10%下行中
14:5219.71−1.45%下行中
14:5519.68−1.60%t_low
14:5819.70−1.50%小幅回抽
15:0019.69−1.55%收盘

按约定:14:36 首次跌破 −0.3% 阈值,但 14:38 回抽至 −0.25%,未满足“不再回到阈值上方”,因此起点顺延至 14:41 的确认跌破。最低点出现在 14:55,价格 19.68 元。

计算:Δt = 14:55 − 14:41 = 14 分钟;k = (19.68 − 19.90) / 14 ≈ −0.0157 元/分钟,即每分钟约下跌 1.57 分钱,折合相对斜率约 −0.079%/分钟。

判读口径建议:Δt ≤ 10 分钟且 |k| ≥ 0.02 元/分钟,判为急跌;Δt ≥ 20 分钟且 |k| ≤ 0.01 元/分钟,判为缓跌;其余为中性回落。该阈值需按标的波动率校准,本节仅作示例口径。
尾段回落时间窗口定位:t_start 与 t_low 的时间差 示例数据(假设值)· 尾段 14:30–15:00 · 阈值 −0.3% 14:30 14:38 14:46 14:54 15:00 20.00 19.85 19.70 −0.3% 阈值(19.94) t_start = 14:41 19.90 元 · 确认跌破 t_low = 14:55 19.68 元 · 区间最低 Δt = 14 分钟 k ≈ −0.0157 元/分 回落斜率 k 判读 Δt ≤ 10 且 |k| 大 → 急跌 Δt ≥ 20 且 |k| 小 → 缓跌 其余 → 中性回落

可复用的定位与统计代码

下面的 Python 片段把上述约定固化为函数,输入为按时间升序的分时记录(含 time 与 price 两列),输出起点、低点、时间差与斜率。代码自洽可运行,示例数据内嵌,便于直接验证。

import pandas as pd

def locate_tail_pullback(df, tail_start="14:30", threshold=0.003):
    """定位尾段回落起点与低点,返回时间差与斜率。"""
    df = df.copy()
    df["time"] = pd.to_datetime(df["time"])
    tail = df[df["time"].dt.strftime("%H:%M") >= tail_start].reset_index(drop=True)
    if len(tail) < 3:
        return None

    p0 = tail.loc[0, "price"]                 # 尾段起始价
    line = p0 * (1 - threshold)               # 跌破阈值线

    # 起点:首次跌破后不再回到阈值上方
    t_start_idx = None
    for i in range(len(tail)):
        if tail.loc[i, "price"] < line:
            if (tail.loc[i:, "price"] >= line).sum() == 0:
                t_start_idx = i
                break
    if t_start_idx is None:
        return None

    seg = tail.loc[t_start_idx:].reset_index(drop=True)
    low_idx = seg["price"].idxmin()           # 最早出现的最小值
    t_start = seg.loc[0, "time"]
    t_low = seg.loc[low_idx, "time"]
    p_start = seg.loc[0, "price"]
    p_low = seg.loc[low_idx, "price"]

    dt_min = (t_low - t_start).total_seconds() / 60
    slope = (p_low - p_start) / dt_min if dt_min > 0 else float("nan")
    return {
        "t_start": t_start.strftime("%H:%M"),
        "t_low": t_low.strftime("%H:%M"),
        "dt_min": dt_min,
        "p_start": p_start,
        "p_low": p_low,
        "slope_yuan_per_min": round(slope, 4),
        "slope_pct_per_min": round(slope / p_start * 100, 4),
    }

# 教学示例数据(假设值)
demo = pd.DataFrame({
    "time": ["2024-06-03 14:30","2024-06-03 14:33","2024-06-03 14:36",
             "2024-06-03 14:38","2024-06-03 14:41","2024-06-03 14:47",
             "2024-06-03 14:52","2024-06-03 14:55","2024-06-03 14:58",
             "2024-06-03 15:00"],
    "price": [20.00, 19.96, 19.93, 19.95, 19.90, 19.78, 19.71, 19.68, 19.70, 19.69]
})
print(locate_tail_pullback(demo))

按示例数据运行,预期输出为:t_start=14:41、t_low=14:55、dt_min=14.0、slope_yuan_per_min≈-0.0157、slope_pct_per_min≈-0.0789。若实际输出与上述不一致,优先检查时间列是否被解析为字符串、阈值是否按小数传入(0.003 而非 0.3)。

批量统计与形态归类

单日定位只是第一步,本节目标还要求“统计”。把函数套到多日样本上,按 Δt 与 |k| 分箱,即可得到急跌/缓跌的分布。下面给出统计骨架,字段与判读口径和上文一致。

def classify(row, fast_dt=10, fast_k=0.02, slow_dt=20, slow_k=0.01):
    if row is None:
        return "无回落"
    dt, k = row["dt_min"], abs(row["slope_yuan_per_min"])
    if dt <= fast_dt and k >= fast_k:
        return "急跌"
    if dt >= slow_dt and k <= slow_k:
        return "缓跌"
    return "中性回落"

results = []
for day, g in all_days.groupby("date"):
    r = locate_tail_pullback(g)
    results.append({"date": day, "shape": classify(r), **(r or {})})

summary = pd.DataFrame(results)["shape"].value_counts(normalize=True)
print(summary)
统计时建议同时保留 Δt 的中位数与 |k| 的四分位数,而不是只看分类占比。分类阈值一旦调整,占比会整体漂移,中位数与分位数则更稳定,便于跨标的横向比较。

结果判读与常见偏差

  • 起点被“假跌破”带偏:若阈值过松(如 −0.1%),14:33 就会被误判为起点,Δt 被拉长、斜率被摊平,急跌会被误读成缓跌。示例中 14:36 的回抽正是需要排除的情形。
  • 低点取到收盘集合竞价:14:57 后的价格波动机制不同,若把 15:00 收盘价当作 t_low,Δt 会系统性偏大。建议在尾段内单独切出 14:57–15:00 并标注。
  • 斜率单位混用:元/分钟与 %/分钟不可直接比较。跨标的统计时统一用 %/分钟,单标的复盘时元/分钟更直观。
  • Δt 为 0 的边界:若起点当分钟即为最低点,斜率为无穷大,代码中返回 NaN,应单独归为“瞬时跳水”而非急跌。

完成上述定位与统计后,本节的核心产出是一张按日排列的 t_start / t_low / Δt / k 明细表,以及基于 Δt 与 |k| 的形态分布。它直接为后续的量能配合分析与高位/低位分类提供时间锚点。