JavaScript Examples

These examples work in both browser (native fetch and WebSocket) and Node.js 18+ without any polyfills.


1 — Base Client

// ForexClient.js
export class ForexClient {
  static BASE_URL = "https://api.pipdata.net";

  constructor(apiKey) {
    this.apiKey = apiKey;
  }

  get #headers() {
    return { "X-API-Key": this.apiKey };
  }

  async #get(path, params = {}) {
    const url = new URL(`${ForexClient.BASE_URL}${path}`);
    Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v)));

    const resp = await fetch(url, { headers: this.#headers });
    if (!resp.ok) {
      const err = await resp.json().catch(() => ({}));
      throw new Error(`${err?.error?.code ?? resp.status}: ${err?.error?.message ?? resp.statusText}`);
    }
    return resp.json();
  }

  /** List accessible symbols. */
  symbols(params = {}) {
    return this.#get("/v1/symbols", params);
  }

  /** Fetch historical OHLCV bars. */
  history(symbol, resolution, countback = 500) {
    return this.#get("/v1/history", { symbol, resolution, countback });
  }

  /** Fetch OHLCV bars between two Unix timestamps. */
  historyRange(symbol, resolution, from, to) {
    return this.#get("/v1/history", { symbol, resolution, from, to });
  }

  /** Get available data date ranges. */
  coverage(symbol = null) {
    return this.#get("/v1/coverage", symbol ? { symbol } : {});
  }

  /** Check feed health. */
  status() {
    return this.#get("/v1/status");
  }

  /** Get API key usage statistics. */
  usage(days = 7) {
    return this.#get("/v1/usage", { days });
  }
}

2 — Fetch Historical Data

import { ForexClient } from "./ForexClient.js";

const client = new ForexClient("fxd_your_key_here");

async function main() {
  // Service health check
  const st = await client.status();
  console.log("Service status:", st.status);

  // Fetch last 200 H1 bars for EUR/USD
  const data = await client.history("WMMARKETS:ZERO:EURUSD", "60", 200);

  if (data.s === "ok") {
    // Zip into bar objects
    const bars = data.t.map((t, i) => ({
      time:   new Date(t * 1000).toISOString(),
      open:   data.o[i],
      high:   data.h[i],
      low:    data.l[i],
      close:  data.c[i],
      volume: data.v[i],
    }));

    const last = bars[bars.length - 1];
    console.log(`${data.meta.bar_count} bars received`);
    console.log(`Latest bar: ${last.time}  close=${last.close.toFixed(5)}`);
  }
}

main().catch(console.error);

3 — TradingView Charting Library DataFeed

Drop-in DataFeed implementation for the TradingView Charting Library:

// datafeed.js
const API_KEY  = "fxd_your_key_here";
const BASE_URL = "https://api.pipdata.net";
const HEADERS  = { "X-API-Key": API_KEY };

