TickAtlas
Tutorial 12 min read · March 28, 2026

Creating a Real-Time Forex Dashboard with React and API Data

Build a live forex dashboard using React, Tailwind CSS, and the TickAtlas API. Displays real-time prices, indicator signals, and currency pair comparisons.

CG
By the TickAtlas team

What We Are Building

A responsive dashboard that shows real-time price quotes, RSI/MACD status, and Bollinger Band positions for your forex watchlist. It auto-refreshes every 30 seconds and highlights pairs with active signals.

Step 1: Project Setup

npx create-react-app forex-dashboard
cd forex-dashboard
npm install axios
# We'll use Tailwind CSS for styling

Step 2: API Service Layer

Create a service module that handles all TickAtlas API calls. Never put your API key in client-side code in production — use a backend proxy. For development, we will use an environment variable.

python
// src/services/api.js
import axios from 'axios';

const API_BASE = 'https://tickatlas.com/v1';
const API_KEY = process.env.REACT_APP_CLAW_API_KEY;

const client = axios.create({
  baseURL: API_BASE,
  headers: { 'X-API-Key': API_KEY }
});

export async function getQuotes(symbols) {
  const promises = symbols.map(symbol =>
    client.get('/quotes', { params: { symbol } })
      .then(res => ({ symbol, ...res.data.data }))
      .catch(() => ({ symbol, error: true }))
  );
  return Promise.all(promises);
}

export async function getIndicators(symbol, timeframe = 'H1') {
  const res = await client.get('/indicators', {
    params: {
      symbol,
      timeframe,
      indicators: 'RSI_14,MACD,BBANDS_20,ADX_14'
    }
  });
  return res.data.data;
}

export async function getOHLC(symbol, timeframe = 'H1', bars = 24) {
  const res = await client.get('/ohlc', {
    params: { symbol, timeframe, bars }
  });
  return res.data.data.candles;
}

Step 3: Pair Card Component

javascript
// src/components/PairCard.jsx
import { useState, useEffect } from 'react';
import { getIndicators } from '../services/api';

