PageSourceSearch

https://gex-levels.com/assets/crypto.js?v=cx3

js gex-levels.com collected 2026-09-24 14:44:02 UTC 13,251 bytes, 186 lines download raw bytes

1/* crypto.js — /crypto : niveaux dealer BTC/ETH calcules par nous depuis la chaine
2   d'options Deribit (publique). Trois lectures du worker, rien d'autre :
3     /public/crypto/levels   (derniere photo, cron 10 min)
4     /public/crypto/candles  (perp Deribit, 10 min)
5     /public/crypto/history  (photos du jour -> murs/flip dans le temps)
6   CSP du site : connect-src = api.gex-levels.com uniquement, d'ou tout passe par le worker. */
7(function () {
8  "use strict";
9  var publicEnv = (window.__GEX_PUBLIC_ENV__ || {});
10  var apiBase = window.GEX_LEVELS_API_BASE || publicEnv.apiBaseUrl || "https://api.gex-levels.com";
11  var $ = function (s) { return document.querySelector(s); };
12  // Tout ce qui vient du worker passe par esc() avant innerHTML — meme si le
13  // worker est a nous, une reponse alteree ne doit jamais devenir du HTML.
14  var esc = function (v) { return String(v == null ? "" : v).replace(/[&<>"']/g, function (c) { return { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;" }[c]; }); };
15  var currency = (location.hash || "").replace("#", "").toUpperCase() === "ETH" ? "ETH" : "BTC";
16  var lwChart = null, candleSeries = null, priceLines = [], lineValues = [];
17  var REFRESH_MS = 60 * 1000;
18  var fmt = function (n, d) { return n == null || !isFinite(n) ? "—" : Number(n).toLocaleString("en-US", { maximumFractionDigits: d == null ? 0 : d, minimumFractionDigits: d == null ? 0 : d }); };
19  var money = function (n) { var a = Math.abs(n); var s = a >= 1e9 ? (a / 1e9).toFixed(2) + "B" : a >= 1e6 ? (a / 1e6).toFixed(1) + "M" : a >= 1e3 ? (a / 1e3).toFixed(0) + "K" : String(Math.round(a)); return (n < 0 ? "−$" : "$") + s; };
20  var pct = function (a, b) { return a == null || b == null || !b ? "" : ((a - b) / b * 100).toFixed(2) + "%"; };
21
22  function setStatus(state, text) { var el = $("[data-cx-status]"); if (!el) return; el.setAttribute("data-state", state); el.querySelector("[data-cx-status-text]").textContent = text; }
23
24  function getJson(path) {
25    // credentials: la session compte (cookie) decide si les niveaux sont servis.
26    return fetch(apiBase + path, { headers: { Accept: "application/json" }, credentials: "include" }).then(function (r) { return r.json().catch(function () { return {}; }).then(function (j) { j.__status = r.status; return j; }); });
27  }
28
29  // Niveaux verrouilles derriere l'abonnement (decision Marc 05/09) : le prix reste
30  // visible, tout ce qui est calcule par nous demande une session abonnee.
31  function renderLocked(status) {
32    var signedIn = status === 402 || status === 403;
33    var lock = $("[data-cx-lock]");
34    if (lock) {
35      lock.hidden = false;
36      lock.querySelector("[data-cx-lock-text]").textContent = signedIn
37        ? "Your account is signed in but has no active Indicator subscription. The BTC and ETH maps are part of the Indicator plan."
38        : "BTC and ETH levels are part of the GEX Levels Indicator subscription. Sign in to see today's map, or start a free trial.";
39      lock.querySelector("[data-cx-lock-signin]").hidden = signedIn;
40    }
41    ["[data-cx-readings]", "[data-cx-ladder]", "[data-cx-profile]"].forEach(function (s) { var el = $(s); if (el) el.innerHTML = ""; });
42    var hn = $("[data-cx-hist-note]"); if (hn) hn.textContent = "Subscribers see the day's structure over time here.";
43    var svg = $("[data-cx-hist]"); if (svg) svg.innerHTML = "";
44    setStatus("stale", currency + " · levels locked · subscribe to unlock");
45  }
46
47  function renderReadings(s) {
48    var regime = s.regime_by_model === "positive_gamma" ? "Positive gamma" : s.regime_by_model === "negative_gamma" ? "Negative gamma" : "—";
49    var regimeNote = s.regime_by_model === "positive_gamma" ? "dealers dampen moves (model)" : s.regime_by_model === "negative_gamma" ? "dealers amplify moves (model)" : "";
50    var rows = [
51      ["Spot (index)", fmt(s.spot, s.currency === "ETH" ? 2 : 0), esc(s.currency) + "/USD · Deribit index"],
52      ["Regime", regime, regimeNote],
53      ["Net dealer gamma", money(s.net_gex_usd), "per 1% move · " + esc(s.expiry_scope)],
54      ["DVOL", s.dvol == null ? "—" : Number(s.dvol).toFixed(1), "Deribit implied vol index"],
55      ["Open interest", fmt(s.oi_total, 0) + " " + esc(s.currency), esc(s.contracts) + " usable contracts · " + esc(s.instruments) + " listed"]
56    ];
57    $("[data-cx-readings]").innerHTML = rows.map(function (r) { return "<div class=\"cx-read\"><b>" + r[0] + "</b><span>" + r[1] + "</span><small>" + r[2] + "</small></div>"; }).join("");
58  }
59
60  function renderLadder(s) {
61    var items = [
62      { k: "call_wall", name: "Call Wall", note: "largest positive net gamma strike", color: "#7fb37a" },
63      { k: "zero_gamma", name: "Zero Gamma (model)", note: s.zg_band_lo != null ? "band " + fmt(s.zg_band_lo) + " – " + fmt(s.zg_band_hi) + (s.zg_confidence ? " · confidence " + esc(s.zg_confidence) : "") : "repriced from the chain", color: "#d4b483" },
64      { k: "gamma_flip", name: "Gamma Flip", note: "cumulative net gamma crosses zero", color: "#e8dcc2" },
65      { k: "put_wall", name: "Put Wall", note: "largest negative net gamma strike", color: "#c86b5a" }
66    ].filter(function (i) { return s[i.k] != null; });
67    items.push({ k: "spot", name: "Spot", note: "now", color: "#f5f1e9", spot: true });
68    items.sort(function (a, b) { return s[b.k] - s[a.k]; });
69    $("[data-cx-ladder]").innerHTML = items.map(function (i) {
70      var v = s[i.k];
71      return "<li" + (i.spot ? " data-spot=\"1\"" : "") + "><span class=\"cx-mark cx-mark--" + i.k + "\"></span><span class=\"cx-name\">" + i.name + "<small>" + i.note + "</small></span><span class=\"cx-px\">" + fmt(v, s.currency === "ETH" ? 2 : 0) + (i.spot ? "" : "<small>" + pct(v, s.spot) + " from spot</small>") + "</span></li>";
72    }).join("");
73  }
74
75  function renderProfile(s) {
76    var prof = (s.profile || []).slice();
77    if (!prof.length) { $("[data-cx-profile]").innerHTML = ""; return; }
78    var max = prof.reduce(function (m, r) { return Math.max(m, Math.abs(r[1])); }, 0) || 1;
79    // du plus haut strike au plus bas, comme une echelle de prix
80    prof.sort(function (a, b) { return b[0] - a[0]; });
81    var spotDone = false;
82    var html = prof.map(function (r) {
83      var w = Math.abs(r[1]) / max * 50;
84      var row = "<span class=\"cx-k\">" + fmt(r[0]) + "</span><span class=\"cx-bar\"><i class=\"" + (r[1] >= 0 ? "pos" : "neg") + "\" data-w=\"" + w.toFixed(1) + "\"></i></span><span class=\"cx-v\">" + money(r[1]) + "</span>";
85      if (!spotDone && r[0] <= s.spot) { spotDone = true; row = "<span class=\"cx-k cx-spot\">spot " + fmt(s.spot) + "</span><span class=\"cx-bar\"></span><span class=\"cx-v cx-spot\">—</span>" + row; }
86      return row;
87    }).join("");
88    $("[data-cx-profile]").innerHTML = html;
89    // Largeurs via CSSOM : un attribut style inline serait bloque par la CSP.
90    $("[data-cx-profile]").querySelectorAll("i[data-w]").forEach(function (el) { el.style.width = el.getAttribute("data-w") + "%"; });
91  }
92
93  function ensureChart() {
94    if (lwChart || !window.LightweightCharts) return;
95    var wrap = $("[data-cx-chart]");
96    lwChart = LightweightCharts.createChart(wrap, {
97      layout: { background: { type: "solid", color: "transparent" }, textColor: "#8a8478", fontFamily: "JetBrains Mono, monospace", fontSize: 11 },
98      grid: { vertLines: { color: "rgba(245,241,233,0.04)" }, horzLines: { color: "rgba(245,241,233,0.04)" } },
99      rightPriceScale: { borderColor: "rgba(245,241,233,0.1)" },
100      timeScale: { borderColor: "rgba(245,241,233,0.1)", timeVisible: true, secondsVisible: false },
101      crosshair: { mode: 0 }, handleScroll: true, handleScale: true, autoSize: true
102    });
103    candleSeries = lwChart.addCandlestickSeries({ upColor: "#7fb37a", downColor: "#c86b5a", borderVisible: false, wickUpColor: "#7fb37a", wickDownColor: "#c86b5a" });
104    // Les lignes de niveaux hors de la plage des bougies seraient dessinees hors
105    // ecran : l'autoscale inclut les niveaux proches du spot (±12 %).
106    candleSeries.applyOptions({ autoscaleInfoProvider: function (orig) {
107      var r = orig();
108      if (!r || !r.priceRange || !lineValues.length) return r;
109      var lo = r.priceRange.minValue, hi = r.priceRange.maxValue;
110      lineValues.forEach(function (v) { if (v < lo) lo = v; if (v > hi) hi = v; });
111      return { priceRange: { minValue: lo, maxValue: hi }, margins: r.margins };
112    } });
113  }
114
115  function renderChart(s, candles) {
116    s = s || {};
117    ensureChart();
118    if (!candleSeries) return;
119    candleSeries.setData(candles.map(function (c) { return { time: c.t, open: c.o, high: c.h, low: c.l, close: c.c }; }));
120    priceLines.forEach(function (l) { try { candleSeries.removePriceLine(l); } catch (_) {} });
121    priceLines = [];
122    lineValues = [];
123    var lines = [["call_wall", "Call Wall", "#7fb37a", 0], ["gamma_flip", "Gamma Flip", "#e8dcc2", 2], ["zero_gamma", "Zero Gamma", "#d4b483", 1], ["put_wall", "Put Wall", "#c86b5a", 0]];
124    lines.forEach(function (l) {
125      if (s[l[0]] == null) return;
126      if (s.spot && Math.abs(s[l[0]] - s.spot) <= s.spot * 0.12) lineValues.push(s[l[0]]);
127      priceLines.push(candleSeries.createPriceLine({ price: s[l[0]], color: l[2], lineWidth: 1, lineStyle: l[3], axisLabelVisible: true, title: l[1] }));
128    });
129    if (s.zg_band_lo != null && s.zg_band_hi != null) {
130      [s.zg_band_lo, s.zg_band_hi].forEach(function (p) { priceLines.push(candleSeries.createPriceLine({ price: p, color: "rgba(212,180,131,0.35)", lineWidth: 1, lineStyle: 3, axisLabelVisible: false, title: "" })); });
131    }
132    lwChart.timeScale().fitContent();
133  }
134
135  function renderHistory(rows, s) {
136    var svg = $("[data-cx-hist]");
137    if (!svg) return;
138    var W = 600, H = 120, P = 6;
139    if (!rows || rows.length < 2) { svg.innerHTML = "<text x=\"8\" y=\"64\" fill=\"#8a8478\" font-size=\"11\" font-family=\"JetBrains Mono, monospace\">History fills in as the sampler runs (one point every 10 minutes).</text>"; return; }
140    var series = [["call_wall", "#7fb37a"], ["gamma_flip", "#e8dcc2"], ["zero_gamma", "#d4b483"], ["put_wall", "#c86b5a"], ["spot", "#f5f1e9"]];
141    var vals = [];
142    rows.forEach(function (r) { series.forEach(function (sr) { if (r[sr[0]] != null) vals.push(r[sr[0]]); }); });
143    var lo = Math.min.apply(null, vals), hi = Math.max.apply(null, vals); if (hi === lo) hi = lo + 1;
144    var t0 = rows[0].ts, t1 = rows[rows.length - 1].ts; if (t1 === t0) t1 = t0 + 1;
145    var x = function (t) { return P + (t - t0) / (t1 - t0) * (W - 2 * P); }, y = function (v) { return H - P - (v - lo) / (hi - lo) * (H - 2 * P); };
146    svg.setAttribute("viewBox", "0 0 " + W + " " + H);
147    svg.innerHTML = series.map(function (sr) {
148      var pts = rows.filter(function (r) { return r[sr[0]] != null; }).map(function (r) { return x(r.ts).toFixed(1) + "," + y(r[sr[0]]).toFixed(1); });
149      return pts.length ? "<polyline fill=\"none\" stroke=\"" + sr[1] + "\" stroke-width=\"" + (sr[0] === "spot" ? 1.6 : 1) + "\" points=\"" + pts.join(" ") + "\"/>" : "";
150    }).join("");
151    $("[data-cx-hist-note]").textContent = rows.length + " snapshots · " + new Date(t0).toUTCString().slice(17, 22) + " → " + new Date(t1).toUTCString().slice(17, 22) + " UTC";
152  }
153
154  function load() {
155    var to = Date.now(), from = to - 24 * 3600 * 1000;
156    setStatus("live", "Loading " + currency + "…");
157    return Promise.all([
158      getJson("/public/crypto/levels?currency=" + currency),
159      getJson("/public/crypto/candles?currency=" + currency + "&resolution=10&hours=48"),
160      getJson("/public/crypto/history?currency=" + currency + "&from=" + from + "&to=" + to + "&limit=400")
161    ]).then(function (res) {
162      var s = res[0], c = res[1], h = res[2];
163      if (c && c.ok) renderChart(s && s.ok ? s : {}, c.candles || []);
164      if (s && (s.__status === 401 || s.__status === 402 || s.__status === 403)) { renderLocked(s.__status); return; }
165      var lock = $("[data-cx-lock]"); if (lock) lock.hidden = true;
166      if (!s || !s.ok) { setStatus("error", s && s.error === "no_sample_yet" ? "First snapshot lands within 10 minutes." : "Levels unavailable right now."); return; }
167      var age = Math.round((Date.now() - s.ts) / 60000);
168      setStatus(age > 25 ? "stale" : "live", currency + " · computed " + age + " min ago from " + Number(s.instruments || 0) + " Deribit options · refreshes every 10 min");
169      renderReadings(s); renderLadder(s); renderProfile(s);
170      renderHistory(h && h.ok ? h.rows : [], s);
171      document.title = currency + " gamma exposure levels — Call Wall " + fmt(s.call_wall) + " · Put Wall " + fmt(s.put_wall) + " — GEX Levels";
172    }).catch(function () { setStatus("error", "Could not reach the data service."); });
173  }
174
175  document.querySelectorAll("[data-cx-currency]").forEach(function (b) {
176    b.addEventListener("click", function () {
177      currency = b.getAttribute("data-cx-currency");
178      document.querySelectorAll("[data-cx-currency]").forEach(function (x) { x.setAttribute("aria-pressed", x === b ? "true" : "false"); });
179      history.replaceState(null, "", "#" + currency.toLowerCase());
180      load();
181    });
182    b.setAttribute("aria-pressed", b.getAttribute("data-cx-currency") === currency ? "true" : "false");
183  });
184  load();
185  setInterval(load, REFRESH_MS);
186})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.