惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Latest
Security Latest
Recorded Future
Recorded Future
人人都是产品经理
人人都是产品经理
S
SegmentFault 最新的问题
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
Know Your Adversary
Know Your Adversary
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
量子位
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tor Project blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
T
Threatpost
T
Tenable Blog
有赞技术团队
有赞技术团队
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
C
Cisco Blogs
H
Heimdal Security Blog
Attack and Defense Labs
Attack and Defense Labs
A
About on SuperTechFans
Last Week in AI
Last Week in AI
N
News and Events Feed by Topic
T
Threat Research - Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
I
Intezer
V
V2EX
Cyberwarzone
Cyberwarzone
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
V
Vulnerabilities – Threatpost
N
Netflix TechBlog - Medium
T
The Blog of Author Tim Ferriss
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
U
Unit 42
PCI Perspectives
PCI Perspectives
P
Privacy International News Feed
D
Docker

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
Python 行情数据清洗实战:Z-Score、MAD 与分位数过滤的异常值检测
Walter先生 · 2026-05-02 · via 博客园_首页

阅读指南

  • 如果你只想要代码:直接跳转第三章,三种检测方法的完整实现可复制运行。
  • 如果你想理解方法选型:从第二章开始,有 Z-Score vs MAD vs 分位数的对比表。
  • 如果你关心生产级细节:第四章有各方法在金融数据上的误判场景和人工审核队列设计。

一、回测收益翻倍?先检查是不是数据错了

拿到 10 年历史 K 线数据后,大多数人的第一反应是直接跑策略回测。结果出来夏普比率 3.2,最大回撤仅 8%,年化收益 35%。兴奋地部署实盘,三个月后亏了 15%。

回测记录里通常有一个隐蔽的凶手:未被清洗的异常数据。

一笔真实成交价 150 元的股票,因为数据源错误记录了 1500 元——你的策略在这一天检测到“突破信号”大举买入。这笔交易在回测中贡献了 10% 的收益,但在实盘中永远不会发生。

问题在于:不是所有价格跳空都是数据错误。财报发布后的真实跳空、拆股除息带来的价格调整、流动性枯竭时的极端波动——这些是需要保留的市场信号。自动清洗的边界在于区分错误和异动。

数据清洗的核心不是“剔除所有异常”,而是区分数据错误(剔除)和真实市场异动(保留)。一条未被清洗的异常 K 线能让回测虚增 5-15% 的年化收益——但实盘无法复现。

二、三种统计方法的原理与金融数据适配性

展开之前,先给出结论速查:

方法选择速查:Z-Score 最常用,但在金融数据上误判率最高,仅适合截面比较。MAD 用中位数免疫极端值,是金融时间序列异常检测的主力方案。分位数过滤适合做第一道粗筛。

2.1 数据准备:从 API 到 DataFrame

在进入异常检测之前,先解决一个工程问题:数据怎么来。这里的重点是类型转换——很多行情 API 的价格和成交量字段返回的是字符串,不转成 float 之前做任何统计计算都会出错。

以下用 TickDB 的历史 K 线接口作为示例演示数据加载流程:

curl "https://api.tickdb.ai/v1/market/kline?symbol=700.HK&interval=1d&limit=100" \
  -H "X-API-Key: YOUR_KEY"

Pandas 加载时注意两件事:时间戳是毫秒 UTC,价格和成交量是字符串——必须在初始化 DataFrame 时显式转换类型:

import pandas as pd

def load_klines_to_df(resp_json: dict) -> pd.DataFrame:
    """将 kline 接口的原始响应转为 DataFrame"""
    df = pd.DataFrame(resp_json["data"]["klines"])
    df["time"] = pd.to_datetime(df["time"], unit="ms")
    df.set_index("time", inplace=True)
    df[["open", "high", "low", "close", "volume"]] = (
        df[["open", "high", "low", "close", "volume"]].astype(float)
    )
    return df

跳过了 String→Float 转换,MAD 函数会直接抛 TypeError。这是对接新数据源时最常见的“第一行代码报错”。

2.2 Z-Score:最常用,但最不适合金融数据

Z-Score 计算每个数据点距离均值有多少个标准差。当 |z| > 3 时,标记为异常值。

这个方法建立在正态分布假设之上。金融收益率分布是典型的厚尾分布——标准差的 3 倍之外并不是稀有事件。美股单日涨跌超过 3 个标准差的情况,每年实际发生 5-8 次,而正态分布预测的次数不到 1 次。

