Download scripts/correlation_breakdown.py from Dev2506/flash-crash-watchdog: direct link, hf CLI and curl.
- Browser
- Download file 2.77 kB
-
https://huggingface.co/Dev2506/flash-crash-watchdog/resolve/main/scripts/correlation_breakdown.py
- Command line
-
hf download hf://Dev2506/flash-crash-watchdog/scripts/correlation_breakdown.py
-
curl -L -o correlation_breakdown.py https://huggingface.co/Dev2506/flash-crash-watchdog/resolve/main/scripts/correlation_breakdown.py
2.77 kB
| #!/usr/bin/env python3 | |
| """AF-1 core-signal MVP: cross-asset correlation-breakdown detection. | |
| Aligns two symbols' mid-prices on a common time grid and measures the rolling | |
| pairwise correlation. Test: on a CRASH day (BTC/ETH 2021-05-19), does the | |
| correlation COLLAPSE around the crash trough vs the calm baseline of the same | |
| day? If yes, a rolling-correlation-drop rule is a viable early-warning signal | |
| that the (disabled) Stage-4 was supposed to provide. | |
| Usage: PYTHONPATH=ml python scripts/correlation_breakdown.py | |
| """ | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| sys.path.insert(0, "ml") | |
| def load_mid(parquet, start_ts=None, end_ts=None, step=1): | |
| df = pd.read_parquet(parquet, columns=["timestamp_ms", "mid_price"]) | |
| if start_ts is not None: | |
| df = df[(df.timestamp_ms >= start_ts) & (df.timestamp_ms <= end_ts)] | |
| df = df.dropna(subset=["mid_price"]) | |
| # resample to 1s grid via mean mid per second | |
| df["sec"] = df.timestamp_ms // 1000 | |
| s = df.groupby("sec").mid_price.mean() | |
| return s | |
| def main() -> int: | |
| btc = load_mid("data/parquet/BTCUSDT_2021-05-19.parquet") | |
| eth = load_mid("data/parquet/ETHUSDT_2021-05-19.parquet") | |
| # inner join on the 1s grid | |
| both = pd.concat([btc, eth], axis=1, keys=["btc", "eth"]).dropna() | |
| b = both.btc; e = both.eth | |
| print("aligned 1s samples:", len(b)) | |
| print("BTC day range %.2f -> %.2f | ETH %.2f -> %.2f" | |
| % (b.min(), b.max(), e.min(), e.max())) | |
| # crash trough in this slice | |
| trough = int(e.idxmin()) | |
| print("ETH trough at sec", trough) | |
| # rolling correlation (log-returns over a ~120s window) | |
| rb = np.log(b / b.shift(1)); re = np.log(e / e.shift(1)) | |
| corr = rb.rolling(120).corr(re) | |
| # z-score vs the day's calm (pre-crash) baseline | |
| pre = corr.loc[: trough - 600].dropna() | |
| calm_mean, calm_std = pre.mean(), pre.std() | |
| print("pre-crash (calm) rolling-corr: mean=%.3f std=%.3f" % (calm_mean, calm_std)) | |
| crash_win = corr.loc[trough - 300: trough + 60].dropna() | |
| if len(crash_win) and calm_std > 1e-6: | |
| z = (crash_win.mean() - calm_mean) / calm_std | |
| print("crash-window rolling-corr mean=%.3f (z=%.2f vs calm)" % (crash_win.mean(), z)) | |
| print("correlation COLLAPSE" if z <= -2.0 else | |
| ("correlation weakens (z=%.1f)" % z)) | |
| else: | |
| print("insufficient data for crash-window corr") | |
| # how early does the corr drop? find first time corr < calm_mean - 2*std | |
| thr = calm_mean - 2.0 * calm_std | |
| below = corr[corr < thr] | |
| if len(below): | |
| first = below.index.min() | |
| print("corr first < calm-2std at sec %d (%.0f s before ETH trough)" % (first, trough - first)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |