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 :

Pour qui ce tutoriel est conçu

Ce tutoriel est fait pour vous si :

Ce tutoriel n'est pas adapté si :

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 à :

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

// 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

Ressources connexes

Articles connexes