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%
8.0 KB · 209 lines javascript
Raw Blame History
1/* ============================================================================2 * HFChart — transformations de séries (from scratch, zéro dépendance)3 * Author : Simon-Pierre Boucher — contact@spboucher.ai4 * ----------------------------------------------------------------------------5 * Un "bar" = {t,o,h,l,c,v}. Chaque transform retourne un tableau de barres6 * pseudo-temporelles ; l'axe X reste l'index de barre, comme sur tout chart HF.7 * ==========================================================================*/8(function () {9  'use strict';10  const HF = (window.HF = window.HF || {});1112  /* Arrondi "joli" (1 / 2 / 2.5 / 5 × 10^n) — sert de taille de brique auto. */13  function niceRound(x) {14    if (!(x > 0)) return 1;15    const p = Math.pow(10, Math.floor(Math.log10(x)));16    const m = x / p;17    const step = m < 1.5 ? 1 : m < 2.25 ? 2 : m < 3.5 ? 2.5 : m < 7.5 ? 5 : 10;18    return step * p;19  }2021  /* Brique auto façon ATR : moyenne des true ranges des ~500 dernières barres. */22  function autoBrick(bars, k) {23    const n = bars.length;24    if (n < 3) return niceRound((n ? bars[n - 1].c : 1) * 0.005);25    let sum = 0, cnt = 0;26    for (let i = Math.max(1, n - 500); i < n; i++) {27      const b = bars[i], p = bars[i - 1];28      sum += Math.max(b.h - b.l, Math.abs(b.h - p.c), Math.abs(b.l - p.c));29      cnt++;30    }31    return niceRound((sum / cnt) * (k || 1)) || niceRound(bars[n - 1].c * 0.002);32  }3334  function heikinAshi(bars) {35    const out = new Array(bars.length);36    let po = 0, pc = 0;37    for (let i = 0; i < bars.length; i++) {38      const b = bars[i];39      const c = (b.o + b.h + b.l + b.c) / 4;40      const o = i === 0 ? (b.o + b.c) / 2 : (po + pc) / 2;41      out[i] = { t: b.t, o, h: Math.max(b.h, o, c), l: Math.min(b.l, o, c), c, v: b.v };42      po = o; pc = c;43    }44    return out;45  }4647  /* Renko : briques de taille fixe, renversement = 2 briques. */48  function renko(bars, opts) {49    const brick = (opts && opts.brick) || autoBrick(bars, 1);50    const out = [];51    if (!bars.length || !(brick > 0)) return out;52    let lo = Math.floor(bars[0].c / brick) * brick; // plancher de la brique courante53    let hi = lo + brick;54    let dir = 0, vol = 0;55    const push = (t, up) => {56      if (up) { out.push({ t, o: hi, c: hi + brick, h: hi + brick, l: hi, v: vol, up: true }); lo = hi; hi += brick; dir = 1; }57      else { out.push({ t, o: lo, c: lo - brick, h: lo, l: lo - brick, v: vol, up: false }); hi = lo; lo -= brick; dir = -1; }58      vol = 0;59    };60    for (const b of bars) {61      vol += b.v;62      for (;;) {63        if (b.c >= hi + brick - 1e-9 && dir >= 0) push(b.t, true);64        else if (b.c <= lo - brick + 1e-9 && dir <= 0) push(b.t, false);65        else if (dir > 0 && b.c <= lo - brick + 1e-9) push(b.t, false); // renversement (2 briques sous hi)66        else if (dir < 0 && b.c >= hi + brick - 1e-9) push(b.t, true);  // renversement haussier67        else break;68      }69    }70    return out;71  }7273  /* Three-Line Break : nouvelle barre seulement si le close casse les N dernières. */74  function lineBreak(bars, opts) {75    const nBreak = (opts && opts.lines) || 3;76    const out = [];77    for (const b of bars) {78      if (!out.length) {79        if (b.c !== b.o) out.push({ t: b.t, o: b.o, c: b.c, h: Math.max(b.o, b.c), l: Math.min(b.o, b.c), v: b.v, up: b.c >= b.o });80        continue;81      }82      const last = out[out.length - 1];83      const win = out.slice(-nBreak);84      let maxC = -Infinity, minC = Infinity;85      for (const x of win) { maxC = Math.max(maxC, x.o, x.c); minC = Math.min(minC, x.o, x.c); }86      if (b.c > maxC + 1e-9) {87        const o = Math.max(last.o, last.c);88        out.push({ t: b.t, o, c: b.c, h: b.c, l: o, v: b.v, up: true });89      } else if (b.c < minC - 1e-9) {90        const o = Math.min(last.o, last.c);91        out.push({ t: b.t, o, c: b.c, h: o, l: b.c, v: b.v, up: false });92      }93    }94    return out;95  }9697  /* Range Bars : chaque barre couvre exactement `range` de haut en bas. */98  function rangeBars(bars, opts) {99    const range = (opts && opts.range) || autoBrick(bars, 1.5);100    const out = [];101    if (!bars.length || !(range > 0)) return out;102    let cur = null;103    for (const b of bars) {104      for (const p of [b.o, b.h, b.l, b.c]) {105        if (!cur) { cur = { t: b.t, o: p, h: p, l: p, c: p, v: 0 }; continue; }106        cur.h = Math.max(cur.h, p); cur.l = Math.min(cur.l, p);107        cur.c = p; cur.t = b.t;108        if (cur.h - cur.l >= range - 1e-9) {109          const up = cur.c >= cur.o;110          const h = cur.l + range, l = cur.l;111          const hh = up ? h : cur.h, ll = up ? cur.l : cur.h - range;112          const close = up ? hh : ll;113          out.push({ t: cur.t, o: cur.o, h: hh, l: ll, c: close, v: cur.v + b.v, up });114          cur = { t: b.t, o: close, h: close, l: close, c: close, v: 0 };115        }116      }117      if (cur) cur.v += b.v;118    }119    return out;120  }121122  /* Kagi : item = une ligne verticale {o: départ, c: arrivée, up: yang (épaisse)}.123   * Passe yang au-dessus de l'épaule précédente, yin sous la taille précédente. */124  function kagi(bars, opts) {125    const rev = (opts && opts.reversal) || autoBrick(bars, 3);126    const out = [];127    if (!bars.length || !(rev > 0)) return out;128    let dir = 0, anchor = bars[0].c, extreme = bars[0].c, yang = true;129    let prevShoulder = null, prevWaist = null;130    for (const b of bars) {131      const p = b.c;132      if (dir === 0) {133        extreme = p;134        if (p >= anchor + rev) dir = 1;135        else if (p <= anchor - rev) dir = -1;136        continue;137      }138      if (dir > 0) {139        if (p > extreme) extreme = p;140        if (extreme - p >= rev) {141          if (prevShoulder != null && extreme > prevShoulder) yang = true;142          out.push({ t: b.t, o: anchor, c: extreme, h: extreme, l: anchor, v: 0, up: yang });143          prevShoulder = extreme;144          anchor = extreme; extreme = p; dir = -1;145        }146      } else {147        if (p < extreme) extreme = p;148        if (p - extreme >= rev) {149          if (prevWaist != null && extreme < prevWaist) yang = false;150          out.push({ t: b.t, o: anchor, c: extreme, h: anchor, l: extreme, v: 0, up: yang });151          prevWaist = extreme;152          anchor = extreme; extreme = p; dir = 1;153        }154      }155    }156    if (dir !== 0 && extreme !== anchor) {157      out.push({ t: bars[bars.length - 1].t, o: anchor, c: extreme,158                 h: Math.max(anchor, extreme), l: Math.min(anchor, extreme), v: 0, up: yang });159    }160    return out;161  }162163  /* Point & Figure : colonnes de X (hausse) / O (baisse), renversement N boîtes.164   * item = {type:'X'|'O', lo, n, box} → n boîtes empilées à partir de lo. */165  function pnf(bars, opts) {166    const box = (opts && opts.box) || autoBrick(bars, 1);167    const revN = (opts && opts.reversal) || 3;168    const cols = [];169    if (!bars.length || !(box > 0)) return cols;170    const fb = p => Math.floor(p / box + 1e-9);171    let dir = 0, top = fb(bars[0].c), bot = top;172    const pushCol = (t, type) => {173      cols.push({174        t, type, lo: bot * box, n: top - bot + 1, box,175        o: type === 'X' ? bot * box : (top + 1) * box,176        c: type === 'X' ? (top + 1) * box : bot * box,177        h: (top + 1) * box, l: bot * box, v: 0, up: type === 'X',178      });179    };180    for (const b of bars) {181      const hiB = fb(b.h), loB = fb(b.l);182      if (dir === 0) {183        if (hiB > top) { dir = 1; top = hiB; }184        else if (loB < bot) { dir = -1; bot = loB; }185        continue;186      }187      if (dir > 0) {188        if (hiB > top) top = hiB;189        else if (top - loB >= revN) {190          pushCol(b.t, 'X');191          const newTop = top - 1;192          bot = loB; top = newTop; dir = -1;193        }194      } else {195        if (loB < bot) bot = loB;196        else if (hiB - bot >= revN) {197          pushCol(b.t, 'O');198          const newBot = bot + 1;199          top = hiB; bot = newBot; dir = 1;200        }201      }202    }203    if (dir !== 0 && top >= bot) pushCol(bars[bars.length - 1].t, dir > 0 ? 'X' : 'O');204    return cols;205  }206207  HF.transforms = { heikinAshi, renko, lineBreak, rangeBars, kagi, pnf, autoBrick, niceRound };208})();209