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.
- package/dist/agents/handlers/hyperliquid.js +5 -2
- package/dist/agents/handlers/hyperliquid.js.map +1 -1
- package/dist/agents/handlers/kalshi.js +25 -0
- package/dist/agents/handlers/kalshi.js.map +1 -1
- package/dist/agents/handlers/opinion.js +1 -1
- package/dist/agents/handlers/opinion.js.map +1 -1
- package/dist/agents/handlers/polymarket.js +8 -4
- package/dist/agents/handlers/polymarket.js.map +1 -1
- package/dist/agents/index.js +77 -11
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/subagents.js +6 -1
- package/dist/agents/subagents.js.map +1 -1
- package/dist/auth/copilot.js +2 -2
- package/dist/channels/telegram/index.js +143 -133
- package/dist/channels/telegram/index.js.map +1 -1
- package/dist/channels/twitch/index.js +76 -66
- package/dist/channels/twitch/index.js.map +1 -1
- package/dist/channels/whatsapp/index.js +131 -126
- package/dist/channels/whatsapp/index.js.map +1 -1
- package/dist/cli/commands/repl.js +9 -2
- package/dist/cli/commands/repl.js.map +1 -1
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -1
- package/dist/cron/index.js +3 -0
- package/dist/cron/index.js.map +1 -1
- package/dist/db/index.js +3 -1
- package/dist/db/index.js.map +1 -1
- package/dist/execution/bracket-orders.js +102 -31
- package/dist/execution/bracket-orders.js.map +1 -1
- package/dist/execution/circuit-breaker.js +3 -4
- package/dist/execution/circuit-breaker.js.map +1 -1
- package/dist/execution/dca.js +46 -3
- package/dist/execution/dca.js.map +1 -1
- package/dist/execution/futures.js +8 -5
- package/dist/execution/futures.js.map +1 -1
- package/dist/execution/index.js +150 -44
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/twap.js +35 -2
- package/dist/execution/twap.js.map +1 -1
- package/dist/extensions/task-runner/index.js +38 -0
- package/dist/extensions/task-runner/index.js.map +1 -1
- package/dist/feeds/betfair/index.js +8 -1
- package/dist/feeds/betfair/index.js.map +1 -1
- package/dist/feeds/crypto/whale-tracker.js +24 -5
- package/dist/feeds/crypto/whale-tracker.js.map +1 -1
- package/dist/feeds/hedgehog/index.js +12 -0
- package/dist/feeds/hedgehog/index.js.map +1 -1
- package/dist/feeds/kalshi/index.js +7 -7
- package/dist/feeds/kalshi/index.js.map +1 -1
- package/dist/feeds/manifold/index.js +12 -0
- package/dist/feeds/manifold/index.js.map +1 -1
- package/dist/feeds/polymarket/index.js +3 -2
- package/dist/feeds/polymarket/index.js.map +1 -1
- package/dist/feeds/polymarket/user-ws.js +32 -9
- package/dist/feeds/polymarket/user-ws.js.map +1 -1
- package/dist/feeds/smarkets/index.js +7 -1
- package/dist/feeds/smarkets/index.js.map +1 -1
- package/dist/gateway/api-routes.js +32 -2
- package/dist/gateway/api-routes.js.map +1 -1
- package/dist/gateway/dca-routes.d.ts +5 -1
- package/dist/gateway/dca-routes.js +32 -1
- package/dist/gateway/dca-routes.js.map +1 -1
- package/dist/gateway/index.js +6 -5
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/payments-routes.js +1 -1
- package/dist/gateway/payments-routes.js.map +1 -1
- package/dist/gateway/percolator-routes.js +3 -3
- package/dist/gateway/percolator-routes.js.map +1 -1
- package/dist/gateway/risk-routes.js +2 -2
- package/dist/gateway/risk-routes.js.map +1 -1
- package/dist/gateway/server.js +12 -2
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/signal-bus.js +23 -4
- package/dist/gateway/signal-bus.js.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/infra/index.js +27 -22
- package/dist/infra/index.js.map +1 -1
- package/dist/link-understanding/index.js +7 -1
- package/dist/link-understanding/index.js.map +1 -1
- package/dist/monitoring/alerts.js +1 -1
- package/dist/monitoring/alerts.js.map +1 -1
- package/dist/monitoring/metrics.js +49 -0
- package/dist/monitoring/metrics.js.map +1 -1
- package/dist/opportunity/analytics.js +2 -2
- package/dist/opportunity/analytics.js.map +1 -1
- package/dist/opportunity/executor.js +2 -2
- package/dist/opportunity/executor.js.map +1 -1
- package/dist/opportunity/index.d.ts +2 -0
- package/dist/opportunity/index.js +18 -2
- package/dist/opportunity/index.js.map +1 -1
- package/dist/opportunity/scoring.d.ts +1 -0
- package/dist/opportunity/scoring.js.map +1 -1
- package/dist/providers/discovery.d.ts +1 -1
- package/dist/services/feature-engineering/rolling-window.js +7 -0
- package/dist/services/feature-engineering/rolling-window.js.map +1 -1
- package/dist/services/tick-streamer/index.js +4 -0
- package/dist/services/tick-streamer/index.js.map +1 -1
- package/dist/session/index.js +14 -0
- package/dist/session/index.js.map +1 -1
- package/dist/sessions/index.js +1 -1
- package/dist/sessions/index.js.map +1 -1
- package/dist/signal-router/router.js +7 -0
- package/dist/signal-router/router.js.map +1 -1
- package/dist/skills/bundled/dca/index.js +3 -1
- package/dist/skills/bundled/dca/index.js.map +1 -1
- package/dist/skills/bundled/hyperliquid/index.js +1 -1
- package/dist/skills/bundled/hyperliquid/index.js.map +1 -1
- package/dist/skills/bundled/qmd/index.js +1 -1
- package/dist/skills/bundled/qmd/index.js.map +1 -1
- package/dist/skills/bundled/ticks/index.js +1 -1
- package/dist/skills/bundled/ticks/index.js.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/solana/pump-swarm.js +1 -1
- package/dist/solana/pump-swarm.js.map +1 -1
- package/dist/solana/swarm-builders.js +26 -0
- package/dist/solana/swarm-builders.js.map +1 -1
- package/dist/strategies/crypto-hft/index.js +7 -3
- package/dist/strategies/crypto-hft/index.js.map +1 -1
- package/dist/terminal/index.js +11 -5
- package/dist/terminal/index.js.map +1 -1
- package/dist/tools/browser.js +8 -1
- package/dist/tools/browser.js.map +1 -1
- package/dist/tools/sql.js +2 -1
- package/dist/tools/sql.js.map +1 -1
- package/dist/trading/backtest.js +1 -1
- package/dist/trading/backtest.js.map +1 -1
- package/dist/trading/copy-trading.js +7 -0
- package/dist/trading/copy-trading.js.map +1 -1
- package/dist/trading/futures/index.js +5 -5
- package/dist/trading/futures/index.js.map +1 -1
- package/dist/trading/index.js +7 -6
- package/dist/trading/index.js.map +1 -1
- package/dist/trading/kelly.js +21 -0
- package/dist/trading/kelly.js.map +1 -1
- package/dist/trading/logger.js +13 -1
- package/dist/trading/logger.js.map +1 -1
- package/dist/trading/ml-signals.js +15 -6
- package/dist/trading/ml-signals.js.map +1 -1
- package/dist/trading/orchestrator.js +25 -1
- package/dist/trading/orchestrator.js.map +1 -1
- package/dist/trading/safety.js +7 -9
- package/dist/trading/safety.js.map +1 -1
- package/dist/utils/config.js +3 -0
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -1
package/dist/execution/index.js
CHANGED
|
@@ -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
|
|
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()
|
|
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 (
|
|
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)
|
|
1298
|
-
|
|
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
|
-
|
|
1797
|
-
|
|
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
|
|
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()
|
|
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
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
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
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
if (
|
|
2079
|
-
|
|
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
|
-
|
|
2085
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|