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.
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.
// 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
// 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
// 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
// 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
// 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.
Keep reading
All articles- Tutorial 11 min read
24/7 Crypto Monitoring: Building Always-On Analysis Systems
Build a monitoring system that watches crypto markets around the clock, detects significant moves, and sends alerts when conditions match your criteria.
March 28, 2026
- Tutorial 12 min read
How to Build an AI Market Analyst That Runs 24/7
Build a production-ready AI market analyst that monitors forex and crypto markets around the clock, generates daily briefings, and alerts you to opportunities via Telegram.
March 28, 2026
- Tutorial 10 min read
Using ATR for Dynamic Stop-Loss Placement
Learn how to use Average True Range (ATR) to set volatility-adjusted stop losses that adapt to market conditions, with full code examples.
March 28, 2026