clodds 1.6.10 → 1.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/agents/handlers/hyperliquid.js +5 -2
  2. package/dist/agents/handlers/hyperliquid.js.map +1 -1
  3. package/dist/agents/handlers/kalshi.js +25 -0
  4. package/dist/agents/handlers/kalshi.js.map +1 -1
  5. package/dist/agents/handlers/opinion.js +1 -1
  6. package/dist/agents/handlers/opinion.js.map +1 -1
  7. package/dist/agents/handlers/polymarket.js +8 -4
  8. package/dist/agents/handlers/polymarket.js.map +1 -1
  9. package/dist/agents/index.js +77 -11
  10. package/dist/agents/index.js.map +1 -1
  11. package/dist/agents/subagents.js +6 -1
  12. package/dist/agents/subagents.js.map +1 -1
  13. package/dist/auth/copilot.js +2 -2
  14. package/dist/channels/telegram/index.js +143 -133
  15. package/dist/channels/telegram/index.js.map +1 -1
  16. package/dist/channels/twitch/index.js +76 -66
  17. package/dist/channels/twitch/index.js.map +1 -1
  18. package/dist/channels/whatsapp/index.js +131 -126
  19. package/dist/channels/whatsapp/index.js.map +1 -1
  20. package/dist/cli/commands/repl.js +9 -2
  21. package/dist/cli/commands/repl.js.map +1 -1
  22. package/dist/config/index.js +3 -0
  23. package/dist/config/index.js.map +1 -1
  24. package/dist/cron/index.js +3 -0
  25. package/dist/cron/index.js.map +1 -1
  26. package/dist/db/index.js +3 -1
  27. package/dist/db/index.js.map +1 -1
  28. package/dist/execution/bracket-orders.js +102 -31
  29. package/dist/execution/bracket-orders.js.map +1 -1
  30. package/dist/execution/circuit-breaker.js +3 -4
  31. package/dist/execution/circuit-breaker.js.map +1 -1
  32. package/dist/execution/dca.js +46 -3
  33. package/dist/execution/dca.js.map +1 -1
  34. package/dist/execution/futures.js +8 -5
  35. package/dist/execution/futures.js.map +1 -1
  36. package/dist/execution/index.js +150 -44
  37. package/dist/execution/index.js.map +1 -1
  38. package/dist/execution/twap.js +35 -2
  39. package/dist/execution/twap.js.map +1 -1
  40. package/dist/extensions/task-runner/index.js +38 -0
  41. package/dist/extensions/task-runner/index.js.map +1 -1
  42. package/dist/feeds/betfair/index.js +8 -1
  43. package/dist/feeds/betfair/index.js.map +1 -1
  44. package/dist/feeds/crypto/whale-tracker.js +24 -5
  45. package/dist/feeds/crypto/whale-tracker.js.map +1 -1
  46. package/dist/feeds/hedgehog/index.js +12 -0
  47. package/dist/feeds/hedgehog/index.js.map +1 -1
  48. package/dist/feeds/kalshi/index.js +7 -7
  49. package/dist/feeds/kalshi/index.js.map +1 -1
  50. package/dist/feeds/manifold/index.js +12 -0
  51. package/dist/feeds/manifold/index.js.map +1 -1
  52. package/dist/feeds/polymarket/index.js +3 -2
  53. package/dist/feeds/polymarket/index.js.map +1 -1
  54. package/dist/feeds/polymarket/user-ws.js +32 -9
  55. package/dist/feeds/polymarket/user-ws.js.map +1 -1
  56. package/dist/feeds/smarkets/index.js +7 -1
  57. package/dist/feeds/smarkets/index.js.map +1 -1
  58. package/dist/gateway/api-routes.js +32 -2
  59. package/dist/gateway/api-routes.js.map +1 -1
  60. package/dist/gateway/dca-routes.d.ts +5 -1
  61. package/dist/gateway/dca-routes.js +32 -1
  62. package/dist/gateway/dca-routes.js.map +1 -1
  63. package/dist/gateway/index.js +6 -5
  64. package/dist/gateway/index.js.map +1 -1
  65. package/dist/gateway/payments-routes.js +1 -1
  66. package/dist/gateway/payments-routes.js.map +1 -1
  67. package/dist/gateway/percolator-routes.js +3 -3
  68. package/dist/gateway/percolator-routes.js.map +1 -1
  69. package/dist/gateway/risk-routes.js +2 -2
  70. package/dist/gateway/risk-routes.js.map +1 -1
  71. package/dist/gateway/server.js +12 -2
  72. package/dist/gateway/server.js.map +1 -1
  73. package/dist/gateway/signal-bus.js +23 -4
  74. package/dist/gateway/signal-bus.js.map +1 -1
  75. package/dist/index.js +14 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/infra/index.js +27 -22
  78. package/dist/infra/index.js.map +1 -1
  79. package/dist/link-understanding/index.js +7 -1
  80. package/dist/link-understanding/index.js.map +1 -1
  81. package/dist/monitoring/alerts.js +1 -1
  82. package/dist/monitoring/alerts.js.map +1 -1
  83. package/dist/monitoring/metrics.js +49 -0
  84. package/dist/monitoring/metrics.js.map +1 -1
  85. package/dist/opportunity/analytics.js +2 -2
  86. package/dist/opportunity/analytics.js.map +1 -1
  87. package/dist/opportunity/executor.js +2 -2
  88. package/dist/opportunity/executor.js.map +1 -1
  89. package/dist/opportunity/index.d.ts +2 -0
  90. package/dist/opportunity/index.js +18 -2
  91. package/dist/opportunity/index.js.map +1 -1
  92. package/dist/opportunity/scoring.d.ts +1 -0
  93. package/dist/opportunity/scoring.js.map +1 -1
  94. package/dist/providers/discovery.d.ts +1 -1
  95. package/dist/services/feature-engineering/rolling-window.js +7 -0
  96. package/dist/services/feature-engineering/rolling-window.js.map +1 -1
  97. package/dist/services/tick-streamer/index.js +4 -0
  98. package/dist/services/tick-streamer/index.js.map +1 -1
  99. package/dist/session/index.js +14 -0
  100. package/dist/session/index.js.map +1 -1
  101. package/dist/sessions/index.js +1 -1
  102. package/dist/sessions/index.js.map +1 -1
  103. package/dist/signal-router/router.js +7 -0
  104. package/dist/signal-router/router.js.map +1 -1
  105. package/dist/skills/bundled/dca/index.js +3 -1
  106. package/dist/skills/bundled/dca/index.js.map +1 -1
  107. package/dist/skills/bundled/hyperliquid/index.js +1 -1
  108. package/dist/skills/bundled/hyperliquid/index.js.map +1 -1
  109. package/dist/skills/bundled/qmd/index.js +1 -1
  110. package/dist/skills/bundled/qmd/index.js.map +1 -1
  111. package/dist/skills/bundled/ticks/index.js +1 -1
  112. package/dist/skills/bundled/ticks/index.js.map +1 -1
  113. package/dist/skills/index.js +1 -1
  114. package/dist/skills/index.js.map +1 -1
  115. package/dist/solana/pump-swarm.js +1 -1
  116. package/dist/solana/pump-swarm.js.map +1 -1
  117. package/dist/solana/swarm-builders.js +26 -0
  118. package/dist/solana/swarm-builders.js.map +1 -1
  119. package/dist/strategies/crypto-hft/index.js +7 -3
  120. package/dist/strategies/crypto-hft/index.js.map +1 -1
  121. package/dist/terminal/index.js +11 -5
  122. package/dist/terminal/index.js.map +1 -1
  123. package/dist/tools/browser.js +8 -1
  124. package/dist/tools/browser.js.map +1 -1
  125. package/dist/tools/sql.js +2 -1
  126. package/dist/tools/sql.js.map +1 -1
  127. package/dist/trading/backtest.js +1 -1
  128. package/dist/trading/backtest.js.map +1 -1
  129. package/dist/trading/copy-trading.js +7 -0
  130. package/dist/trading/copy-trading.js.map +1 -1
  131. package/dist/trading/futures/index.js +5 -5
  132. package/dist/trading/futures/index.js.map +1 -1
  133. package/dist/trading/index.js +7 -6
  134. package/dist/trading/index.js.map +1 -1
  135. package/dist/trading/kelly.js +21 -0
  136. package/dist/trading/kelly.js.map +1 -1
  137. package/dist/trading/logger.js +13 -1
  138. package/dist/trading/logger.js.map +1 -1
  139. package/dist/trading/ml-signals.js +15 -6
  140. package/dist/trading/ml-signals.js.map +1 -1
  141. package/dist/trading/orchestrator.js +25 -1
  142. package/dist/trading/orchestrator.js.map +1 -1
  143. package/dist/trading/safety.js +7 -9
  144. package/dist/trading/safety.js.map +1 -1
  145. package/dist/utils/config.js +3 -0
  146. package/dist/utils/config.js.map +1 -1
  147. package/package.json +1 -1
