Upload scripts/generate_paper_figures.py with huggingface_hub
Browse files- scripts/generate_paper_figures.py +162 -157
scripts/generate_paper_figures.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
| 2 |
⚡ ULTRON Systems // 100% Deterministic Empirical Figure Generator (300 DPI)
|
| 3 |
Zero synthetic data. Reads directly from:
|
| 4 |
- logs/pretraining_telemetry_verified.json (855 real logged pre-training steps)
|
| 5 |
-
- logs/
|
| 6 |
-
|
|
|
|
| 7 |
"""
|
| 8 |
import os
|
| 9 |
import json
|
|
@@ -17,21 +18,20 @@ plt.style.use('seaborn-v0_8-whitegrid' if 'seaborn-v0_8-whitegrid' in plt.style.
|
|
| 17 |
mpl.rcParams['font.family'] = 'serif'
|
| 18 |
mpl.rcParams['font.size'] = 9.5
|
| 19 |
mpl.rcParams['axes.labelsize'] = 10
|
| 20 |
-
mpl.rcParams['axes.titlesize'] =
|
| 21 |
mpl.rcParams['xtick.labelsize'] = 8.5
|
| 22 |
mpl.rcParams['ytick.labelsize'] = 8.5
|
| 23 |
-
mpl.rcParams['legend.fontsize'] = 8
|
| 24 |
mpl.rcParams['figure.titlesize'] = 12
|
| 25 |
|
| 26 |
with open("logs/pretraining_telemetry_verified.json") as f:
|
| 27 |
pre_data = json.load(f)
|
| 28 |
|
| 29 |
-
with open("logs/
|
| 30 |
-
oos_27 = json.load(f)
|
| 31 |
-
|
| 32 |
-
with open("logs/tier1_verified_audit.json") as f:
|
| 33 |
audit = json.load(f)
|
| 34 |
|
|
|
|
|
|
|
| 35 |
# =========================================================================
|
| 36 |
# Figure 1: REAL Empirical Pre-Training Telemetry (855 Logged Steps)
|
| 37 |
# =========================================================================
|
|
@@ -41,7 +41,6 @@ steps = np.array([r["step"] for r in records])
|
|
| 41 |
t_loss = np.array([r["total_loss"] for r in records])
|
| 42 |
d_acc = np.array([r["dir_acc_total"] for r in records])
|
| 43 |
|
| 44 |
-
# Compute strictly causal 25-point moving average for trend visibility alongside raw points
|
| 45 |
def causal_sma(arr, w=25):
|
| 46 |
out = np.zeros_like(arr, dtype=np.float64)
|
| 47 |
for i in range(len(arr)):
|
|
@@ -80,60 +79,53 @@ fig.savefig("papers/figures/fig1_pretraining_curves.png", dpi=300, bbox_inches='
|
|
| 80 |
plt.close()
|
| 81 |
|
| 82 |
# =========================================================================
|
| 83 |
-
# Figure 2:
|
| 84 |
# =========================================================================
|
| 85 |
-
print("Rendering Figure 2 from
|
| 86 |
-
horizons = ["1m", "3m", "5m", "15m", "30m", "60m", "1d"]
|
| 87 |
-
acc_2024 = [oos_27["static_oos_zero_shot"]["2024"]["metrics"]["per_horizon"][h]["dir_acc"] for h in horizons]
|
| 88 |
-
acc_2025 = [oos_27["static_oos_zero_shot"]["2025"]["metrics"]["per_horizon"][h]["dir_acc"] for h in horizons]
|
| 89 |
-
acc_2026 = [oos_27["static_oos_zero_shot"]["2026"]["metrics"]["per_horizon"][h]["dir_acc"] for h in horizons]
|
| 90 |
-
|
| 91 |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.0), dpi=300)
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
x = np.arange(len(horizons))
|
| 94 |
-
w = 0.
|
| 95 |
-
ax1.bar(x - w,
|
| 96 |
-
ax1.bar(x,
|
| 97 |
-
ax1.bar(x + w,
|
| 98 |
-
ax1.axhline(
|
| 99 |
ax1.set_xticks(x)
|
| 100 |
ax1.set_xticklabels(horizons)
|
| 101 |
-
ax1.set_ylim(
|
| 102 |
-
ax1.set_ylabel('
|
| 103 |
-
ax1.set_title('(a)
|
| 104 |
ax1.legend(loc='upper right', frameon=True)
|
| 105 |
ax1.grid(axis='y', alpha=0.3)
|
| 106 |
|
| 107 |
-
# Panel (b):
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
audit["quantitative_baselines_oos"]["2024"]["MeanReversion_5Bar"]["overall_nonzero_dir_acc"],
|
| 123 |
-
audit["multi_checkpoint_oos_audit"]["epoch1"]["2024"]["overall_nonzero_dir_acc"],
|
| 124 |
-
audit["multi_checkpoint_oos_audit"]["epoch2"]["2024"]["overall_nonzero_dir_acc"],
|
| 125 |
-
audit["multi_checkpoint_oos_audit"]["epoch3"]["2024"]["overall_nonzero_dir_acc"]
|
| 126 |
-
]
|
| 127 |
-
xb = np.arange(len(models_lbl))
|
| 128 |
-
ax2.bar(xb - 0.18, raw_vals, 0.35, label='Raw Directional Acc (%)', color='#1E3A8A', edgecolor='black', lw=0.5)
|
| 129 |
-
ax2.bar(xb + 0.18, nz_vals, 0.35, label='Non-Zero Strict Acc (%)', color='#38BDF8', edgecolor='black', lw=0.5)
|
| 130 |
-
ax2.axhline(50.0, color='#E11D48', linestyle='--', lw=1.2)
|
| 131 |
ax2.set_xticks(xb)
|
| 132 |
-
ax2.set_xticklabels(
|
| 133 |
-
ax2.set_ylim(
|
| 134 |
-
ax2.set_ylabel('
|
| 135 |
-
ax2.set_title('(b)
|
| 136 |
-
ax2.legend(loc='upper
|
| 137 |
ax2.grid(axis='y', alpha=0.3)
|
| 138 |
|
| 139 |
plt.tight_layout()
|
|
@@ -141,146 +133,159 @@ fig.savefig("papers/figures/fig2_oos_directional_accuracy.png", dpi=300, bbox_in
|
|
| 141 |
plt.close()
|
| 142 |
|
| 143 |
# =========================================================================
|
| 144 |
-
# Figure 3:
|
| 145 |
# =========================================================================
|
| 146 |
-
print("Rendering Figure 3 from
|
| 147 |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.0), dpi=300)
|
| 148 |
|
| 149 |
-
modules = ["Return\nHead", "Conformer\nBackbone", "
|
| 150 |
-
mod_keys = ["return_regression_head", "
|
| 151 |
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
|
|
|
| 156 |
|
| 157 |
xm = np.arange(len(modules))
|
| 158 |
-
wm = 0.
|
| 159 |
-
ax1.bar(xm -
|
| 160 |
-
ax1.bar(xm
|
| 161 |
-
ax1.bar(xm +
|
| 162 |
-
ax1.
|
| 163 |
-
ax1.set_yscale('log')
|
| 164 |
ax1.set_xticks(xm)
|
| 165 |
-
ax1.set_xticklabels(modules)
|
| 166 |
-
ax1.set_ylabel('
|
| 167 |
-
ax1.set_title('(a)
|
| 168 |
-
ax1.legend(loc='upper left', frameon=True, fontsize=7.
|
| 169 |
-
ax1.grid(True,
|
| 170 |
-
|
| 171 |
-
# Panel (b):
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
ax2.set_xticks(xc)
|
| 182 |
-
ax2.set_xticklabels(
|
| 183 |
-
ax2.set_ylim(0.
|
| 184 |
ax2.set_ylabel('7-Horizon Mean ROC-AUC')
|
| 185 |
-
ax2.set_title('(b)
|
| 186 |
-
ax2.legend(loc='
|
| 187 |
-
ax2.grid(
|
| 188 |
|
| 189 |
plt.tight_layout()
|
| 190 |
fig.savefig("papers/figures/fig3_walk_forward_comparison.png", dpi=300, bbox_inches='tight', pad_inches=0.1)
|
| 191 |
plt.close()
|
| 192 |
|
| 193 |
# =========================================================================
|
| 194 |
-
# Figure 4: Mechanistic
|
| 195 |
# =========================================================================
|
| 196 |
-
print("Rendering Figure 4 from
|
| 197 |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.0), dpi=300)
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
|
|
|
| 201 |
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
logit_l2_1d = [audit["mechanistic_kernel_ablation_7h"]["ablations"][b]["per_horizon"]["1d"]["logit_l2_perturbation_pct"] for b in ab_branches]
|
| 205 |
|
| 206 |
-
xa = np.arange(len(
|
| 207 |
-
wa = 0.
|
| 208 |
-
ax1.bar(xa - wa,
|
| 209 |
-
ax1.bar(xa,
|
| 210 |
-
ax1.
|
| 211 |
ax1.set_xticks(xa)
|
| 212 |
-
ax1.set_xticklabels(
|
| 213 |
-
ax1.set_ylabel('
|
| 214 |
-
ax1.set_title('(a)
|
| 215 |
-
ax1.legend(loc='upper left', frameon=True)
|
| 216 |
ax1.grid(axis='y', alpha=0.3)
|
| 217 |
|
| 218 |
-
# Panel (b):
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
ax2.
|
| 228 |
-
ax2.
|
| 229 |
-
ax2.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
plt.tight_layout()
|
| 232 |
fig.savefig("papers/figures/fig4_kernel_ablation.png", dpi=300, bbox_inches='tight', pad_inches=0.1)
|
| 233 |
plt.close()
|
| 234 |
|
| 235 |
# =========================================================================
|
| 236 |
-
# Figure 5:
|
| 237 |
# =========================================================================
|
| 238 |
-
print("Rendering Figure 5 from
|
| 239 |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.0), dpi=300)
|
| 240 |
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
ax1.
|
| 251 |
-
ax1.
|
| 252 |
-
ax1.
|
| 253 |
-
ax1.axhline(
|
| 254 |
-
ax1.
|
| 255 |
-
ax1.
|
| 256 |
-
ax1.
|
| 257 |
-
ax1.
|
| 258 |
-
ax1.
|
| 259 |
-
ax1.
|
|
|
|
| 260 |
|
| 261 |
-
# Panel (b):
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
ax2.plot(
|
| 272 |
-
ax2.plot(
|
| 273 |
-
ax2.plot(
|
| 274 |
-
ax2.
|
| 275 |
-
ax2.
|
| 276 |
-
ax2.
|
| 277 |
-
ax2.
|
| 278 |
-
ax2.
|
|
|
|
| 279 |
ax2.grid(True, alpha=0.3)
|
| 280 |
|
| 281 |
plt.tight_layout()
|
| 282 |
fig.savefig("papers/figures/fig5_slippage_friction_curve.png", dpi=300, bbox_inches='tight', pad_inches=0.1)
|
| 283 |
plt.close()
|
| 284 |
|
| 285 |
-
print("✅ All 5 deterministic empirical figures rendered at 300 DPI!")
|
| 286 |
-
|
|
|
|
| 2 |
⚡ ULTRON Systems // 100% Deterministic Empirical Figure Generator (300 DPI)
|
| 3 |
Zero synthetic data. Reads directly from:
|
| 4 |
- logs/pretraining_telemetry_verified.json (855 real logged pre-training steps)
|
| 5 |
+
- logs/defensible_tier1_audit.json (Stratified 15-ticker contiguous OOS & Pre-2024 Stress audit,
|
| 6 |
+
exact global cosine similarities, 1m return-magnitude buckets, paired bootstrap CIs,
|
| 7 |
+
mean-ablation + random-channel controls, and non-overlapping state-turnover backtests)
|
| 8 |
"""
|
| 9 |
import os
|
| 10 |
import json
|
|
|
|
| 18 |
mpl.rcParams['font.family'] = 'serif'
|
| 19 |
mpl.rcParams['font.size'] = 9.5
|
| 20 |
mpl.rcParams['axes.labelsize'] = 10
|
| 21 |
+
mpl.rcParams['axes.titlesize'] = 10.5
|
| 22 |
mpl.rcParams['xtick.labelsize'] = 8.5
|
| 23 |
mpl.rcParams['ytick.labelsize'] = 8.5
|
| 24 |
+
mpl.rcParams['legend.fontsize'] = 7.8
|
| 25 |
mpl.rcParams['figure.titlesize'] = 12
|
| 26 |
|
| 27 |
with open("logs/pretraining_telemetry_verified.json") as f:
|
| 28 |
pre_data = json.load(f)
|
| 29 |
|
| 30 |
+
with open("logs/defensible_tier1_audit.json") as f:
|
|
|
|
|
|
|
|
|
|
| 31 |
audit = json.load(f)
|
| 32 |
|
| 33 |
+
horizons = ["1m", "3m", "5m", "15m", "30m", "60m", "1d"]
|
| 34 |
+
|
| 35 |
# =========================================================================
|
| 36 |
# Figure 1: REAL Empirical Pre-Training Telemetry (855 Logged Steps)
|
| 37 |
# =========================================================================
|
|
|
|
| 41 |
t_loss = np.array([r["total_loss"] for r in records])
|
| 42 |
d_acc = np.array([r["dir_acc_total"] for r in records])
|
| 43 |
|
|
|
|
| 44 |
def causal_sma(arr, w=25):
|
| 45 |
out = np.zeros_like(arr, dtype=np.float64)
|
| 46 |
for i in range(len(arr)):
|
|
|
|
| 79 |
plt.close()
|
| 80 |
|
| 81 |
# =========================================================================
|
| 82 |
+
# Figure 2: 1m Base-Rate / Flat-Bar Decomposition, Return Buckets & Baselines
|
| 83 |
# =========================================================================
|
| 84 |
+
print("Rendering Figure 2 from 1m return-magnitude buckets & multi-horizon ROC-AUC...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.0), dpi=300)
|
| 86 |
|
| 87 |
+
# Panel (a): All-Bar ROC-AUC vs Strict Non-Zero ROC-AUC across Horizons (2024 OOS, N=8,044)
|
| 88 |
+
ep3_24 = audit["multi_checkpoint_oos_and_stress_audit"]["epoch3"]["2024"]["per_horizon"]
|
| 89 |
+
ep3_26 = audit["multi_checkpoint_oos_and_stress_audit"]["epoch3"]["2026"]["per_horizon"]
|
| 90 |
+
auc_all_24 = [ep3_24[h]["roc_auc"] for h in horizons]
|
| 91 |
+
auc_nz_24 = [ep3_24[h]["nonzero_roc_auc"] for h in horizons]
|
| 92 |
+
auc_nz_26 = [ep3_26[h]["nonzero_roc_auc"] for h in horizons]
|
| 93 |
+
|
| 94 |
x = np.arange(len(horizons))
|
| 95 |
+
w = 0.26
|
| 96 |
+
ax1.bar(x - w, auc_all_24, w, label='2024 All-Bar ROC-AUC', color='#0284C7', edgecolor='black', lw=0.5)
|
| 97 |
+
ax1.bar(x, auc_nz_24, w, label='2024 Non-Zero (y≠0) ROC-AUC', color='#0D9488', edgecolor='black', lw=0.5)
|
| 98 |
+
ax1.bar(x + w, auc_nz_26, w, label='2026 Non-Zero (y≠0) ROC-AUC', color='#D97706', edgecolor='black', lw=0.5)
|
| 99 |
+
ax1.axhline(0.5000, color='#E11D48', linestyle='--', lw=1.2, label='Random Guess / Flat Heuristic (0.5000)')
|
| 100 |
ax1.set_xticks(x)
|
| 101 |
ax1.set_xticklabels(horizons)
|
| 102 |
+
ax1.set_ylim(0.480, 0.545)
|
| 103 |
+
ax1.set_ylabel('Threshold-Independent ROC-AUC')
|
| 104 |
+
ax1.set_title('(a) All-Bar vs. Strict Non-Zero (y≠0) ROC-AUC by Horizon')
|
| 105 |
ax1.legend(loc='upper right', frameon=True)
|
| 106 |
ax1.grid(axis='y', alpha=0.3)
|
| 107 |
|
| 108 |
+
# Panel (b): 1-Minute Accuracy & ROC-AUC by |y| Return Magnitude Quartile (Q1..Q4, y != 0)
|
| 109 |
+
b_keys = ["Q1_Smallest_Move", "Q2_Moderate_Move", "Q3_Medium_Move", "Q4_Largest_Move"]
|
| 110 |
+
b_lbls = ["Q1 Micro\n(~0.75 bps)", "Q2 Small\n(~2.1 bps)", "Q3 Medium\n(~4.4 bps)", "Q4 Large\n(~12.4 bps)"]
|
| 111 |
+
cal_acc_24 = [ep3_24["1m"]["return_magnitude_buckets"][bk]["calibrated_dir_acc"] for bk in b_keys]
|
| 112 |
+
cal_acc_26 = [ep3_26["1m"]["return_magnitude_buckets"][bk]["calibrated_dir_acc"] for bk in b_keys]
|
| 113 |
+
b_auc_24 = [ep3_24["1m"]["return_magnitude_buckets"][bk]["roc_auc"] * 100.0 for bk in b_keys]
|
| 114 |
+
b_auc_26 = [ep3_26["1m"]["return_magnitude_buckets"][bk]["roc_auc"] * 100.0 for bk in b_keys]
|
| 115 |
+
|
| 116 |
+
xb = np.arange(len(b_lbls))
|
| 117 |
+
wb = 0.20
|
| 118 |
+
ax2.bar(xb - 1.5*wb, cal_acc_24, wb, label='2024 Calibrated Acc (%)', color='#1E3A8A', edgecolor='black', lw=0.5)
|
| 119 |
+
ax2.bar(xb - 0.5*wb, b_auc_24, wb, label='2024 Bucket ROC-AUC (×100)', color='#38BDF8', edgecolor='black', lw=0.5)
|
| 120 |
+
ax2.bar(xb + 0.5*wb, cal_acc_26, wb, label='2026 Calibrated Acc (%)', color='#0F766E', edgecolor='black', lw=0.5)
|
| 121 |
+
ax2.bar(xb + 1.5*wb, b_auc_26, wb, label='2026 Bucket ROC-AUC (×100)', color='#F59E0B', edgecolor='black', lw=0.5)
|
| 122 |
+
ax2.axhline(50.0, color='#E11D48', linestyle='--', lw=1.2, label='50.0% Null Benchmark')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
ax2.set_xticks(xb)
|
| 124 |
+
ax2.set_xticklabels(b_lbls, fontsize=8)
|
| 125 |
+
ax2.set_ylim(49.0, 54.8)
|
| 126 |
+
ax2.set_ylabel('Non-Zero Bucket Accuracy (%) / AUC (×100)')
|
| 127 |
+
ax2.set_title('(b) 1-Minute Non-Zero Discrimination by |Return| Quartile')
|
| 128 |
+
ax2.legend(loc='upper right', frameon=True, fontsize=7.2)
|
| 129 |
ax2.grid(axis='y', alpha=0.3)
|
| 130 |
|
| 131 |
plt.tight_layout()
|
|
|
|
| 133 |
plt.close()
|
| 134 |
|
| 135 |
# =========================================================================
|
| 136 |
+
# Figure 3: Frobenius Drift vs RMS Update Arithmetic & Pre-2024 Stress Forgetting
|
| 137 |
# =========================================================================
|
| 138 |
+
print("Rendering Figure 3 from Frobenius drift / Adam RMS arithmetic & Pre-2024 stress audit...")
|
| 139 |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.0), dpi=300)
|
| 140 |
|
| 141 |
+
modules = ["Return\nHead", "Depthwise\nConv", "SwiGLU\nFFN", "Conformer\nBackbone", "Dir.\nHead", "GQA\nAttn", "Inception\nTokenizer"]
|
| 142 |
+
mod_keys = ["return_regression_head", "sub_depthwise_conv", "sub_swiglu_ffn", "conformer_backbone_all", "directional_class_head", "sub_gqa_attention", "inception_tokenizer"]
|
| 143 |
|
| 144 |
+
wd = audit["weight_drift_and_cosine_audit"]
|
| 145 |
+
drift_wf24 = [wd["epoch3_to_wf2024"]["by_module"][k]["rel_frobenius_pct"] for k in mod_keys]
|
| 146 |
+
drift_wf25 = [wd["epoch3_to_wf2025"]["by_module"][k]["rel_frobenius_pct"] for k in mod_keys]
|
| 147 |
+
rms_dw_wf24 = [wd["epoch3_to_wf2024"]["by_module"][k]["rms_update_magnitude"] * 1e4 for k in mod_keys]
|
| 148 |
+
rms_w_base = [wd["epoch3_to_wf2024"]["by_module"][k]["rms_weight_norm"] * 50.0 for k in mod_keys]
|
| 149 |
|
| 150 |
xm = np.arange(len(modules))
|
| 151 |
+
wm = 0.25
|
| 152 |
+
ax1.bar(xm - wm, drift_wf24, wm, label='Rel. Drift Ep3→WF24 (%)', color='#0284C7', edgecolor='black', lw=0.5)
|
| 153 |
+
ax1.bar(xm, drift_wf25, wm, label='Rel. Drift Ep3→WF25 (%)', color='#0D9488', edgecolor='black', lw=0.5)
|
| 154 |
+
ax1.bar(xm + wm, rms_dw_wf24, wm, label='Abs. RMS Update ||ΔW||_RMS (×10⁴)', color='#F59E0B', edgecolor='black', lw=0.5)
|
| 155 |
+
ax1.plot(xm, rms_w_base, marker='D', color='#E11D48', lw=1.5, linestyle='--', label='Base Weight Norm ||W||_RMS (×50)')
|
|
|
|
| 156 |
ax1.set_xticks(xm)
|
| 157 |
+
ax1.set_xticklabels(modules, fontsize=7.5)
|
| 158 |
+
ax1.set_ylabel('Drift (%) / Scaled RMS Magnitude')
|
| 159 |
+
ax1.set_title('(a) Plasticity Hierarchy: Relative Drift vs. Absolute RMS Update')
|
| 160 |
+
ax1.legend(loc='upper left', frameon=True, fontsize=7.2)
|
| 161 |
+
ax1.grid(True, alpha=0.3)
|
| 162 |
+
|
| 163 |
+
# Panel (b): Pre-2024 Historical Stress Regimes & Forward OOS ROC-AUC (Epoch 3 vs WF-2024 vs WF-2025)
|
| 164 |
+
cohorts_lbl = ["2020-03\nCOVID Crash", "2022-06\nRate Shock", "2023-12\nPre-Anchor", "Pre-2024\nCombined", "2024\nOOS", "2025\nOOS", "2026\nForward"]
|
| 165 |
+
c_keys = ["stress_2020_03", "stress_2022_06", "stress_2023_12", "pre2024_stress", "2024", "2025", "2026"]
|
| 166 |
+
|
| 167 |
+
mc = audit["multi_checkpoint_oos_and_stress_audit"]
|
| 168 |
+
auc_ep3 = [mc["epoch3"][ck]["overall_roc_auc"] for ck in c_keys]
|
| 169 |
+
auc_wf24 = [mc["wf2024"][ck]["overall_roc_auc"] for ck in c_keys]
|
| 170 |
+
auc_wf25 = [mc["wf2025"][ck]["overall_roc_auc"] for ck in c_keys]
|
| 171 |
+
|
| 172 |
+
xc = np.arange(len(cohorts_lbl))
|
| 173 |
+
wc = 0.25
|
| 174 |
+
ax2.bar(xc - wc, auc_ep3, wc, label='Frozen Epoch 3 (2010–2023)', color='#1E3A8A', edgecolor='black', lw=0.5)
|
| 175 |
+
ax2.bar(xc, auc_wf24, wc, label='Online WF-2024 (+2024)', color='#0EA5E9', edgecolor='black', lw=0.5)
|
| 176 |
+
ax2.bar(xc + wc, auc_wf25, wc, label='Online WF-2025 (+2024–25)', color='#10B981', edgecolor='black', lw=0.5)
|
| 177 |
+
ax2.axhline(0.5000, color='#E11D48', linestyle='--', lw=1.1, label='Random Null (0.5000)')
|
| 178 |
ax2.set_xticks(xc)
|
| 179 |
+
ax2.set_xticklabels(cohorts_lbl, fontsize=7.5)
|
| 180 |
+
ax2.set_ylim(0.500, 0.522)
|
| 181 |
ax2.set_ylabel('7-Horizon Mean ROC-AUC')
|
| 182 |
+
ax2.set_title('(b) Backward Stress Forgetting & Forward Transfer Audit')
|
| 183 |
+
ax2.legend(loc='upper right', frameon=True, fontsize=7.2)
|
| 184 |
+
ax2.grid(axis='y', alpha=0.3)
|
| 185 |
|
| 186 |
plt.tight_layout()
|
| 187 |
fig.savefig("papers/figures/fig3_walk_forward_comparison.png", dpi=300, bbox_inches='tight', pad_inches=0.1)
|
| 188 |
plt.close()
|
| 189 |
|
| 190 |
# =========================================================================
|
| 191 |
+
# Figure 4: Controlled Mechanistic Kernel Ablation (Mean-Ablation, Controls & Denominator Effect)
|
| 192 |
# =========================================================================
|
| 193 |
+
print("Rendering Figure 4 from controlled mean-ablation, random-channel controls & RMS logit shift...")
|
| 194 |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.0), dpi=300)
|
| 195 |
|
| 196 |
+
ab = audit["mechanistic_kernel_ablation_controlled"]
|
| 197 |
+
ab_keys = ["MeanAblate_K3", "MeanAblate_K7", "MeanAblate_K15", "MeanAblate_K31", "MeanAblate_K63", "MeanAblate_K127", "Random_341ch_Control", "MeanAblate_Micro_K3_K7", "MeanAblate_Meso_K15_K31", "MeanAblate_Macro_K63_K127", "Random_682ch_Control"]
|
| 198 |
+
ab_lbls = ["K=3", "K=7", "K=15", "K=31", "K=63", "K=127", "Rand\n341ch", "Micro\n(3,7)", "Meso\n(15,31)", "Macro\n(63,127)", "Rand\n682ch"]
|
| 199 |
|
| 200 |
+
cent_mean = [ab["mean_ablations"][k]["mean_centered_logit_l2_pct"] for k in ab_keys]
|
| 201 |
+
cent_zero = [ab["zero_ablations"][k.replace("MeanAblate_", "ZeroAblate_")]["mean_centered_logit_l2_pct"] for k in ab_keys]
|
|
|
|
| 202 |
|
| 203 |
+
xa = np.arange(len(ab_lbls))
|
| 204 |
+
wa = 0.36
|
| 205 |
+
ax1.bar(xa - wa/2, cent_mean, wa, label='In-Distribution Mean-Ablation (Centered L2 %)', color='#4F46E5', edgecolor='black', lw=0.5)
|
| 206 |
+
ax1.bar(xa + wa/2, cent_zero, wa, label='Zero-Clamping Ablation (Centered L2 %)', color='#0EA5E9', edgecolor='black', lw=0.5)
|
| 207 |
+
ax1.axhline(ab["mean_ablations"]["Random_341ch_Control"]["mean_centered_logit_l2_pct"], color='#E11D48', linestyle=':', lw=1.2, label='Random 341-Ch Mean-Ablate Null (20.31%)')
|
| 208 |
ax1.set_xticks(xa)
|
| 209 |
+
ax1.set_xticklabels(ab_lbls, fontsize=7.2)
|
| 210 |
+
ax1.set_ylabel('Centered Logit Shift ||Δ(z - z̄)||_2 / ||z - z̄||_2 (%)')
|
| 211 |
+
ax1.set_title('(a) Centered Logit Sensitivity vs. Random-Channel Controls')
|
| 212 |
+
ax1.legend(loc='upper left', frameon=True, fontsize=7.2)
|
| 213 |
ax1.grid(axis='y', alpha=0.3)
|
| 214 |
|
| 215 |
+
# Panel (b): Resolving the Denominator Effect across Horizons (Uncentered Raw L2 vs Centered L2 vs Abs RMS)
|
| 216 |
+
k7_ph = ab["mean_ablations"]["MeanAblate_K7"]["per_horizon"]
|
| 217 |
+
meso_ph = ab["mean_ablations"]["MeanAblate_Meso_K15_K31"]["per_horizon"]
|
| 218 |
+
raw_l2_k7 = [k7_ph[h]["raw_logit_l2_pct"] for h in horizons]
|
| 219 |
+
cent_l2_k7 = [k7_ph[h]["centered_logit_l2_pct"] for h in horizons]
|
| 220 |
+
rms_k7_scaled = [k7_ph[h]["abs_logit_rms_shift"] * 1000.0 for h in horizons]
|
| 221 |
+
base_rms_scaled = [ab["baseline"]["per_horizon"][h]["logit_rms"] * 200.0 for h in horizons]
|
| 222 |
+
|
| 223 |
+
xh = np.arange(len(horizons))
|
| 224 |
+
ax2.plot(xh, raw_l2_k7, marker='o', lw=2.0, color='#D97706', label='Uncentered ||Δz||_2 / ||z||_2 (%) [Peaks at 15m]')
|
| 225 |
+
ax2.plot(xh, cent_l2_k7, marker='s', lw=2.0, color='#4F46E5', label='Centered ||Δ(z-z̄)||_2 / ||z-z̄||_2 (%) [Variance-Norm]')
|
| 226 |
+
ax2.plot(xh, rms_k7_scaled, marker='^', lw=2.0, color='#0F766E', label='Denominator-Free RMS(Δz) (×1000) [Monotonic]')
|
| 227 |
+
ax2.plot(xh, base_rms_scaled, marker='x', lw=1.4, linestyle='--', color='#64748B', label='Baseline Denom. ||z||_RMS (×200) [Min at 15m]')
|
| 228 |
+
ax2.set_xticks(xh)
|
| 229 |
+
ax2.set_xticklabels(horizons)
|
| 230 |
+
ax2.set_ylabel('Perturbation (%) / Scaled RMS Units')
|
| 231 |
+
ax2.set_title('(b) Decomposition of the 15m Logit Denominator Effect (K=7)')
|
| 232 |
+
ax2.legend(loc='upper right', frameon=True, fontsize=7.0)
|
| 233 |
+
ax2.grid(True, alpha=0.3)
|
| 234 |
|
| 235 |
plt.tight_layout()
|
| 236 |
fig.savefig("papers/figures/fig4_kernel_ablation.png", dpi=300, bbox_inches='tight', pad_inches=0.1)
|
| 237 |
plt.close()
|
| 238 |
|
| 239 |
# =========================================================================
|
| 240 |
+
# Figure 5: Non-Overlapping Contiguous Backtest & Frozen 2024 Policy OOS
|
| 241 |
# =========================================================================
|
| 242 |
+
print("Rendering Figure 5 from non-overlapping state-turnover backtest & frozen 2024 policy...")
|
| 243 |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.0), dpi=300)
|
| 244 |
|
| 245 |
+
# Panel (a): Fixed Hurdle (tau=1.0sigma) Walk-Forward Gross Return & State-Hold Breakeven Across 2024, 2025, 2026
|
| 246 |
+
wf_tau1 = audit["frozen_2024_policy_oos_evaluation"]["walk_forward_weights"]["fixed_tau_1sigma_all_horizons"]
|
| 247 |
+
h_plot = ["1m", "3m", "5m", "15m", "30m", "60m"]
|
| 248 |
+
be_24 = [wf_tau1[h]["2024"]["breakeven_one_way_bps_state_hold"] for h in h_plot]
|
| 249 |
+
be_25 = [wf_tau1[h]["2025"]["breakeven_one_way_bps_state_hold"] for h in h_plot]
|
| 250 |
+
be_26 = [wf_tau1[h]["2026"]["breakeven_one_way_bps_state_hold"] for h in h_plot]
|
| 251 |
+
|
| 252 |
+
xp = np.arange(len(h_plot))
|
| 253 |
+
wp = 0.25
|
| 254 |
+
ax1.bar(xp - wp, be_24, wp, label='2024 OOS State-Hold Breakeven (bps)', color='#0284C7', edgecolor='black', lw=0.5)
|
| 255 |
+
ax1.bar(xp, be_25, wp, label='2025 OOS State-Hold Breakeven (bps)', color='#0D9488', edgecolor='black', lw=0.5)
|
| 256 |
+
ax1.bar(xp + wp, be_26, wp, label='2026 OOS State-Hold Breakeven (bps)', color='#7C3AED', edgecolor='black', lw=0.5)
|
| 257 |
+
ax1.axhline(0.0, color='black', lw=0.9)
|
| 258 |
+
ax1.axhline(1.5, color='#E11D48', linestyle='--', lw=1.2, label='Maker / Midpoint Cost Hurdle (1.5 bps)')
|
| 259 |
+
ax1.set_xticks(xp)
|
| 260 |
+
ax1.set_xticklabels(h_plot)
|
| 261 |
+
ax1.set_ylabel('State-Hold Breakeven One-Way Cost (bps)')
|
| 262 |
+
ax1.set_title('(a) Non-Overlapping Breakeven Cost (Fixed τ=1.0σ, Walk-Forward)')
|
| 263 |
+
ax1.legend(loc='lower left', frameon=True, fontsize=7.2)
|
| 264 |
+
ax1.grid(axis='y', alpha=0.3)
|
| 265 |
|
| 266 |
+
# Panel (b): Frozen 2024 Selected Policy (60m, tau=0.0sigma) vs Fixed 3m (tau=1.0sigma) Across Cost Grid
|
| 267 |
+
cost_bps = [0.0, 1.5, 3.0, 5.0]
|
| 268 |
+
cost_keys = ["0.0bps_oneway", "1.5bps_oneway", "3.0bps_oneway", "5.0bps_oneway"]
|
| 269 |
+
|
| 270 |
+
fpol_wf = audit["frozen_2024_policy_oos_evaluation"]["walk_forward_weights"]["frozen_2024_optimal_policy"]["yearly_performance"]
|
| 271 |
+
sr_f24 = [fpol_wf["2024"]["cost_grid"][ck]["annualized_sharpe_state_hold"] for ck in cost_keys]
|
| 272 |
+
sr_f25 = [fpol_wf["2025"]["cost_grid"][ck]["annualized_sharpe_state_hold"] for ck in cost_keys]
|
| 273 |
+
sr_f26 = [fpol_wf["2026"]["cost_grid"][ck]["annualized_sharpe_state_hold"] for ck in cost_keys]
|
| 274 |
+
sr_3m_26 = [wf_tau1["3m"]["2026"]["cost_grid"][ck]["annualized_sharpe_state_hold"] for ck in cost_keys]
|
| 275 |
+
|
| 276 |
+
ax2.plot(cost_bps, sr_f24, marker='o', lw=2.0, color='#0284C7', label='Frozen Policy on 2024 (Selection: SR=+4.40 at 0bp, +2.04 at 1.5bp)')
|
| 277 |
+
ax2.plot(cost_bps, sr_f25, marker='s', lw=2.0, color='#0D9488', label='Frozen Policy Applied Untouched to 2025 (SR=+1.01 at 0bp)')
|
| 278 |
+
ax2.plot(cost_bps, sr_f26, marker='^', lw=2.0, color='#D97706', label='Frozen Policy Applied Untouched to 2026 (SR=-2.01 at 0bp)')
|
| 279 |
+
ax2.plot(cost_bps, sr_3m_26, marker='D', lw=1.8, linestyle='--', color='#7C3AED', label='2026 3m Demeaned (τ=1.0σ: SR=+3.56 at 0bp)')
|
| 280 |
+
ax2.axhline(0.0, color='black', linestyle='--', lw=1.1, label='Zero Net Sharpe Boundary')
|
| 281 |
+
ax2.set_xlabel('One-Way Execution Cost: Spread + Fee + Slippage (bps)')
|
| 282 |
+
ax2.set_ylabel('Annualized Net Sharpe Ratio (Non-Overlapping)')
|
| 283 |
+
ax2.set_title('(b) Frozen 2024 Policy Untouched OOS Transfer (2024–2026)')
|
| 284 |
+
ax2.legend(loc='lower left', frameon=True, fontsize=7.0)
|
| 285 |
ax2.grid(True, alpha=0.3)
|
| 286 |
|
| 287 |
plt.tight_layout()
|
| 288 |
fig.savefig("papers/figures/fig5_slippage_friction_curve.png", dpi=300, bbox_inches='tight', pad_inches=0.1)
|
| 289 |
plt.close()
|
| 290 |
|
| 291 |
+
print("✅ All 5 upgraded deterministic empirical figures rendered at 300 DPI!")
|
|
|