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%
1/* ============================================================================2 * HFChart Embed — bootstrapper des graphes intégrés (données inline)3 * Author : Simon-Pierre Boucher — contact@spboucher.ai4 * ----------------------------------------------------------------------------5 * Servi concaténé après transforms/indicators/renderers/engine dans /embed.js.6 * Usage (snippet généré par le bouton « Exporter » de www.hfchart.io) :7 * <div id="hfchart-xxxx" style="width:100%;height:420px"></div>8 * <script>(window.HFChartEmbedQueue=window.HFChartEmbedQueue||[])9 * .push({el:'#hfchart-xxxx',config:{symbol,tf,type,theme,scheme,inds,10 * params,decimals,hasVolume,bars:[[t,o,h,l,c,v],…]}});</script>11 * <script async src="https://www.hfchart.io/embed.js"></script>12 * Le graphe est pleinement interactif (zoom, pan, crosshair) et 100 % autonome :13 * les données sont dans la page, aucune requête réseau.14 * ==========================================================================*/15(function () {16 'use strict';17 if (window.__HFEmbedLoaded) return;18 window.__HFEmbedLoaded = true;19 const HF = window.HF;2021 const TYPES = {22 candles: { draw: 'candles' },23 hollow: { draw: 'hollow' },24 ohlc: { draw: 'ohlc' },25 line: { draw: 'line' },26 step: { draw: 'step' },27 area: { draw: 'area' },28 baseline: { draw: 'baseline' },29 heikin: { draw: 'candles', transform: 'heikinAshi' },30 renko: { draw: 'bricks', transform: 'renko' },31 linebreak: { draw: 'bricks', transform: 'lineBreak' },32 rangebars: { draw: 'candles', transform: 'rangeBars' },33 kagi: { draw: 'kagi', transform: 'kagi', noVol: true },34 pnf: { draw: 'pnf', transform: 'pnf', noVol: true },35 };3637 /* Tokens des deux thèmes (mêmes valeurs que style.css de la plateforme). */38 const TOKENS = {39 light: {40 '--surface': '#fcfcfb', '--ink': '#0b0b0b', '--ink2': '#52514e',41 '--muted': '#898781', '--grid': '#e1e0d9', '--axis': '#c3c2b7',42 '--chip': '#0b0b0b', '--chip-text': '#ffffff',43 '--series-1': '#2a78d6', '--series-2': '#eb6834', '--series-3': '#1baf7a',44 '--series-4': '#eda100', '--series-5': '#e87ba4', '--series-6': '#008300',45 '--series-7': '#4a3aa7', '--series-8': '#e34948',46 },47 dark: {48 '--surface': '#1a1a19', '--ink': '#ffffff', '--ink2': '#c3c2b7',49 '--muted': '#898781', '--grid': '#2c2c2a', '--axis': '#383835',50 '--chip': '#ffffff', '--chip-text': '#0b0b0b',51 '--series-1': '#3987e5', '--series-2': '#d95926', '--series-3': '#199e70',52 '--series-4': '#c98500', '--series-5': '#d55181', '--series-6': '#008300',53 '--series-7': '#9085e9', '--series-8': '#e66767',54 },55 };56 const UPDN = {57 classic: { '--up': '#0ca30c', '--dn': '#d03b3b' },58 cvd_light: { '--up': '#2a78d6', '--dn': '#e34948' },59 cvd_dark: { '--up': '#3987e5', '--dn': '#e66767' },60 };6162 const OVERLAYS = {63 sma20: (bars, p) => ({ vals: HF.ind.sma(HF.ind.closes(bars), p.sma20 || 20), slot: 3 }),64 sma50: (bars, p) => ({ vals: HF.ind.sma(HF.ind.closes(bars), p.sma50 || 50), slot: 1 }),65 ema20: (bars, p) => ({ vals: HF.ind.ema(HF.ind.closes(bars), p.ema20 || 20), slot: 2 }),66 boll: (bars, p) => ({ band: HF.ind.bollinger(bars, p.bollN || 20, p.bollK || 2), slot: 6 }),67 vwap: bars => { const v = HF.ind.vwapSession(bars); return { band: { up: v.up2, lo: v.dn2, mid: v.v }, slot: 4, needVol: true }; },68 };69 const PANES = {70 rsi: (bars, p) => ({ kind: 'rsi', data: HF.ind.rsi(bars, p.rsiN || 14) }),71 macd: (bars, p) => ({ kind: 'macd', data: HF.ind.macd(bars, p.macdF || 12, p.macdS || 26, p.macdSig || 9) }),72 atr: (bars, p) => ({ kind: 'atr', data: HF.ind.atr(bars, p.atrN || 14) }),73 };7475 function render(item) {76 const el = typeof item.el === 'string' ? document.querySelector(item.el) : item.el;77 const cfg = item.config || {};78 if (!el || !cfg.bars || !cfg.bars.length) return;79 const theme = cfg.theme === 'dark' ? 'dark' : 'light';80 const scheme = cfg.scheme === 'cvd' ? (theme === 'dark' ? 'cvd_dark' : 'cvd_light') : 'classic';81 el.innerHTML = '';82 el.style.position = el.style.position || 'relative';83 if (!el.style.height) el.style.height = '420px';84 for (const [k, v] of Object.entries(TOKENS[theme])) el.style.setProperty(k, v);85 for (const [k, v] of Object.entries(UPDN[scheme])) el.style.setProperty(k, v);86 el.style.background = TOKENS[theme]['--surface'];87 el.style.borderRadius = el.style.borderRadius || '8px';88 el.style.overflow = 'hidden';89 el.style.fontFamily = 'system-ui, -apple-system, "Segoe UI", sans-serif';9091 const chartDiv = document.createElement('div');92 chartDiv.style.cssText = 'position:absolute;inset:0 0 20px 0;';93 el.appendChild(chartDiv);9495 // barre de crédit (attribution + identité de la vue)96 const credit = document.createElement('div');97 credit.style.cssText =98 `position:absolute;left:0;right:0;bottom:0;height:20px;display:flex;align-items:center;` +99 `justify-content:space-between;padding:0 8px;font-size:10.5px;box-sizing:border-box;` +100 `color:${TOKENS[theme]['--muted']};border-top:1px solid ${TOKENS[theme]['--grid']};`;101 const label = [cfg.symbol, cfg.tf, cfg.typeLabel || cfg.type].filter(Boolean).join(' · ');102 credit.innerHTML =103 `<span style="font-weight:600;color:${TOKENS[theme]['--ink2']}">${label}</span>` +104 `<a href="https://www.hfchart.io" target="_blank" rel="noopener" ` +105 `style="color:${TOKENS[theme]['--muted']};text-decoration:none">HFChart · données hfmarketdata.io</a>`;106 el.appendChild(credit);107108 const bars = cfg.bars.map(b => ({ t: b[0], o: b[1], h: b[2], l: b[3], c: b[4], v: b[5] || 0 }));109 const def = TYPES[cfg.type] || TYPES.candles;110 const p = cfg.params || {};111 let items = bars;112 if (def.transform) {113 const opts = {};114 if (cfg.type === 'renko' && p.brick > 0) opts.brick = p.brick;115 if (cfg.type === 'rangebars' && p.range > 0) opts.range = p.range;116 if (cfg.type === 'kagi' && p.reversal > 0) opts.reversal = p.reversal;117 if (cfg.type === 'pnf') { if (p.box > 0) opts.box = p.box; if (p.pnfRev > 0) opts.reversal = p.pnfRev; }118 if (cfg.type === 'linebreak' && p.lines > 0) opts.lines = p.lines;119 items = HF.transforms[def.transform](bars, opts);120 if (!items.length) items = bars;121 }122123 const chart = new HF.HFChart(chartDiv, {});124 const spec = {125 items,126 draw: def.draw,127 decimals: cfg.decimals != null ? cfg.decimals : 2,128 defaultVisible: Math.min(items.length, cfg.visible || 320),129 hasVolume: !!cfg.hasVolume,130 showVolume: !!cfg.hasVolume && !def.noVol,131 overlays: [],132 panes: [],133 anchor: cfg.type === 'baseline' && items.length ? items[0].c : null,134 };135 for (const id of (cfg.inds || [])) {136 if (OVERLAYS[id]) {137 const made = OVERLAYS[id](items, p);138 if (made.needVol && !cfg.hasVolume) continue;139 spec.overlays.push({ ...made, color: chart.th.series[made.slot] });140 } else if (PANES[id]) {141 spec.panes.push({ id, label: id.toUpperCase(), ...PANES[id](items, p) });142 }143 }144 chart.setSeries(spec);145 }146147 const q = window.HFChartEmbedQueue = window.HFChartEmbedQueue || [];148 const boot = () => { q.splice(0).forEach(render); q.push = it => { render(it); return 0; }; };149 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);150 else boot();151})();152