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.

---

Mustafa Kamal Hossain

Mustafa Kamal Hossain

Founder & Principal Engineer at Manfi. Passionate about Laravel, SaaS architecture, and high-performance engineering.