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
## 核心理念
大多数交易系统试图通过频繁交易来获取收益。本系统恰恰相反。
它**默认保持全额投资**,仅在认为即将出现结构断裂时采取行动——
在崩盘前卖出转为现金,在反弹前买回,其余时间保持不动。每笔交易都会
产生经纪费、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, 安全规则引擎, 异常检测, 无后门, 机器学习, 逆向工具, 量化交易, 金融市场