function SignalBadge({ value, type }) {
  const colors = {
    oversold: 'bg-green-500/20 text-green-400',
    overbought: 'bg-red-500/20 text-red-400',
    bullish: 'bg-green-500/20 text-green-400',
    bearish: 'bg-red-500/20 text-red-400',
    neutral: 'bg-gray-500/20 text-gray-400',
  };
  return (
    <span className={\`px-2 py-0.5 rounded text-xs \${colors[type] || colors.neutral}\`}>
      {value}
    </span>
  );
}

export default function PairCard({ symbol, quote }) {
  const [indicators, setIndicators] = useState(null);

  useEffect(() => {
    getIndicators(symbol).then(setIndicators);
  }, [symbol]);

  if (!indicators) return <div className="card animate-pulse h-32" />;

  const rsi = indicators.indicators.RSI_14;
  const macd = indicators.indicators.MACD;
  const adx = indicators.indicators.ADX_14;

  return (
    <div className="card card-hover">
      <div className="flex justify-between items-center mb-3">
        <h3 className="font-bold text-lg">{symbol}</h3>
        <span className="text-xl font-mono">
          {quote?.bid?.toFixed(5) || '---'}
        </span>
      </div>
      <div className="grid grid-cols-3 gap-2 text-sm">
        <div>
          <p className="text-text-muted text-xs">RSI</p>
          <p className="font-mono">{rsi.value.toFixed(1)}</p>
          <SignalBadge value={rsi.signal} type={rsi.signal} />
        </div>
        <div>
          <p className="text-text-muted text-xs">MACD Hist</p>
          <p className={\`font-mono \${macd.histogram > 0 ? 'text-green-400' : 'text-red-400'}\`}>
            {macd.histogram.toFixed(5)}
          </p>
        </div>
        <div>
          <p className="text-text-muted text-xs">ADX</p>
          <p className="font-mono">{adx.value.toFixed(1)}</p>
          <SignalBadge
            value={adx.value > 25 ? 'trending' : 'ranging'}
            type={adx.value > 25 ? 'bullish' : 'neutral'}
          />
        </div>
      </div>
    </div>
  );
}

Step 4: Dashboard Layout

javascript
// src/App.jsx
import { useState, useEffect } from 'react';
import { getQuotes } from './services/api';
import PairCard from './components/PairCard';

const WATCHLIST = ['EURUSD', 'GBPUSD', 'USDJPY', 'AUDUSD', 'XAUUSD', 'BTCUSD'];

export default function App() {
  const [quotes, setQuotes] = useState({});
  const [lastUpdate, setLastUpdate] = useState(null);

  const fetchQuotes = async () => {
    const data = await getQuotes(WATCHLIST);
    const quoteMap = {};
    data.forEach(q => { quoteMap[q.symbol] = q; });
    setQuotes(quoteMap);
    setLastUpdate(new Date());
  };

  useEffect(() => {
    fetchQuotes();
    const interval = setInterval(fetchQuotes, 30000); // 30s refresh
    return () => clearInterval(interval);
  }, []);

  return (
    <div className="min-h-screen bg-bg-primary text-text-primary">
      <header className="container py-6 flex justify-between items-center">
        <h1 className="text-2xl font-display font-bold">Forex Dashboard</h1>
        <span className="text-sm text-text-muted">
          {lastUpdate ? \`Updated: \${lastUpdate.toLocaleTimeString()}\` : 'Loading...'}
        </span>
      </header>
      <main className="container pb-12">
        <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
          {WATCHLIST.map(symbol => (
            <PairCard
              key={symbol}
              symbol={symbol}
              quote={quotes[symbol]}
            />
          ))}
        </div>
      </main>
    </div>
  );
}

Step 5: Signal Alert Panel

javascript
// src/components/SignalPanel.jsx
export default function SignalPanel({ quotes, indicators }) {
  // Filter for pairs with active signals
  const activeSignals = Object.entries(indicators)
    .filter(([_, data]) => {
      if (!data) return false;
      const rsi = data.indicators?.RSI_14?.value;
      return rsi < 30 || rsi > 70;
    })
    .map(([symbol, data]) => ({
      symbol,
      rsi: data.indicators.RSI_14.value,
      signal: data.indicators.RSI_14.signal,
      direction: data.indicators.RSI_14.value < 30 ? 'BUY' : 'SELL'
    }));

  if (activeSignals.length === 0) {
    return (
      <div className="card text-center text-text-muted py-6">
        No active signals. Monitoring {Object.keys(quotes).length} pairs.
      </div>
    );
  }

  return (
    <div className="space-y-2">
      {activeSignals.map(s => (
        <div key={s.symbol}
          className={\`card border-l-4 \${
            s.direction === 'BUY' ? 'border-green-500' : 'border-red-500'
          }\`}
        >
          <div className="flex justify-between">
            <span className="font-bold">{s.symbol}</span>
            <span className={s.direction === 'BUY' ? 'text-green-400' : 'text-red-400'}>
              {s.direction} — RSI {s.rsi.toFixed(1)}
            </span>
          </div>
        </div>
      ))}
    </div>
  );
}

API Response Reference

json
// GET /v1/quotes?symbol=EURUSD
{
  "success": true,
  "data": {
    "symbol": "EURUSD",
    "bid": 1.08350,
    "ask": 1.08365,
    "spread": 0.00015,
    "timestamp": "2026-03-28T14:00:02Z"
  }
}

Production Considerations

Use a Backend Proxy

Never expose your API key in client-side JavaScript. Create a simple Express or Next.js API route that proxies requests to TickAtlas.

Rate Limit Awareness

With 6 pairs refreshing every 30 seconds, you make ~720 requests per hour. Check your plan limits and adjust the refresh interval accordingly.

Error Handling

Show stale data with a warning badge rather than blank cards when the API is temporarily unreachable.

Further Reading

Try this with live data

Every account gets $2.50 in free PAYG credits. No card required — paste your API key and run the code above against live broker data.