/* ANAX · live-prices.js Universal real-time-ish price ticker for marketing visuals. Strategy: 1. Attempt to fetch live opening prices from free CORS-friendly sources (Stooq for FX/commodities, CoinGecko for crypto). Falls back to hardcoded baseline if unreachable. 2. After init, random-walks every 1.5–2.5s with realistic per-asset volatility. 3. Tags every element with [data-tick="SYMBOL"] gets updated; bid/ask ladders stay coherent (ask > bid by published spread). 4. Up/down flash via .tick-up / .tick-dn classes (CSS provided in brand-sections.css helper). Compliance disclaimer: marketing display only. Live values stream inside MT5. */ (function () { 'use strict'; if (window.__anaxLiveBooted) return; window.__anaxLiveBooted = true; // Real-time feed endpoint — prices.php at the SITE ROOT, resolved depth-independently // from this script's own URL so it works at any deploy base (root or subdirectory). var PRICES_URL = (function () { var sc = document.currentScript; if (!sc) { var ss = document.getElementsByTagName('script'); for (var i = ss.length - 1; i >= 0; i--) { if (ss[i].src && /live-prices\.js/.test(ss[i].src)) { sc = ss[i]; break; } } } if (sc && sc.src) { try { return new URL('../../prices.php', sc.src).href; } catch (e) {} } return 'prices.php'; })(); // ---------- 1. Baseline prices (anchor + vol) ---------- // base = anchor mid · vol = stdev per tick · dp = display decimal places // pipUnit = how much 1 pip equals in price terms (display) var SYMBOLS = { 'EURUSD': { base: 1.1469, vol: 0.00012, dp: 5, pipUnit: 0.00010, spread: 0.00010 }, 'GBPUSD': { base: 1.324, vol: 0.00016, dp: 5, pipUnit: 0.00010, spread: 0.00014 }, 'USDJPY': { base: 160.88, vol: 0.024, dp: 3, pipUnit: 0.010, spread: 0.015 }, 'USDCHF': { base: 0.90240, vol: 0.00012, dp: 5, pipUnit: 0.00010, spread: 0.00014 }, 'USDCAD': { base: 1.36120, vol: 0.00014, dp: 5, pipUnit: 0.00010, spread: 0.00016 }, 'AUDUSD': { base: 0.65340, vol: 0.00012, dp: 5, pipUnit: 0.00010, spread: 0.00012 }, 'NZDUSD': { base: 0.59820, vol: 0.00011, dp: 5, pipUnit: 0.00010, spread: 0.00012 }, 'XAUUSD': { base: 4250.0, vol: 0.85, dp: 2, pipUnit: 0.10, spread: 0.30 }, 'XAGUSD': { base: 66.8, vol: 0.040, dp: 3, pipUnit: 0.010, spread: 0.026 }, 'US500': { base: 7420.0, vol: 1.20, dp: 2, pipUnit: 0.10, spread: 0.60 }, 'US30': { base: 51498.0,vol: 8.50, dp: 2, pipUnit: 1.0, spread: 1.20 }, 'NAS100': { base: 29670.0,vol: 9.20, dp: 2, pipUnit: 1.0, spread: 1.80 }, 'GER40': { base: 24978.0,vol: 6.80, dp: 2, pipUnit: 1.0, spread: 1.20 }, 'UK100': { base: 10409.0, vol: 3.40, dp: 2, pipUnit: 0.10, spread: 0.80 }, 'USOIL': { base: 74.44, vol: 0.085, dp: 2, pipUnit: 0.01, spread: 0.05 }, 'UKOIL': { base: 78.4, vol: 0.090, dp: 2, pipUnit: 0.01, spread: 0.06 }, 'NATGAS': { base: 3.15, vol: 0.012, dp: 3, pipUnit: 0.001, spread: 0.010 }, 'BTCUSD': { base: 64130, vol: 95, dp: 0, pipUnit: 1, spread: 28 }, 'ETHUSD': { base: 1748.0, vol: 8.5, dp: 2, pipUnit: 0.01, spread: 2.40 }, 'DFM30': { base: 5124.20, vol: 1.80, dp: 2, pipUnit: 0.01, spread: 0.60 }, 'ADX25': { base: 9234.80, vol: 3.20, dp: 2, pipUnit: 0.01, spread: 0.80 }, }; // mid -> live bid/ask function quote(sym) { var s = SYMBOLS[sym]; if (!s) return null; var half = s.spread / 2; return { bid: s.base - half, ask: s.base + half, mid: s.base, dp: s.dp, pipUnit: s.pipUnit }; } // ---------- 2. Random walk ---------- function step(sym) { // No synthetic drift. Prices come ONLY from the real prices.php feed, so the // displayed value is always the true market price — never a fabricated one. } function fmt(val, dp) { if (val == null || isNaN(val)) return ''; return val.toLocaleString('en-US', { minimumFractionDigits: dp, maximumFractionDigits: dp }); } // ---------- 3. Element updaters ---------- function flash(el, prev, curr) { if (prev == null) return; if (curr > prev) { el.classList.remove('tick-dn'); el.classList.add('tick-up'); } else if (curr < prev) { el.classList.remove('tick-up'); el.classList.add('tick-dn'); } } function updatePrice(el) { var sym = el.dataset.tick; var side = el.dataset.side || 'mid'; // bid · ask · mid var off = parseFloat(el.dataset.offset || '0'); // ladder offset in pips var q = quote(sym); if (!q) return; var raw = side === 'bid' ? q.bid : (side === 'ask' ? q.ask : q.mid); if (off) raw = raw + (off * q.pipUnit); var disp = fmt(raw, q.dp); var prev = parseFloat(el.dataset.prev || 'NaN'); if (el.textContent.trim() !== disp) { el.textContent = disp; flash(el, prev, raw); el.dataset.prev = raw; } } // Spread cell — shows the published spread for the symbol in pips function updateSpread(el) { var sym = el.dataset.spread; var s = SYMBOLS[sym]; if (!s) return; var pips = (s.spread / s.pipUnit).toFixed(s.dp <= 2 ? 1 : 1); if (el.textContent.trim() !== pips) el.textContent = pips; } // PnL · position-marking text. data-pnl="SYMBOL" data-direction="long|short" // data-lots="0.5" data-entry="2398.10" function updatePnl(el) { var sym = el.dataset.pnl; var dir = el.dataset.direction || 'long'; var lots = parseFloat(el.dataset.lots || '1'); var entry= parseFloat(el.dataset.entry || '0'); var s = SYMBOLS[sym]; if (!s) return; var diff = (s.base - entry) * (dir === 'long' ? 1 : -1); // approx contract size: FX 100k, gold 100, indices 1 (per-pt), crypto 1 var contract = (sym.length === 6 && /^[A-Z]{6}$/.test(sym)) ? 100000 : (sym === 'XAUUSD' || sym === 'XAGUSD') ? 100 : 1; var pnl = diff * contract * lots; var sign = pnl >= 0 ? '+' : '−'; el.textContent = sign + '$' + Math.abs(pnl).toLocaleString('en-US', { maximumFractionDigits: 0 }); el.classList.remove('up','dn'); el.classList.add(pnl >= 0 ? 'up' : 'dn'); } // ---------- 4. Main tick loop (resilient — never dies on error) ---------- function tickAll() { try { var symbols = {}; document.querySelectorAll('[data-tick]').forEach(function (el) { symbols[el.dataset.tick] = 1; }); document.querySelectorAll('[data-spread]').forEach(function (el) { symbols[el.dataset.spread] = 1; }); document.querySelectorAll('[data-pnl]').forEach(function (el) { symbols[el.dataset.pnl] = 1; }); Object.keys(symbols).forEach(step); document.querySelectorAll('[data-tick]').forEach(function(el){ try{updatePrice(el);}catch(e){} }); document.querySelectorAll('[data-spread]').forEach(function(el){ try{updateSpread(el);}catch(e){} }); document.querySelectorAll('[data-pnl]').forEach(function(el){ try{updatePnl(el);}catch(e){} }); } catch (err) { if (window.__anaxLiveVerbose) console.warn('[live-prices] tickAll err', err); } } // setInterval (not recursive setTimeout) — survives any single-tick exception. // Jitter not critical; 1800ms feels live between 4s TV polls. function scheduleTick() { setInterval(tickAll, 1800); } // ---------- 5. TradingView scanner feed · real-time prices ---------- // POST scanner.tradingview.com — same endpoint TradingView's own scanner uses. // Public, CORS-open. Returns latest close + change % per ticker. // Symbol map: ANAX internal → TradingView ticker // Verified TradingView scanner tickers (probed Nov 2025 — all return live data) var TV_MAP = { // FX majors · FX_IDC continuous feed 'EURUSD': { cat: 'forex', tv: 'FX_IDC:EURUSD' }, 'GBPUSD': { cat: 'forex', tv: 'FX_IDC:GBPUSD' }, 'USDJPY': { cat: 'forex', tv: 'FX_IDC:USDJPY' }, 'USDCHF': { cat: 'forex', tv: 'FX_IDC:USDCHF' }, 'USDCAD': { cat: 'forex', tv: 'FX_IDC:USDCAD' }, 'AUDUSD': { cat: 'forex', tv: 'FX_IDC:AUDUSD' }, 'NZDUSD': { cat: 'forex', tv: 'FX_IDC:NZDUSD' }, // Metals 'XAUUSD': { cat: 'cfd', tv: 'OANDA:XAUUSD' }, 'XAGUSD': { cat: 'cfd', tv: 'TVC:SILVER' }, // Indices 'US500': { cat: 'cfd', tv: 'SP:SPX' }, 'US30': { cat: 'cfd', tv: 'TVC:DJI' }, 'NAS100': { cat: 'america',tv: 'NASDAQ:NDX' }, 'GER40': { cat: 'cfd', tv: 'XETR:DAX' }, 'UK100': { cat: 'cfd', tv: 'TVC:UKX' }, // Energy · TV free scanner doesn't carry continuous oil/gas — keep sim baseline 'USOIL': { cat: 'cfd', tv: null }, 'UKOIL': { cat: 'cfd', tv: null }, 'NATGAS': { cat: 'cfd', tv: null }, // Crypto · BITSTAMP feed 'BTCUSD': { cat: 'crypto', tv: 'BITSTAMP:BTCUSD' }, 'ETHUSD': { cat: 'crypto', tv: 'BITSTAMP:ETHUSD' }, // UAE locals — TV scanner doesn't cover ADX/DFM tickers; sim only 'DFM30': { cat: 'cfd', tv: null }, 'ADX25': { cat: 'cfd', tv: null } }; function fetchTVCategory(category, sym2tv) { var tickers = Object.values(sym2tv).filter(Boolean); if (!tickers.length) return Promise.resolve(null); var ctl = (typeof AbortController !== 'undefined') ? new AbortController() : null; var to = ctl ? setTimeout(function(){ try{ ctl.abort(); }catch(e){} }, 10000) : null; return fetch('https://scanner.tradingview.com/' + category + '/scan', { method: 'POST', headers: { 'Content-Type': 'text/plain;charset=UTF-8' }, body: JSON.stringify({ symbols: { tickers: tickers, query: { types: [] } }, columns: ['close', 'change'] }), cache: 'no-store', signal: ctl ? ctl.signal : undefined }) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (data) { if (to) clearTimeout(to); if (!data || !data.data) return null; var out = {}; data.data.forEach(function (row) { out[row.s] = row.d; }); return out; }) .catch(function (err) { if (to) clearTimeout(to); if (window.__anaxLiveVerbose) console.warn('[live-prices] TV fetch fail:', category, err && err.message); return null; }); } function pullTVPrices() { return fetch(PRICES_URL, { cache: 'no-store' }) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (data) { if (data && data.prices) { Object.keys(data.prices).forEach(function (sym) { var px = data.prices[sym] && data.prices[sym].p; if (typeof px === 'number' && !isNaN(px) && SYMBOLS[sym]) { SYMBOLS[sym].base = px; SYMBOLS[sym]._anchor = px; } }); } tickAll(); }) .catch(function () { tickAll(); }); // keep last real value on any error — never blank/wrong } function scheduleTVPoll() { // Poll TradingView scanner every 4s — real-feed primary setInterval(pullTVPrices, 3000); } // ---------- 6. Recovery — re-pull on visibility/focus/online change ---------- // Browsers throttle setInterval to ~1/min when tab hidden. When user returns, // values appear "frozen". These listeners fire an immediate fresh pull. function attachRecoveryListeners() { var lastRecover = 0; function recover() { var now = Date.now(); if (now - lastRecover < 1500) return; // debounce lastRecover = now; pullTVPrices(); tickAll(); } document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'visible') recover(); }); window.addEventListener('focus', recover); window.addEventListener('online', recover); window.addEventListener('pageshow', recover); // back/forward cache restore } // ---------- 7. Boot ---------- function boot() { pullTVPrices(); // immediate first pull tickAll(); scheduleTick(); // gentle interpolation between polls scheduleTVPoll(); // real TV feed refresh attachRecoveryListeners(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } })();