| 303 |
303 |
ax.set_ylabel(f"Pooled QLIKE ratio to GARCH(1,1)-N, h = {h}"); ax.legend(ncol=3, fontsize=6) |
| 304 |
304 |
ps.save(fig, path); _audit(path.name, ["results/metrics/analysis_*/per_symbol/*.json"], len(specs) * len(keys)) |
| 305 |
305 |
return path |
|
306 |
+ |
|
307 |
+ |
|
308 |
+# ----------------------------------------------------------------------------- 13. extensions vs parents (exploratory) |
|
309 |
+EXT_PARENT = {"stgarch11": "gjr11", "betategarch11": "egarch11", "garch_midas": "garch11", "hygarch11": "figarch11", |
|
310 |
+ "fiaparch11": "aparch11", "fiegarch11": "egarch11", "realegarch11": "realgarch11"} |
|
311 |
+ |
|
312 |
+ |
|
313 |
+def extension_heatmap(per_symbol: dict, h: int, path: Path, dist_pref=("t", "normal")) -> Path: |
|
314 |
+ """Instruments x (extension, parent) pairs: QLIKE ratio of the extension to its parent (same innovation law), log colour scale.""" |
|
315 |
+ syms = sorted(per_symbol, key=lambda s: (CLASS_ORDER.index(CLASS_OF[s]), s)) |
|
316 |
+ pairs = [] |
|
317 |
+ for ext, par in EXT_PARENT.items(): |
|
318 |
+ for d in dist_pref: |
|
319 |
+ e_id, p_id = f"{ext}__{d}__w1000_r5", f"{par}__{d}__w1000_r5" |
|
320 |
+ if any(e_id in per_symbol[s]["horizons"][str(h)]["specs"] and p_id in per_symbol[s]["horizons"][str(h)]["specs"] for s in syms): |
|
321 |
+ pairs.append((e_id, p_id)); break |
|
322 |
+ M = np.full((len(syms), len(pairs)), np.nan) |
|
323 |
+ for i, s in enumerate(syms): |
|
324 |
+ d = per_symbol[s]["horizons"][str(h)]["specs"] |
|
325 |
+ for j, (e_id, p_id) in enumerate(pairs): |
|
326 |
+ re_, rp = d.get(e_id, {}).get("ratio", np.nan), d.get(p_id, {}).get("ratio", np.nan) |
|
327 |
+ if np.isfinite(re_) and np.isfinite(rp) and rp > 0: |
|
328 |
+ M[i, j] = re_ / rp |
|
329 |
+ fig, ax = plt.subplots(figsize=(ps.W1 * 1.4, 0.16 * len(syms) + 1.5)) |
|
330 |
+ im = ax.imshow(np.log(M), cmap=ps.DIV, vmin=np.log(0.8), vmax=np.log(1.25), aspect="auto") |
|
331 |
+ for i in range(len(syms)): |
|
332 |
+ for j in range(len(pairs)): |
|
333 |
+ if np.isfinite(M[i, j]): |
|
334 |
+ ax.text(j, i, f"{M[i, j]:.2f}", ha="center", va="center", fontsize=4.8, color=ps.INK) |
|
335 |
+ ax.set_xticks(range(len(pairs))); ax.set_xticklabels([f"{spec_label(e)}\nvs {spec_label(p)}" for e, p in pairs], rotation=90, fontsize=5.5) |
|
336 |
+ ax.set_yticks(range(len(syms))); ax.set_yticklabels(syms, fontsize=5.5) |
|
337 |
+ for k in range(1, len(syms)): |
|
338 |
+ if CLASS_OF[syms[k]] != CLASS_OF[syms[k - 1]]: |
|
339 |
+ ax.axhline(k - 0.5, color=ps.INK, lw=0.6) |
|
340 |
+ cb = fig.colorbar(im, ax=ax, fraction=0.03, pad=0.01); cb.set_ticks(np.log([0.8, 0.9, 1.0, 1.1, 1.25])); cb.set_ticklabels(["0.80", "0.90", "1.00", "1.10", "1.25"]) |
|
341 |
+ cb.set_label("QLIKE ratio, extension / parent") |
|
342 |
+ ax.grid(False); ax.set_title(f"Post-registration extensions vs their parents, h = {h} (exploratory)", loc="left", fontsize=8) |
|
343 |
+ ps.save(fig, path); _audit(path.name, ["results/metrics/analysis_*/per_symbol/*.json"], int(np.isfinite(M).sum()), "exploratory") |
|
344 |
+ return path |
| 306 |
345 |
|