SPB Git

spb/hfchart Public

HFChart — la référence des charts haute fréquence : 14 types rendus canvas from scratch (zéro dépendance), données HF Market Data — www.hfchart.io

JavaScript 82.1% CSS 10% HTML 5.8% Python 2.1%
6.8 KB · 178 lines javascript
Raw Blame History
1/* ============================================================================2 * HFChart Options Lab — module de calcul PUR (payoff, breakevens, Greeks)3 * Author : Simon-Pierre Boucher — contact@spboucher.ai4 * ----------------------------------------------------------------------------5 * Aucune dépendance, aucun accès DOM/réseau : utilisable dans le navigateur6 * (window.HF.optmath) et sous Node pour les tests unitaires (module.exports).7 *8 * Une jambe (leg) :9 *   {10 *     kind : 'c' | 'p' | 's',          // call, put, ou action (stock)11 *     strike : number,                  // 0 / ignoré pour 's'12 *     qty : number,                     // signé : + long, − short (contrats ; actions pour 's')13 *     price : number,                   // prime par action (prix d'achat pour 's')14 *     mult : number,                    // 100 par défaut, 1 pour 's'15 *     greeks : {delta,gamma,theta,vega,rho}  // par action, côté long (API HF Market Data)16 *   }17 * Conventions : P&L en $ pour la position ; débit net positif = on paie.18 * ==========================================================================*/19(function (root) {20  'use strict';2122  const multOf = leg => (leg.mult != null ? leg.mult : (leg.kind === 's' ? 1 : 100));2324  /* Valeur intrinsèque par action à l'échéance. */25  function intrinsic(kind, strike, S) {26    if (kind === 'c') return Math.max(S - strike, 0);27    if (kind === 'p') return Math.max(strike - S, 0);28    return S; // 's' : l'action vaut S29  }3031  /* P&L de la position à l'échéance pour un spot S. */32  function payoffAt(legs, S) {33    let pnl = 0;34    for (const leg of legs) {35      pnl += leg.qty * multOf(leg) * (intrinsic(leg.kind, leg.strike, S) - leg.price);36    }37    return pnl;38  }3940  /* Coût net d'ouverture (débit > 0, crédit < 0). */41  function netCost(legs) {42    let c = 0;43    for (const leg of legs) c += leg.qty * multOf(leg) * leg.price;44    return c;45  }4647  /* Greeks agrégés de la position ($ par unité de facteur). */48  function netGreeks(legs) {49    const out = { delta: 0, gamma: 0, theta: 0, vega: 0, rho: 0 };50    for (const leg of legs) {51      const g = leg.greeks || {};52      const k = leg.qty * multOf(leg);53      out.delta += k * (leg.kind === 's' ? 1 : (g.delta || 0));54      out.gamma += k * (g.gamma || 0);55      out.theta += k * (g.theta || 0);56      out.vega += k * (g.vega || 0);57      out.rho += k * (g.rho || 0);58    }59    return out;60  }6162  /* Points de cassure du payoff : les strikes des jambes optionnelles. */63  function kinks(legs) {64    const ks = [...new Set(legs.filter(l => l.kind !== 's').map(l => l.strike))];65    ks.sort((a, b) => a - b);66    return ks;67  }6869  /* Points d'évaluation exacts : 0, chaque strike, et un point au-delà du70   * dernier strike (le payoff est linéaire par morceaux entre ces points). */71  function evalPoints(legs) {72    const ks = kinks(legs);73    const far = ks.length ? ks[ks.length - 1] * 1.5 + 100 : 200;74    return [0, ...ks, far];75  }7677  /* Pente du payoff au-delà du dernier strike ($ par $ de spot). */78  function slopeRight(legs) {79    let s = 0;80    for (const leg of legs) {81      if (leg.kind === 'c' || leg.kind === 's') s += leg.qty * multOf(leg);82    }83    return s;84  }8586  /* Breakevens exacts (zéros du payoff à l'échéance). */87  function breakevens(legs) {88    const pts = evalPoints(legs);89    const out = [];90    for (let i = 1; i < pts.length; i++) {91      const x0 = pts[i - 1], x1 = pts[i];92      const y0 = payoffAt(legs, x0), y1 = payoffAt(legs, x1);93      if (y0 === 0 && (i === 1 || payoffAt(legs, pts[i - 2]) !== 0)) out.push(x0);94      if ((y0 < 0 && y1 > 0) || (y0 > 0 && y1 < 0)) {95        out.push(x0 + (x1 - x0) * (-y0) / (y1 - y0));96      } else if (y1 === 0 && i === pts.length - 1) {97        out.push(x1);98      }99    }100    // au-delà du dernier point, la pente est constante : un zéro éventuel est déjà couvert101    return [...new Set(out.map(v => +v.toFixed(6)))].sort((a, b) => a - b);102  }103104  /* Profit max / perte max à l'échéance. Infinity si non borné à droite. */105  function extremes(legs) {106    const pts = evalPoints(legs);107    let maxP = -Infinity, minP = Infinity;108    for (const p of pts) {109      const y = payoffAt(legs, p);110      if (y > maxP) maxP = y;111      if (y < minP) minP = y;112    }113    const s = slopeRight(legs);114    if (s > 1e-9) maxP = Infinity;115    if (s < -1e-9) minP = -Infinity;116    return { maxProfit: maxP, maxLoss: minP };117  }118119  /* Courbe de payoff à l'échéance sur [sMin, sMax] — insère les strikes pour120   * que les cassures soient exactes. */121  function payoffCurve(legs, sMin, sMax, n) {122    n = n || 160;123    const xs = new Set();124    for (let i = 0; i <= n; i++) xs.add(sMin + (i / n) * (sMax - sMin));125    for (const k of kinks(legs)) if (k > sMin && k < sMax) xs.add(k);126    const sorted = [...xs].sort((a, b) => a - b);127    return sorted.map(s => ({ s, pnl: payoffAt(legs, s) }));128  }129130  /* P&L « aujourd'hui » estimé par développement de Taylor sur les Greeks :131   *   ΔP&L ≈ Σ qty·mult·( Δ·dS + ½Γ·dS² + Θ·jours + ν·dIV )132   * S0 = spot de référence (jour de construction), days = jours écoulés,133   * dIV = variation d'IV en points (ex. +0.02). Estimation locale : fiable134   * près de S0, indicative loin de S0. */135  function pnlToday(legs, S, S0, opts) {136    const days = (opts && opts.days) || 0;137    const dIV = (opts && opts.dIV) || 0;138    const dS = S - S0;139    let pnl = 0;140    for (const leg of legs) {141      const g = leg.greeks || {};142      const k = leg.qty * multOf(leg);143      if (leg.kind === 's') { pnl += k * dS; continue; }144      pnl += k * ((g.delta || 0) * dS + 0.5 * (g.gamma || 0) * dS * dS +145                  (g.theta || 0) * days + (g.vega || 0) * dIV * 100);146    }147    return pnl;148  }149150  function pnlTodayCurve(legs, sMin, sMax, S0, opts, n) {151    n = n || 160;152    const out = [];153    for (let i = 0; i <= n; i++) {154      const s = sMin + (i / n) * (sMax - sMin);155      out.push({ s, pnl: pnlToday(legs, s, S0, opts) });156    }157    return out;158  }159160  /* Prix de référence d'un contrat : mid (bid+ask)/2, fallback last.161   * Retourne {price, source: 'mid'|'last'|null}. */162  function refPrice(rec) {163    const bid = +rec.bid || 0, ask = +rec.ask || 0, last = +rec.last_price || 0;164    if (bid > 0 && ask > 0 && ask >= bid) return { price: (bid + ask) / 2, source: 'mid' };165    if (ask > 0 && bid === 0) return { price: last > 0 ? last : ask, source: last > 0 ? 'last' : 'mid' };166    if (last > 0) return { price: last, source: 'last' };167    return { price: 0, source: null };168  }169170  const M = {171    intrinsic, payoffAt, netCost, netGreeks, kinks, breakevens, extremes,172    payoffCurve, pnlToday, pnlTodayCurve, slopeRight, refPrice, multOf,173  };174175  if (typeof module !== 'undefined' && module.exports) module.exports = M;176  else (root.HF = root.HF || {}).optmath = M;177})(typeof window !== 'undefined' ? window : globalThis);178