The data layer is the foundation of any trading terminal. Get the candle data wrong — wrong timestamps, wrong OHLCV values, wrong interval alignment — and every indicator built on top of it is garbage.
AI Smart Market Analyzer sources live data from three different providers, each with a different API response format, rate limit structure, and coverage universe. The fetchLiveCandles() function unifies all three into a single array of {open, high, low, close, volume} objects that the indicator engine consumes without knowing which source it came from.
Source 1: Binance (Crypto, free, no key required)
Binance's REST /api/v3/klines endpoint returns crypto candlestick data in array format: [openTime, open, high, low, close, volume, ...]. No authentication needed for public market data, and rate limits are generous (1,200 requests/minute). This covers BTC/USD, ETH/USD, XRP/USD, and SOL/USD mapped to their USDT pairs.
Source 2: Polygon.io (Forex, Crypto, institutional quality)
Polygon.io is prioritized when the user provides a key. It offers direct SIP (Securities Information Processor) feeds for Forex with sub-millisecond latency on paid plans. The API response includes an results array with {o, h, l, c, v} fields. Symbol format differs: Forex pairs use C:EURUSD, Crypto uses X:BTCUSD. The multiplier and timespan parameters allow fetching any candle interval directly.
Source 3: Twelve Data (Forex & Commodities)
Twelve Data returns a values array in reverse chronological order (newest first) — which means the response must be .reverse()d before passing to the indicator engine. Symbol format uses slash notation: EUR/USD, XAU/USD, WTI/USD. The interval parameter maps to their format: 1min, 5min, 15min, 4h.
The candle grouping engine
For 2-minute candles, the API doesn't directly offer a 2m interval — only 1m. For 10-minute, only 5m. The groupCandles() function takes a ratio (2 for 2m from 1m, 2 for 10m from 5m) and merges consecutive raw candles:
const groupCandles = (candles, ratio) => {
const grouped = [];
for (let i = 0; i < candles.length; i += ratio) {
const chunk = candles.slice(i, i + ratio);
if (chunk.length < ratio) break;
grouped.push({
open: chunk[0].open,
close: chunk[chunk.length - 1].close,
high: Math.max(...chunk.map(c => c.high)),
low: Math.min(...chunk.map(c => c.low)),
volume: chunk.reduce((sum, c) => sum + c.volume, 0),
});
}
return grouped;
};This ensures that a 2m candle's high is the true high of both 1m candles, and the close is the second candle's close — matching what a native 2m candle feed would provide.
---
