Retry requestReport on 5xx/network errors with backoff
Tradovate's report server occasionally returns 502, causing accounts like PAAPEX5776400000019 to end up with empty data when both Cash History and Fills fail. Now retries up to 3 times with exponential backoff (500ms, 1s, 2s) on transient errors before falling through to the existing 5-min outer retry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
e6a83eed2f
commit
3da96fe73a
+48
-25
@@ -457,33 +457,56 @@ export class TradovateClient {
|
||||
if (!this.lastFetchRaw) this.lastFetchRaw = {};
|
||||
if (!this.lastFetchErrors) this.lastFetchErrors = {};
|
||||
|
||||
// Treat 5xx and network-level errors as retryable (Tradovate report server is flaky).
|
||||
const isTransient = (err: unknown): boolean => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const status = (err as any)?.response?.status;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const code = (err as any)?.code;
|
||||
return (typeof status === 'number' && status >= 500 && status < 600)
|
||||
|| code === 'ECONNRESET' || code === 'ETIMEDOUT' || code === 'ECONNABORTED';
|
||||
};
|
||||
|
||||
const requestReport = async (name: string, accountName: string) => {
|
||||
let reportData = (await axios.post(
|
||||
'https://rpt-demo.tradovateapi.com/v1/reports/requestreport',
|
||||
{
|
||||
name, representationType: 'json', timezone: -300,
|
||||
params: [
|
||||
{ name: 'startDate', value: fmtDate(start) },
|
||||
{ name: 'endDate', value: fmtDate(now) },
|
||||
{ name: 'startTime', value: '00:00:00' },
|
||||
{ name: 'endTime', value: '00:00:00' },
|
||||
{ name: 'account', value: accountName },
|
||||
],
|
||||
},
|
||||
{ headers: authHeaders }
|
||||
)).data;
|
||||
let pollAttempts = 0;
|
||||
while (reportData?.['p-ticket'] && pollAttempts < 30) {
|
||||
const pTicket: string = reportData['p-ticket'];
|
||||
const pTime: number = Math.max(1, reportData['p-time'] ?? 1);
|
||||
await new Promise((r) => setTimeout(r, pTime * 1000));
|
||||
reportData = (await axios.get(
|
||||
'https://rpt-demo.tradovateapi.com/v1/reports/getreport',
|
||||
{ params: { 'p-ticket': pTicket }, headers: authHeaders }
|
||||
)).data;
|
||||
pollAttempts++;
|
||||
const maxAttempts = 3;
|
||||
let lastErr: unknown = null;
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
let reportData = (await axios.post(
|
||||
'https://rpt-demo.tradovateapi.com/v1/reports/requestreport',
|
||||
{
|
||||
name, representationType: 'json', timezone: -300,
|
||||
params: [
|
||||
{ name: 'startDate', value: fmtDate(start) },
|
||||
{ name: 'endDate', value: fmtDate(now) },
|
||||
{ name: 'startTime', value: '00:00:00' },
|
||||
{ name: 'endTime', value: '00:00:00' },
|
||||
{ name: 'account', value: accountName },
|
||||
],
|
||||
},
|
||||
{ headers: authHeaders }
|
||||
)).data;
|
||||
let pollAttempts = 0;
|
||||
while (reportData?.['p-ticket'] && pollAttempts < 30) {
|
||||
const pTicket: string = reportData['p-ticket'];
|
||||
const pTime: number = Math.max(1, reportData['p-time'] ?? 1);
|
||||
await new Promise((r) => setTimeout(r, pTime * 1000));
|
||||
reportData = (await axios.get(
|
||||
'https://rpt-demo.tradovateapi.com/v1/reports/getreport',
|
||||
{ params: { 'p-ticket': pTicket }, headers: authHeaders }
|
||||
)).data;
|
||||
pollAttempts++;
|
||||
}
|
||||
return typeof reportData?.data === 'string' ? reportData.data : '[]';
|
||||
} catch (err) {
|
||||
lastErr = err;
|
||||
if (!isTransient(err) || attempt === maxAttempts) throw err;
|
||||
const backoffMs = 500 * Math.pow(2, attempt - 1); // 500ms, 1s, 2s
|
||||
console.warn(`[requestReport] ${accountName} ${name}: transient error (attempt ${attempt}/${maxAttempts}), retrying in ${backoffMs}ms`);
|
||||
await new Promise((r) => setTimeout(r, backoffMs));
|
||||
}
|
||||
}
|
||||
return typeof reportData?.data === 'string' ? reportData.data : '[]';
|
||||
throw lastErr;
|
||||
};
|
||||
|
||||
// Merge fresh API entries on top of cached historical entries (fresh takes precedence for overlapping dates)
|
||||
|
||||
Reference in New Issue
Block a user