@@ -148,6 +148,29 @@ const feeRateCache = new Map();
148
148
  const orderbookCache = new Map();
149
149
  const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour for static data
150
150
  const ORDERBOOK_CACHE_TTL_MS = 5000; // 5 seconds for orderbook (needs to be fresh)
151
+ /**
152
+ * Evict expired entries from module-level caches to prevent unbounded growth.
153
+ * Called periodically by the execution service.
154
+ */
155
+ function evictExpiredCaches() {
156
+ const now = Date.now();
157
+ for (const [key, val] of tickSizeCache) {
158
+ if (now - val.cachedAt > 86400000)
159
+ tickSizeCache.delete(key); // 24h
160
+ }
161
+ for (const [key, val] of negRiskCache) {
162
+ if (now - val.cachedAt > 86400000)
163
+ negRiskCache.delete(key); // 24h
164
+ }
165
+ for (const [key, val] of feeRateCache) {
166
+ if (now - val.cachedAt > 3600000)
167
+ feeRateCache.delete(key); // 1h
168
+ }
169
+ for (const [key, val] of orderbookCache) {
170
+ if (now - val.cachedAt > 30000)
171
+ orderbookCache.delete(key); // 30s
172
+ }
173
+ }
151
174
  // Nonce tracking to prevent duplicate orders