用 Z-Score 检测金融异常值,就像用测量体温的方式判断一个人是否在跑马拉松——马拉松选手完赛时体温超过 38°C 是正常的,不是发烧。

Z-Score 在金融数据上会碰到三个具体问题:2020 年 3 月美股单日 -12% 会被 Z=3.5 误判为异常——这是厚尾敏感。10 年涨 20 倍的股票,前 5 年的价格看起来全是“异常低值”——这是均值漂移。一条真正的错误数据会拉高均值和标准差,导致其他异常漏检——这叫掩蔽效应。

掩蔽效应的直观理解:假设你有一组正常价格在 100 元附近波动的数据。如果混入一条 1500 元的错误数据,均值会被拉高,标准差会被撑大,Z-Score 的阈值区间随之膨胀。结果这条 1500 元的异常值本身可能刚好落在 ±3 标准差的边界内,而周围真正的异常反倒被“保护”了。

场景 正常数据均值 正常数据标准差 ±3σ 阈值区间 异常值1500的Z-Score
无异常值 100.2 2.5 [92.7, 107.7]
含一条1500 128.1 198.3 [-466.8, 723.0] 6.92 → 仍被检出
含两条1500 156.0 277.6 [-676.8, 988.8] 4.84 → 漏检边缘

随着异常值增多,掩蔽效应呈非线性恶化。这就是为什么 Z-Score 在金融数据清洗中不能单独使用。

Z-Score 的唯一适用场景是截面数据比较,不适合时间序列异常检测。

2.3 MAD:针对厚尾分布的鲁棒替代

MAD 用中位数替代均值,用中位数偏差替代标准差。0.6745 是常数,使 MAD 在正态分布下与标准差具有可比性。

为什么 MAD 更适合金融数据:中位数不受极端值影响——一条错误的高价数据不会拉偏参考基准。Modified Z-Score 的 3.5 阈值在实际测试中比 Z=3 的误判率低得多。适用于时间序列,不需要分布假设。

中位数是你的“正常参考点”,即便有一个离谱数据点,参考点纹丝不动。均值则是被极端值来回拉扯的橡皮筋。

MAD 的局限:对低流动性标的不友好。成交量偏低的股票,价格波动本身就不稳定,MAD 会标记太多“假阳性”。

2.4 分位数过滤:最简单,但需要领域知识

直接设定上下分位数阈值,超出范围即标记。

优点是不依赖任何分布假设,解释性强,适合作为其他方法的第一道粗筛。

缺点是需要人工设定阈值,缺乏自适应性。对趋势性标的失效——10 年涨 20 倍的股票,前期的正常价格会被后期抬高的分位数误判。不同价格区间的标的不能用同一套分位数。

2.5 三方法对比速查

方法 分布假设 鲁棒性 自适应性 适用场景
Z-Score 正态分布 截面排名,不做异常检测主力
Modified MAD 时间序列异常检测主力方案
分位数过滤 第一道粗筛 + 价格合法性检查

Z-Score 在金融数据上是误判率最高的方法——厚尾分布不是 bug,是 feature。生产环境推荐 MAD 做主力、分位数做粗筛的双层过滤架构。

三、生产级代码实现

3.1 理解原理:全局统计量版本(教学用途,含前视偏差)

以下为教学版实现,使用全量数据的全局中位数和分位数,便于理解方法本质。

此版本在真实回测中存在前视偏差——用后来的数据判断早期是否异常。生产环境请使用下一节的滚动窗口版本,此处仅供理解原理。

import numpy as np
import pandas as pd


def detect_by_zscore(series: pd.Series, threshold: float = 3.0) -> pd.Series:
    """Z-Score 异常检测(教学用途)。警告:对金融时间序列误判率极高。"""
    mean = series.mean()
    std = series.std()
    if std == 0:
        return pd.Series(False, index=series.index)
    z_scores = np.abs((series - mean) / std)
    return z_scores > threshold


def detect_by_mad_global(series: pd.Series, threshold: float = 3.5) -> pd.Series:
    """全局 MAD 异常检测(教学用途,含前视偏差)。"""
    median = series.median()
    mad = np.median(np.abs(series - median))
    if mad == 0:
        mad = 1e-8
    modified_z = 0.6745 * (series - median) / mad
    return np.abs(modified_z) > threshold


