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.1 KB · 134 lines javascript
Raw Blame History
1/* ============================================================================2 * HFChart Options Lab — tests unitaires du module de calcul pur3 * Author : Simon-Pierre Boucher — contact@spboucher.ai4 * ----------------------------------------------------------------------------5 *   node --test tests/6 * ==========================================================================*/7'use strict';8const { test } = require('node:test');9const assert = require('node:assert/strict');10const M = require('../web/js/options-math.js');1112const G0 = { delta: 0, gamma: 0, theta: 0, vega: 0, rho: 0 };13const leg = (kind, strike, qty, price, greeks) =>14  ({ kind, strike, qty, price, greeks: greeks || G0 });1516test('long call — payoff à l’échéance', () => {17  const legs = [leg('c', 100, 1, 5)];18  assert.equal(M.payoffAt(legs, 90), -500);        // OTM : perte = prime19  assert.equal(M.payoffAt(legs, 100), -500);       // ATM20  assert.equal(M.payoffAt(legs, 105), 0);          // breakeven21  assert.equal(M.payoffAt(legs, 120), 1500);       // ITM22  assert.deepEqual(M.breakevens(legs), [105]);23  const ex = M.extremes(legs);24  assert.equal(ex.maxProfit, Infinity);25  assert.equal(ex.maxLoss, -500);26  assert.equal(M.netCost(legs), 500);              // débit27});2829test('short put — payoff, crédit et perte max bornée à S=0', () => {30  const legs = [leg('p', 50, -1, 2)];31  assert.equal(M.netCost(legs), -200);             // crédit32  assert.equal(M.payoffAt(legs, 60), 200);         // OTM : on garde la prime33  assert.equal(M.payoffAt(legs, 48), 0);           // breakeven 4834  assert.equal(M.payoffAt(legs, 0), -4800);        // pire cas35  assert.deepEqual(M.breakevens(legs), [48]);36  const ex = M.extremes(legs);37  assert.equal(ex.maxProfit, 200);38  assert.equal(ex.maxLoss, -4800);39});4041test('bull call spread — bornes et breakeven exacts', () => {42  const legs = [leg('c', 100, 1, 6), leg('c', 110, -1, 2)];43  assert.equal(M.netCost(legs), 400);44  assert.equal(M.payoffAt(legs, 95), -400);45  assert.equal(M.payoffAt(legs, 104), 0);          // BE = 100 + 446  assert.equal(M.payoffAt(legs, 110), 600);47  assert.equal(M.payoffAt(legs, 150), 600);        // plafonné48  assert.deepEqual(M.breakevens(legs), [104]);49  const ex = M.extremes(legs);50  assert.equal(ex.maxProfit, 600);51  assert.equal(ex.maxLoss, -400);52});5354test('straddle long — deux breakevens', () => {55  const legs = [leg('c', 100, 1, 4), leg('p', 100, 1, 3)];56  assert.deepEqual(M.breakevens(legs), [93, 107]);57  const ex = M.extremes(legs);58  assert.equal(ex.maxProfit, Infinity);            // côté call59  assert.equal(ex.maxLoss, -700);                  // pile sur le strike60  assert.equal(M.payoffAt(legs, 100), -700);61});6263test('iron condor — quatre jambes, profit = crédit net, perte bornée', () => {64  // put 90 short / put 85 long / call 110 short / call 115 long, crédit net 2 $65  const legs = [66    leg('p', 90, -1, 1.8), leg('p', 85, 1, 0.8),67    leg('c', 110, -1, 1.6), leg('c', 115, 1, 0.6),68  ];69  assert.equal(+M.netCost(legs).toFixed(2), -200); // crédit 2.0070  assert.equal(+M.payoffAt(legs, 100).toFixed(6), 200);  // au centre : on garde tout71  assert.equal(+M.payoffAt(legs, 85).toFixed(6), -300);  // aile basse : largeur 5 − crédit 272  assert.equal(+M.payoffAt(legs, 120).toFixed(6), -300); // aile haute73  const be = M.breakevens(legs);74  assert.deepEqual(be, [88, 112]);                 // 90−2 et 110+275  const ex = M.extremes(legs);76  assert.equal(+ex.maxProfit.toFixed(6), 200);77  assert.equal(+ex.maxLoss.toFixed(6), -300);78});7980test('covered call — jambe action + call short', () => {81  const legs = [82    { kind: 's', strike: 0, qty: 100, price: 95, mult: 1, greeks: G0 },83    leg('c', 100, -1, 3),84  ];85  assert.equal(M.payoffAt(legs, 100), 800);        // +500 action, +300 prime86  assert.equal(M.payoffAt(legs, 120), 800);        // plafonné au-dessus du strike87  assert.equal(M.payoffAt(legs, 92), 0);           // BE = 95 − 388  assert.deepEqual(M.breakevens(legs), [92]);89  const ex = M.extremes(legs);90  assert.equal(ex.maxProfit, 800);91  assert.equal(ex.maxLoss, -9200);                 // action à 0, prime gardée92});9394test('netGreeks — agrégation signée ×100, action delta 1', () => {95  const legs = [96    leg('c', 100, 2, 4, { delta: 0.55, gamma: 0.02, theta: -0.05, vega: 0.11, rho: 0.03 }),97    leg('p', 95, -1, 2, { delta: -0.30, gamma: 0.015, theta: -0.04, vega: 0.09, rho: -0.02 }),98    { kind: 's', strike: 0, qty: 50, price: 98, mult: 1, greeks: G0 },99  ];100  const g = M.netGreeks(legs);101  assert.equal(+g.delta.toFixed(4), 2 * 100 * 0.55 - 100 * -0.30 + 50); // 190102  assert.equal(+g.gamma.toFixed(4), +(200 * 0.02 - 100 * 0.015).toFixed(4));103  assert.equal(+g.theta.toFixed(4), +(200 * -0.05 - 100 * -0.04).toFixed(4));104  assert.equal(+g.vega.toFixed(4), +(200 * 0.11 - 100 * 0.09).toFixed(4));105});106107test('pnlToday — Taylor : delta, gamma, theta', () => {108  const legs = [leg('c', 100, 1, 5, { delta: 0.5, gamma: 0.04, theta: -0.08, vega: 0.1, rho: 0 })];109  // dS = +2 : 100·(0.5·2 + 0.5·0.04·4) = 108110  assert.equal(+M.pnlToday(legs, 102, 100).toFixed(4), 108);111  // 3 jours plus tard sans bouger : 100·(−0.08·3) = −24112  assert.equal(+M.pnlToday(legs, 100, 100, { days: 3 }).toFixed(4), -24);113  // jambe action : delta pur114  const stock = [{ kind: 's', strike: 0, qty: 100, price: 95, mult: 1, greeks: G0 }];115  assert.equal(M.pnlToday(stock, 97, 95), 200);116});117118test('payoffCurve — inclut les strikes comme points exacts', () => {119  const legs = [leg('c', 100, 1, 6), leg('c', 110, -1, 2)];120  const curve = M.payoffCurve(legs, 80, 130, 25);121  assert.ok(curve.some(p => p.s === 100));122  assert.ok(curve.some(p => p.s === 110));123  assert.ok(curve.length >= 26);124  const last = curve[curve.length - 1];125  assert.equal(last.pnl, 600);126});127128test('refPrice — mid, fallback last, illiquide', () => {129  assert.deepEqual(M.refPrice({ bid: 1.0, ask: 1.2, last_price: 1.4 }), { price: 1.1, source: 'mid' });130  assert.deepEqual(M.refPrice({ bid: 0, ask: 0, last_price: 0.9 }), { price: 0.9, source: 'last' });131  assert.deepEqual(M.refPrice({ bid: 0, ask: 0.01, last_price: 0 }), { price: 0.01, source: 'mid' });132  assert.deepEqual(M.refPrice({ bid: 0, ask: 0, last_price: 0 }), { price: 0, source: null });133});134