File size: 3,691 Bytes
4dbfe21 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | #!/usr/bin/env python3
"""Forecast fan-chart gallery, one facet per model.
Rows = assets, cols = models; per timeframe one PNG. Each facet: recent
real series (grey), the actual future (black), the model's median forecast
(dashed) and its quantile bands (10-90% and 30-70% shaded; for a 13-level
model the trained 2.5-97.5% tail band is shaded very lightly underneath).
Quantile indices are taken from each dump's own `levels` array.
Usage: zoo_plot2.py <tf> (reads data/forecast-dumps/plotdump-<tag>-<tf>.npz)
"""
import sys
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
TAGS = [("yv02", "Yumoto v0.2", "#0072B2"),
("yv01", "Yumoto v0.1", "#56B4E9"),
("toto25b", "Toto-2.0-2.5B", "#E69F00"),
("tirex2", "TiRex-2", "#009E73"),
("timesfm3", "TimesFM-3.0", "#CC79A7")]
TF_TITLE = {"5m": "5-minute", "1h": "hourly", "4h": "4-hourly", "1d": "daily"}
tf = sys.argv[1]
dumps = {}
for tag, _, _ in TAGS:
try:
dumps[tag] = np.load(f"data/forecast-dumps/plotdump-{tag}-{tf}.npz",
allow_pickle=True)
except FileNotFoundError:
pass
ref = next(iter(dumps.values()))
assets = [str(x) for x in ref["assets"]]
def q_at(d, name, level):
lv = np.asarray(d["levels"], float)
i = int(np.argmin(np.abs(lv - level)))
assert abs(lv[i] - level) < 1e-6, (level, lv)
return np.asarray(d[f"q_{name}"])[:, i]
fig, axes = plt.subplots(len(assets), len(TAGS),
figsize=(3.6 * len(TAGS), 2.7 * len(assets)),
squeeze=False, dpi=150)
for r, name in enumerate(assets):
for c, (tag, label, color) in enumerate(TAGS):
ax = axes[r][c]
if tag not in dumps:
ax.axis("off"); continue
d = dumps[tag]
ctx = np.asarray(d[f"ctx_{name}"], float)
act = np.asarray(d[f"act_{name}"], float)
H = act.size
# show a context tail ~1.5x the horizon so the forecast has room
keep = min(ctx.size, int(1.5 * H))
xs_c = np.arange(-keep, 0); xs_f = np.arange(H)
lv = np.asarray(d["levels"], float)
if lv.min() < 0.05: # 13-level model: show the trained tail band
ax.fill_between(xs_f, q_at(d, name, 0.025), q_at(d, name, 0.975),
color=color, alpha=0.10, lw=0)
ax.fill_between(xs_f, q_at(d, name, 0.1), q_at(d, name, 0.9),
color=color, alpha=0.22, lw=0)
ax.fill_between(xs_f, q_at(d, name, 0.3), q_at(d, name, 0.7),
color=color, alpha=0.38, lw=0)
ax.plot(xs_c, ctx[-keep:], color="#999999", lw=0.9)
ax.plot(xs_f, act, color="#111111", lw=1.1)
ax.plot(xs_f, q_at(d, name, 0.5), color=color, lw=1.2, ls="--")
ax.axvline(0, color="#bbbbbb", lw=0.7)
lo = min(ctx[-keep:].min(), act.min()); hi = max(ctx[-keep:].max(), act.max())
pad = 0.35 * (hi - lo + 1e-12)
ax.set_ylim(lo - pad, hi + pad)
ax.tick_params(labelsize=6.5)
for s in ("top", "right"): ax.spines[s].set_visible(False)
if r == 0: ax.set_title(label, fontsize=10, color=color)
if c == 0: ax.set_ylabel(name, fontsize=9)
if r == len(assets) - 1: ax.set_xlabel("steps from forecast origin", fontsize=7.5)
fig.suptitle(f"{TF_TITLE[tf]} candles — held-out forecasts "
"(grey: context · black: what actually happened · "
"dashed: median forecast · shading: quantile bands)",
fontsize=11, y=1.005)
fig.tight_layout()
fig.savefig(f"plots/forecasts/{tf}.png", bbox_inches="tight")
print("wrote", f"plots/forecasts/{tf}.png")
|