def detect_by_quantile(
    series: pd.Series, lower_q: float = 0.01, upper_q: float = 0.99
) -> pd.Series:
    """分位数过滤异常检测。适合作为第一道粗筛。"""
    lower = series.quantile(lower_q)
    upper = series.quantile(upper_q)
    return (series < lower) | (series > upper)

3.2 生产级实现:滚动 MAD 检测(杜绝前视偏差)

在生产环境中,你必须保证检测 T 日数据时,绝不使用 T+1 日及以后的任何信息。下面的滚动窗口实现严格满足这一点:

def detect_by_rolling_mad(
    series: pd.Series,
    window: int = 252,
    threshold: float = 3.5
) -> pd.Series:
    """
    生产级滚动 Modified Z-Score 异常检测。
    基于过去 window 个交易日的滚动窗口,杜绝前视偏差。

    window=252 对应约一年的交易日。窗口不足一半时返回 False,
    即需要约半年数据才开始检测——这是保守但安全的选择。
    """
    min_periods = window // 2  # 至少需要半年的数据才开始计算

    rolling_median = series.rolling(window=window, min_periods=min_periods).median()
    abs_deviation = (series - rolling_median).abs()
    rolling_mad = abs_deviation.rolling(window=window, min_periods=min_periods).median()

    # 避免零 MAD 导致除零错误
    rolling_mad = rolling_mad.replace(0, 1e-8)

    modified_z = 0.6745 * (series - rolling_median) / rolling_mad
    return modified_z.abs() > threshold

这段滚动 MAD 实现可以直接拷进你的实盘回测框架。它保证了在检测任意一天的数据时,参考基准完全由过去数据构成,不掺杂未来信息。

3.3 双层过滤架构:粗筛 + 精检

from typing import Tuple, Dict


def clean_price_data(
    df: pd.DataFrame,
    price_col: str = "close",
    volume_col: str = "volume",
    mad_window: int = 252,
    mad_threshold: float = 3.5,
    quantile_range: Tuple[float, float] = (0.005, 0.995)
) -> Tuple[pd.DataFrame, Dict]:
    """
    双层过滤:先分位数粗筛,再滚动 MAD 精检。

    返回:
    - df: 带异常标记列的 DataFrame(不删除数据)
    - report: 异常统计报告
    """
    df = df.copy()

    # 第一层:分位数粗筛(价格合法性检查)
    price_lower = df[price_col].quantile(quantile_range[0])
    price_upper = df[price_col].quantile(quantile_range[1])
    quantile_masked = (df[price_col] < price_lower) | (df[price_col] > price_upper)

    # 第二层:滚动 MAD 精检(生产级——杜绝前视偏差)
    price_mad_anomalies = detect_by_rolling_mad(
        df[price_col], window=mad_window, threshold=mad_threshold
    )
    volume_mad_anomalies = detect_by_rolling_mad(
        df[volume_col], window=mad_window, threshold=mad_threshold
    )

    # 标记但不删除——流入人工审核队列
    df["anomaly_price"] = price_mad_anomalies
    df["anomaly_volume"] = volume_mad_anomalies
    df["anomaly_quantile"] = quantile_masked
    df["anomaly_any"] = (
        df["anomaly_price"] | df["anomaly_volume"] | df["anomaly_quantile"]
    )

    report = {
        "total_rows": len(df),
        "quantile_outliers": int(quantile_masked.sum()),
        "mad_price_outliers": int(price_mad_anomalies.sum()),
        "mad_volume_outliers": int(volume_mad_anomalies.sum()),
        "total_flagged": int(df["anomaly_any"].sum()),
        "flagged_pct": round(df["anomaly_any"].sum() / len(df) * 100, 2),
        "price_range": (float(price_lower), float(price_upper)),
        "method": f"Quantile({quantile_range}) + RollingMAD(window={mad_window}, threshold={mad_threshold})"
    }
    return df, report

3.4 人工审核队列

def build_review_queue(df: pd.DataFrame) -> pd.DataFrame:
    """将标记为异常的数据点组织为人工审核队列,按置信度降序排列。"""
    if "anomaly_any" not in df.columns:
        raise ValueError("请先运行 clean_price_data 生成异常标记")

    review = df[df["anomaly_any"]].copy()

    median = df["close"].median()
    mad = np.median(np.abs(df["close"] - median))
    if mad == 0:
        mad = 1e-8
    review["confidence"] = np.abs(0.6745 * (review["close"] - median) / mad)
    review = review.sort_values("confidence", ascending=False)

    review["prev_close"] = df["close"].shift(1).loc[review.index]
    review["pct_change"] = (
        (review["close"] - review["prev_close"]) / review["prev_close"] * 100
    )

    return review[[
        "close", "prev_close", "pct_change",
        "volume", "anomaly_price", "anomaly_volume",
        "anomaly_quantile", "confidence"
    ]]

