UNPKG

ccxt

Version:

A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go

117 lines (114 loc) 4.41 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD //@ts-nocheck /* ------------------------------------------------------------------------ */ import { now, sleep } from './time.js'; /* ------------------------------------------------------------------------ */ class Throttler { constructor(config) { this.config = { 'refillRate': 1.0, 'delay': 0.001, 'capacity': 1.0, 'tokens': 0, 'cost': 1.0, 'algorithm': 'leakyBucket', 'windowSize': 60000.0, 'maxWeight': undefined, // rolling window - rollingWindowSize / rateLimit // ms_of_window / ms_of_rate_limit }; Object.assign(this.config, config); if (this.config['algorithm'] !== 'leakyBucket') { this.config['maxWeight'] = this.config.windowSize / this.config.rateLimit; } this.queue = []; this.running = false; this.timestamps = []; } async leakyBucketLoop() { let lastTimestamp = now(); while (this.running) { const { resolver, cost } = this.queue[0]; if (this.config['tokens'] >= 0) { this.config['tokens'] -= cost; resolver(); this.queue.shift(); // contextswitch await Promise.resolve(); if (this.queue.length === 0) { this.running = false; } } else { await sleep(this.config['delay'] * 1000); const current = now(); const elapsed = current - lastTimestamp; lastTimestamp = current; const tokens = this.config['tokens'] + (this.config['refillRate'] * elapsed); this.config['tokens'] = Math.min(tokens, this.config['capacity']); } } } async rollingWindowLoop() { while (this.running) { const { resolver, cost } = this.queue[0]; const nowTime = now(); const cutOffTime = nowTime - this.config.windowSize; let totalCost = 0; // Remove expired timestamps & sum the remaining requests const timestamps = []; for (let i = 0; i < this.timestamps.length; i++) { const element = this.timestamps[i]; if (element.timestamp > cutOffTime) { totalCost += element.cost; timestamps.push(element); } } this.timestamps = timestamps; // handle current request if (totalCost + cost <= this.config.maxWeight) { // Enough capacity, proceed with request this.timestamps.push({ timestamp: nowTime, cost }); resolver(); this.queue.shift(); await Promise.resolve(); // Yield control to event loop if (this.queue.length === 0) { this.running = false; } } else { // Calculate the wait time until the oldest request expires const earliestRequestTime = this.timestamps[0].timestamp; const waitTime = (earliestRequestTime + this.config.windowSize) - nowTime; // Ensure waitTime is positive before sleeping if (waitTime > 0) { await sleep(waitTime); } } } } async loop() { if (this.config['algorithm'] === 'leakyBucket') { await this.leakyBucketLoop(); } else { await this.rollingWindowLoop(); } } throttle(cost = undefined) { let resolver; const promise = new Promise((resolve, reject) => { resolver = resolve; }); cost = (cost === undefined) ? this.config['cost'] : cost; this.queue.push({ resolver, cost }); if (!this.running) { this.running = true; this.loop(); } return promise; } } export { Throttler, }; // ----------------------------------------