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%
5.9 KB · 165 lines javascript
Raw Blame History
1/* ============================================================================2 * HFChart — indicateurs techniques, calculés from scratch3 * Author : Simon-Pierre Boucher — contact@spboucher.ai4 * ----------------------------------------------------------------------------5 * Toutes les fonctions retournent des tableaux alignés sur l'index des barres6 * (null avant la période de chauffe).7 * ==========================================================================*/8(function () {9  'use strict';10  const HF = (window.HF = window.HF || {});1112  const closes = bars => bars.map(b => b.c);1314  function sma(vals, n) {15    const out = new Array(vals.length).fill(null);16    let sum = 0;17    for (let i = 0; i < vals.length; i++) {18      sum += vals[i];19      if (i >= n) sum -= vals[i - n];20      if (i >= n - 1) out[i] = sum / n;21    }22    return out;23  }2425  function ema(vals, n) {26    const out = new Array(vals.length).fill(null);27    const k = 2 / (n + 1);28    let prev = null, seed = 0;29    for (let i = 0; i < vals.length; i++) {30      if (i < n - 1) { seed += vals[i]; continue; }31      if (i === n - 1) { prev = (seed + vals[i]) / n; out[i] = prev; continue; }32      prev = vals[i] * k + prev * (1 - k);33      out[i] = prev;34    }35    return out;36  }3738  function bollinger(bars, n, mult) {39    n = n || 20; mult = mult || 2;40    const c = closes(bars);41    const mid = sma(c, n);42    const up = new Array(c.length).fill(null);43    const lo = new Array(c.length).fill(null);44    let sum = 0, sumSq = 0;45    for (let i = 0; i < c.length; i++) {46      sum += c[i]; sumSq += c[i] * c[i];47      if (i >= n) { sum -= c[i - n]; sumSq -= c[i - n] * c[i - n]; }48      if (i >= n - 1) {49        const mean = sum / n;50        const sd = Math.sqrt(Math.max(0, sumSq / n - mean * mean));51        up[i] = mean + mult * sd;52        lo[i] = mean - mult * sd;53      }54    }55    return { mid, up, lo };56  }5758  function rsi(bars, n) {59    n = n || 14;60    const c = closes(bars);61    const out = new Array(c.length).fill(null);62    let avgG = 0, avgL = 0;63    for (let i = 1; i < c.length; i++) {64      const d = c[i] - c[i - 1];65      const g = d > 0 ? d : 0, l = d < 0 ? -d : 0;66      if (i <= n) {67        avgG += g / n; avgL += l / n;68        if (i === n) out[i] = avgL === 0 ? 100 : 100 - 100 / (1 + avgG / avgL);69      } else {70        avgG = (avgG * (n - 1) + g) / n;71        avgL = (avgL * (n - 1) + l) / n;72        out[i] = avgL === 0 ? 100 : 100 - 100 / (1 + avgG / avgL);73      }74    }75    return out;76  }7778  function macd(bars, fast, slow, signal) {79    fast = fast || 12; slow = slow || 26; signal = signal || 9;80    const c = closes(bars);81    const ef = ema(c, fast), es = ema(c, slow);82    const line = c.map((_, i) => (ef[i] != null && es[i] != null) ? ef[i] - es[i] : null);83    // EMA du signal sur la portion définie84    const start = line.findIndex(v => v != null);85    const sig = new Array(c.length).fill(null);86    if (start >= 0) {87      const seg = ema(line.slice(start), signal);88      for (let i = 0; i < seg.length; i++) sig[start + i] = seg[i];89    }90    const hist = line.map((v, i) => (v != null && sig[i] != null) ? v - sig[i] : null);91    return { line, signal: sig, hist };92  }9394  function atr(bars, n) {95    n = n || 14;96    const out = new Array(bars.length).fill(null);97    let prev = null;98    for (let i = 1; i < bars.length; i++) {99      const b = bars[i], p = bars[i - 1];100      const tr = Math.max(b.h - b.l, Math.abs(b.h - p.c), Math.abs(b.l - p.c));101      if (i <= n) {102        prev = (prev || 0) + tr / n;103        if (i === n) out[i] = prev;104      } else {105        prev = (prev * (n - 1) + tr) / n;106        out[i] = prev;107      }108    }109    return out;110  }111112  /* VWAP de session (reset à chaque journée) + bandes ±1σ / ±2σ.113   * Nécessite du volume — désactivé pour les indices. */114  function vwapSession(bars) {115    const v = new Array(bars.length).fill(null);116    const up1 = v.slice(), dn1 = v.slice(), up2 = v.slice(), dn2 = v.slice();117    let day = null, cumV = 0, cumPV = 0, cumP2V = 0;118    for (let i = 0; i < bars.length; i++) {119      const b = bars[i];120      const d = Math.floor(b.t / 86400);121      if (d !== day) { day = d; cumV = 0; cumPV = 0; cumP2V = 0; }122      const tp = (b.h + b.l + b.c) / 3;123      cumV += b.v; cumPV += tp * b.v; cumP2V += tp * tp * b.v;124      if (cumV > 0) {125        const w = cumPV / cumV;126        const sd = Math.sqrt(Math.max(0, cumP2V / cumV - w * w));127        v[i] = w; up1[i] = w + sd; dn1[i] = w - sd; up2[i] = w + 2 * sd; dn2[i] = w - 2 * sd;128      }129    }130    return { v, up1, dn1, up2, dn2 };131  }132133  /* Profil de volume sur une plage d'items visibles.134   * Retourne {rows:[{p0,p1,up,dn,total}], max, poc} — poc = index de la ligne max. */135  function volumeProfile(bars, i0, i1, nRows) {136    nRows = nRows || 24;137    let lo = Infinity, hi = -Infinity;138    for (let i = i0; i <= i1; i++) { const b = bars[i]; if (!b) continue; lo = Math.min(lo, b.l); hi = Math.max(hi, b.h); }139    if (!(hi > lo)) return null;140    const rows = Array.from({ length: nRows }, (_, k) => ({141      p0: lo + (k / nRows) * (hi - lo), p1: lo + ((k + 1) / nRows) * (hi - lo), up: 0, dn: 0, total: 0,142    }));143    const step = (hi - lo) / nRows;144    for (let i = i0; i <= i1; i++) {145      const b = bars[i];146      if (!b || !(b.v > 0)) continue;147      // volume réparti uniformément sur la plage h..l de la barre148      const span = Math.max(b.h - b.l, step * 0.5);149      const k0 = Math.max(0, Math.floor((b.l - lo) / step));150      const k1 = Math.min(nRows - 1, Math.floor((b.h - lo) / step));151      const per = b.v / (k1 - k0 + 1);152      const isUp = b.c >= b.o;153      for (let k = k0; k <= k1; k++) {154        rows[k].total += per;155        if (isUp) rows[k].up += per; else rows[k].dn += per;156      }157    }158    let max = 0, poc = 0;159    rows.forEach((r, k) => { if (r.total > max) { max = r.total; poc = k; } });160    return { rows, max, poc };161  }162163  HF.ind = { sma, ema, bollinger, rsi, macd, atr, vwapSession, volumeProfile, closes };164})();165