import { describe, expect, it } from "vitest";
import { initialTable, twoWay } from "../../grid/rowModel";
import { DEFAULT_ALL_IN, SX5E_MARKET as m, SX5E_TRF_RUN } from "../../market/sx5e";
import { settlementFraction } from "../../pricing";
import { carryRoll, curveSpread, type CurvePoint } from "../carry";
import { trfCurve } from "../curve";
/**
* Day-by-day check of carry + roll-down: walk every calendar day to the near expiry with the
* TRF spread curve static in time-to-maturity, mark each leg's basis s_TRF(τ_d)·τ_d on today's
* curve (residual risk hedged at no cost, so that is the whole P&L) and add up the daily P&L.
*/
describe("carry + roll-down vs a day-by-day simulation", () => {
const rows = initialTable(m, SX5E_TRF_RUN.map((q) => ({ expiry: q.expiry, trf: twoWay(q.bid, q.offer) })), DEFAULT_ALL_IN);
const N = 100_000_000;
it.each([
["Dec26", "Dec27", false],
["Dec27", "Dec28", false],
["Dec26", "Dec28", true],
] as const)("%s → %s (longFar=%s)", (near, far, longFar) => {
const nearExpiry = rows.find((r) => r.expiry.label === near)!.expiry.date;
const curve: CurvePoint[] = trfCurve(m, rows, nearExpiry);
const expiryOf = (label: string) => rows.find((r) => r.expiry.label === label)!.expiry.date;
const s = longFar ? 1 : -1;
const legs = [
{ label: near, sign: -s },
{ label: far, sign: s },
];
// leg value on day d: sign · N · s_TRF(τ_d) · τ_d on today's curve (0 once expired)
const value = (d: Date) =>
legs.reduce((acc, leg) => {
const T = expiryOf(leg.label);
if (d.getTime() >= T.getTime()) return acc;
const tau = settlementFraction(d, T);
return acc + leg.sign * N * curveSpread(curve, tau) * 1e-4 * tau;
}, 0);
const daily: number[] = [];
let prev = value(m.valuationDate);
for (let d = new Date(m.valuationDate.getTime() + 86_400_000); d.getTime() <= nearExpiry.getTime(); d = new Date(d.getTime() + 86_400_000)) {
const v = value(d);
daily.push(v - prev);
prev = v;
}
const simulated = daily.reduce((a, b) => a + b, 0);
const r = carryRoll({ curve, near, far, notional: N, longFar });
expect(simulated).toBeCloseTo(r.total, 6);
});
});