152
175
  // Uses atomic counter to avoid race conditions in concurrent async operations
153
176
  // Format: timestamp_base + counter ensures uniqueness across restarts and within process
@@ -361,12 +384,17 @@ async function getPolymarketBalance(auth, address) {
361
384
  headers: { 'Content-Type': 'application/json' },
362
385
  body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_call', params: [{ to: USDC_POLYGON, data }, 'latest'], id: 1 }),
363
386
  });
387
+ if (!rpcResponse.ok) {
388
+ logger_1.logger.warn({ status: rpcResponse.status }, 'Polygon RPC error fetching balance');
389
+ throw new Error(`RPC error: ${rpcResponse.status}`);
390
+ }
364
391
  const rpcData = await rpcResponse.json();
365
- const balance = parseInt(rpcData.result || '0x0', 16) / 1e6;
392
+ const rawBalance = BigInt(rpcData.result || '0x0');
393
+ const balance = Number(rawBalance) / 1e6;
366
394
  return { balance, allowance: balance };
367
395
  }
368
396
  catch (error) {
369
- logger_1.logger.error({ error, walletAddress }, 'Failed to fetch Polymarket balance');
397
+ logger_1.logger.error({ error, walletAddress: walletAddress ? `${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}` : 'unknown' }, 'Failed to fetch Polymarket balance');
370
398
  return { balance: 0, allowance: 0 };
371
399
  }
372
400
  }