const ForexDatafeed = {
  // ── onReady ─────────────────────────────────────────────────────────────
  onReady(callback) {
    fetch(`${BASE_URL}/v1/config`, { headers: HEADERS })
      .then(r => r.json())
      .then(cfg => setTimeout(() => callback(cfg), 0));
  },

  // ── searchSymbols ────────────────────────────────────────────────────────
  searchSymbols(query, exchange, type, callback) {
    fetch(`${BASE_URL}/v1/symbols?search=${encodeURIComponent(query)}&limit=30`, { headers: HEADERS })
      .then(r => r.json())
      .then(data => callback(
        (data.symbols ?? []).map(s => ({
          symbol:      s.symbol,
          full_name:   s.symbol,
          description: s.description,
          exchange:    s.exchange,
          type:        s.type,
        }))
      ));
  },

  // ── resolveSymbol ────────────────────────────────────────────────────────
  resolveSymbol(symbolName, onResolve, onError) {
    fetch(`${BASE_URL}/v1/symbols?symbol=${encodeURIComponent(symbolName)}`, { headers: HEADERS })
      .then(r => r.json())
      .then(s => onResolve(s))
      .catch(err => onError(err.message));
  },

  // ── getBars ──────────────────────────────────────────────────────────────
  getBars(symbolInfo, resolution, periodParams, onResult, onError) {
    const { from, to, countBack } = periodParams;
    const params = new URLSearchParams({ symbol: symbolInfo.name, resolution });

    if (countBack) {
      params.set("countback", countBack);
      if (to) params.set("to", to);
    } else {
      params.set("from", from);
      params.set("to",   to);
    }

    fetch(`${BASE_URL}/v1/history?${params}`, { headers: HEADERS })
      .then(r => r.json())
      .then(data => {
        if (data.s === "no_data") { onResult([], { noData: true }); return; }
        if (data.s !== "ok")      { onError(data.errmsg || "Unknown error"); return; }

        const bars = data.t.map((t, i) => ({
          time:   t * 1000,  // TradingView expects milliseconds
          open:   data.o[i],
          high:   data.h[i],
          low:    data.l[i],
          close:  data.c[i],
          volume: data.v[i],
        }));
        onResult(bars, { noData: bars.length === 0 });
      })
      .catch(err => onError(err.message));
  },

  // ── subscribeBars / unsubscribeBars ──────────────────────────────────────
  _ws:          null,
  _subscribers: {},

  subscribeBars(symbolInfo, resolution, onTick, listenerGuid) {
    this._subscribers[listenerGuid] = { symbol: symbolInfo.name, onTick };
    this._ensureWS();

    if (this._ws.readyState === WebSocket.OPEN) {
      this._sendSubscribe([symbolInfo.name]);
    }
  },

  unsubscribeBars(listenerGuid) {
    delete this._subscribers[listenerGuid];
  },

  _ensureWS() {
    if (this._ws && this._ws.readyState !== WebSocket.CLOSED) return;

    this._ws = new WebSocket(`wss://api.pipdata.net/v1/stream?api_key=${API_KEY}`);

    this._ws.onopen = () => {
      const symbols = [...new Set(Object.values(this._subscribers).map(s => s.symbol))];
      if (symbols.length) this._sendSubscribe(symbols);
      setInterval(() => {
        if (this._ws.readyState === WebSocket.OPEN)
          this._ws.send(JSON.stringify({ action: "ping" }));
      }, 30_000);
    };

    this._ws.onmessage = (ev) => {
      const msg = JSON.parse(ev.data);
      if (msg.type !== "bar") return;
      for (const sub of Object.values(this._subscribers)) {
        if (sub.symbol === msg.symbol) {
          sub.onTick({
            time:   msg.bar.time * 1000,
            open:   msg.bar.open,
            high:   msg.bar.high,
            low:    msg.bar.low,
            close:  msg.bar.close,
            volume: msg.bar.volume,
          });
        }
      }
    };
  },

  _sendSubscribe(symbols) {
    this._ws.send(JSON.stringify({ action: "subscribe", symbols, type: "bar" }));
  },

  // ── getServerTime ────────────────────────────────────────────────────────
  // Bar timestamps are broker UTC+3. TradingView must be told the same "now"
  // or it treats bars after real-UTC-now as future data and hides them.
  getServerTime(callback) {
    callback(Math.floor(Date.now() / 1000) + 10800);
  },
};

// Initialize TradingView widget
new TradingView.widget({
  container_id: "tv_chart_container",
  datafeed:     ForexDatafeed,
  library_path: "/charting_library/",
  symbol:       "WMMARKETS:ZERO:EURUSD",
  interval:     "60",
  locale:       "en",
  theme:        "dark",
});

4 — Node.js WebSocket

// For Node.js — install: npm install ws
const WebSocket = require("ws");

const API_KEY = "fxd_your_key_here";
const ws = new WebSocket(`wss://api.pipdata.net/v1/stream?api_key=${API_KEY}`);

let pingTimer;

ws.on("open", () => {
  console.log("Connected");

  ws.send(JSON.stringify({
    action:  "subscribe",
    symbols: ["WMMARKETS:ZERO:EURUSD", "WMMARKETS:ZERO:XAUUSD", "WMMARKETS:ZERO:BTCUSD"],
    type:    "bar"
  }));

  pingTimer = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN)
      ws.send(JSON.stringify({ action: "ping" }));
  }, 30_000);
});

ws.on("message", (data) => {
  const msg = JSON.parse(data);

  if (msg.type === "subscribed") {
    console.log(`Subscribed to ${msg.symbols.length} symbols`);
  }

  if (msg.type === "bar") {
    const sym  = msg.symbol.split(":")[2];
    const time = new Date(msg.bar.time * 1000).toISOString().slice(11, 16);
    console.log(`[${time}] ${sym.padEnd(8)} close=${msg.bar.close}`);
  }
});

ws.on("close", () => {
  clearInterval(pingTimer);
  console.log("Disconnected — restarting in 5s");
  setTimeout(() => process.exit(1), 5000);
});

ws.on("error", (err) => {
  console.error("WebSocket error:", err.message);
});