fix(xsfunding): runner robust to panel gaps — use last-known qvol for cost (KeyError on real data)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-18 23:41:38 +02:00
parent b5f065b887
commit 75006af0ef
2 changed files with 15 additions and 3 deletions

View File

@@ -64,9 +64,7 @@ class FundingBacktestRunner:
borrowable = {s for s in elig if self._p[s].get(d, (0.0, 0.0, 0.0))[1] >= self._borrow_q}
funding_next = {s: self._p[s][nxt][2] for s in elig if nxt in self._p[s]}
held = set().union(*(prev_w[m] for m in _MODES)) if any(prev_w[m] for m in _MODES) else set()
cost = {s: self._coin_cost(self._p[s][d][1]) for s in elig}
for s in held - set(cost):
cost[s] = self._coin_cost(self._last_qvol(s, d)) # exit cost for dropped-out holdings
cost = {s: self._coin_cost(self._last_qvol(s, d)) for s in set(elig) | held}
names_seen += len(elig)
n_rebals += 1
for m in _MODES:

View File

@@ -88,6 +88,20 @@ def test_dropped_out_holding_charged_nonzero_exit_cost(monkeypatch):
assert exit_cost == 0.0008 + 0.005 # cost_bps 8 -> 8e-4 ; slip_cap 50bps -> 5e-3
def test_runner_handles_panel_gaps_without_keyerror():
panel = {}
fund = {"HI": 0.0015, "MID": 0.0008, "LO": 0.0001, "NEG": -0.0010}
for sym, f in fund.items():
panel[sym] = {1000 + i: (1.0, 5e6, f) for i in range(200)}
# GAPCOIN: liquid + long history but MISSING several interior union-days (gaps) and stops early (delists)
panel["GAP"] = {1000 + i: (1.0, 5e6, 0.0012) for i in range(200) if i not in (50, 51, 120) and i < 180}
runner = FundingBacktestRunner(panel, min_qvol=1e6, min_history=20, lookback_days=7,
quantile=0.5, cost_bps=8.0, slip_coef=0.0005)
res = runner.run() # must NOT raise KeyError
for series in res.returns_by_mode.values():
assert len(series) > 50 and np.isfinite(series).all()
def test_book_carry_and_turnover():
# carry on the NEW book = sum w * funding_next
w = {"A": 0.5, "B": -0.5}