REST API Integration
Learn how to integrate TradeX Chart with REST API backends for fetching historical and real-time market data.
Overview
REST APIs are the most common way to fetch market data. This guide covers:
- Fetching initial chart data
- Loading historical data on scroll
- Pagination and data management
- Error handling and retry logic
- Rate limiting considerations
Basic Setup
1. Fetch Initial Data
import { Chart } from 'tradex-chart'
async function fetchOHLCV(symbol, timeframe, limit = 500) {
const response = await fetch(
`https://api.example.com/v1/ohlcv?symbol=${symbol}&timeframe=${timeframe}&limit=${limit}`
)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
// Transform to TradeX format [timestamp, open, high, low, close, volume]
return data.map(candle => [
candle.timestamp,
candle.open,
candle.high,
candle.low,
candle.close,
candle.volume
])
}
// Initialize chart with fetched data
async function initChart() {
const ohlcv = await fetchOHLCV('BTCUSDT', '1h', 500)
const config = {
title: 'BTC/USDT',
symbol: 'BTCUSDT',
state: {
ohlcv: ohlcv
}
}
const chart = document.createElement('tradex-chart')
document.getElementById('chart-container').appendChild(chart)
chart.start(config)
}
initChart().catch(console.error)
2. Load More Data on Scroll
class ChartDataManager {
constructor(chart, symbol, timeframe) {
this.chart = chart
this.symbol = symbol
this.timeframe = timeframe
this.isLoading = false
this.hasMoreData = true
this.setupScrollListener()
}
setupScrollListener() {
this.chart.on('chart_pan', this.handlePan.bind(this))
}
async handlePan(event) {
const scrollPos = this.chart.scrollPos
// Load more data when near the start
if (scrollPos < 100 && !this.isLoading && this.hasMoreData) {
await this.loadMoreData()
}
}
async loadMoreData() {
this.isLoading = true
try {
const currentData = this.chart.state.data.chart.data
const oldestTimestamp = currentData[0][0]
// Fetch data before the oldest timestamp
const response = await fetch(
`https://api.example.com/v1/ohlcv?` +
`symbol=${this.symbol}&` +
`timeframe=${this.timeframe}&` +
`end=${oldestTimestamp}&` +
`limit=100`
)
const newData = await response.json()
if (newData.length === 0) {
this.hasMoreData = false
return
}
// Format and prepend new data
const formattedData = newData.map(candle => [
candle.timestamp,
candle.open,
candle.high,
candle.low,
candle.close,
candle.volume
])
this.chart.mergeData(formattedData, 'prepend')
} catch (error) {
console.error('Failed to load more data:', error)
} finally {
this.isLoading = false
}
}
}
// Usage
const dataManager = new ChartDataManager(chart, 'BTCUSDT', '1h')
Advanced Patterns
3. Data Caching
class CachedDataFetcher {
constructor(baseURL) {
this.baseURL = baseURL
this.cache = new Map()
this.cacheExpiry = 5 * 60 * 1000 // 5 minutes
}
getCacheKey(symbol, timeframe, start, end) {
return `${symbol}:${timeframe}:${start}:${end}`
}
async fetchOHLCV(symbol, timeframe, start, end) {
const cacheKey = this.getCacheKey(symbol, timeframe, start, end)
const cached = this.cache.get(cacheKey)
// Return cached data if valid
if (cached && Date.now() - cached.timestamp < this.cacheExpiry) {
return cached.data
}
// Fetch fresh data
const url = new URL(`${this.baseURL}/ohlcv`)
url.searchParams.append('symbol', symbol)
url.searchParams.append('timeframe', timeframe)
if (start) url.searchParams.append('start', start)
if (end) url.searchParams.append('end', end)
const response = await fetch(url)
const data = await response.json()
// Cache the result
this.cache.set(cacheKey, {
data,
timestamp: Date.now()
})
return data
}
clearCache() {
this.cache.clear()
}
}
const fetcher = new CachedDataFetcher('https://api.example.com/v1')
4. Error Handling with Retry Logic
class RobustDataFetcher {
constructor(baseURL, maxRetries = 3) {
this.baseURL = baseURL
this.maxRetries = maxRetries
}
async fetchWithRetry(url, options = {}, retryCount = 0) {
try {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 10000)
const response = await fetch(url, {
...options,
signal: controller.signal
})
clearTimeout(timeout)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return await response.json()
} catch (error) {
if (retryCount < this.maxRetries) {
// Exponential backoff
const delay = Math.pow(2, retryCount) * 1000
console.log(`Retry ${retryCount + 1}/${this.maxRetries} after ${delay}ms`)
await new Promise(resolve => setTimeout(resolve, delay))
return this.fetchWithRetry(url, options, retryCount + 1)
}
throw error
}
}
async fetchOHLCV(symbol, timeframe, params = {}) {
const url = new URL(`${this.baseURL}/ohlcv`)
url.searchParams.append('symbol', symbol)
url.searchParams.append('timeframe', timeframe)
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value)
})
return this.fetchWithRetry(url.toString())
}
}
5. Rate Limiting
class RateLimitedFetcher {
constructor(baseURL, requestsPerSecond = 10) {
this.baseURL = baseURL
this.minInterval = 1000 / requestsPerSecond
this.lastRequestTime = 0
this.queue = []
this.processing = false
}
async fetch(endpoint, params = {}) {
return new Promise((resolve, reject) => {
this.queue.push({ endpoint, params, resolve, reject })
this.processQueue()
})
}
async processQueue() {
if (this.processing || this.queue.length === 0) return
this.processing = true
while (this.queue.length > 0) {
const now = Date.now()
const timeSinceLastRequest = now - this.lastRequestTime
if (timeSinceLastRequest < this.minInterval) {
await new Promise(resolve =>
setTimeout(resolve, this.minInterval - timeSinceLastRequest)
)
}
const { endpoint, params, resolve, reject } = this.queue.shift()
this.lastRequestTime = Date.now()
try {
const url = new URL(`${this.baseURL}${endpoint}`)
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value)
})
const response = await fetch(url)
const data = await response.json()
resolve(data)
} catch (error) {
reject(error)
}
}
this.processing = false
}
}
const fetcher = new RateLimitedFetcher('https://api.example.com/v1', 10)
Exchange-Specific Examples
Binance API
async function fetchBinanceData(symbol, interval, limit = 500) {
const response = await fetch(
`https://api.binance.com/api/v3/klines?` +
`symbol=${symbol}&interval=${interval}&limit=${limit}`
)
const data = await response.json()
return data.map(candle => [
candle[0], // timestamp
parseFloat(candle[1]), // open
parseFloat(candle[2]), // high
parseFloat(candle[3]), // low
parseFloat(candle[4]), // close
parseFloat(candle[5]) // volume
])
}
// Usage
const ohlcv = await fetchBinanceData('BTCUSDT', '1h', 500)
Coinbase API
async function fetchCoinbaseData(productId, granularity, start, end) {
const response = await fetch(
`https://api.exchange.coinbase.com/products/${productId}/candles?` +
`granularity=${granularity}&start=${start}&end=${end}`
)
const data = await response.json()
// Coinbase returns [time, low, high, open, close, volume]
return data.map(candle => [
candle[0] * 1000, // timestamp (convert to ms)
candle[3], // open
candle[2], // high
candle[1], // low
candle[4], // close
candle[5] // volume
]).reverse() // Coinbase returns newest first
}
Best Practices
1. Data Validation
function validateOHLCV(data) {
if (!Array.isArray(data)) {
throw new Error('Data must be an array')
}
return data.filter(candle => {
if (!Array.isArray(candle) || candle.length < 6) return false
const [timestamp, open, high, low, close, volume] = candle
// Validate types
if (typeof timestamp !== 'number') return false
if (typeof open !== 'number' || isNaN(open)) return false
if (typeof high !== 'number' || isNaN(high)) return false
if (typeof low !== 'number' || isNaN(low)) return false
if (typeof close !== 'number' || isNaN(close)) return false
if (typeof volume !== 'number' || isNaN(volume)) return false
// Validate relationships
if (high < low) return false
if (high < open || high < close) return false
if (low > open || low > close) return false
return true
})
}
2. Loading States
class ChartWithLoading {
constructor(containerId) {
this.container = document.getElementById(containerId)
this.chart = null
this.loadingIndicator = this.createLoadingIndicator()
}
createLoadingIndicator() {
const loader = document.createElement('div')
loader.className = 'chart-loading'
loader.innerHTML = '<div class="spinner"></div><p>Loading chart data...</p>'
return loader
}
showLoading() {
this.container.appendChild(this.loadingIndicator)
}
hideLoading() {
this.loadingIndicator.remove()
}
async initialize(symbol, timeframe) {
this.showLoading()
try {
const data = await fetchOHLCV(symbol, timeframe)
this.chart = document.createElement('tradex-chart')
this.container.appendChild(this.chart)
this.chart.start({
title: symbol,
state: { ohlcv: data }
})
} catch (error) {
this.showError(error.message)
} finally {
this.hideLoading()
}
}
showError(message) {
const error = document.createElement('div')
error.className = 'chart-error'
error.textContent = `Error: ${message}`
this.container.appendChild(error)
}
}
3. Timeframe Conversion
const TIMEFRAME_MAP = {
'1m': 60,
'5m': 300,
'15m': 900,
'30m': 1800,
'1h': 3600,
'4h': 14400,
'1d': 86400,
'1w': 604800
}
function getTimeframeSeconds(timeframe) {
return TIMEFRAME_MAP[timeframe] || 3600
}
function convertTimeframe(from, to) {
const fromSeconds = TIMEFRAME_MAP[from]
const toSeconds = TIMEFRAME_MAP[to]
if (!fromSeconds || !toSeconds) {
throw new Error('Invalid timeframe')
}
return toSeconds / fromSeconds
}
Complete Example
import { Chart } from 'tradex-chart'
class TradingChartApp {
constructor(containerId, apiConfig) {
this.container = document.getElementById(containerId)
this.apiConfig = apiConfig
this.chart = null
this.dataManager = null
}
async initialize(symbol, timeframe) {
try {
// Fetch initial data
const data = await this.fetchData(symbol, timeframe, 500)
// Create chart
this.chart = document.createElement('tradex-chart')
this.container.appendChild(this.chart)
this.chart.start({
title: symbol,
symbol: symbol,
state: {
ohlcv: data
}
})
// Setup data manager for infinite scroll
this.dataManager = new ChartDataManager(
this.chart,
symbol,
timeframe
)
} catch (error) {
console.error('Failed to initialize chart:', error)
this.showError(error.message)
}
}
async fetchData(symbol, timeframe, limit) {
const response = await fetch(
`${this.apiConfig.baseURL}/ohlcv?` +
`symbol=${symbol}&timeframe=${timeframe}&limit=${limit}`,
{
headers: {
'Authorization': `Bearer ${this.apiConfig.apiKey}`
}
}
)
if (!response.ok) {
throw new Error(`API error: ${response.status}`)
}
const data = await response.json()
return this.formatData(data)
}
formatData(data) {
return data.map(candle => [
candle.timestamp,
candle.open,
candle.high,
candle.low,
candle.close,
candle.volume
])
}
showError(message) {
const errorDiv = document.createElement('div')
errorDiv.className = 'error-message'
errorDiv.textContent = message
this.container.appendChild(errorDiv)
}
}
// Usage
const app = new TradingChartApp('chart-container', {
baseURL: 'https://api.example.com/v1',
apiKey: 'your-api-key'
})
app.initialize('BTCUSDT', '1h')
Related Documentation
- WebSocket Integration - Real-time data streaming
- GraphQL Integration - Alternative API approach
- Data Caching Strategies - Optimize data fetching
- Configuration - Chart configuration options
- State Management - Managing chart state
Troubleshooting
CORS Issues
If you encounter CORS errors:
- Configure your API server to allow CORS
- Use a proxy server
- Contact your API provider for CORS support
Rate Limiting
Most APIs have rate limits. Implement:
- Request queuing
- Exponential backoff
- Caching strategies
- Batch requests when possible
Data Gaps
Handle missing data gracefully:
- Validate data before rendering
- Fill gaps with null values or interpolation
- Show warnings to users
- Log issues for debugging