@@ -416,7 +444,7 @@ async function getPolymarketTrades(auth, limit = 100) {
416
444
  .map(t => ({
417
445
  id: t.transactionHash || '',
418
446
  tokenId: t.asset || '',
419
- side: (t.side?.toUpperCase() || 'BUY'),
447
+ side: (t.side?.toUpperCase() ?? 'BUY'),
420
448
  price: t.price || 0,
421
449
  size: t.size || t.usdcSize || 0,
422
450
  timestamp: new Date((t.timestamp || 0) * 1000),
@@ -657,8 +685,14 @@ async function placePolymarketOrder(auth, tokenId, side, price, size, orderType
657
685
  headers: { ...headers, 'Content-Type': 'application/json' },
658
686
  body: JSON.stringify(order),
659
687
  });
688
+ if (!response.ok) {
689
+ const errorBody = await response.text().catch(() => '');
690
+ const redactedError = errorBody.slice(0, 200).replace(/0x[a-fA-F0-9]{20,}/g, '0x***').replace(/"(api[Kk]ey|secret|password|token)"\s*:\s*"[^"]+"/g, '"$1":"***"');
691
+ logger_1.logger.error({ status: response.status, errorBody: redactedError, tokenId, side, price, size }, 'Polymarket order failed');
692
+ return { success: false, error: `HTTP ${response.status}: ${redactedError}` };
693
+ }
660
694
  const data = (await response.json());
661
- if (!response.ok || data.errorMsg) {
695
+ if (data.errorMsg) {
662
696
  const { code, message } = parsePolymarketError(data.errorMsg, response.status);
663
697
  logger_1.logger.error({ status: response.status, errorCode: code, error: message }, 'Polymarket order failed');
664
698
  return { success: false, error: `[${code}] ${message}` };
@@ -1009,13 +1043,18 @@ async function placeKalshiOrder(auth, ticker, side, action, price, count, orderT
1009
1043
  logger_1.logger.warn({ err, ticker }, 'Failed to check slippage, proceeding with market order');
1010
1044
  }
1011
1045
  }
1046
+ // FAK (Fill-and-Kill) is not natively supported on Kalshi; treat as FOK (closest equivalent)
1047
+ if (orderType === 'FAK') {
1048
+ logger_1.logger.warn('[execution] FAK order type not supported on Kalshi, using FOK instead');
1049
+ orderType = 'FOK';
1050
+ }
1012
1051
  const order = {
1013
1052
  ticker,
1014
1053
  side,
1015
1054
  action,
1016
1055
  type: orderType === 'FOK' && maxSlippage !== undefined ? 'limit' : (orderType === 'FOK' ? 'market' : 'limit'),
1017
- yes_price: side === 'yes' ? Math.round(effectivePrice * 100) : undefined,
1018
- no_price: side === 'no' ? Math.round(effectivePrice * 100) : undefined,
1056
+ yes_price: side === 'yes' ? Math.round((effectivePrice + Number.EPSILON) * 100) : undefined,
1057
+ no_price: side === 'no' ? Math.round((effectivePrice + Number.EPSILON) * 100) : undefined,
1019
1058
  count,
1020
1059
  };
1021
1060
  try {
@@ -1294,8 +1333,14 @@ async function placeOpinionOrder(auth, tokenId, side, price, size, orderType = '
1294
1333
  }
1295
1334
  const config = toOpinionConfig(auth);
1296
1335
  // Extract marketId from tokenId (Opinion tokens use format: marketId-outcomeIndex)
1297
- const marketId = parseInt(tokenId.split('-')[0], 10) || 0;
1298
- const result = await opinion.placeOrder(config, marketId, tokenId, side === 'buy' ? 'BUY' : 'SELL', price, size, orderType === 'FOK' ? 'MARKET' : 'LIMIT');
1336
+ const marketId = parseInt(tokenId.split('-')[0], 10);
1337
+ if (Number.isNaN(marketId)) {
1338
+ throw new Error(`Invalid tokenId format: ${tokenId}`);
1339
+ }
1340
+ const result = await opinion.placeOrder(config, marketId, tokenId, side === 'buy' ? 'BUY' : 'SELL', price, size, (orderType === 'FOK' || orderType === 'FAK') ? 'MARKET' : 'LIMIT');
1341
+ if (orderType === 'FAK') {
1342
+ logger_1.logger.warn('[execution] FAK order type not supported on Opinion, using FOK/MARKET instead');
1343
+ }
1299
1344
  return {
1300
1345
  success: result.success,
1301
1346
  orderId: result.orderId,
@@ -1586,6 +1631,10 @@ async function getPolymarketUSDCAllowance(ownerAddress, spenderAddress) {
1586
1631
  id: 1,
1587
1632
  }),
1588
1633
  });
1634
+ if (!response.ok) {
1635
+ logger_1.logger.warn({ status: response.status }, 'Polygon RPC error fetching USDC allowance');
1636
+ throw new Error(`RPC error: ${response.status}`);
1637
+ }
1589
1638
  const result = await response.json();
1590
1639
  if (!result.result || result.result === '0x') {
1591
1640
  return 0;
@@ -1735,6 +1784,9 @@ function createExecutionService(config) {
1735
1784
  if (userWs?.isConnected()) {
1736
1785
  return;
1737
1786
  }
1787
+ // Clean up any previous WebSocket before creating a new one to prevent
1788
+ // listener accumulation on reconnect (each call adds on('fill'), etc.)
1789
+ disconnectFillsWebSocket();
1738
1790
  const userId = config.polymarket.funderAddress || config.polymarket.apiKey;
1739
1791
  userWs = (0, user_ws_1.createUserWebSocket)(userId, {
1740
1792
  privateKey: config.polymarket.privateKey || '',
@@ -1763,6 +1815,7 @@ function createExecutionService(config) {
1763
1815
  // ==========================================================================
1764
1816
  let heartbeatId = null;
1765
1817
  let heartbeatInterval = null;
1818
+ let heartbeatInProgress = false;
1766
1819
  const HEARTBEAT_INTERVAL_MS = 8000; // 8s to be safe (10s timeout)
1767
1820
  async function postHeartbeat(existingId) {
1768
1821
  if (!config.polymarket) {
@@ -1792,16 +1845,23 @@ function createExecutionService(config) {
1792
1845
  return data.heartbeat_id || existingId || '';
1793
1846
  }
1794
1847
  async function startHeartbeat() {
1848
+ // Stop any existing interval before starting a new one to prevent
1849
+ // duplicate intervals if startHeartbeat() is called twice.
1795
1850
  if (heartbeatInterval) {
1796
- // Already running, just return current ID
1797
- if (heartbeatId)
1798
- return heartbeatId;
1851
+ clearInterval(heartbeatInterval);
1852
+ heartbeatInterval = null;
1799
1853
  }
1854
+ heartbeatInProgress = false;
1800
1855
  // Initial heartbeat
1801
1856
  heartbeatId = await postHeartbeat();
1802
1857
  logger_1.logger.info({ heartbeatId }, 'Polymarket heartbeat started');
1803
- // Start recurring heartbeat
1858
+ // Start recurring heartbeat with overlap guard: if postHeartbeat takes
1859
+ // longer than HEARTBEAT_INTERVAL_MS, skip the next tick instead of
1860
+ // running two concurrent heartbeat requests.
1804
1861
  heartbeatInterval = setInterval(async () => {
1862
+ if (heartbeatInProgress)
1863
+ return;
1864
+ heartbeatInProgress = true;
1805
1865
  try {
1806
1866
  heartbeatId = await postHeartbeat(heartbeatId || undefined);
1807
1867
  logger_1.logger.debug({ heartbeatId }, 'Heartbeat sent');
@@ -1809,6 +1869,9 @@ function createExecutionService(config) {
1809
1869
  catch (err) {
1810
1870
  logger_1.logger.error({ err }, 'Heartbeat failed - orders may be cancelled');
1811
1871
  }
1872
+ finally {
1873
+ heartbeatInProgress = false;
1874
+ }
1812
1875
  }, HEARTBEAT_INTERVAL_MS);
1813
1876
  return heartbeatId;
1814
1877
  }
@@ -1887,17 +1950,16 @@ function createExecutionService(config) {
1887
1950
  function recordOrderToCircuitBreaker(result, sizeUsd) {
1888
1951
  if (!circuitBreaker)
1889
1952
  return;
1890
- // Record as trade (P&L = 0 for now, actual P&L tracked on fills/closes)
1953
+ // Record as trade (P&L = 0 at order time — actual P&L tracked on fills/closes).
1954
+ // recordTrade already increments errorCount when success=false (line 251),
1955
+ // so do NOT also call recordError() — that would double-count errors in both
1956
+ // totalTrades and errorCount, inflating the error rate.
1891
1957
  circuitBreaker.recordTrade({
1892
1958
  pnlUsd: 0, // P&L unknown at order time
1893
1959
  success: result.success,
1894
1960
  sizeUsd,
1895
1961
  error: result.error,
1896
1962
  });
1897
- // Record error if order failed
1898
- if (!result.success && result.error) {
1899
- circuitBreaker.recordError(result.error);
1900
- }
1901
1963
  }
1902
1964
  async function executeOrder(request) {
1903
1965
  // Validate
@@ -1962,7 +2024,10 @@ function createExecutionService(config) {
1962
2024
  if (!config.kalshi) {
1963
2025
  return { success: false, error: 'Kalshi not configured' };
1964
2026
  }
1965
- const outcome = request.outcome?.toLowerCase() || 'yes';
2027
+ const outcome = (request.outcome?.toLowerCase() ?? 'yes');
2028
+ if (outcome !== 'yes' && outcome !== 'no') {
2029
+ return { success: false, error: `Invalid Kalshi outcome: ${request.outcome}. Must be 'yes' or 'no'.`, status: 'rejected' };
2030
+ }
1966
2031
  return placeKalshiOrder(config.kalshi, request.marketId, outcome, request.side, request.price, request.size, request.orderType, request.maxSlippage);
1967
2032
  }
1968
2033
  if (request.platform === 'opinion') {
@@ -2044,47 +2109,71 @@ function createExecutionService(config) {
2044
2109
  return 0;
2045
2110
  }
2046
2111
  let count = 0;
2112
+ const errors = [];
2047
2113
  if ((!platform || platform === 'polymarket') && config.polymarket) {
2048
- count += await cancelAllPolymarketOrders(config.polymarket, marketId);
2049
- // Auto-stop heartbeat if all Polymarket orders cancelled (no market filter)
2050
- // Heartbeat is only needed when there are open GTC/GTD orders
2051
- if (!marketId && isHeartbeatActive()) {
2052
- stopHeartbeat();
2053
- logger_1.logger.info('Heartbeat auto-stopped after cancelling all orders');
2114
+ try {
2115
+ count += await cancelAllPolymarketOrders(config.polymarket, marketId);
2116
+ // Auto-stop heartbeat if all Polymarket orders cancelled (no market filter)
2117
+ // Heartbeat is only needed when there are open GTC/GTD orders
2118
+ if (!marketId && isHeartbeatActive()) {
2119
+ stopHeartbeat();
2120
+ logger_1.logger.info('Heartbeat auto-stopped after cancelling all orders');
2121
+ }
2122
+ }
2123
+ catch (err) {
2124
+ errors.push(`Polymarket: ${err.message}`);
2054
2125
  }
2055
2126
  }
2056
2127
  // Kalshi: fetch open orders, batch cancel matching ones
2057
2128
  if ((!platform || platform === 'kalshi') && config.kalshi) {
2058
- const orders = await getKalshiOpenOrders(config.kalshi);
2059
- const toCancel = orders
2060
- .filter(o => !marketId || o.marketId === marketId)
2061
- .map(o => o.orderId);
2062
- if (toCancel.length > 0) {
2063
- const results = await cancelKalshiOrdersBatch(config.kalshi, toCancel);
2064
- count += results.filter(r => r.success).length;
2129
+ try {
2130
+ const orders = await getKalshiOpenOrders(config.kalshi);
2131
+ const toCancel = orders
2132
+ .filter(o => !marketId || o.marketId === marketId)
2133
+ .map(o => o.orderId);
2134
+ if (toCancel.length > 0) {
2135
+ const results = await cancelKalshiOrdersBatch(config.kalshi, toCancel);
2136
+ count += results.filter(r => r.success).length;
2137
+ }
2138
+ }
2139
+ catch (err) {
2140
+ errors.push(`Kalshi: ${err.message}`);
2065
2141
  }
2066
2142
  }
2067
2143
  // Opinion: use SDK's cancelAllOrders (handles batch internally)
2068
2144
  if ((!platform || platform === 'opinion') && config.opinion) {
2069
- count += await cancelAllOpinionOrders(config.opinion, marketId);
2145
+ try {
2146
+ count += await cancelAllOpinionOrders(config.opinion, marketId);
2147
+ }
2148
+ catch (err) {
2149
+ errors.push(`Opinion: ${err.message}`);
2150
+ }
2070
2151
  }
2071
2152
  // PredictFun has bulk cancel support
2072
2153
  if ((!platform || platform === 'predictfun') && config.predictfun) {
2073
- if (marketId) {
2074
- // Filter by market if specified
2075
- const orders = await getPredictFunOpenOrders(config.predictfun);
2076
- for (const order of orders) {
2077
- if (order.marketId === marketId) {
2078
- if (await cancelPredictFunOrder(config.predictfun, order.orderId)) {
2079
- count++;
2154
+ try {
2155
+ if (marketId) {
2156
+ // Filter by market if specified
2157
+ const orders = await getPredictFunOpenOrders(config.predictfun);
2158
+ for (const order of orders) {
2159
+ if (order.marketId === marketId) {
2160
+ if (await cancelPredictFunOrder(config.predictfun, order.orderId)) {
2161
+ count++;
2162
+ }
2080
2163
  }
2081
2164
  }
2082
2165
  }
2166
+ else {
2167
+ count += await cancelAllPredictFunOrders(config.predictfun);
2168
+ }
2083
2169
  }
2084
- else {
2085
- count += await cancelAllPredictFunOrders(config.predictfun);
2170
+ catch (err) {
2171
+ errors.push(`PredictFun: ${err.message}`);
2086
2172
  }
2087
2173
  }
2174
+ if (errors.length > 0) {
2175
+ logger_1.logger.warn({ errors }, 'Some platforms failed during cancelAllOrders');
2176
+ }
2088
2177
  return count;
2089
2178
  },
2090
2179
  async getOpenOrders(platform) {
@@ -2473,18 +2562,27 @@ function createExecutionService(config) {
2473
2562
  clearOldFills(maxAgeMs = 3600000) {
2474
2563
  const now = Date.now();
2475
2564
  let cleared = 0;
2565
+ // Collect keys to delete first to avoid mutation during iteration
2566
+ const toDelete = [];
2476
2567
  for (const [orderId, fill] of trackedFills) {
2477
2568
  if (now - fill.receivedAt > maxAgeMs) {
2478
- trackedFills.delete(orderId);
2479
- cleared++;
2569
+ toDelete.push(orderId);
2480
2570
  }
2481
2571
  }
2572
+ for (const orderId of toDelete) {
2573
+ trackedFills.delete(orderId);
2574
+ cleared++;
2575
+ }
2482
2576
  // Also clear old orders
2577
+ const ordersToDelete = [];
2483
2578
  for (const [orderId, order] of trackedOrders) {
2484
2579
  if (now - order.receivedAt > maxAgeMs) {
2485
- trackedOrders.delete(orderId);
2580
+ ordersToDelete.push(orderId);
2486
2581
  }
2487
2582
  }
2583
+ for (const orderId of ordersToDelete) {
2584
+ trackedOrders.delete(orderId);
2585
+ }
2488
2586
  return cleared;
2489
2587
  },
2490
2588
  waitForFill(orderId, timeoutMs) {
@@ -2563,6 +2661,10 @@ function createExecutionService(config) {
2563
2661
  clearInterval(fillCleanupInterval);
2564
2662
  fillCleanupInterval = null;
2565
2663
  }
2664
+ if (cacheEvictionInterval) {
2665
+ clearInterval(cacheEvictionInterval);
2666
+ cacheEvictionInterval = null;
2667
+ }
2566
2668
  },
2567
2669
  };
2568
2670
  // Periodic cleanup of old tracked fills/orders (every 10 minutes)
@@ -2574,6 +2676,10 @@ function createExecutionService(config) {
2574
2676
  }, 10 * 60 * 1000);
2575
2677
  fillCleanupTimer.unref();
2576
2678
  let fillCleanupInterval = fillCleanupTimer;
2679
+ // Periodic eviction of expired module-level caches (every 60 seconds)
2680
+ const cacheEvictionTimer = setInterval(evictExpiredCaches, 60000);
2681
+ cacheEvictionTimer.unref();
2682
+ let cacheEvictionInterval = cacheEvictionTimer;
2577
2683
  return service;
2578
2684
  }
2579
2685
  // Exchange addresses