{
  "$schema": "https://hedgefund.wiki/api/schema.json#/definitions/Calculator",
  "endpoint": "https://hedgefund.wiki/api/calculators.json",
  "version": "2026.05.0",
  "description": "Implementable calculator definitions. Each entry specifies typed input/output schemas, the formula reference, and a self-contained reference implementation. Drop-in for any client.",
  "count": 16,
  "calculators": [
    {
      "id": "sharpe-ratio-calc",
      "name": "Sharpe Ratio",
      "summary": "Annualized Sharpe from a return series.",
      "formula_id": "sharpe-ratio",
      "inputs": [
        { "id": "returns", "label": "Periodic returns (decimal)", "type": "array<number>", "required": true, "description": "e.g., [0.01, -0.005, 0.003, ...]" },
        { "id": "rf_periodic", "label": "Risk-free rate per period (decimal)", "type": "number", "default": 0, "required": false },
        { "id": "periods_per_year", "label": "Periods per year", "type": "integer", "default": 252, "required": false }
      ],
      "outputs": [
        { "id": "sharpe", "label": "Annualized Sharpe Ratio", "type": "number" },
        { "id": "mean_excess", "label": "Mean periodic excess return", "type": "number" },
        { "id": "std", "label": "Std of excess returns", "type": "number" }
      ],
      "reference_implementation": {
        "language": "javascript",
        "code": "function sharpeRatio(returns, rfPeriodic = 0, periodsPerYear = 252) {\n  const ex = returns.map(r => r - rfPeriodic);\n  const mean = ex.reduce((a, b) => a + b, 0) / ex.length;\n  const variance = ex.reduce((s, r) => s + (r - mean) ** 2, 0) / (ex.length - 1);\n  const std = Math.sqrt(variance);\n  const sharpe = (mean / std) * Math.sqrt(periodsPerYear);\n  return { sharpe, mean_excess: mean, std };\n}"
      },
      "examples": [
        {
          "name": "Daily series",
          "inputs": { "returns": [0.001, -0.002, 0.003, 0.001, -0.001, 0.002, 0.004, -0.003], "rf_periodic": 0, "periods_per_year": 252 },
          "outputs": { "sharpe": 1.18 }
        }
      ]
    },
    {
      "id": "sortino-ratio-calc",
      "name": "Sortino Ratio",
      "formula_id": "sortino-ratio",
      "inputs": [
        { "id": "returns", "label": "Returns", "type": "array<number>", "required": true },
        { "id": "target", "label": "Target return per period", "type": "number", "default": 0, "required": false },
        { "id": "periods_per_year", "label": "Periods per year", "type": "integer", "default": 252, "required": false }
      ],
      "outputs": [{ "id": "sortino", "label": "Annualized Sortino Ratio", "type": "number" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "function sortinoRatio(returns, target = 0, periodsPerYear = 252) {\n  const ex = returns.map(r => r - target);\n  const mean = ex.reduce((a,b) => a + b, 0) / ex.length;\n  const downside = ex.filter(r => r < 0);\n  const dd = Math.sqrt(downside.reduce((s, r) => s + r * r, 0) / ex.length);\n  return { sortino: (mean / dd) * Math.sqrt(periodsPerYear) };\n}"
      }
    },
    {
      "id": "max-drawdown-calc",
      "name": "Maximum Drawdown",
      "formula_id": "max-drawdown",
      "inputs": [
        { "id": "nav_series", "label": "NAV series (or cumulative return path)", "type": "array<number>", "required": true }
      ],
      "outputs": [
        { "id": "mdd", "label": "Maximum Drawdown (decimal, negative)", "type": "number" },
        { "id": "peak_index", "label": "Index of peak NAV", "type": "integer" },
        { "id": "trough_index", "label": "Index of trough NAV", "type": "integer" },
        { "id": "duration", "label": "Periods peak to trough", "type": "integer" }
      ],
      "reference_implementation": {
        "language": "javascript",
        "code": "function maxDrawdown(nav) {\n  let peak = nav[0], peakIdx = 0, mdd = 0, troughIdx = 0;\n  for (let i = 1; i < nav.length; i++) {\n    if (nav[i] > peak) { peak = nav[i]; peakIdx = i; }\n    const dd = (nav[i] - peak) / peak;\n    if (dd < mdd) { mdd = dd; troughIdx = i; }\n  }\n  return { mdd, peak_index: peakIdx, trough_index: troughIdx, duration: troughIdx - peakIdx };\n}"
      }
    },
    {
      "id": "calmar-ratio-calc",
      "name": "Calmar Ratio",
      "formula_id": "calmar-ratio",
      "inputs": [
        { "id": "annualized_return", "label": "Annualized return (decimal)", "type": "number", "required": true },
        { "id": "max_drawdown", "label": "Max drawdown (decimal, may be negative)", "type": "number", "required": true }
      ],
      "outputs": [{ "id": "calmar", "label": "Calmar Ratio", "type": "number" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "function calmar(annRet, mdd) { return { calmar: annRet / Math.abs(mdd) }; }"
      }
    },
    {
      "id": "var-parametric-calc",
      "name": "Parametric VaR",
      "formula_id": "parametric-var",
      "inputs": [
        { "id": "portfolio_value", "label": "Portfolio value (USD)", "type": "currency", "required": true },
        { "id": "sigma_periodic", "label": "Periodic vol (decimal)", "type": "number", "required": true },
        { "id": "horizon_periods", "label": "Horizon (periods)", "type": "number", "default": 1, "required": false },
        { "id": "confidence", "label": "Confidence level (e.g., 0.99)", "type": "number", "default": 0.99, "required": false }
      ],
      "outputs": [{ "id": "var_usd", "label": "VaR ($)", "type": "currency" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "function inverseNormal(p) {\n  // Beasley-Springer-Moro approximation\n  const a = [-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02, 1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00];\n  const b = [-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02, 6.680131188771972e+01, -1.328068155288572e+01];\n  const c = [-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00, -2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00];\n  const d = [7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00, 3.754408661907416e+00];\n  const pl = 0.02425, ph = 1 - pl;\n  let q, r;\n  if (p < pl) { q = Math.sqrt(-2*Math.log(p)); return (((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1); }\n  if (p <= ph) { q = p - 0.5; r = q*q; return (((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q / (((((b[0]*r+b[1])*r+b[2])*r+b[3])*r+b[4])*r+1); }\n  q = Math.sqrt(-2*Math.log(1-p));\n  return -(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1);\n}\nfunction parametricVaR(value, sigma, horizon = 1, conf = 0.99) {\n  const z = inverseNormal(conf);\n  const v = value * sigma * Math.sqrt(horizon) * z;\n  return { var_usd: v };\n}"
      },
      "examples": [
        { "name": "$100m, 1.2% daily, 99%", "inputs": { "portfolio_value": 100000000, "sigma_periodic": 0.012, "horizon_periods": 1, "confidence": 0.99 }, "outputs": { "var_usd": 2791567 } }
      ]
    },
    {
      "id": "kelly-fraction-calc",
      "name": "Kelly Fraction",
      "formula_id": "kelly-criterion",
      "inputs": [
        { "id": "expected_return", "label": "Expected return (decimal)", "type": "number", "required": true },
        { "id": "risk_free", "label": "Risk-free rate (decimal)", "type": "number", "default": 0, "required": false },
        { "id": "variance", "label": "Variance of return", "type": "number", "required": true },
        { "id": "fraction", "label": "Kelly fraction multiplier (e.g., 0.5 for half-Kelly)", "type": "number", "default": 1, "required": false }
      ],
      "outputs": [{ "id": "leverage", "label": "Optimal leverage (multiple of NAV)", "type": "number" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "function kelly(mu, rf, variance, fraction = 1) { return { leverage: fraction * (mu - rf) / variance }; }"
      }
    },
    {
      "id": "performance-fee-calc",
      "name": "Performance Fee with HWM and Hurdle",
      "summary": "Computes performance fee given prior HWM, current NAV, hurdle, and rate.",
      "formula_id": "sharpe-ratio",
      "inputs": [
        { "id": "starting_nav", "label": "Starting NAV per share", "type": "number", "required": true },
        { "id": "ending_nav_pre_fee", "label": "Ending NAV per share (pre-fee)", "type": "number", "required": true },
        { "id": "high_water_mark", "label": "High water mark NAV per share", "type": "number", "required": true },
        { "id": "hurdle_rate", "label": "Hurdle rate (decimal, periodic)", "type": "percent", "default": 0, "required": false },
        { "id": "performance_fee_rate", "label": "Performance fee rate (decimal)", "type": "percent", "default": 0.20, "required": false },
        { "id": "hurdle_type", "label": "'hard' or 'soft' (with catch-up)", "type": "string", "default": "hard", "required": false }
      ],
      "outputs": [
        { "id": "performance_fee_per_share", "label": "Performance fee per share", "type": "number" },
        { "id": "ending_nav_post_fee", "label": "Ending NAV per share (post-fee)", "type": "number" },
        { "id": "new_high_water_mark", "label": "Updated HWM", "type": "number" }
      ],
      "reference_implementation": {
        "language": "javascript",
        "code": "function performanceFee(start, endPre, hwm, hurdle = 0, rate = 0.2, type = 'hard') {\n  const hurdleNav = Math.max(hwm, start) * (1 + hurdle);\n  const above = endPre - hurdleNav;\n  if (above <= 0) return { performance_fee_per_share: 0, ending_nav_post_fee: endPre, new_high_water_mark: Math.max(endPre, hwm) };\n  let fee;\n  if (type === 'soft') {\n    const totalProfit = endPre - Math.max(hwm, start);\n    fee = rate * totalProfit;\n  } else {\n    fee = rate * above;\n  }\n  const post = endPre - fee;\n  return { performance_fee_per_share: fee, ending_nav_post_fee: post, new_high_water_mark: Math.max(post, hwm) };\n}"
      }
    },
    {
      "id": "annualize-return-calc",
      "name": "Annualize Return",
      "formula_id": "sharpe-ratio",
      "inputs": [
        { "id": "total_return", "label": "Total return over period (decimal, e.g., 0.18 = 18%)", "type": "number", "required": true },
        { "id": "years", "label": "Period length in years", "type": "number", "required": true }
      ],
      "outputs": [{ "id": "annualized", "label": "Annualized return (decimal)", "type": "number" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "function annualize(totalReturn, years) { return { annualized: Math.pow(1 + totalReturn, 1 / years) - 1 }; }"
      }
    },
    {
      "id": "vol-annualize-calc",
      "name": "Annualize Volatility",
      "inputs": [
        { "id": "periodic_vol", "label": "Periodic vol", "type": "number", "required": true },
        { "id": "periods_per_year", "label": "Periods per year", "type": "integer", "required": true }
      ],
      "outputs": [{ "id": "annualized_vol", "label": "Annualized vol", "type": "number" }],
      "formula_id": "sharpe-ratio",
      "reference_implementation": {
        "language": "javascript",
        "code": "function annVol(v, n) { return { annualized_vol: v * Math.sqrt(n) }; }"
      }
    },
    {
      "id": "black-scholes-call-calc",
      "name": "Black-Scholes Call Price + Greeks",
      "formula_id": "black-scholes",
      "inputs": [
        { "id": "S", "label": "Spot", "type": "number", "required": true },
        { "id": "K", "label": "Strike", "type": "number", "required": true },
        { "id": "T", "label": "Time to expiry (years)", "type": "number", "required": true },
        { "id": "r", "label": "Risk-free rate", "type": "number", "required": true },
        { "id": "sigma", "label": "Volatility (annualized)", "type": "number", "required": true },
        { "id": "q", "label": "Dividend yield", "type": "number", "default": 0, "required": false }
      ],
      "outputs": [
        { "id": "call", "label": "Call price", "type": "number" },
        { "id": "delta", "label": "Delta", "type": "number" },
        { "id": "gamma", "label": "Gamma", "type": "number" },
        { "id": "vega", "label": "Vega (per 1.0 vol unit)", "type": "number" },
        { "id": "theta", "label": "Theta (per year)", "type": "number" },
        { "id": "rho", "label": "Rho", "type": "number" }
      ],
      "reference_implementation": {
        "language": "javascript",
        "code": "function normCdf(x){const a1=0.254829592,a2=-0.284496736,a3=1.421413741,a4=-1.453152027,a5=1.061405429,p=0.3275911;const sign=x<0?-1:1;x=Math.abs(x)/Math.SQRT2;const t=1/(1+p*x);const y=1-(((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*Math.exp(-x*x);return 0.5*(1+sign*y);}\nfunction normPdf(x){return Math.exp(-x*x/2)/Math.sqrt(2*Math.PI);}\nfunction bsCall(S,K,T,r,sigma,q=0){\n  const d1=(Math.log(S/K)+(r-q+sigma*sigma/2)*T)/(sigma*Math.sqrt(T));\n  const d2=d1-sigma*Math.sqrt(T);\n  const Nd1=normCdf(d1),Nd2=normCdf(d2),nd1=normPdf(d1);\n  const call=S*Math.exp(-q*T)*Nd1-K*Math.exp(-r*T)*Nd2;\n  return {\n    call,\n    delta: Math.exp(-q*T)*Nd1,\n    gamma: Math.exp(-q*T)*nd1/(S*sigma*Math.sqrt(T)),\n    vega: S*Math.exp(-q*T)*nd1*Math.sqrt(T),\n    theta: -(S*Math.exp(-q*T)*nd1*sigma)/(2*Math.sqrt(T)) - r*K*Math.exp(-r*T)*Nd2 + q*S*Math.exp(-q*T)*Nd1,\n    rho: K*T*Math.exp(-r*T)*Nd2\n  };\n}"
      },
      "examples": [
        { "name": "ATM 1y", "inputs": { "S": 100, "K": 100, "T": 1, "r": 0.04, "sigma": 0.20 }, "outputs": { "call": 9.92 } }
      ]
    },
    {
      "id": "implied-vol-newton-calc",
      "name": "Implied Volatility (Newton-Raphson)",
      "formula_id": "black-scholes",
      "inputs": [
        { "id": "market_price", "label": "Observed call price", "type": "number", "required": true },
        { "id": "S", "label": "Spot", "type": "number", "required": true },
        { "id": "K", "label": "Strike", "type": "number", "required": true },
        { "id": "T", "label": "Time to expiry (years)", "type": "number", "required": true },
        { "id": "r", "label": "Risk-free rate", "type": "number", "required": true },
        { "id": "q", "label": "Dividend yield", "type": "number", "default": 0, "required": false }
      ],
      "outputs": [{ "id": "implied_vol", "label": "Implied volatility", "type": "number" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "// Requires bsCall from black-scholes-call-calc\nfunction impliedVol(price, S, K, T, r, q = 0) {\n  let sigma = 0.30;\n  for (let i = 0; i < 100; i++) {\n    const o = bsCall(S, K, T, r, sigma, q);\n    const diff = o.call - price;\n    if (Math.abs(diff) < 1e-8) return { implied_vol: sigma };\n    sigma -= diff / o.vega;\n    if (sigma <= 0) sigma = 0.001;\n  }\n  return { implied_vol: sigma };\n}"
      }
    },
    {
      "id": "duration-modified-calc",
      "name": "Modified Duration",
      "formula_id": "duration-modified",
      "inputs": [
        { "id": "cash_flows", "label": "Cash flows array", "type": "array<number>", "required": true },
        { "id": "times", "label": "Time of each cash flow (years)", "type": "array<number>", "required": true },
        { "id": "ytm", "label": "Yield to maturity (decimal)", "type": "number", "required": true },
        { "id": "compounding", "label": "Compounding periods per year", "type": "integer", "default": 2, "required": false }
      ],
      "outputs": [
        { "id": "macaulay", "label": "Macaulay Duration (years)", "type": "number" },
        { "id": "modified", "label": "Modified Duration (years)", "type": "number" },
        { "id": "price", "label": "Present value", "type": "number" }
      ],
      "reference_implementation": {
        "language": "javascript",
        "code": "function durations(cfs, times, ytm, m = 2) {\n  let pv = 0, weighted = 0;\n  for (let i = 0; i < cfs.length; i++) {\n    const df = Math.pow(1 + ytm/m, -times[i]*m);\n    pv += cfs[i] * df;\n    weighted += times[i] * cfs[i] * df;\n  }\n  const mac = weighted / pv;\n  const mod = mac / (1 + ytm/m);\n  return { macaulay: mac, modified: mod, price: pv };\n}"
      }
    },
    {
      "id": "cds-spread-calc",
      "name": "CDS Spread (Simplified)",
      "formula_id": "cds-pricing",
      "inputs": [
        { "id": "default_probability", "label": "Annual default probability (decimal)", "type": "number", "required": true },
        { "id": "recovery_rate", "label": "Recovery rate (decimal)", "type": "number", "default": 0.40, "required": false }
      ],
      "outputs": [{ "id": "spread_bps", "label": "Spread (bps)", "type": "number" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "function cdsSpread(p, R = 0.40) { return { spread_bps: p * (1 - R) * 10000 }; }"
      }
    },
    {
      "id": "merger-arb-annualized-spread-calc",
      "name": "Merger Arb Annualized Spread",
      "summary": "Annualizes the deal spread given the days remaining to close.",
      "formula_id": "irr-newton",
      "inputs": [
        { "id": "deal_price", "label": "Announced acquisition price per share", "type": "number", "required": true },
        { "id": "current_price", "label": "Current target price per share", "type": "number", "required": true },
        { "id": "days_to_close", "label": "Estimated days to close", "type": "number", "required": true }
      ],
      "outputs": [
        { "id": "raw_spread_pct", "label": "Raw spread (decimal)", "type": "number" },
        { "id": "annualized_spread_pct", "label": "Annualized spread (decimal)", "type": "number" }
      ],
      "reference_implementation": {
        "language": "javascript",
        "code": "function mergerSpread(deal, current, days) {\n  const raw = deal / current - 1;\n  const ann = raw * (365 / Math.max(days, 1));\n  return { raw_spread_pct: raw, annualized_spread_pct: ann };\n}"
      }
    },
    {
      "id": "perp-funding-rate-pnl-calc",
      "name": "Perpetual Futures Funding-Rate Carry P&L",
      "summary": "Annualized P&L from a cash-and-carry trade in crypto perps.",
      "formula_id": "irr-newton",
      "inputs": [
        { "id": "funding_rate_per_period", "label": "Funding rate per period (decimal)", "type": "number", "required": true },
        { "id": "periods_per_day", "label": "Funding intervals per day", "type": "integer", "default": 3, "required": false }
      ],
      "outputs": [{ "id": "annualized", "label": "Annualized rate (decimal)", "type": "number" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "function perpFundingAnn(rate, perDay = 3) { return { annualized: rate * perDay * 365 }; }"
      }
    },
    {
      "id": "irr-calc",
      "name": "IRR (Newton)",
      "formula_id": "irr-newton",
      "inputs": [
        { "id": "cash_flows", "label": "Cash flows (CF_0 typically negative)", "type": "array<number>", "required": true },
        { "id": "guess", "label": "Initial guess", "type": "number", "default": 0.10, "required": false }
      ],
      "outputs": [{ "id": "irr", "label": "IRR (decimal)", "type": "number" }],
      "reference_implementation": {
        "language": "javascript",
        "code": "function irr(cfs, guess = 0.10) {\n  let r = guess;\n  for (let i = 0; i < 100; i++) {\n    let f = 0, fp = 0;\n    for (let t = 0; t < cfs.length; t++) {\n      f += cfs[t] / Math.pow(1 + r, t);\n      fp -= t * cfs[t] / Math.pow(1 + r, t + 1);\n    }\n    const next = r - f / fp;\n    if (Math.abs(next - r) < 1e-9) return { irr: next };\n    r = next;\n  }\n  return { irr: r };\n}"
      }
    }
  ]
}
