File size: 2,626 Bytes
e3fe258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
 * twelveDataAPI.js  (SERVER-SIDE ONLY)
 *
 * Fetches OHLC candles from Twelve Data (OANDA-sourced forex / metals) so the chart
 * and scanners match broker / TradingView data instead of Yahoo's coarse FX feed.
 *
 * API keys are passed in by the caller (read from server environment variables) and
 * are NEVER sent to the browser. The browser only ever reads the already-fetched
 * candles from the server's /api/market-data cache.
 */

// App symbol -> Twelve Data symbol (forex/metals use slash notation).
const TD_SYMBOL = {
  XAUUSD: 'XAU/USD',
  GBPUSD: 'GBP/USD',
  USDCAD: 'USD/CAD',
  BTCUSDT: 'BTC/USD',
};

// App timeframe -> Twelve Data interval.
const TD_INTERVAL = {
  '1m': '1min', '5m': '5min', '15m': '15min', '1H': '1h', '4H': '4h', '1D': '1day',
};

/**
 * Fetch candles from Twelve Data.
 *
 * @param {string} symbol     App symbol, e.g. 'USDCAD'.
 * @param {string} timeframe  App timeframe, e.g. '15m'.
 * @param {number} outputsize Number of candles to request.
 * @param {string} apiKey     Twelve Data API key for this symbol.
 * @returns {Promise<Array<{time:number,open:number,high:number,low:number,close:number,volume:number}>>}
 */
export async function fetchTwelveCandles(symbol, timeframe = '15m', outputsize = 500, apiKey) {
  if (!apiKey) throw new Error(`No Twelve Data API key provided for ${symbol}`);

  const tdSymbol = TD_SYMBOL[symbol] || symbol;
  const interval = TD_INTERVAL[timeframe] || '15min';
  const url = `https://api.twelvedata.com/time_series?symbol=${encodeURIComponent(tdSymbol)}`
    + `&interval=${interval}&outputsize=${outputsize}&timezone=UTC&format=JSON&apikey=${apiKey}`;

  const res = await fetch(url);
  const data = await res.json();

  // Twelve Data signals problems via { status: 'error', message, code }.
  if (!data || data.status === 'error' || !Array.isArray(data.values)) {
    const msg = data && data.message ? data.message : 'no values returned';
    throw new Error(`Twelve Data error for ${symbol}: ${msg}`);
  }

  // Values are newest-first; convert datetime (UTC) -> unix seconds and reverse.
  const candles = data.values.map(v => {
    const dt = v.datetime.includes(' ')
      ? v.datetime.replace(' ', 'T') + 'Z'
      : v.datetime + 'T00:00:00Z';
    return {
      time: Math.floor(new Date(dt).getTime() / 1000),
      open: parseFloat(v.open),
      high: parseFloat(v.high),
      low: parseFloat(v.low),
      close: parseFloat(v.close),
      volume: parseFloat(v.volume || 0),
    };
  }).filter(c => Number.isFinite(c.time) && Number.isFinite(c.close)).reverse();

  return candles;
}

export default { fetchTwelveCandles };