SPB Git

spb/wp40_uqo

Public MIT
19.9 KB · 182 lines python
1#!/usr/bin/env python32"""Journal-style figures for the manuscript, generated from the tracked artifacts. Writes paper/figures/fig_*.pdf and3appends their SHA-256 to experiments/manifests/paper_assets.json. Run after make_paper_assets.py."""4import os, sys, json, collections, hashlib, statistics5import numpy as np6sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))7import paper_style as ps; ps.setup()8import matplotlib.pyplot as plt9from matplotlib.ticker import FuncFormatter, LogLocator10ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."); RES = os.path.join(ROOT, "experiments", "results"); RAW = os.path.join(RES, "raw")11FIG = os.path.join(ROOT, "paper", "figures"); os.makedirs(FIG, exist_ok=True)12key = json.load(open(os.path.join(RES, "key_numbers.json"))); rng = np.random.default_rng(20261102); outputs = {}13def sha(p): return hashlib.sha256(open(p, "rb").read()).hexdigest()14def load(name):15    p = os.path.join(RAW, name); return [json.loads(l) for l in open(p) if l.strip()] if os.path.exists(p) else []16def ci(x, n=2000): x = np.asarray(x, float); m = [np.median(rng.choice(x, x.size, replace=True)) for _ in range(n)]; return float(np.median(x)), float(np.percentile(m, 2.5)), float(np.percentile(m, 97.5))17def save(fig, name): p = os.path.join(FIG, name); fig.savefig(p, bbox_inches="tight", metadata={"CreationDate": None}); plt.close(fig); outputs["figures/" + name] = sha(p); print("wrote", name)18W = 6.3  # text width in inches (letter, 1in margins)1920# ---------------------------------------------------------------- Fig 1: primitives — throughput (A) and speedup with CIs (B)21prim = [r for r in load("bench_primitives.jsonl") if r.get("kernel") != "shader_library_load"]22g = collections.defaultdict(list)23for r in prim: g[(r["kernel"], r["S"], r["n"], r["backend"], r["threads"] == 1)].append(r["wall_seconds"])24med = {k: ci(v) for k, v in g.items()}25kernels = [("realized_fused", "Realized"), ("regression_stats", "Regression"), ("rolling_sumsq", "Rolling sum"), ("garch_nll_P64", "GARCH NLL")]26fig, axes = plt.subplots(2, 4, figsize=(W, 4.2), gridspec_kw={"hspace": 0.6, "wspace": 0.42})27for j, (k, title) in enumerate(kernels):28    ax = axes[0, j]29    for be, one in [("cpu", True), ("cpu", False), ("metal", False)]:30        tag = "cpu1" if one else ("cpuN" if be == "cpu" else "metal")31        pts = sorted([(S * n, S * n / med[(kk, S, n, b, o)][0]) for (kk, S, n, b, o) in med if kk == k and b == be and o == one])32        if pts: ax.plot([p[0] for p in pts], [p[1] for p in pts], marker=ps.MARK[tag], ms=2.6, lw=0.8, color=ps.PALETTE[tag], label=ps.LABEL[tag], markerfacecolor="white" if tag == "cpu1" else None, markeredgewidth=0.6)33    ax.set_xscale("log"); ax.set_yscale("log"); ps.panel(ax, "ABCD"[j], title); ps.light_grid(ax)34    if j == 0: ax.set_ylabel("Elements per second")35    ax = axes[1, j]36    pts = []37    for (kk, S, n, b, o) in med:38        if kk == k and b == "metal":39            c = med.get((k, S, n, "cpu", False)); m = med[(kk, S, n, b, o)]40            if c: pts.append((S * n, c[0] / m[0], c[0] / m[2], c[0] / m[1]))41    pts.sort()42    if pts:43        x = [p[0] for p in pts]; y = [p[1] for p in pts]; lo = [p[2] for p in pts]; hi = [p[3] for p in pts]44        ax.errorbar(x, y, yerr=[np.subtract(y, lo), np.subtract(hi, y)], fmt="^", ms=3, color=ps.PALETTE["metal"], ecolor="#b2182b", elinewidth=0.6, capsize=1.5, lw=0)45    ax.set_xscale("log"); ax.set_yscale("log"); ps.ref_line(ax, 1.0, None); ps.light_grid(ax); ax.set_xlabel("Elements")46    if j == 0: ax.set_ylabel("Speedup, Metal / CPU 18 threads")47    ps.panel(ax, "EFGH"[j], "Speedup")48h, l = axes[0, 0].get_legend_handles_labels(); fig.legend(h, l, loc="lower center", ncol=3, bbox_to_anchor=(0.5, -0.04), fontsize=7)49save(fig, "fig_primitives.pdf")5051# ---------------------------------------------------------------- Fig 2: E1 — (A) throughput real, (B) speedup grid heat, (C) fusion gain52e1 = [r for r in load("e1_batched_rv.jsonl") if r.get("kind") == "bench"]53g1 = collections.defaultdict(list)54for r in e1: g1[(r["source"], r["S"], r["segment"], r["backend"])].append(r["wall_seconds"])55m1 = {k: ci(v) for k, v in g1.items()}56segs = ["day", "week", "month", "year"]; Ss = sorted({r["S"] for r in e1 if r["source"] == "real"})57fig, axes = plt.subplots(1, 3, figsize=(W, 2.4), gridspec_kw={"wspace": 0.55, "width_ratios": [1.4, 1, 1]})58ax = axes[0]59for tag, be in [("cpu1", "cpu1"), ("cpuN", "cpuN"), ("metal", "metal"), ("metal_unfused", "metal_unfused")]:60    pts = sorted([(next(r["elements"] for r in e1 if (r["source"], r["S"], r["segment"], r["backend"]) == ("real", S, s, be)), next(r["elements"] for r in e1 if (r["source"], r["S"], r["segment"], r["backend"]) == ("real", S, s, be)) / m1[("real", S, s, be)][0]) for S in Ss for s in segs if ("real", S, s, be) in m1])61    ax.plot([p[0] for p in pts], [p[1] for p in pts], ls="none", marker=ps.MARK[tag], ms=3, color=ps.PALETTE[tag], label=ps.LABEL[tag], markerfacecolor="white" if tag == "cpu1" else None, markeredgewidth=0.6)62ax.set_xscale("log"); ax.set_yscale("log"); ps.light_grid(ax); ax.set_xlabel("Returns in the batch"); ax.set_ylabel("Returns per second"); ps.panel(ax, "A", "Throughput, real panel"); ax.legend(fontsize=6, loc="lower right", handletextpad=0.3)63ax = axes[1]; M = np.array([[m1[("real", S, s, "cpuN")][0] / m1[("real", S, s, "metal")][0] for s in segs] for S in Ss])64im = ax.imshow(M, cmap="RdGy_r", vmin=0.6, vmax=1.4, aspect="auto"); ax.set_xticks(range(4)); ax.set_xticklabels(segs); ax.set_yticks(range(len(Ss))); ax.set_yticklabels([str(S) for S in Ss]); ax.set_xlabel("Segment length"); ax.set_ylabel("Panel width $S$")65for i in range(len(Ss)):66    for j in range(4): ax.text(j, i, f"{M[i, j]:.2f}", ha="center", va="center", fontsize=6.5, color="white" if abs(M[i, j] - 1) > 0.25 else "black")67ps.panel(ax, "B", "Speedup vs. CPU"); ax.spines["top"].set_visible(True); ax.spines["right"].set_visible(True)68ax = axes[2]; F = np.array([[m1[("real", S, s, "metal_unfused")][0] / m1[("real", S, s, "metal")][0] for s in segs] for S in Ss])69im2 = ax.imshow(F, cmap="Greys", vmin=1.0, vmax=3.5, aspect="auto"); ax.set_xticks(range(4)); ax.set_xticklabels(segs); ax.set_yticks(range(len(Ss))); ax.set_yticklabels([str(S) for S in Ss]); ax.set_xlabel("Segment length")70for i in range(len(Ss)):71    for j in range(4): ax.text(j, i, f"{F[i, j]:.2f}", ha="center", va="center", fontsize=6.5, color="white" if F[i, j] > 2.2 else "black")72ps.panel(ax, "C", "Fusion gain"); ax.spines["top"].set_visible(True); ax.spines["right"].set_visible(True)73save(fig, "fig_e1.pdf")7475# ---------------------------------------------------------------- Fig 3: precision — Metal vs FP64 relative error vs segment length (E1) + theory76fig, ax = plt.subplots(figsize=(W * 0.6, 2.3))77pts = collections.defaultdict(list)78for r in e1:79    if r["backend"] == "metal" and r["source"] == "real": pts[r["segment"]].append((r["elements"] / r["segments"], r["max_rel_err_rv_vs_fp64"]))80xs, ys = [], []81for s in segs:82    for M_, e in pts[s]: xs.append(M_); ys.append(e)83ax.scatter(xs, ys, s=10, color=ps.PALETTE["metal"], marker="^", zorder=3, label="Observed maximum over segments (E1)")84Mgrid = np.logspace(2.4, 5.1, 50); u = 2 ** -2485ax.plot(Mgrid, 2 * u * np.sqrt(3 / Mgrid), color="#4d4d4d", lw=0.8, ls=(0, (4, 2)), label="RMS prediction $2u\\sqrt{3/M}$")86ax.plot(Mgrid, 3.2 * 2 * u * np.sqrt(3 / Mgrid), color="#4d4d4d", lw=0.8, ls=(0, (1, 1.5)), label="Expected maximum over 926 segments ($3.2\\sigma$)")87ax.set_xscale("log"); ax.set_yscale("log"); ax.set_xlabel("Returns per segment $M$"); ax.set_ylabel("Relative error"); ps.light_grid(ax, "both"); ax.legend(fontsize=6.5, loc="upper right"); ax.set_title("Metal vs. FP64 realized variance", loc="left", fontsize=8.5, fontweight="bold", pad=6)88save(fig, "fig_precision.pdf")8990# ---------------------------------------------------------------- Fig 4: E6 — stacked stages (A) and engines with CIs (B)91e6 = load("e6_pipeline.jsonl"); e6b = load("e6_baselines.jsonl")92fig, axes = plt.subplots(1, 2, figsize=(W, 2.3), gridspec_kw={"wspace": 0.4, "width_ratios": [1, 1.2]})93ax = axes[0]; stages = ["read_seconds", "prep_seconds", "batch_seconds", "estimate_seconds"]; names = ["Parquet read", "Session filter + returns", "Batch build", "Estimators"]; cols = ["#1a1a1a", "#4d4d4d", "#999999", "#b2182b"]94for i, be in enumerate(["cpu", "metal"]):95    rs = [r for r in e6 if r["backend_used"] == be]; bottom = 096    for st, nm, c in zip(stages, names, cols):97        v = statistics.median(r[st] for r in rs); ax.bar(i, v, bottom=bottom, color=c, width=0.6, label=nm if i == 0 else None, edgecolor="white", linewidth=0.4); bottom += v98ax.set_xticks([0, 1]); ax.set_xticklabels(["RAID, CPU", "RAID, Metal"]); ax.set_xlim(-0.6, 1.6); ax.set_ylabel("Seconds per file (2.4M bars)"); ps.light_grid(ax); ax.legend(fontsize=6.5, loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=2); ps.panel(ax, "A", "Stage breakdown")99ax = axes[1]; eng = [("RAID, CPU", [r["total_seconds"] for r in e6 if r["backend_used"] == "cpu"], ps.PALETTE["raid"]), ("RAID, Metal", [r["total_seconds"] for r in e6 if r["backend_used"] == "metal"], "#ef8a62"), ("Polars", [r["total_seconds"] for r in e6b if r["engine"] == "polars"], ps.PALETTE["polars"]), ("DuckDB", [r["total_seconds"] for r in e6b if r["engine"] == "duckdb"], ps.PALETTE["duckdb"]), ("pandas", [r["total_seconds"] for r in e6b if r["engine"] == "pandas"], ps.PALETTE["pandas"])]100for i, (nm, v, c) in enumerate(eng):101    m, lo, hi = ci(v); ax.barh(i, m, color=c, height=0.6, edgecolor="white", linewidth=0.4); ax.errorbar(m, i, xerr=[[m - lo], [hi - m]], fmt="none", ecolor="#1a1a1a", elinewidth=0.6, capsize=2); ax.text(m * 1.08, i, f"{m:.3f} s", va="center", fontsize=7)102ax.set_yticks(range(len(eng))); ax.set_yticklabels([e[0] for e in eng]); ax.invert_yaxis(); ax.set_xscale("log"); ax.set_xlabel("Seconds per file (median, 95% CI)"); ax.set_xlim(0.05, 6); ps.light_grid(ax, "x"); ps.panel(ax, "B", "End-to-end engines")103save(fig, "fig_e6.pdf")104105# ---------------------------------------------------------------- Fig 5: E7 — speedup vs GPU cores (A) and cold/warm compile (B)106e7 = key["E7"]; order = sorted(e7, key=lambda n: e7[n]["gpu_cores"])107fig, axes = plt.subplots(1, 2, figsize=(W, 2.6), gridspec_kw={"wspace": 0.45, "width_ratios": [1.4, 1]})108ax = axes[0]109for k, lab, c, mk in [("realized_fused@1000x3900_speedup_MT", "Fused realized, $1000\\times3900$", ps.PALETTE["metal"], "^"), ("garch_nll_P64@1000x390_speedup_MT", "GARCH likelihood, $P=64$", "#1a1a1a", "o"), ("regression_stats@1000x3900_speedup_MT", "Regression statistics", "#2166ac", "s"), ("rolling_sumsq@1000x3900_speedup_MT", "Rolling sum of squares", "#999999", "v")]:110    ax.scatter([e7[n]["gpu_cores"] for n in order], [e7[n][k] for n in order], s=14, color=c, marker=mk, label=lab, zorder=3)111for n in order: ax.annotate(e7[n]["chip"].replace("Apple ", ""), (e7[n]["gpu_cores"], e7[n]["realized_fused@1000x3900_speedup_MT"]), fontsize=5.5, xytext=(3, 3), textcoords="offset points", color="#4d4d4d")112ps.ref_line(ax, 1.0, None); ax.set_yscale("log"); ax.set_xlabel("GPU cores"); ax.set_ylabel("Speedup, Metal / CPU all threads"); ax.set_ylim(0.12, 12); ps.light_grid(ax, "y"); ax.legend(fontsize=6.3, loc="upper center", bbox_to_anchor=(0.5, -0.28), ncol=2); ps.panel(ax, "A", "Eleven Apple Silicon machines")113ax = axes[1]; y = np.arange(len(order))114ax.barh(y - 0.18, [1e3 * e7[n]["cold_compile_s"] for n in order], height=0.36, color="#1a1a1a", label="Cold (salted source)"); ax.barh(y + 0.18, [1e3 * e7[n]["warm_compile_s"] for n in order], height=0.36, color="#999999", label="Warm (shader cache)")115ax.set_yticks(y); ax.set_yticklabels([e7[n]["chip"].replace("Apple ", "") for n in order], fontsize=6.5); ax.set_xscale("log"); ax.set_xlabel("Runtime MSL compilation (ms)"); ax.invert_yaxis(); ps.light_grid(ax, "x"); ax.legend(fontsize=6.3, loc="lower right"); ps.panel(ax, "B", "Shader compilation")116save(fig, "fig_e7.pdf")117118# ---------------------------------------------------------------- Fig 6: E2 — QLIKE distributions by horizon (A) and runtime (B)119e2 = load("e2_har.jsonl"); acc = [r for r in e2 if r.get("kind") == "accuracy"]; rt = [r for r in e2 if r.get("kind") == "runtime"]120fig, axes = plt.subplots(1, 2, figsize=(W, 2.4), gridspec_kw={"wspace": 0.4, "width_ratios": [1.5, 1]})121ax = axes[0]; pos = 0; ticks = []; labels = []122for h in [1, 5, 22]:123    rs = [r for r in acc if r["h"] == h]124    for m, nm, c in [("random_walk", "Random walk", "#999999"), ("har", "HAR-RV", "#1a1a1a"), ("loghar", "log-HAR", ps.PALETTE["metal"]), ("harq", "HARQ", "#2166ac")]:125        v = [r[m]["qlike"] for r in rs if "qlike" in r.get(m, {})]126        bp = ax.boxplot(v, positions=[pos], widths=0.6, showfliers=False, patch_artist=True, medianprops={"color": "white", "lw": 1}, boxprops={"facecolor": c, "edgecolor": c, "lw": 0.5}, whiskerprops={"color": c, "lw": 0.6}, capprops={"color": c, "lw": 0.6})127        if h == 1: bp["boxes"][0].set_label(nm)128        pos += 1129    ticks.append(pos - 2.5); labels.append(f"$h={h}$"); pos += 1130ax.set_xticks(ticks); ax.set_xticklabels(labels); ax.set_ylabel("QLIKE across 100 symbols"); ps.light_grid(ax); ax.legend(fontsize=6.5, loc="upper left", ncol=2); ps.panel(ax, "A", "Rolling causal forecasts, 2024")131ax = axes[1]; g2 = collections.defaultdict(list)132for r in rt: g2[(r["S"], r["mode"])].append(r["wall_seconds"])133Ss2 = sorted({r["S"] for r in rt})134for m, nm, c, mk in [("per_series_cpu1", "Per-series QR, 1 thread", "#8c8c8c", "s"), ("per_series_cpuMT", "Per-series QR, 18 threads", "#1a1a1a", "o"), ("batched_cpu", "Batched statistics, CPU", "#2166ac", "D"), ("batched_metal", "Batched statistics, Metal", ps.PALETTE["metal"], "^")]:135    ax.plot(Ss2, [1e3 * ci(g2[(S, m)])[0] for S in Ss2], marker=mk, ms=3, color=c, label=nm, markerfacecolor="white" if m == "per_series_cpu1" else None, markeredgewidth=0.6)136ax.set_xscale("log"); ax.set_yscale("log"); ax.set_xlabel("Series ($T=252$ each)"); ax.set_ylabel("Milliseconds"); ps.light_grid(ax, "both"); ax.legend(fontsize=6, loc="upper left"); ps.panel(ax, "B", "HAR estimation runtime")137save(fig, "fig_e2.pdf")138139# ---------------------------------------------------------------- Fig 7: E3 — convergence (A), persistence distribution (B), QLIKE (C)140e3 = load("e3_garch.jsonl"); fits = [r for r in e3 if r.get("kind") == "fit"]141models = [("garch.n", "GARCH"), ("gjr.n", "GJR"), ("egarch.n", "EGARCH"), ("garch.t", "GARCH-$t$")]142fig, axes = plt.subplots(1, 3, figsize=(W, 2.3), gridspec_kw={"wspace": 0.5})143ax = axes[0]144for i, (m, nm) in enumerate(models):145    rs = [r for r in fits if r["model"] == m]; conv = 100 * np.mean([bool(r.get("converged")) for r in rs]); ax.bar(i, conv, color="#1a1a1a" if conv > 99 else ("#4d4d4d" if conv > 95 else ps.PALETTE["metal"]), width=0.6); ax.text(i, conv + 1, f"{conv:.1f}%", ha="center", fontsize=6.5)146ax.set_xticks(range(4)); ax.set_xticklabels([m[1] for m in models], rotation=25, ha="right"); ax.set_ylim(80, 103); ax.set_ylabel("Converged fits (%)"); ps.light_grid(ax); ps.panel(ax, "A", "Convergence, 888 series")147ax = axes[1]148for i, (m, nm) in enumerate(models):149    v = [r["persistence"] for r in fits if r["model"] == m and r.get("converged") and r.get("persistence") is not None and 0 < r["persistence"] < 1.05]150    ax.boxplot(v, positions=[i], widths=0.55, showfliers=False, patch_artist=True, medianprops={"color": "white", "lw": 1}, boxprops={"facecolor": "#4d4d4d", "edgecolor": "#4d4d4d", "lw": 0.5}, whiskerprops={"color": "#4d4d4d", "lw": 0.6}, capprops={"color": "#4d4d4d", "lw": 0.6})151ax.set_xticks(range(4)); ax.set_xticklabels([m[1] for m in models], rotation=25, ha="right"); ax.set_ylabel("Persistence"); ps.light_grid(ax); ps.panel(ax, "B", "Persistence")152ax = axes[2]153for i, (m, nm) in enumerate(models):154    v = [r["qlike_vs_rv"] for r in fits if r["model"] == m and r.get("converged") and r.get("qlike_vs_rv") is not None]155    ax.boxplot(v, positions=[i], widths=0.55, showfliers=False, patch_artist=True, medianprops={"color": "white", "lw": 1}, boxprops={"facecolor": "#1a1a1a", "edgecolor": "#1a1a1a", "lw": 0.5}, whiskerprops={"color": "#1a1a1a", "lw": 0.6}, capprops={"color": "#1a1a1a", "lw": 0.6})156rw = [r["qlike_rw_rv"] for r in fits if r["model"] == "garch.n" and r.get("qlike_rw_rv") is not None]; ax.axhline(np.median(rw), color=ps.PALETTE["metal"], lw=0.8, ls=(0, (4, 2))); ax.text(-0.45, np.median(rw) + 0.03, "RV random walk (median)", fontsize=6, color=ps.PALETTE["metal"], ha="left", va="bottom")157ax.set_xticks(range(4)); ax.set_xticklabels([m[1] for m in models], rotation=25, ha="right"); ax.set_ylabel("QLIKE vs. next-day RV"); ax.set_ylim(0, 1.5); ps.light_grid(ax); ps.panel(ax, "C", "One-day forecasts")158save(fig, "fig_e3.pdf")159160# ---------------------------------------------------------------- Fig 8: E5 — coverage (A) and bias/RMSE (B)161e5 = key["E5"]; fig, axes = plt.subplots(1, 2, figsize=(W, 2.5), gridspec_kw={"wspace": 0.45})162ax = axes[0]; params = ["mu", "omega", "alpha", "beta"]; lab = ["$\\mu$", "$\\omega$", "$\\alpha$", "$\\beta$"]; x = np.arange(4)163ax.bar(x - 0.18, [e5["garch_n500"][p]["coverage95"] for p in params], width=0.36, color="#999999", label="GARCH(1,1), $n=500$"); ax.bar(x + 0.18, [e5["garch_n2000"][p]["coverage95"] for p in params], width=0.36, color="#1a1a1a", label="GARCH(1,1), $n=2000$")164ax.bar(np.arange(4) + 5 - 0.18, e5["har_h1"], width=0.36, color="#4393c3", label="HAR $h=1$ (HAC)"); ax.bar(np.arange(4) + 5 + 0.18, e5["har_h5"], width=0.36, color="#2166ac", label="HAR $h=5$ (HAC)")165ax.axhline(0.95, color=ps.PALETTE["metal"], lw=0.8, ls=(0, (4, 2))); ax.set_xticks(list(x) + list(np.arange(4) + 5)); ax.set_xticklabels(lab + ["$\\beta_0$", "$\\beta_d$", "$\\beta_w$", "$\\beta_m$"]); ax.set_ylim(0.8, 1.0); ax.set_ylabel("Empirical coverage of 95% intervals"); ps.light_grid(ax); ax.legend(fontsize=6, loc="upper center", bbox_to_anchor=(0.5, -0.22), ncol=2); ps.panel(ax, "A", "Coverage, $R=1000$")166ax = axes[1]167for i, (n, c) in enumerate([("garch_n500", "#999999"), ("garch_n2000", "#1a1a1a")]):168    ax.errorbar(x + (i - 0.5) * 0.3, [e5[n][p]["bias"] for p in params], yerr=[e5[n][p]["rmse"] for p in params], fmt="o", ms=3, color=c, ecolor=c, elinewidth=0.7, capsize=2, label=f"$n={n[7:]}$: bias $\\pm$ RMSE")169ax.axhline(0, color="#4d4d4d", lw=0.6); ax.set_xticks(x); ax.set_xticklabels(lab); ax.set_ylabel("Bias (markers) and RMSE (bars)"); ps.light_grid(ax); ax.legend(fontsize=6.5); ps.panel(ax, "B", "GARCH(1,1) QML")170save(fig, "fig_e5.pdf")171172# ---------------------------------------------------------------- Fig 9: E4 — conditioning (A), shrinkage (B), connectedness and time (C)173e4 = key["E4"]; Ns = sorted(int(n) for n in e4)174fig, axes = plt.subplots(1, 3, figsize=(W, 2.2), gridspec_kw={"wspace": 0.6})175ax = axes[0]; ax.plot(Ns, [min(e4[str(n)]["cond_median"], 1e9) for n in Ns], marker="o", ms=3, color="#1a1a1a", label="Median"); ax.plot(Ns, [min(e4[str(n)]["cond_max"], 1e9) for n in Ns], marker="s", ms=3, color="#999999", label="Maximum"); ax.set_yscale("log"); ax.set_xlabel("$N$ (assets)"); ax.set_ylabel("Condition number of daily RCov"); ax.axvline(78, color=ps.PALETTE["metal"], lw=0.7, ls=(0, (4, 2))); ax.text(80, 3e2, "$M=78$ bins", rotation=90, fontsize=6.5, color=ps.PALETTE["metal"], ha="left", va="bottom"); ps.light_grid(ax, "y"); ax.legend(fontsize=6.5, loc="upper left"); ps.panel(ax, "A", "Conditioning")176ax = axes[1]; ax.plot(Ns, [e4[str(n)]["lw"] for n in Ns], marker="^", ms=3, color="#2166ac"); ax.set_xlabel("$N$ (assets)"); ax.set_ylabel("Ledoit–Wolf shrinkage $\\delta^\\star$"); ps.light_grid(ax, "y"); ps.panel(ax, "B", "Shrinkage")177ax = axes[2]; ax.plot(Ns, [e4[str(n)]["dy_total"] for n in Ns], marker="o", ms=3, color="#1a1a1a", label="Total connectedness (%)"); ax.set_xlabel("$N$ (assets)"); ax.set_ylabel("Mean total connectedness (%)"); ps.light_grid(ax, "y"); ps.panel(ax, "C", "Connectedness")178for n in Ns: ax.annotate(f"{e4[str(n)]['dy_ms']:.1f} ms", (n, e4[str(n)]["dy_total"]), xytext=(5, 2) if n < 100 else (-28, -9), textcoords="offset points", fontsize=6, color="#4d4d4d")179save(fig, "fig_e4.pdf")180181mf = os.path.join(ROOT, "experiments", "manifests", "paper_assets.json"); man = json.load(open(mf)); man["outputs"].update(outputs); json.dump(man, open(mf, "w"), indent=1); print("manifest updated")182
Files · edf90b664f59d98f1489651599148df98a2fb1d1
Menu

Theme