src/lib/pricing/forward.ts

61 lines
import type { ForwardArgs } from "./types";

/** Sum of gross dividends going ex in (0, t], each carried from pay date to expiry at €STR (no settlement lag). */
export function carriedGrossDividends(a: ForwardArgs): number {
  let sum = 0;
  for (const d of a.dividends) {
    if (d.t > 0 && d.t <= a.t) sum += d.gross * Math.exp(a.rate * (a.t - d.t));
  }
  return sum;
}

/** Undiscounted gross dividends going ex in (0, t]. */
export function cumulativeGrossDividends(a: Pick<ForwardArgs, "dividends" | "t">): number {
  let sum = 0;
  for (const d of a.dividends) if (d.t > 0 && d.t <= a.t) sum += d.gross;
  return sum;
}

/** Dividend multiplier: `divScale` for dividends after `divScaleFrom`, 1 for earlier (pre-scaled) ones. */
export const divFactor = (a: ForwardArgs, exTime: number) =>
  exTime > (a.divScaleFrom ?? -Infinity) ? a.divScale : 1;

/** Gross dividends going ex in (0, t], with the scaling curve applied. */
export function scaledCumulativeDividends(a: ForwardArgs): number {
  let sum = 0;
  for (const d of a.dividends) if (d.t > 0 && d.t <= a.t) sum += d.gross * divFactor(a, d.t);
  return sum;
}

/**
 * Net dividends going ex in (0, exCutoff] (pay date = ex date), each received on its pay date
 * and carried to `settle` at €STR:
 *
 *   FV = allIn · Σ kᵢ·Dᵢ·e^{€STR·(settle − payᵢ)}
 *
 * At €STR, not €STR + s: once paid, a dividend is cash, reinvested at the cash rate.
 */
export function carriedNetDividends(a: ForwardArgs, exCutoff = a.t, settle = a.settle?.expiry ?? a.t): number {
  let sum = 0;
  for (const d of a.dividends) {
    if (d.t > 0 && d.t <= exCutoff) sum += divFactor(a, d.t) * d.gross * Math.exp(a.rate * (settle - d.t));
  }
  return a.allIn * sum;
}

/**
 * Cost-of-carry forward, continuous compounding:
 *
 *   F = S · e^{R·(T_settle − t_settle)} − FV(net divs),   R = €STR + s
 *
 * The cash index is funded at R from spot settlement (t + 2 weekdays) to the expiry's
 * settlement (T + 2 weekdays); dividends going ex in (t, T] (pay = ex) are carried from
 * their pay date to the expiry's settlement at €STR.
 * Without `settle` both lags are zero and this is S·e^{Rt} − allIn·k·Σ Dᵢ·e^{€STR(t−tᵢ)}.
 */
export function priceForward(a: ForwardArgs): number {
  const R = a.rate + a.fundingSpread;
  const carry = (a.settle?.expiry ?? a.t) - (a.settle?.spot ?? 0);
  return a.spot * Math.exp(R * carry) - carriedNetDividends(a);
}