Skip to content

Commit 2882850

Browse files
committed
fix(ci): guard evaluator against empty live-ledger + align secrets baseline
python-fast-tests (tests/ops/test_live_scoreboard_schema.py::test_scoreboard_appends_not_overwrites): When the spike paper-state ledger is absent (as on CI runners), _load_live_ledger returned an empty DataFrame, but _compute_live_metrics then accessed live["net_ret"] before the n==0 early-return, raising KeyError on the schema-less empty frame. Added a guard: if live.empty or "net_ret" not in live.columns, return the empty_metrics dict immediately. Evaluator now exits 0 with a BUILDING_SAMPLE / OPERATIONALLY_UNSAFE scoreboard row when no ledger is available (verified locally against /tmp nonexistent). secrets-supply-chain (2 remaining Hex High Entropy String hits): Regenerated .secrets.baseline AFTER finalising SOURCE_HASHES.json byte content — earlier regen had picked up the intermediate hash. Baseline now records: results/cross_asset_kuramoto/PARAMETER_LOCK.json results/cross_asset_kuramoto/offline_robustness/SOURCE_HASHES.json results/cross_asset_kuramoto/shadow_validation/daily/2026-04-10/run_manifest.json results/wave1_fx/universe.json as known-acceptable Hex High Entropy String findings (4 entries out of 90 total baseline entries). No new secret-like content. SOURCE_HASHES.json regenerated against current-branch byte state (including the evaluator empty-ledger guard above) so the hashes- frozen test stays consistent through the fix chain. No signal logic changed. No parameter touched. No evidence CSV modified. combo_v1 closure enforcement intact.
1 parent b6634d1 commit 2882850

3 files changed

Lines changed: 22 additions & 17 deletions

File tree

.secrets.baseline

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2558,7 +2558,7 @@
25582558
{
25592559
"type": "Hex High Entropy String",
25602560
"filename": "results/cross_asset_kuramoto/offline_robustness/SOURCE_HASHES.json",
2561-
"hashed_secret": "997fe2cc9f3a1a792b16e41a22414079f6ebfc48",
2561+
"hashed_secret": "5033b7e6ea228b93bd021b3bc219f6f277802b8c",
25622562
"is_verified": false,
25632563
"line_number": 27
25642564
},
@@ -3423,5 +3423,5 @@
34233423
}
34243424
]
34253425
},
3426-
"generated_at": "2026-04-22T00:29:26Z"
3426+
"generated_at": "2026-04-22T00:45:46Z"
34273427
}

results/cross_asset_kuramoto/offline_robustness/SOURCE_HASHES.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424
"core/cross_asset_kuramoto/invariants.py": "f5627c2ed1d25bab11f816c00f3af74bd23380725b1c01344f3b250e016e035e",
2525
"scripts/demo_cross_asset_kuramoto.py": "36041afa804e5ad46189eaa9166e064a0714aa6f47a6a16e4556054bc03deb79",
2626
"scripts/run_cross_asset_kuramoto_shadow.py": "f760e17b2caea33ae1cd962ac191590c3832c4e325a839c8a7db5009d899938b",
27-
"scripts/evaluate_cross_asset_kuramoto_shadow.py": "8c4fd4f80a366b5eb991e9c09683890e879ba11f1698a10854962e869e94949d",
27+
"scripts/evaluate_cross_asset_kuramoto_shadow.py": "35f8801a37df3280d727a1adf74ba03c386c3402024de4d2db146285c3da8fe6",
2828
"scripts/render_cross_asset_kuramoto_shadow_report.py": "b12b35a6989d61e7dbf1dadd08247f16fb0ab2a07659683eacf9553cf0425dbf",
2929
"scripts/push_shadow_evidence.sh": "33b91955c0ec61bd274e34d309421079b06dccd3026aa3109aaa8632614b442d",
3030
"ops/systemd/cross_asset_kuramoto_shadow.service": "673905e2206bacce78707a669d86f29b4a2f73eeb87a5fdfe820ae5460d54a44",
3131
"ops/systemd/cross_asset_kuramoto_shadow.timer": "b87272d9adb3ddd967d1b92e07301168d71a1fb1787ce396656103a197553015"
3232
},
33-
"regenerated_utc": "2026-04-22T00:30:24Z"
33+
"regenerated_utc": "2026-04-22T00:43:10Z"
3434
}

scripts/evaluate_cross_asset_kuramoto_shadow.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -211,22 +211,27 @@ def _load_pipeline_status_for_latest() -> dict[str, Any]:
211211

212212

213213
def _compute_live_metrics(live: pd.DataFrame) -> dict[str, Any]:
214+
empty_metrics: dict[str, Any] = {
215+
"live_bars_completed": 0,
216+
"cumulative_net_return": 0.0,
217+
"annualized_return_live": float("nan"),
218+
"annualized_vol_live": float("nan"),
219+
"sharpe_live": float("nan"),
220+
"max_dd_live": float("nan"),
221+
"turnover_ann_live": float("nan"),
222+
"hit_rate_live": float("nan"),
223+
"avg_win_live": float("nan"),
224+
"avg_loss_live": float("nan"),
225+
"cost_drag_bps_live": float("nan"),
226+
}
227+
# Guard against an empty or schema-less ledger (paper-state absent on
228+
# CI runners, or the spike has not yet written its first tick).
229+
if live.empty or "net_ret" not in live.columns:
230+
return empty_metrics
214231
r = live["net_ret"].astype(float).to_numpy()
215232
n = int(len(r))
216233
if n == 0:
217-
return {
218-
"live_bars_completed": 0,
219-
"cumulative_net_return": 0.0,
220-
"annualized_return_live": float("nan"),
221-
"annualized_vol_live": float("nan"),
222-
"sharpe_live": float("nan"),
223-
"max_dd_live": float("nan"),
224-
"turnover_ann_live": float("nan"),
225-
"hit_rate_live": float("nan"),
226-
"avg_win_live": float("nan"),
227-
"avg_loss_live": float("nan"),
228-
"cost_drag_bps_live": float("nan"),
229-
}
234+
return empty_metrics
230235
ann_ret = float(np.mean(r) * BARS_PER_YEAR)
231236
ann_vol = float(np.std(r, ddof=1) * np.sqrt(BARS_PER_YEAR)) if n > 1 else float("nan")
232237
sharpe = ann_ret / ann_vol if ann_vol and ann_vol > 0 else float("nan")

0 commit comments

Comments
 (0)