3.5 使用示例

df_cleaned, report = clean_price_data(df)

print(f"清洗报告:")
print(f"  总数据量: {report['total_rows']} 条")
print(f"  分位数异常: {report['quantile_outliers']} 条")
print(f"  MAD 价格异常: {report['mad_price_outliers']} 条")
print(f"  MAD 成交量异常: {report['mad_volume_outliers']} 条")
print(f"  总计标记: {report['total_flagged']} 条 ({report['flagged_pct']}%)")

review_queue = build_review_queue(df_cleaned)
print(f"\n前 5 条待审核异常点:")
print(review_queue.head(5))

输出示例(示意性数据):

清洗报告:
  总数据量: 2518 条
  分位数异常: 25 条
  MAD 价格异常: 18 条
  MAD 成交量异常: 32 条
  总计标记: 52 条 (2.07%)
  价格范围: (12.50, 385.00)

前 5 条待审核异常点:
            close  prev_close  pct_change    volume  confidence
2020-03-16  85.30      105.20      -18.92  48200000        5.21
2018-08-03  185.40     150.60       23.11  38100000        4.68
2019-01-14  205.10     218.30       -6.05  12100000        3.95

双层过滤标记了约 2% 的数据点为异常——在 10 年级别的原始行情数据中,这个比例是合理的。审核队列按置信度降序排列,可以从最可疑的数据开始处理。

四、踩坑记录:各方法在金融数据上的真实误判场景

问题 现象 根因 解决方案
拆股被误判为暴跌 股价“腰斩”,Z 值极高 拆股导致价格断崖式变化 先用复权因子调整价格再做检测
财报跳空被误判 跳空高开 20% 被标记 真实市场异动,不应剔除 检查当日成交量——真实异动通常放量
低流动性标的 MAD 失效 MAD 为零,除零报错 交易极不活跃,价格连续不变 跳过该标的,标记“数据不足”
成交量天然高方差 同一标的成交量波动极大 成交量分布极度右偏 取对数后再做 MAD 检测
滚动窗口初期数据不足 前 126 天无 MAD 输出 min_periods 设置过长 适当降低 min_periods 或对早期数据单独处理

你在历史数据中遇到过最离谱的异常值是什么?是价格后面多了一个零,还是成交量为负数?关于“真实异动”和“数据错误”的区分,你的策略是怎么处理的?

五、结语

数据清洗不是“把异常值删掉”,而是标记→审核→决策——自动删除的每一次操作,都可能是在删除市场真相。

本文实现了一套双层过滤架构和人工审核队列。为什么不用 Z-Score:金融数据是厚尾分布,误判率偏高,掩蔽效应会让异常值互相“保护”。为什么用滚动 MAD:中位数免疫极端值,滚动窗口杜绝未来函数,不需要正态假设。为什么不自动删除:财报跳空、拆股、流动性枯竭——这些都是需要保留的市场信号。

异常检测的误判率高度依赖数据源本身的质量。如果你使用的数据源已经做了标准化处理——比如时间戳统一为毫秒 UTC、成交量字段经过合法性校验——那么 MAD 检测的基线噪声会小很多。反之,多数据源混合或自行爬取的数据,建议在统计检测之前先做一轮字段级合法性检查:成交量不为负、最高价不低于最低价、时间戳严格递增。市面上的标准化行情接口,例如 TickDB 的 kline 接口,可以作为一个参考起点,具体字段定义可搜索官方文档了解。

扩展方向

  • 成交量对数变换:对成交量取对数后再做 MAD 检测,改善右偏分布。
df["log_volume"] = np.log(df["volume"] + 1)
volume_anomalies = detect_by_rolling_mad(df["log_volume"])
  • 多维度联合检测:结合价格 MAD、成交量 MAD、日内形态做三维异常评分。
  • 自适应窗口:根据标的流动性动态调整窗口大小——大盘股用 252,小盘股用 504。

📡 数据由 TickDB.ai 提供

本文不构成任何投资建议。异常值检测结果仅供数据清洗参考,不构成买卖依据。

工程笔记