Sourabh16/Stock-Market-Crash-Detection-System

GitHub: Sourabh16/Stock-Market-Crash-Detection-System

基于 Isolation Forest 异常检测的印度股市崩盘与反弹预测系统,通过严格的因果性规则和数据质量门控提供可靠的交易退出信号。

Stars: 2 | Forks: 0

QBEAST — 股市崩盘检测系统

基于 Isolation-Forest 异常检测的印度股票崩盘与反弹预测
NIFTY 100 · 日线数据 · 2016–2026

Python tests universe bars status

## 核心理念 大多数交易系统试图通过频繁交易来获取收益。本系统恰恰相反。 它**默认保持全额投资**,仅在认为即将出现结构断裂时采取行动—— 在崩盘前卖出转为现金,在反弹前买回,其余时间保持不动。每笔交易都会 产生经纪费、STT、印花税、GST 和滑点成本,因此每笔交易都必须有 充分的理由。 本系统通过两项指标进行评判: | 指标 | 回答的问题 | |:--|:--| | **回撤降低幅度** | 我们规避了多少买入并持有策略中最严重的峰谷损失? | | **预警提前期** | 在崩盘*之前*多少个交易日我们发出了警报? | 预警提前期以**每次崩盘事件的分布情况**呈现,绝不使用平均值—— 因为平均值会悄然掩盖那些完全被漏掉的事件。 ## 工作原理 Isolation Forest 是一种无监督的异常检测器。它从未见过带标签的崩盘数据。 它构建许多随机树,在随机特征上按随机值进行分割,直到每个数据点都被单独隔离, 然后计算每个点所需的切割次数。异常值只需少数几次切割就会被隔离; 而位于密集区域的普通点则需要多次切割。路径短 = 异常。 **影响整个架构的关键在于:** Isolation Forest 具有*方向盲性*。它 标记的是**异常**,而不是**异常糟糕**——剧烈的反弹与剧烈的崩盘 在异常程度上完全相同。因此,仅凭异常分数永远无法产生买入或卖出信号。 这就是为什么检测器被分为三个部分: ``` Isolation Forest ──▶ something unusual is happening today + slope ──▶ ...and it is moving DOWN + acceleration ─▶ ...and it is getting WORSE, not exhausting ═══════════════════════════════════ → confirmed sell signal ``` 斜率和加速度是**对数**价格的一阶和二阶导数。符号对就是全部的信号逻辑: | 斜率 | 加速度 | 阶段 | 解读 | |:--|:--|:--|:--| | ▼ | ▼ | `AcceleratingDecline` | 下跌且加速恶化 → **退出** | | ▼ | ▲ | `DeceleratingDecline` | 抛售动能耗尽 → **观望** | | ▲ | ▲ | `AcceleratingAdvance` | 反弹正在形成 → **进入** | | ▲ | ▼ | `DeceleratingAdvance` | 见顶回落 → **谨慎** | ## 目前发现 **全市场触发条件,由数据校准而非主观猜测。** 无需任何调参,股票池中处于 `AcceleratingDecline` 状态的比例就能精准隔离出真实的市场事件: | 日期 | 股票池下跌比例 | 中位数斜率 (σ/天) | 事件 | |:--|--:|--:|:--| | 2020-03-12 | 92.1% | **−2.11** | COVID 崩盘 | | 2026-03-31 | 90.5% | −0.79 | — | | 2016-02-11 | 90.1% | −0.94 | 2016年2月全球抛售 | | 2022-09-26 | 87.2% | −0.67 | 美联储 / 英镑危机 | | 2021-12-20 | 86.2% | −0.82 | Omicron | 广度**单独是不够的**——在 COVID 崩盘和温和回调期间,均有约 90% 的股票池在下跌。 只有中位数斜率能区分系统性崩盘和轻微震荡(−2.11 对比 ≈−0.8)。 因此确立了双条件触发机制:`breadth ≥ 75% AND median slope ≤ −1.5σ`。 **某次崩盘曾削弱了其自身的信号。** 使用*同期* 60 天波动率对斜率进行归一化, 导致 COVID 崩盘的得分仅为 −1.67σ。到 2020 年 3 月中旬,波动率计算窗口本身 已充满了崩盘交易日,导致分母变大,使得这次崩盘的严重程度在计算中被悄然 除掉。将分母滞后 20 天——使其描述当前变动*正在偏离的* 平稳状态——同一事件的得分达到了 **−3.01σ**。 **继承的市场状态检测器中存在未来函数漏洞。** 它基于*整个*价格历史 计算百分位阈值,并将其应用于每一根 K 线,同时还在文档中自称是因果性的。 在 RELIANCE 2016–2026 的数据上,一旦使其具备正确的因果属性,**9.9% 的状态标签会发生变化**—— 而且带有偏差的版本产生了*更多*的 `Rally`(反弹)和*更多*的 `Crashing`(崩盘)判定, 因为事先知道了完整的数据分布,使它能以一种名不副实的自信将某些交易日归入尾部区间。 而这些标签恰恰正是驱动入场和出场的核心。 ## 数据完整性 在编写任何模型代码之前,对 100 个原始 CSV 文件进行了审核。共发现了六处缺陷, 每一处都会悄然破坏结果——而且没有任何一处会引发错误、警告或崩溃: | # | 缺陷 | 证据 | 处理方式 | |:--|:--|:--|:--| | 1 | `adj_close` 并非前复权序列 | 在约 5,800 根 K 线中,约有 30 根与 `close` 不同 | 弃用;`close` 已经是前复权数据 | | 2 | 捏造的 IPO 前历史数据 | MAZDOCK 包含 2017 年起的月线数据,但实际于 2020 年 10 月上市 | 通过日历缺口规则截断 | | 3 | 零振幅 K 线 | BAJFINANCE:5,803 根 K 线中有 1,092 根满足 `O=H=L=C` | 标记,但从不剔除 | | 4 | 参差不齐的结束日期 | 文件结束日期从 2026-06-03 跨越至 2026-06-22 | 截取至共同的 2026-06-05 | | 5 | 幸存者偏差 | 股票池是*今天的* NIFTY 100 | 作为局限性记录在案 | | 6 | 未调整的公司行动 | CGPOWER 在 2016-03-15 从 155.30 变为 53.05(分拆) | 截断;保留了真实的崩盘数据 | **缺口规则完美还原了真实的 NSE 上市日期。** 任何超过 10 天的日历缺口都是 数据供应商造成的假象——NSE 从未闭市那么长时间——因此第一个真实的数据 K 线就是 最后一次此类缺口之后的那一根: | 代码 | 规则检测到的日期 | 实际 NSE 上市日期 | | |:--|:--|:--|:--| | VBL | 2016-11-08 | 2016-11-08 | ✅ | | DMART | 2017-03-21 | 2017-03-21 | ✅ | | SBILIFE | 2017-10-03 | 2017-10-03 | ✅ | | MAZDOCK | 2020-10-12 | 2020-10-12 | ✅ | 这是对照*独立于本代码库*获取的日期源进行的验证,而不是根据其自身的 输出进行验证——这正是使其成为真正检验而非同义反复的原因。 **第 6 项缺陷最具启发意义。** 一根分拆(demerger)产生的 K 线在内部结构上是完全一致的—— 最高价高于收盘价,收盘价高于最低价,价格为正,没有缺口——因此它能通过所有*结构性* 检查。只有**收益率**会暴露它。而它的影响比例极其失真,因为 Isolation Forest 是无监督的: 训练窗口内一个单日跌幅 −66% 的数据点会成为样本中最极端的点, 并将异常边界拉向它,使得真正的崩盘看起来很平常。一个糟糕的数据点会降低 后续每一个分数的准确性。 难点不在于将其过滤掉,而在于**不要过度过滤**——ADANIENT 那次 −26.1% 的 Hindenburg 崩盘和 TRENT 那次 −31.9% 的暴跌必须保留下来,因为那正是本项目旨在检测的事件。 **缺口永远不进行前向填充。** 被顺延的价格会制造出恰好为零的收益率, 而一连串的零在模型看来就像是一段反常的平静期,从而将所有 波动率估计值向下偏倚。这是一种能逃过大多数健全性检查的隐秘谎言。 缺失数据必须保持缺失状态。 ## 因果性规则 每个特征都必须具备**因果性**:第 *t* 天的值只能使用截至第 *t* 天的数据, 绝不能使用之后的数据。未来函数是构建回测的最简单的作弊手段——这种回测看起来 完美无缺,但在实盘中会亏钱——因为模型正在接受一场它已经看过答案的考试。 这一点是**强制执行的,而非默认假设**: - **未来扰动测试**——计算一个特征,暴力篡改数据的未来部分, 重新计算,并断言之前的每个值在比特级别上完全一致。 - **流式等于批量测试**——每天喂入一次数据,必须能 完全复现基于全量历史数据的计算结果。这就是实盘交易的契约。 `tests/test_regime.py` 中包含了一项测试,证明*原始的*全样本方法 无法通过此项检查,从而证明该防护机制切实有效。 ## 入门指南 ``` pip install numpy pandas pyarrow scikit-learn pytest ``` 价格数据**不通过 git 进行版本追踪**。请将每个代码对应的 CSV 放置为: ``` data/ ├── raw/ ← your CSVs go here │ ├── RELIANCE.csv │ ├── TCS.csv │ └── NIFTY100.csv ← required: supplies the master trading calendar ├── interim/ ← generated: cleaned per-symbol parquet └── processed/ ← generated: aligned panel + listing mask ``` 所需列:`date` (`DD-MM-YYYY`)、`open`、`high`、`low`、`close`、`volume`。 可选元数据(`_source`、`_dq_score`、`_gap_filled`)如果存在,将会被保留。 ``` python scripts/run_all_phases.py # every phase, in order python scripts/run_all_phases.py --phase 2 # one phase python scripts/run_all_phases.py --list # what exists python -m pytest tests/ -q # 125 tests ``` 该 pipeline 是确定性的。删除 `data/interim/`、`data/processed/` 和 `reports/` 并重新运行,即可复现所有产物——完整重建耗时约 5 秒。 预期输出: ``` loading universe ... 96 usable symbols, 5820 trading days [PASS] universe_size 96 usable symbols loaded (need >= 80) [PASS] no_residual_gaps no calendar gaps remain after truncation [PASS] ohlc_consistent high/low bracket open/close everywhere [PASS] train_window_coverage 82 symbols have history at 2016-01-01 [WARN] flat_bar_burden BAJFINANCE 1092 zero-range bars [WARN] symbols_dropped ENRIN, TATACAP, TMCV (too little history) ``` ## 布局 ``` qbeast_crash/ ├── config.py single source of truth — paths, windows, thresholds ├── data.py Phase 1: load, clean, calendar, quality gate ├── features.py Phase 2: slope/accel, precursors, cross-sectional └── regime.py regime detection; HMM drops in behind RegimeDetector scripts/ ├── run_all_phases.py single entry point for the whole pipeline └── build_docs.js regenerates the per-phase documentation tests/ 125 tests, causality enforced docs/phases/ one document per phase ``` 斜率/加速度层中的两次回归都在均匀分布的窗口上坍缩为**固定权重向量**, 因此每个特征都是一次加权求和。这不仅执行效率高—— 更使得因果性具备*结构性*。根本不存在能够窥探未来的代码路径,因此它不会 被后续的修改所破坏。 ## 路线图 | | 阶段 | 状态 | |:--|:--|:--| | 0 | 数据审计——发现五处缺陷 | ✅ 完成 | | 1 | 数据层——加载器、日历、质量门控 | ✅ 完成 | | 2 | 特征——预兆指标、横截面数据、斜率/加速度 | ✅ 完成 | | 3 | Isolation Forest + 异常强度 | ✅ 完成 | | 4 | 崩盘标签 + 预警提前期测量 | ✅ 完成 — **详见下文发现** | | 5 | 信号——单只股票及全市场 | ✅ 完成 | | 6 | 回测引擎 + 印度成本/税收模型 | ⬜ | | 7 | 重训练方法对比 | ⬜ | | 8 | 回撤分析 + 单只股票图表 | ⬜ | | 9 | 稳健性实验 | ⬜ | | 10 | HTML 看板 | ⬜ | **第 4 阶段决定了项目是否可行。** 它衡量了模型实际能提供多少天的预警时间。 如果预警时间不存在,那么信号、回测和看板都只是围绕着一个失效检测器搭建的 脚手架——与其在第 8 阶段的资产净值曲线上发现这一点,远不如在第 4 阶段的预警时间直方图上发现它。 ### 待对比的重训练方法 1. 滚动 3 年窗口 2. 增量重训练,步长为 1 个月 3. EWMA,衰减系数 0.994 4. **剔除波动率的滚动窗口** — 提议中 第四种方法基于一个反直觉的观点。Isolation Forest 将*常态*定义为它所训练的数据, 因此输入危机数据会使危机显得**不那么**异常——模型会拉伸其常态的 概念以覆盖这些危机,导致检测能力在最关键的时刻发生退化。 因此,解决方法不是输入*更多*危机数据,而是*更少*:在剔除波动率位于 最高十分位数的交易日后,再拟合滚动窗口,这样模型就能学到纯粹的常态, 而任何类似危机的事件都会远远偏离常态。 评估指标是**单位换手率的回撤降低幅度——而不是原始收益率,因为 频繁交易的方案可以买入更好看的回撤数据,但其代价只会在 账单税单中显现出来。 ## 成本模型 印度单笔交易的完整成本堆栈已被建模:交割双向的 STT、仅针对买入的印花税、 仅针对卖出的 DP 费用、交易所交易费用、SEBI 周转费,以及 以适当基数为基准的 GST。计划进行以下三项优化: - **资本利得税**,在财务年度层面的组合级别进行计算,具备日期感知能力(税率 于 2024 年 7 月 23 日发生变更)。 - **波动率缩放滑点**——固定百分比在这里是不对的拟合方式,因为本 策略*仅在*崩盘和反弹日交易,而此时买卖价差恰好会扩大数倍。 - **具备日期感知能力的交易所费用**(于 2024 年 10 月 1 日变更)。 ## 贡献者须知 - **市场状态检测**——`regime.py` 定义了一个 `RegimeDetector` 接口。HMM 替代方案必须具备因果性:随着时间推移进行重拟合,或者至少使用*过滤后的*状态 概率 `P(state_t | data ≤ t)`,而不是*平滑后的*概率 `P(state_t | all data)`。 在全样本上拟合 HMM 然后再解码,等同于换上了更高明伪装的未来函数漏洞。在将其 接入之前,请先将 `tests/test_regime.py` 指向它进行测试。 - **数据质量**——`data.py` 中的质量门控在遇到 ERROR 级别的失败时会阻塞 pipeline。如果它 触发了,请修复数据,而不是放松检查标准。 - **文档**——`docs/phases/*.docx` 是自动生成的。请编辑 `scripts/build_docs.js` 并 重新运行 `node scripts/build_docs.js`;切勿手动编辑 docx,否则它会与代码产生偏差。
标签:Apex, Python, 安全规则引擎, 异常检测, 无后门, 机器学习, 逆向工具, 量化交易, 金融市场