Download scripts/make_method_diagram.py from chonthichar/crypto-forecasting-ai: direct link, hf CLI and curl.
- Browser
- Download file 6.86 kB
-
https://huggingface.co/spaces/chonthichar/crypto-forecasting-ai/resolve/main/scripts/make_method_diagram.py
- Command line
-
hf download hf://spaces/chonthichar/crypto-forecasting-ai/scripts/make_method_diagram.py
-
curl -L -o make_method_diagram.py https://huggingface.co/spaces/chonthichar/crypto-forecasting-ai/resolve/main/scripts/make_method_diagram.py
6.86 kB
| """Method diagram: the live price-range forecaster (model B) next to the | |
| volatility-regime classifier, step by step, with how each was tested. | |
| Numbers are read from the deployed artifacts where they exist, so the figure | |
| matches what is live: btc_deployment_models/price_range/params.json and | |
| outputs/price_backtest_fixes.json. | |
| Usage: | |
| python scripts/make_method_diagram.py | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| from matplotlib.patches import FancyArrowPatch, FancyBboxPatch | |
| ROOT = Path(__file__).resolve().parents[1] | |
| SURFACE, INK, INK_2, GRID = "#fcfcfb", "#0b0b0b", "#52514e", "#e6e5e1" | |
| BLUE, ORANGE = "#2a78d6", "#eb6834" | |
| FILL = {BLUE: "#eef3fb", ORANGE: "#fdf0ea"} | |
| def main() -> None: | |
| p = json.loads((ROOT / "btc_deployment_models" / "price_range" / "params.json").read_text()) | |
| bt = json.loads((ROOT / "outputs" / "price_backtest_fixes.json").read_text())["walk_forward"] | |
| hw = p["hour_of_week_variance"] | |
| cov = bt["24h | + Fed/CPI calendar"]["coverage"] | |
| n = bt["24h | + Fed/CPI calendar"]["n"] | |
| q80 = p["quantiles"]["24"]["0.8"] | |
| left = [ | |
| ("1 Recent volatility", | |
| "Last 168 hourly BTC prices \u2192 \u03c3 = how much\nthe price typically moves per hour"), | |
| ("2 Weekly pattern", | |
| f"Each of the 168 hours of the week gets a factor:\nweekends {p['weekend_factor']:.2f}\u00d7, " | |
| f"range {min(hw):.2f}\u00d7 \u2013 {max(hw):.2f}\u00d7 normal"), | |
| ("3 Fed / CPI calendar", | |
| f"Release hour {p['k_release']:.1f}\u00d7 variance, next 2 hours " | |
| f"{p['k_after']:.2f}\u00d7\n(dates are published months ahead: no look-ahead)"), | |
| ("4 Combine over the horizon", | |
| "\u03c3\u2095 = \u03c3 \u00d7 \u221a(sum of the factors of the next h hours)"), | |
| ("5 Fat tails \u2192 range", | |
| f"price \u00d7 exp(\u00b1 q \u00d7 \u03c3\u2095), q learned from 2 years of\nreal outcomes " | |
| f"(80%: q = {q80:.2f} instead of the normal 1.28)"), | |
| ] | |
| vt = json.loads((ROOT / "btc_deployment_models" / "volatility" | |
| / "volatility_btc_24h_vaetransformer.json").read_text()) | |
| right = [ | |
| ("1 Recent market data", | |
| "Last 168 hours (+ 90 days for the reference level)\nof hourly price, volume, trades, high/low"), | |
| ("2 Features relative to normal", | |
| f"{len(vt['features'])} features per hour (volatility 6\u2013168h, ATR, volume \u2026),\n" | |
| "volatility ones divided by the 90-day median"), | |
| ("3 Question (target)", | |
| "Will the next 24h be MORE volatile than\nthe 90-day median? yes / no"), | |
| ("4 Model: VAE + Transformer", | |
| f"VAE encodes each hour, Transformer reads the {vt['sequence_length']}h\nsequence " | |
| f"\u2192 P(high); {vt['seeds']}-seed average, cut-off {vt['decision_threshold']:.2f}"), | |
| ("5 Answer", | |
| "\u201cHIGH\u201d or \u201cLOW\u201d volatility for the next 24h"), | |
| ] | |
| # Both examples are the live Space's own answers for the 17 Oct 2026 12:00 UTC candle. | |
| outputs = [ | |
| (BLUE, "Output: a price range", | |
| "live, from \\$81,520, next 24h:\n80% chance between \\$80,519 and \\$82,534"), | |
| (ORANGE, "Output: a yes/no regime", | |
| "live, next 24h: LOW volatility\n(probability of high: 0.31)"), | |
| ] | |
| tests = [ | |
| (BLUE, "Tested walk-forward 2023 \u2013 2026", | |
| f"Refit every quarter on the previous 2 years,\n{n:,} unseen forecasts per horizon\n\n" | |
| f"50 / 80 / 95% ranges held\n{cov['50%'] * 100:.1f} / {cov['80%'] * 100:.1f} / " | |
| f"{cov['95%'] * 100:.1f}% of the time (24h)\n" | |
| "3\u20137% better than plain volatility (1\u201324h)"), | |
| # Persistence on the same held-out span, recomputed from btc_1h.csv | |
| # (which ends 2026-09-27, a few days before the model's holdout does). | |
| (ORANGE, "Tested on unseen hours, Mar \u2013 Sep 2026", | |
| f"Last {vt['holdout_rows']:,} hours held out, trained on the {vt['trained_rows']:,} before\n\n" | |
| f"Accuracy {vt['holdout_accuracy'] * 100:.1f}% vs \u2248 64.5% for \u201csame as now\u201d\n" | |
| f"ROC-AUC {vt['holdout_auc_ensemble']:.2f}\n" | |
| "(earlier tree model, 5 walk-forward folds 2022\u201326:\n71.3% \u00b1 2.2 vs 65.6% persistence)"), | |
| ] | |
| plt.rcParams.update({"font.family": "DejaVu Sans", "text.color": INK}) | |
| fig = plt.figure(figsize=(14, 13.2), facecolor=SURFACE) | |
| ax = fig.add_axes([0, 0, 1, 1]) | |
| ax.set_xlim(0, 14); ax.set_ylim(0, 13.2); ax.axis("off") | |
| def box(x, y, w, h, title, body, color, strong=False): | |
| ax.add_patch(FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.02,rounding_size=0.12", | |
| facecolor=color if strong else FILL[color], | |
| edgecolor=color, linewidth=1.6 if strong else 1.1)) | |
| ink = "white" if strong else INK | |
| ax.text(x + 0.22, y + h - 0.2, title, fontsize=11, fontweight="bold", va="top", color=ink) | |
| ax.text(x + 0.22, y + h - 0.58, body, fontsize=9.6, va="top", color="white" if strong else INK_2, | |
| linespacing=1.4) | |
| def arrow(x, y0, y1, color): | |
| ax.add_patch(FancyArrowPatch((x, y0), (x, y1), arrowstyle="-|>", mutation_scale=13, | |
| color=color, linewidth=1.3)) | |
| ax.text(0.5, 12.85, "Two forecasts from the same idea: volatility clusters and is predictable", | |
| fontsize=15, fontweight="bold", va="top") | |
| ax.text(0.5, 12.38, "Price direction is a coin flip in every test we ran. How much the price " | |
| "will move is not, and both models below forecast that, in two different forms.", | |
| fontsize=9.8, color=INK_2, va="top") | |
| cols = [(0.5, BLUE, "A Price range (live: \u201cWhere will the price be?\u201d)", left), | |
| (7.2, ORANGE, "B Volatility regime (live: 24h volatility panel)", right)] | |
| w, h, gap, top = 6.3, 1.12, 0.32, 11.55 | |
| for x, color, head, steps in cols: | |
| ax.text(x, top + 0.42, head, fontsize=12, fontweight="bold", color=color, va="center") | |
| y = top | |
| for i, (title, body) in enumerate(steps): | |
| box(x, y - h, w, h, title, body, color) | |
| arrow(x + w / 2, y - h, y - h - gap, color) | |
| y -= h + gap | |
| k = 0 if color == BLUE else 1 | |
| oc, ot, ob = outputs[k] | |
| box(x, y - 1.05, w, 1.05, ot, ob, oc, strong=True) | |
| arrow(x + w / 2, y - 1.05, y - 1.05 - gap, color) | |
| y -= 1.05 + gap | |
| tc, tt, tb = tests[k] | |
| box(x, y - 2.15, w, 2.15, tt, tb, tc) | |
| fig.savefig(ROOT / "outputs" / "figures" / "fig14_method_diagram.png", dpi=150, facecolor=SURFACE) | |
| plt.close(fig) | |
| print("written: outputs/figures/fig14_method_diagram.png") | |
| if __name__ == "__main__": | |
| main() | |