|
1 | 1 | // SPDX-License-Identifier: AGPL-3.0-only |
2 | 2 |
|
3 | | -const BINANCE_API_URL = 'https://api.binance.com/api/v3'; //'https://testnet.binance.vision/api/v3'; |
| 3 | +const BINANCE_API_URL_DEFAULT = 'https://api.binance.com/api/v3'; |
| 4 | +const BINANCE_API_URL_US = 'https://api.binance.us/api/v3'; |
| 5 | + |
4 | 6 | const timeframe_to_binance = { |
5 | 7 | '1': '1m', // 1 minute |
6 | 8 | '3': '3m', // 3 minutes |
@@ -84,11 +86,55 @@ class CacheManager<T> { |
84 | 86 |
|
85 | 87 | export class BinanceProvider implements IProvider { |
86 | 88 | private cacheManager: CacheManager<any[]>; |
| 89 | + private activeApiUrl: string | null = null; // Persist the working endpoint |
87 | 90 |
|
88 | 91 | constructor() { |
89 | 92 | this.cacheManager = new CacheManager(5 * 60 * 1000); // 5 minutes cache duration |
90 | 93 | } |
91 | 94 |
|
| 95 | + /** |
| 96 | + * Resolves the working Binance API endpoint. |
| 97 | + * Tries default first, then falls back to US endpoint. |
| 98 | + * Caches the working endpoint for future calls. |
| 99 | + */ |
| 100 | + private async getBaseUrl(): Promise<string> { |
| 101 | + if (this.activeApiUrl) { |
| 102 | + return this.activeApiUrl; |
| 103 | + } |
| 104 | + |
| 105 | + // Try default endpoint |
| 106 | + try { |
| 107 | + const controller = new AbortController(); |
| 108 | + const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout |
| 109 | + const response = await fetch(`${BINANCE_API_URL_DEFAULT}/ping`, { signal: controller.signal }); |
| 110 | + clearTimeout(timeoutId); |
| 111 | + if (response.ok) { |
| 112 | + this.activeApiUrl = BINANCE_API_URL_DEFAULT; |
| 113 | + return this.activeApiUrl; |
| 114 | + } |
| 115 | + } catch (e) { |
| 116 | + // Default failed, try US endpoint |
| 117 | + // console.warn('Binance default API unreachable, trying US endpoint...'); |
| 118 | + } |
| 119 | + |
| 120 | + // Try US endpoint |
| 121 | + try { |
| 122 | + const controller = new AbortController(); |
| 123 | + const timeoutId = setTimeout(() => controller.abort(), 5000); |
| 124 | + const response = await fetch(`${BINANCE_API_URL_US}/ping`, { signal: controller.signal }); |
| 125 | + clearTimeout(timeoutId); |
| 126 | + if (response.ok) { |
| 127 | + this.activeApiUrl = BINANCE_API_URL_US; |
| 128 | + return this.activeApiUrl; |
| 129 | + } |
| 130 | + } catch (e) { |
| 131 | + // Both failed |
| 132 | + } |
| 133 | + |
| 134 | + // Fallback to default if check fails entirely (let actual request fail) |
| 135 | + return BINANCE_API_URL_DEFAULT; |
| 136 | + } |
| 137 | + |
92 | 138 | async getMarketDataInterval(tickerId: string, timeframe: string, sDate: number, eDate: number): Promise<any> { |
93 | 139 | try { |
94 | 140 | const interval = timeframe_to_binance[timeframe.toUpperCase()]; |
@@ -184,7 +230,8 @@ export class BinanceProvider implements IProvider { |
184 | 230 | } |
185 | 231 |
|
186 | 232 | // Single request for <= 1000 candles |
187 | | - let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`; |
| 233 | + const baseUrl = await this.getBaseUrl(); |
| 234 | + let url = `${baseUrl}/klines?symbol=${tickerId}&interval=${interval}`; |
188 | 235 |
|
189 | 236 | //example https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1m&limit=1000 |
190 | 237 | if (limit) { |
@@ -276,7 +323,8 @@ export class BinanceProvider implements IProvider { |
276 | 323 | // We keep it EXACTLY as is for ticker field (Pine Script includes .P) |
277 | 324 |
|
278 | 325 | let marketType: 'crypto' | 'futures' = 'crypto'; |
279 | | - let apiUrl = BINANCE_API_URL; // Default to spot API |
| 326 | + const baseUrl = await this.getBaseUrl(); |
| 327 | + let apiUrl = baseUrl; // Default to resolved spot API |
280 | 328 | let apiSymbol = tickerId; // Symbol for API call |
281 | 329 | let contractType = ''; |
282 | 330 |
|
|
0 commit comments