import type { RowState } from "../grid/rowModel";
import type { MarketData } from "../market/sx5e";
import { settlementFraction } from "../pricing";
import type { CurvePoint } from "./carry";
/**
* A per-tenor curve for the monitor: one point per tenor (first row per expiry), at the mid of
* the grid's bid/ask for the chosen column — so it follows edits and live repricing. Δ are the
* settlement-adjusted ACT/360 fractions (spot settlement → expiry settlement: both the TRF
* basis day count and the carry model's funding period), today and at the horizon date (a
* tenor expiring on or before the horizon has Δ′ = 0).
*/
function midCurve(m: MarketData, rows: RowState[], horizon: Date, col: "trf" | "fund"): CurvePoint[] {
const seen = new Set<string>();
return [...rows]
.sort((a, b) => a.expiry.date.getTime() - b.expiry.date.getTime())
.filter((r) => !seen.has(r.expiry.label) && seen.add(r.expiry.label) && !r.error)
.map((r) => ({
label: r.expiry.label,
delta: settlementFraction(m.valuationDate, r.expiry.date),
deltaAfter: r.expiry.date.getTime() > horizon.getTime() ? settlementFraction(horizon, r.expiry.date) : 0,
spread: (r[col].bid + r[col].ask) / 2,
}));
}
/** TRF spreads (bp): the curve the calendar spread's legs trade on — carry, roll-down, DV01. */
export const trfCurve = (m: MarketData, rows: RowState[], horizon: Date) => midCurve(m, rows, horizon, "trf");
/** Term funding (bp) from the asset's carry model: the curve forward funding is read from. */
export const fundingCurve = (m: MarketData, rows: RowState[], horizon: Date) => midCurve(m, rows, horizon, "fund");