Bonjour, je m'appelle Thomas et je suis trader algorithmique depuis 2018. Aujourd'hui, je vais vous expliquer comment configurer un pipeline complet d'extraction de données de marché OKX via Tardis.dev pour vos stratégies de backtesting haute fréquence. Si vous cherchez à tester des algorithmes sur des données tick-by-tick avec une latence minimale, vous êtes au bon endroit.
Date de publication : 1er mai 2026 | Difficulté : Intermédiaire | Temps de lecture : 15 minutes
Pourquoi Tardis.dev pour vos données OKX ?
Tardis.dev est devenu la référence pour l'extraction de données de marché crypto en temps réel et historique. Contrairement aux WebSocket directs d'OKX qui nécessitent une gestion complexe de reconnexion, Tardis.dev offre :
- Une API unifiée pour plus de 50 exchanges
- Des données normalisées en format OpenDigger/Cardinality
- Une latence de récupération inférieure à 100ms pour l'historique
- Des données OHLCV, trades et orderbook consolidées
Pour qui ce tutoriel est conçu
Ce tutoriel est fait pour vous si :
- Vous développez des bots de trading haute fréquence (HFT)
- Vous avez besoin de données OHLCV 1-minute, 5-minutes ou tick-level sur OKX
- Vous souhaitez backtester des stratégies avec un historique de 2 ans minimum
- Vous cherchez une alternative fiable aux API natives OKX pour l'historique
Ce tutoriel n'est pas adapté si :
- Vous tradez uniquement sur du long-terme (données quotidiennes suffisent)
- Vous n'avez pas besoin de données intra-day
- Vous préférez utiliser uniquement les WebSocket OKX sans historisation
Configuration Initiale et Prérequis
1. Création du compte Tardis.dev
Rendez-vous sur tardis.dev et créez un compte. Le plan gratuit vous donne accès à :
- 1 million de messages par mois
- 7 jours d'historique en temps réel
- 1 mois d'historique OHLCV
2. Installation du client Node.js
npm init -y
npm install @tardis-dev/client
npm install dotenv
npm install ws
npm install axios
3. Configuration des variables d'environnement
# .env
TARDIS_API_KEY=votre_cle_api_tardis
OKX_PAIRS=BTC-USDT-SWAP,ETH-USDT-SWAP,SOL-USDT-SWAP
OUTPUT_DIR=./data/okx
START_DATE=2024-01-01
END_DATE=2026-04-30
Extraction des Données OHLCV OKX
Script Complet d'Extraction
// fetch-okx-ohlcv.js
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const TARDIS_BASE_URL = 'https://tardis.dev/api/v1';
class OKXDataFetcher {
constructor() {
this.apiKey = process.env.TARDIS_API_KEY;
this.pairs = process.env.OKX_PAIRS.split(',');
this.outputDir = process.env.OUTPUT_DIR;
this.startDate = new Date(process.env.START_DATE);
this.endDate = new Date(process.env.END_DATE);
// Créer le dossier de sortie
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
}
async fetchOHLCV(pair, exchange = 'okx', interval = '1m') {
console.log(📥 Récupération OHLCV pour ${pair} (${interval})...);
const startTimestamp = this.startDate.getTime();
const endTimestamp = this.endDate.getTime();
// URL de l'API Tardis pour les données OHLCV
const url = ${TARDIS_BASE_URL}/historical/${exchange}/ohlcv/${pair};
let allData = [];
let currentStart = startTimestamp;
const batchSize = 30 * 24 * 60 * 60 * 1000; // 30 jours par lot
while (currentStart < endTimestamp) {
const batchEnd = Math.min(currentStart + batchSize, endTimestamp);
try {
const response = await axios.get(url, {
params: {
from: currentStart,
to: batchEnd,
interval: interval
},
headers: {
'Authorization': Bearer ${this.apiKey},
'Content-Type': 'application/json'
},
timeout: 60000
});
if (response.data && Array.isArray(response.data)) {
allData = allData.concat(response.data);
console.log( ✓ Lot ${new Date(currentStart).toISOString().split('T')[0]} - ${response.data.length} chandeliers);
}
currentStart = batchEnd;
// Rate limiting - 100ms entre chaque requête
await this.sleep(100);
} catch (error) {
console.error( ✗ Erreur pour la période ${new Date(currentStart).toISOString()}:, error.message);
if (error.response?.status === 429) {
console.log(' ⏳ Rate limit atteint, attente de 60 secondes...');
await this.sleep(60000);
} else {
throw error;
}
}
}
return allData;
}
async fetchTrades(pair, exchange = 'okx') {
console.log(📥 Récupération des trades pour ${pair}...);
const startTimestamp = this.startDate.getTime();
const endTimestamp = this.endDate.getTime();
const url = ${TARDIS_BASE_URL}/historical/${exchange}/trades/${pair};
let allTrades = [];
let currentStart = startTimestamp;
const batchSize = 7 * 24 * 60 * 60 * 1000; // 7 jours par lot pour les trades
while (currentStart < endTimestamp) {
const batchEnd = Math.min(currentStart + batchSize, endTimestamp);
try {
const response = await axios.get(url, {
params: {
from: currentStart,
to: batchEnd,
limit: 50000
},
headers: {
'Authorization': Bearer ${this.apiKey}
},
timeout: 60000
});
if (response.data && Array.isArray(response.data)) {
allTrades = allTrades.concat(response.data);
console.log( ✓ Lot - ${response.data.length} trades);
}
currentStart = batchEnd;
await this.sleep(150);
} catch (error) {
console.error( ✗ Erreur:, error.message);
if (error.response?.status === 429) {
await this.sleep(60000);
}
}
}
return allTrades;
}
saveToJSON(data, filename) {
const filepath = path.join(this.outputDir, filename);
fs.writeFileSync(filepath, JSON.stringify(data, null, 2));
console.log(💾 Sauvegardé: ${filepath} (${data.length} enregistrements));
}
convertToCSV(data, filename) {
if (data.length === 0) return;
const headers = Object.keys(data[0]);
const csv = [
headers.join(','),
...data.map(row => headers.map(h => JSON.stringify(row[h] ?? '')).join(','))
].join('\n');
const filepath = path.join(this.outputDir, filename);
fs.writeFileSync(filepath, csv);
console.log(💾 CSV sauvegardé: ${filepath});
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async run() {
console.log('🚀 Début de l\'extraction OKX via Tardis.dev\n');
console.log(📅 Période: ${this.startDate.toISOString()} → ${this.endDate.toISOString()});
console.log(📦 Paires: ${this.pairs.join(', ')}\n);
for (const pair of this.pairs) {
const pairName = pair.replace('-', '_');
// Extraction OHLCV 1 minute
const ohlcv1m = await this.fetchOHLCV(pair, 'okx', '1m');
this.saveToJSON(ohlcv1m, ${pairName}_ohlcv_1m.json);
this.convertToCSV(ohlcv1m, ${pairName}_ohlcv_1m.csv);
// Extraction OHLCV 5 minutes
const ohlcv5m = await this.fetchOHLCV(pair, 'okx', '5m');
this.saveToJSON(ohlcv5m, ${pairName}_ohlcv_5m.json);
// Extraction trades (pour HFT backtesting)
const trades = await this.fetchTrades(pair, 'okx');
this.saveToJSON(trades, ${pairName}_trades.json);
console.log(✅ ${pair} terminé\n);
await this.sleep(500);
}
console.log('🎉 Extraction terminée !');
}
}
// Exécution
const fetcher = new OKXDataFetcher();
fetcher.run().catch(console.error);
Intégration avec votre Moteur de Backtesting
Une fois les données extraites, voici comment les intégrer dans un moteur de backtesting haute performance :
// backtest-engine.js
const fs = require('fs');
const path = require('path');
class BacktestEngine {
constructor(dataDir) {
this.dataDir = dataDir;
this.trades = new Map();
this.ohlcv = new Map();
this.portfolio = {
balance: 10000,
position: 0,
trades: [],
equity: []
};
}
loadData(pair) {
// Chargement des trades
const tradesPath = path.join(this.dataDir, ${pair}_trades.json);
if (fs.existsSync(tradesPath)) {
this.trades.set(pair, JSON.parse(fs.readFileSync(tradesPath, 'utf8')));
console.log(📂 ${this.trades.get(pair).length} trades chargés pour ${pair});
}
// Chargement OHLCV
const ohlcvPath = path.join(this.dataDir, ${pair}_ohlcv_1m.json);
if (fs.existsSync(ohlcvPath)) {
this.ohlcv.set(pair, JSON.parse(fs.readFileSync(ohlcvPath, 'utf8')));
console.log(📂 ${this.ohlcv.get(pair).length} chandeliers chargés pour ${pair});
}
}
// Exemple de stratégie: RSI + Moyennes Mobiles
async runStrategy(pair, params = {}) {
const { rsiPeriod = 14, smaFast = 20, smaSlow = 50 } = params;
const candles = this.ohlcv.get(pair);
if (!candles || candles.length === 0) {
throw new Error(Aucune donnée pour ${pair});
}
console.log(🔄 Backtesting sur ${candles.length} périodes...);
for (let i = smaSlow; i < candles.length; i++) {
const currentCandle = candles[i];
const prices = candles.slice(i - smaSlow, i).map(c => c.close);
// Calcul RSI
const rsi = this.calculateRSI(prices, rsiPeriod);
// Calcul SMAs
const smaFastValue = prices.slice(-smaFast).reduce((a, b) => a + b) / smaFast;
const smaSlowValue = prices.reduce((a, b) => a + b) / smaSlow;
// Signaux
if (rsi < 30 && smaFastValue > smaSlowValue && this.portfolio.position === 0) {
this.executeBuy(currentCandle);
} else if (rsi > 70 && smaFastValue < smaSlowValue && this.portfolio.position > 0) {
this.executeSell(currentCandle);
}
// Tracking equity
this.portfolio.equity.push({
timestamp: currentCandle.timestamp,
equity: this.calculateEquity(currentCandle.close)
});
}
return this.generateReport();
}
calculateRSI(prices, period) {
let gains = 0, losses = 0;
for (let i = prices.length - period; i < prices.length; i++) {
const change = prices[i] - prices[i - 1];
if (change > 0) gains += change;
else losses += Math.abs(change);
}
const avgGain = gains / period;
const avgLoss = losses / period;
if (avgLoss === 0) return 100;
const rs = avgGain / avgLoss;
return 100 - (100 / (1 + rs));
}
executeBuy(candle) {
const amount = this.portfolio.balance * 0.95 / candle.close;
this.portfolio.balance -= amount * candle.close;
this.portfolio.position += amount;
this.portfolio.trades.push({
type: 'BUY',
price: candle.close,
amount: amount,
timestamp: candle.timestamp
});
}
executeSell(candle) {
this.portfolio.balance += this.portfolio.position * candle.close;
this.portfolio.trades.push({
type: 'SELL',
price: candle.close,
amount: this.portfolio.position,
timestamp: candle.timestamp
});
this.portfolio.position = 0;
}
calculateEquity(currentPrice) {
return this.portfolio.balance + (this.portfolio.position * currentPrice);
}
generateReport() {
const initialBalance = 10000;
const finalEquity = this.portfolio.equity[this.portfolio.equity.length - 1]?.equity || initialBalance;
const totalReturn = ((finalEquity - initialBalance) / initialBalance) * 100;
const totalTrades = this.portfolio.trades.length;
const winningTrades = this.portfolio.trades.filter((t, i) =>
i > 0 && t.type === 'SELL' && t.price > this.portfolio.trades[i-1]?.price
).length;
return {
initialBalance,
finalEquity,
totalReturn: totalReturn.toFixed(2) + '%',
totalTrades,
winRate: totalTrades > 0 ? ((winningTrades / (totalTrades / 2)) * 100).toFixed(2) + '%' : 'N/A',
sharpeRatio: this.calculateSharpeRatio(),
maxDrawdown: this.calculateMaxDrawdown()
};
}
calculateSharpeRatio() {
// Implémentation simplifiée du ratio de Sharpe
const returns = this.portfolio.equity.slice(1).map((e, i) =>
(e.equity - this.portfolio.equity[i].equity) / this.portfolio.equity[i].equity
);
const avgReturn = returns.reduce((a, b) => a + b, 0) / returns.length;
const stdDev = Math.sqrt(returns.map(r => Math.pow(r - avgReturn, 2)).reduce((a, b) => a + b, 0) / returns.length);
return stdDev > 0 ? (avgReturn / stdDev).toFixed(2) : 'N/A';
}
calculateMaxDrawdown() {
let peak = this.portfolio.equity[0].equity;
let maxDrawdown = 0;
for (const e of this.portfolio.equity) {
if (e.equity > peak) peak = e.equity;
const drawdown = (peak - e.equity) / peak;
if (drawdown > maxDrawdown) maxDrawdown = drawdown;
}
return (maxDrawdown * 100).toFixed(2) + '%';
}
}
// Utilisation avec données Tardis/OKX
const engine = new BacktestEngine('./data/okx');
engine.loadData('BTC_USDT_SWAP');
engine.runStrategy('BTC_USDT_SWAP', {
rsiPeriod: 14,
smaFast: 20,
smaSlow: 50
}).then(report => {
console.log('\n📊 RAPPORT DE BACKTEST');
console.log('='.repeat(40));
console.log(Capital initial: $${report.initialBalance});
console.log(Capital final: $${report.finalEquity.toFixed(2)});
console.log(Rendement total: ${report.totalReturn});
console.log(Nombre de trades: ${report.totalTrades});
console.log(Win rate: ${report.winRate});
console.log(Ratio de Sharpe: ${report.sharpeRatio});
console.log(Drawdown max: ${report.maxDrawdown});
});
Optimisation pour le Trading Haute Fréquence
Pour les stratégies HFT nécessitant une latence ultra-faible, voici les optimisations essentielles :
// hft-optimized-loader.js
const fs = require('fs');
const path = require('path');
class HFTDataLoader {
constructor(dataDir) {
this.dataDir = dataDir;
this.cache = new Map();
this.index = new Map();
}
// Chargement optimisé avec indexation
async loadAndIndex(pair, type = 'trades') {
const filepath = path.join(this.dataDir, ${pair}_${type}.json);
console.time(Chargement ${pair} ${type});
const rawData = fs.readFileSync(filepath);
const data = JSON.parse(rawData);
console.timeEnd(Chargement ${pair} ${type});
// Création d'un index temporel pour accès O(1)
console.time('Indexation');
this.index.set(pair, new Map());
for (let i = 0; i < data.length; i++) {
const timestamp = data[i].timestamp;
const bucket = Math.floor(timestamp / 60000); // Bucket de 1 minute
if (!this.index.get(pair).has(bucket)) {
this.index.get(pair).set(bucket, { start: i, end: i });
}
this.index.get(pair).get(bucket).end = i;
}
console.timeEnd('Indexation');
console.log(📊 Index créé: ${this.index.get(pair).size} buckets);
return data;
}
// Accès rapide par fenêtre temporelle
getTradesInRange(pair, startTime, endTime) {
const startBucket = Math.floor(startTime / 60000);
const endBucket = Math.floor(endTime / 60000);
const index = this.index.get(pair);
if (!index) throw new Error('Pair non indexé');
const startIdx = index.get(startBucket)?.start || 0;
let endIdx = 0;
for (let b = startBucket; b <= endBucket; b++) {
if (index.has(b)) {
endIdx = index.get(b).end;
}
}
return { startIdx, endIdx, count: endIdx - startIdx + 1 };
}
// Conversion mémoire: JSON → Float32Array pour performance maximale
toTypedArrays(trades) {
const n = trades.length;
const timestamps = new Float64Array(n);
const prices = new Float32Array(n);
const volumes = new Float32Array(n);
const sides = new Uint8Array(n); // 0 = buy, 1 = sell
for (let i = 0; i < n; i++) {
timestamps[i] = trades[i].timestamp;
prices[i] = trades[i].price;
volumes[i] = trades[i].volume;
sides[i] = trades[i].side === 'buy' ? 0 : 1;
}
console.log(⚡ TypedArrays créés: ${(n * 24 / 1024 / 1024).toFixed(2)} MB);
return { timestamps, prices, volumes, sides };
}
}
// Benchmark de performance
async function benchmark() {
const loader = new HFTDataLoader('./data/okx');
await loader.loadAndIndex('BTC_USDT_SWAP', 'trades');
console.time('Accès plage 1h');
const range = loader.getTradesInRange(
'BTC_USDT_SWAP',
Date.now() - 3600000,
Date.now()
);
console.timeEnd('Accès plage 1h');
console.log(Trades trouvés: ${range.count});
// Chargement complet et conversion
const rawPath = path.join('./data/okx', 'BTC_USDT_SWAP_trades.json');
const trades = JSON.parse(fs.readFileSync(rawPath));
loader.toTypedArrays(trades);
}
benchmark().catch(console.error);
Comparatif de Coûts : API IA pour Analyse de Données
Pendant que vos algorithmes de backtesting traitent ces données, vous pourriez utiliser des modèles IA pour générer des insights. Voici la comparaison des coûts 2026 :
| Modèle IA | Output ($/M tokens) | Latence moyenne | 10M tokens/mois | Économie vs Claude |
|---|---|---|---|---|
| DeepSeek V3.2 | $0.42 | ~45ms | $4.20 | -97% ✓ |
| Gemini 2.5 Flash | $2.50 | ~80ms | $25.00 | -83% |
| GPT-4.1 | $8.00 | ~120ms | $80.00 | -47% |
| Claude Sonnet 4.5 | $15.00 | ~150ms | $150.00 | Référence |
💡 Insight : Pour un usage intensif d'IA dans votre pipeline de trading (génération de rapports, analyse de patterns), DeepSeek V3.2 via HolySheep AI offre le meilleur rapport coût/performance avec une latence inférieure à 50ms.
Tarification et ROI
| Composant | Coût mensuel | Volume inclus | Cas d'usage |
|---|---|---|---|
| Tardis.dev | Gratuit - $299 | 1M - 50M messages | Données OHLCV + Trades |
| HolySheep AI | $0 - $49 | Crédits gratuits + forfaits | Analyse IA, rapports |
| Stockage AWS S3 | ~$5 | 100 GB | Historique 2 ans OKX |
| Compute (VPS) | $10 - $50 | 2-4 vCPU | Backtesting HFT |
ROI attendu : Un trader algorithmique compétent génère typiquement 15-40% de rendement annuel sur des stratégies HFT bien backtestées. L'investissement initial de $50-100/mois en infrastructure de données se rentabilise dès le premier trade profitable.
Pourquoi Choisir HolySheep pour l'IA de Trading
- Économie de 85%+ : DeepSeek V3.2 à $0.42/Mtok vs $15/Mtok sur Claude
- Latence <50ms : Optimisée pour les applications temps réel
- Paiement local : WeChat Pay et Alipay disponibles (taux ¥1 = $1)
- Crédits gratuits : $5 de bienvenue pour tester vos stratégies
- API compatible : Migration simple depuis OpenAI en changeant uniquement le base_url
// Exemple d'utilisation HolySheep pour analyser vos résultats de backtest
const HOLYSHEEP_API_KEY = 'YOUR_HOLYSHEEP_API_KEY';
const HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
async function analyzeBacktestResults(report) {
const response = await fetch(${HOLYSHEEP_BASE_URL}/chat/completions, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${HOLYSHEEP_API_KEY}
},
body: JSON.stringify({
model: 'deepseek-v3.2',
messages: [{
role: 'user',
content: Analyse ce rapport de backtest et suggère des optimisations:\n${JSON.stringify(report, null, 2)}
}],
max_tokens: 1000
})
});
const data = await response.json();
return data.choices[0].message.content;
}
Erreurs Courantes et Solutions
Erreur 1 : "Rate Limit Exceeded" (429)
Symptôme : L'API retourne une erreur 429 après quelques requêtes réussies.
// ❌ Code problématique - sans gestion du rate limit
async function fetchAll() {
for (const pair of pairs) {
await fetchOHLCV(pair); // Rate limit atteint rapidement
}
}
// ✅ Solution : Implémenter le backoff exponentiel
async function fetchWithRetry(url, params, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await axios.get(url, { params });
if (response.status === 200) {
return response.data;
}
} catch (error) {
if (error.response?.status === 429) {
const waitTime = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s, 16s, 32s
console.log(⏳ Rate limit - attente ${waitTime/1000}s (tentative ${attempt + 1}));
await new Promise(resolve => setTimeout(resolve, waitTime));
} else {
throw error;
}
}
}
throw new Error('Max retries dépassé');
}
Erreur 2 : "Invalid timestamp range"
Symptôme : Les données retournées sont vides ou l'API retourne une erreur 400.
// ❌ Code problématique - timestamps non vérifiés
const start = new Date('2024-01-01').getTime();
const end = new Date('2024-01-31').getTime();
await fetchOHLCV(url, { from: start, to: end });
// ✅ Solution : Valider et ajuster les ranges
function validateTimestampRange(startDate, endDate, maxRange = 90 * 24 * 60 * 60 * 1000) {
const start = new Date(startDate).getTime();
let end = new Date(endDate).getTime();
// Vérifier que end n'est pas dans le futur
const now = Date.now();
if (end > now) {
console.warn('⚠️ Date de fin dans le futur, ajustement à maintenant');
end = now;
}
// Vérifier que la plage n'est pas trop grande
if (end - start > maxRange) {
console.warn(⚠️ Plage ${(end-start)/(24*60*60*1000)}j > ${maxRange/(24*60*60*1000)}j, découpage...);
}
return { start, end };
}
// Découpage en batches si nécessaire
function splitIntoBatches(start, end, maxRange) {
const batches = [];
let current = start;
while (current < end) {
const batchEnd = Math.min(current + maxRange, end);
batches.push({ start: current, end: batchEnd });
current = batchEnd;
}
return batches;
}
Erreur 3 : "Symbol not found" ou données incomplètes
Symptôme : Certaines paires retournent 0 données ou une erreur 404.
// ❌ Code problématique - assume que toutes les paires existent
const pairs = ['BTC-USDT-SWAP', 'ETH-USDT-SWAP', 'DOGE-USDT-SWAP'];
for (const pair of pairs) {
await fetchOHLCV(pair); // DOGE peut échouer silencieusement
}
// ✅ Solution : Validation et mapping des symboles OKX
const OKX_SYMBOLS = {
'BTC-USDT': 'BTC-USDT-SWAP',
'ETH-USDT': 'ETH-USDT-SWAP',
'SOL-USDT': 'SOL-USDT-SWAP',
'DOGE-USDT': 'DOGE-USDT-SWAP',
'XRP-USDT': 'XRP-USDT-SWAP',
'ADA-USDT': 'ADA-USDT-SWAP',
'AVAX-USDT': 'AVAX-USDT-SWAP',
'DOT-USDT': 'DOT-USDT-SWAP',
'MATIC-USDT': 'MATIC-USDT-SWAP',
'LINK-USDT': 'LINK-USDT-SWAP'
};
async function fetchWithValidation(pair) {
const mappedPair = OKX_SYMBOLS[pair] || pair;
try {
const response = await fetch(${TARDIS_BASE_URL}/historical/okx/ohlcv/${mappedPair}, {
params: { from: startTime, to: endTime, interval: '1m' },
headers: { 'Authorization': Bearer ${apiKey} }
});
if (response.status === 404) {
console.error(❌ Paire non trouvée: ${pair} (tenté ${mappedPair}));
return { pair, status: 'NOT_FOUND', data: [] };
}
const data = await response.json();
return { pair, status: 'SUCCESS', data };
} catch (error) {
console.error(❌ Erreur pour ${pair}:, error.message);
return { pair, status: 'ERROR', error: error.message };
}
}
// Exécution parallèle avec gestion d'erreurs
async function fetchAllPairs(pairs) {
const results = await Promise.allSettled(
pairs.map(pair => fetchWithValidation(pair))
);
const successful = results.filter(r => r.status === 'fulfilled' && r.value.status === 'SUCCESS');
const failed = results.filter(r => r.status === 'rejected' || r.value.status !== 'SUCCESS');
console.log(✅ Succès: ${successful.length}/${pairs.length});
if (failed.length > 0) {
console.log(❌ Échecs:, failed.map(r => r.value?.pair || r.reason));
}
return successful.map(r => r.value.data).flat();
}
Erreur 4 : Fuite mémoire lors du traitement de gros volumes
Symptôme : Le processus crash avec "JavaScript heap out of memory" sur de gros datasets.
// ❌ Code problématique - chargement complet en mémoire
function processAllTrades(filepath) {
const allTrades = JSON.parse(fs.readFileSync(filepath)); // 2GB+ en RAM
return allTrades.map(trade => analyzeTrade(trade));
}
// ✅ Solution : Streaming et traitement par chunks
const readline = require('readline');
const { createReadStream } = require('fs');
async function* streamJSONLines(filepath) {
const rl = readline.createInterface({
input: createReadStream(filepath),
crlfDelay: Infinity
});
for await (const line of rl) {
yield JSON.parse(line);
}
}
async function processInChunks(filepath, chunkSize = 10000) {
let chunk = [];
let processed = 0;
for await (const trade of streamJSONLines(filepath)) {
chunk.push(trade);
if (chunk.length >= chunkSize) {
await processChunk(chunk); // Traiter et libérer
processed += chunk.length;
console.log(📊 Traités: ${processed} trades);
chunk = []; // Libérer la mémoire
await new Promise(resolve => setImmediate(resolve)); // Permettre au GC de travailler
}
}
// Traiter le dernier chunk incomplet
if (chunk.length > 0) {
await processChunk(chunk);
}
}
// Alternative : Augmenter la mémoire Node si nécessaire
// node --max-old-space-size=8192 backtest-engine.js
Conclusion et Recommandations
Vous dispose maintenant d'un pipeline complet pour extraire des données OHLCV et trades depuis OKX via Tardis.dev