hedgequantx 2.6.162 → 2.7.0
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/README.md +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +6 -3
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +235 -0
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +60 -291
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -361
- package/src/services/rithmic/protobuf.js +5 -195
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/index.js +0 -526
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-manager.js +0 -528
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data.js +0 -549
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-tick.js +0 -507
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -505
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -7,20 +7,42 @@
|
|
|
7
7
|
|
|
8
8
|
const EventEmitter = require('events');
|
|
9
9
|
const { RithmicConnection } = require('./connection');
|
|
10
|
-
const { RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS
|
|
11
|
-
const { createOrderHandler, createPnLHandler
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const { RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS } = require('./constants');
|
|
11
|
+
const { createOrderHandler, createPnLHandler } = require('./handlers');
|
|
12
|
+
const {
|
|
13
|
+
fetchAccounts,
|
|
14
|
+
getTradingAccounts,
|
|
15
|
+
requestPnLSnapshot,
|
|
16
|
+
subscribePnLUpdates,
|
|
17
|
+
getPositions,
|
|
18
|
+
} = require('./accounts');
|
|
19
|
+
const { placeOrder, cancelOrder, getOrders, getOrderHistory, closePosition } = require('./orders');
|
|
20
|
+
const { getContracts, searchContracts } = require('./contracts');
|
|
21
|
+
const { TIMEOUTS } = require('../../config/settings');
|
|
16
22
|
const { logger } = require('../../utils/logger');
|
|
17
23
|
|
|
18
|
-
// Extracted modules
|
|
19
|
-
const { CME_CONTRACT_SPECS, PROPFIRM_CONFIGS, checkMarketHours } = require('./specs');
|
|
20
|
-
const { getTradeHistory, setupOrderFillListener } = require('./trade-history');
|
|
21
|
-
|
|
22
24
|
const log = logger.scope('Rithmic');
|
|
23
25
|
|
|
26
|
+
/** PropFirm configurations */
|
|
27
|
+
const PROPFIRM_CONFIGS = {
|
|
28
|
+
apex: { name: 'Apex Trader Funding', systemName: 'Apex', gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
29
|
+
apex_rithmic: { name: 'Apex Trader Funding', systemName: 'Apex', gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
30
|
+
topstep_r: { name: 'Topstep (Rithmic)', systemName: RITHMIC_SYSTEMS.TOPSTEP, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
31
|
+
bulenox_r: { name: 'Bulenox (Rithmic)', systemName: RITHMIC_SYSTEMS.BULENOX, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
32
|
+
earn2trade: { name: 'Earn2Trade', systemName: RITHMIC_SYSTEMS.EARN_2_TRADE, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
33
|
+
mescapital: { name: 'MES Capital', systemName: RITHMIC_SYSTEMS.MES_CAPITAL, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
34
|
+
tradefundrr: { name: 'TradeFundrr', systemName: RITHMIC_SYSTEMS.TRADEFUNDRR, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
35
|
+
thetradingpit: { name: 'The Trading Pit', systemName: RITHMIC_SYSTEMS.THE_TRADING_PIT, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
36
|
+
fundedfutures: { name: 'Funded Futures Network', systemName: RITHMIC_SYSTEMS.FUNDED_FUTURES_NETWORK, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
37
|
+
propshop: { name: 'PropShop Trader', systemName: RITHMIC_SYSTEMS.PROPSHOP_TRADER, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
38
|
+
'4proptrader': { name: '4PropTrader', systemName: RITHMIC_SYSTEMS.FOUR_PROP_TRADER, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
39
|
+
daytraders: { name: 'DayTraders.com', systemName: RITHMIC_SYSTEMS.DAY_TRADERS, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
40
|
+
'10xfutures': { name: '10X Futures', systemName: RITHMIC_SYSTEMS.TEN_X_FUTURES, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
41
|
+
lucidtrading: { name: 'Lucid Trading', systemName: RITHMIC_SYSTEMS.LUCID_TRADING, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
42
|
+
thrivetrading: { name: 'Thrive Trading', systemName: RITHMIC_SYSTEMS.THRIVE_TRADING, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
43
|
+
legendstrading: { name: 'Legends Trading', systemName: RITHMIC_SYSTEMS.LEGENDS_TRADING, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
44
|
+
};
|
|
45
|
+
|
|
24
46
|
/**
|
|
25
47
|
* Rithmic Service for prop firm trading
|
|
26
48
|
*/
|
|
@@ -45,10 +67,8 @@ class RithmicService extends EventEmitter {
|
|
|
45
67
|
this.accountPnL = new Map();
|
|
46
68
|
this.positions = new Map();
|
|
47
69
|
this.orders = [];
|
|
48
|
-
this.completedTrades = [];
|
|
49
70
|
this.user = null;
|
|
50
71
|
this.credentials = null;
|
|
51
|
-
this.tradeRoutes = new Map();
|
|
52
72
|
|
|
53
73
|
// Cache
|
|
54
74
|
this._contractsCache = null;
|
|
@@ -83,15 +103,6 @@ class RithmicService extends EventEmitter {
|
|
|
83
103
|
this.loginInfo = data;
|
|
84
104
|
this.user = { userName: username, fcmId: data.fcmId, ibId: data.ibId };
|
|
85
105
|
|
|
86
|
-
// Fetch trade routes
|
|
87
|
-
try {
|
|
88
|
-
await this._fetchTradeRoutes();
|
|
89
|
-
log.debug('Fetched trade routes', { count: this.tradeRoutes.size });
|
|
90
|
-
} catch (err) {
|
|
91
|
-
log.warn('Failed to fetch trade routes', { error: err.message });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Fetch accounts
|
|
95
106
|
try {
|
|
96
107
|
await fetchAccounts(this);
|
|
97
108
|
log.debug('Fetched accounts', { count: this.accounts.length });
|
|
@@ -99,28 +110,8 @@ class RithmicService extends EventEmitter {
|
|
|
99
110
|
log.warn('Failed to fetch accounts', { error: err.message });
|
|
100
111
|
}
|
|
101
112
|
|
|
102
|
-
// Subscribe to order updates
|
|
103
|
-
try {
|
|
104
|
-
for (const acc of this.accounts) {
|
|
105
|
-
this.orderConn.send('RequestSubscribeForOrderUpdates', {
|
|
106
|
-
templateId: REQ.ORDER_UPDATES,
|
|
107
|
-
userMsg: ['HQX'],
|
|
108
|
-
fcmId: acc.fcmId || data.fcmId,
|
|
109
|
-
ibId: acc.ibId || data.ibId,
|
|
110
|
-
accountId: acc.accountId,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
} catch (err) {
|
|
114
|
-
log.warn('Failed to subscribe to order updates', { error: err.message });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Setup order fill listener for P&L tracking
|
|
118
|
-
setupOrderFillListener(this);
|
|
119
|
-
|
|
120
|
-
// Store credentials for reconnection
|
|
121
113
|
this.credentials = { username, password };
|
|
122
114
|
|
|
123
|
-
// Connect to PNL_PLANT
|
|
124
115
|
try {
|
|
125
116
|
const pnlConnected = await this.connectPnL(username, password);
|
|
126
117
|
if (pnlConnected && this.pnlConn) {
|
|
@@ -229,45 +220,6 @@ class RithmicService extends EventEmitter {
|
|
|
229
220
|
}
|
|
230
221
|
}
|
|
231
222
|
|
|
232
|
-
// ==================== TRADE ROUTES ====================
|
|
233
|
-
|
|
234
|
-
async _fetchTradeRoutes() {
|
|
235
|
-
if (!this.orderConn || !this.loginInfo) return;
|
|
236
|
-
|
|
237
|
-
return new Promise((resolve) => {
|
|
238
|
-
const timeout = setTimeout(() => resolve(), 5000);
|
|
239
|
-
|
|
240
|
-
const onTradeRoute = (res) => {
|
|
241
|
-
if (res.tradeRoute && res.exchange) {
|
|
242
|
-
this.tradeRoutes.set(res.exchange, res.tradeRoute);
|
|
243
|
-
}
|
|
244
|
-
if (res.rpCode?.[0] === '0' && !res.tradeRoute) {
|
|
245
|
-
clearTimeout(timeout);
|
|
246
|
-
this.removeListener('tradeRoutes', onTradeRoute);
|
|
247
|
-
resolve();
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
this.on('tradeRoutes', onTradeRoute);
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
this.orderConn.send('RequestTradeRoutes', {
|
|
255
|
-
templateId: REQ.TRADE_ROUTES,
|
|
256
|
-
userMsg: ['HQX'],
|
|
257
|
-
subscribeForUpdates: true,
|
|
258
|
-
});
|
|
259
|
-
} catch (e) {
|
|
260
|
-
clearTimeout(timeout);
|
|
261
|
-
this.removeListener('tradeRoutes', onTradeRoute);
|
|
262
|
-
resolve();
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
getTradeRoute(exchange) {
|
|
268
|
-
return this.tradeRoutes.get(exchange) || null;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
223
|
// ==================== DELEGATED METHODS ====================
|
|
272
224
|
|
|
273
225
|
async getTradingAccounts() { return getTradingAccounts(this); }
|
|
@@ -276,51 +228,22 @@ class RithmicService extends EventEmitter {
|
|
|
276
228
|
async getOrderHistory(date) { return getOrderHistory(this, date); }
|
|
277
229
|
async placeOrder(orderData) { return placeOrder(this, orderData); }
|
|
278
230
|
async cancelOrder(orderId) { return cancelOrder(this, orderId); }
|
|
279
|
-
async cancelAllOrders(accountId) { return cancelAllOrders(this, accountId); }
|
|
280
231
|
async closePosition(accountId, symbol) { return closePosition(this, accountId, symbol); }
|
|
281
|
-
async
|
|
282
|
-
async
|
|
283
|
-
|
|
284
|
-
// ==================== FAST SCALPING ====================
|
|
285
|
-
|
|
286
|
-
fastEntry(orderData) { return fastEntry(this, orderData); }
|
|
287
|
-
fastExit(orderData) { return fastExit(this, orderData); }
|
|
288
|
-
|
|
289
|
-
async warmup() {
|
|
290
|
-
const results = [];
|
|
291
|
-
if (this.orderConn) results.push(await this.orderConn.warmup());
|
|
292
|
-
if (this.pnlConn) results.push(await this.pnlConn.warmup());
|
|
293
|
-
log.debug('Connection warmup complete', { success: results.filter(Boolean).length, total: results.length });
|
|
294
|
-
return results.every(Boolean);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
getLatencyStats() { return LatencyTracker.getStats(); }
|
|
298
|
-
getRecentLatencies(n = 10) { return LatencyTracker.getRecent(n); }
|
|
299
|
-
|
|
300
|
-
getDiagnostics() {
|
|
301
|
-
return {
|
|
302
|
-
orderConn: this.orderConn?.getDiagnostics() || null,
|
|
303
|
-
pnlConn: this.pnlConn?.getDiagnostics() || null,
|
|
304
|
-
tickerConn: this.tickerConn?.getDiagnostics() || null,
|
|
305
|
-
latency: this.getLatencyStats(),
|
|
306
|
-
accounts: this.accounts.length,
|
|
307
|
-
positions: this.positions.size,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
232
|
+
async getContracts() { return getContracts(this); }
|
|
233
|
+
async searchContracts(searchText) { return searchContracts(this, searchText); }
|
|
310
234
|
|
|
311
235
|
// ==================== STUBS ====================
|
|
312
236
|
|
|
313
237
|
async getUser() { return this.user; }
|
|
314
238
|
async getLifetimeStats() { return { success: true, stats: null }; }
|
|
315
239
|
async getDailyStats() { return { success: true, stats: [] }; }
|
|
316
|
-
async getTradeHistory(
|
|
240
|
+
async getTradeHistory() { return { success: true, trades: [] }; }
|
|
317
241
|
|
|
318
242
|
async getMarketStatus() {
|
|
319
|
-
const status = checkMarketHours();
|
|
243
|
+
const status = this.checkMarketHours();
|
|
320
244
|
return { success: true, isOpen: status.isOpen, message: status.message };
|
|
321
245
|
}
|
|
322
246
|
|
|
323
|
-
checkMarketHours() { return checkMarketHours(); }
|
|
324
247
|
getToken() { return this.loginInfo ? 'connected' : null; }
|
|
325
248
|
getPropfirm() { return this.propfirmKey || 'apex'; }
|
|
326
249
|
|
|
@@ -334,183 +257,27 @@ class RithmicService extends EventEmitter {
|
|
|
334
257
|
};
|
|
335
258
|
}
|
|
336
259
|
|
|
337
|
-
// ====================
|
|
338
|
-
|
|
339
|
-
async getContracts() {
|
|
340
|
-
if (this._contractsCache && Date.now() - this._contractsCacheTime < CACHE.CONTRACTS_TTL) {
|
|
341
|
-
return { success: true, contracts: this._contractsCache, source: 'cache' };
|
|
342
|
-
}
|
|
260
|
+
// ==================== MARKET HOURS ====================
|
|
343
261
|
|
|
344
|
-
|
|
262
|
+
checkMarketHours() {
|
|
263
|
+
const now = new Date();
|
|
264
|
+
const utcDay = now.getUTCDay();
|
|
265
|
+
const utcHour = now.getUTCHours();
|
|
345
266
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (!connected) return { success: false, error: 'Failed to connect to TICKER_PLANT' };
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
this.tickerConn.setMaxListeners(5000);
|
|
353
|
-
log.debug('Fetching contracts from Rithmic API');
|
|
354
|
-
const contracts = await this._fetchAllFrontMonths();
|
|
355
|
-
|
|
356
|
-
if (!contracts.length) return { success: false, error: 'No tradeable contracts found' };
|
|
357
|
-
|
|
358
|
-
this._contractsCache = contracts;
|
|
359
|
-
this._contractsCacheTime = Date.now();
|
|
360
|
-
|
|
361
|
-
return { success: true, contracts, source: 'api' };
|
|
362
|
-
} catch (err) {
|
|
363
|
-
log.error('getContracts error', { error: err.message });
|
|
364
|
-
return { success: false, error: err.message };
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async searchContracts(searchText) {
|
|
369
|
-
const result = await this.getContracts();
|
|
370
|
-
if (!searchText || !result.success) return result.contracts || [];
|
|
371
|
-
|
|
372
|
-
const search = searchText.toUpperCase();
|
|
373
|
-
return result.contracts.filter(c =>
|
|
374
|
-
c.symbol.toUpperCase().includes(search) ||
|
|
375
|
-
c.name.toUpperCase().includes(search)
|
|
267
|
+
const isDST = now.getTimezoneOffset() < Math.max(
|
|
268
|
+
new Date(now.getFullYear(), 0, 1).getTimezoneOffset(),
|
|
269
|
+
new Date(now.getFullYear(), 6, 1).getTimezoneOffset()
|
|
376
270
|
);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (msg.templateId !== 112) return;
|
|
388
|
-
|
|
389
|
-
const decoded = this._decodeProductCodes(msg.data);
|
|
390
|
-
if (!decoded.productCode || !decoded.exchange) return;
|
|
391
|
-
|
|
392
|
-
const validExchanges = ['CME', 'CBOT', 'NYMEX', 'COMEX', 'NYBOT', 'CFE'];
|
|
393
|
-
if (!validExchanges.includes(decoded.exchange)) return;
|
|
394
|
-
|
|
395
|
-
const name = (decoded.productName || '').toLowerCase();
|
|
396
|
-
if (name.includes('option') || name.includes('swap') || name.includes('spread')) return;
|
|
397
|
-
|
|
398
|
-
const key = `${decoded.productCode}:${decoded.exchange}`;
|
|
399
|
-
if (!productsToCheck.has(key)) {
|
|
400
|
-
productsToCheck.set(key, {
|
|
401
|
-
productCode: decoded.productCode,
|
|
402
|
-
productName: decoded.productName || decoded.productCode,
|
|
403
|
-
exchange: decoded.exchange,
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
const frontMonthHandler = (msg) => {
|
|
409
|
-
if (msg.templateId !== 114) return;
|
|
410
|
-
|
|
411
|
-
const decoded = decodeFrontMonthContract(msg.data);
|
|
412
|
-
if (decoded.rpCode[0] === '0' && decoded.tradingSymbol) {
|
|
413
|
-
contracts.set(decoded.userMsg, {
|
|
414
|
-
symbol: decoded.tradingSymbol,
|
|
415
|
-
baseSymbol: decoded.userMsg,
|
|
416
|
-
exchange: decoded.exchange,
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
this.tickerConn.on('message', productHandler);
|
|
422
|
-
this.tickerConn.on('message', frontMonthHandler);
|
|
423
|
-
|
|
424
|
-
this.tickerConn.send('RequestProductCodes', {
|
|
425
|
-
templateId: 111,
|
|
426
|
-
userMsg: ['get-products'],
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
setTimeout(() => {
|
|
430
|
-
this.tickerConn.removeListener('message', productHandler);
|
|
431
|
-
log.debug('Collected products', { count: productsToCheck.size });
|
|
432
|
-
|
|
433
|
-
for (const product of productsToCheck.values()) {
|
|
434
|
-
this.tickerConn.send('RequestFrontMonthContract', {
|
|
435
|
-
templateId: 113,
|
|
436
|
-
userMsg: [product.productCode],
|
|
437
|
-
symbol: product.productCode,
|
|
438
|
-
exchange: product.exchange,
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
setTimeout(() => {
|
|
443
|
-
this.tickerConn.removeListener('message', frontMonthHandler);
|
|
444
|
-
|
|
445
|
-
const results = [];
|
|
446
|
-
for (const [baseSymbol, contract] of contracts) {
|
|
447
|
-
const productKey = `${baseSymbol}:${contract.exchange}`;
|
|
448
|
-
const product = productsToCheck.get(productKey);
|
|
449
|
-
const specs = CME_CONTRACT_SPECS[baseSymbol] || null;
|
|
450
|
-
const productName = specs?.name || product?.productName || baseSymbol;
|
|
451
|
-
|
|
452
|
-
results.push({
|
|
453
|
-
symbol: contract.symbol,
|
|
454
|
-
baseSymbol,
|
|
455
|
-
name: contract.symbol,
|
|
456
|
-
description: productName,
|
|
457
|
-
exchange: contract.exchange,
|
|
458
|
-
tickSize: specs?.tickSize ?? null,
|
|
459
|
-
tickValue: specs?.tickValue ?? null,
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
results.sort((a, b) => a.baseSymbol.localeCompare(b.baseSymbol));
|
|
464
|
-
log.debug('Got contracts from API', { count: results.length });
|
|
465
|
-
resolve(results);
|
|
466
|
-
}, TIMEOUTS.RITHMIC_PRODUCTS);
|
|
467
|
-
}, TIMEOUTS.RITHMIC_CONTRACTS);
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
_decodeProductCodes(buffer) {
|
|
472
|
-
const result = {};
|
|
473
|
-
let offset = 0;
|
|
474
|
-
|
|
475
|
-
const readVarint = (buf, off) => {
|
|
476
|
-
let value = 0, shift = 0;
|
|
477
|
-
while (off < buf.length) {
|
|
478
|
-
const byte = buf[off++];
|
|
479
|
-
value |= (byte & 0x7F) << shift;
|
|
480
|
-
if (!(byte & 0x80)) break;
|
|
481
|
-
shift += 7;
|
|
482
|
-
}
|
|
483
|
-
return [value, off];
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
const readString = (buf, off) => {
|
|
487
|
-
const [len, newOff] = readVarint(buf, off);
|
|
488
|
-
return [buf.slice(newOff, newOff + len).toString('utf8'), newOff + len];
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
while (offset < buffer.length) {
|
|
492
|
-
try {
|
|
493
|
-
const [tag, tagOff] = readVarint(buffer, offset);
|
|
494
|
-
const wireType = tag & 0x7;
|
|
495
|
-
const fieldNumber = tag >>> 3;
|
|
496
|
-
offset = tagOff;
|
|
497
|
-
|
|
498
|
-
if (wireType === 0) {
|
|
499
|
-
const [, newOff] = readVarint(buffer, offset);
|
|
500
|
-
offset = newOff;
|
|
501
|
-
} else if (wireType === 2) {
|
|
502
|
-
const [val, newOff] = readString(buffer, offset);
|
|
503
|
-
offset = newOff;
|
|
504
|
-
if (fieldNumber === 110101) result.exchange = val;
|
|
505
|
-
if (fieldNumber === 100749) result.productCode = val;
|
|
506
|
-
if (fieldNumber === 100003) result.productName = val;
|
|
507
|
-
} else {
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
} catch { break; }
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return result;
|
|
271
|
+
const ctOffset = isDST ? 5 : 6;
|
|
272
|
+
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
273
|
+
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
274
|
+
|
|
275
|
+
if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
|
|
276
|
+
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
277
|
+
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
278
|
+
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance (4:00-5:00 PM CT)' };
|
|
279
|
+
|
|
280
|
+
return { isOpen: true, message: 'Market is open' };
|
|
514
281
|
}
|
|
515
282
|
|
|
516
283
|
// ==================== CLEANUP ====================
|
|
@@ -520,7 +287,11 @@ class RithmicService extends EventEmitter {
|
|
|
520
287
|
|
|
521
288
|
for (const conn of connections) {
|
|
522
289
|
if (conn) {
|
|
523
|
-
try {
|
|
290
|
+
try {
|
|
291
|
+
await conn.disconnect();
|
|
292
|
+
} catch (err) {
|
|
293
|
+
log.warn('Disconnect error', { error: err.message });
|
|
294
|
+
}
|
|
524
295
|
}
|
|
525
296
|
}
|
|
526
297
|
|
|
@@ -531,8 +302,6 @@ class RithmicService extends EventEmitter {
|
|
|
531
302
|
this.accountPnL.clear();
|
|
532
303
|
this.positions.clear();
|
|
533
304
|
this.orders = [];
|
|
534
|
-
this.completedTrades = [];
|
|
535
|
-
this._openEntries = null;
|
|
536
305
|
this.loginInfo = null;
|
|
537
306
|
this.user = null;
|
|
538
307
|
this.credentials = null;
|
|
@@ -542,4 +311,4 @@ class RithmicService extends EventEmitter {
|
|
|
542
311
|
}
|
|
543
312
|
}
|
|
544
313
|
|
|
545
|
-
module.exports = { RithmicService, RITHMIC_SYSTEMS, RITHMIC_ENDPOINTS
|
|
314
|
+
module.exports = { RithmicService, RITHMIC_SYSTEMS, RITHMIC_ENDPOINTS };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Market hours utilities for Rithmic
|
|
3
|
+
* @module services/rithmic/market
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if market is currently open
|
|
8
|
+
* @returns {{isOpen: boolean, message: string}}
|
|
9
|
+
*/
|
|
10
|
+
const checkMarketHours = () => {
|
|
11
|
+
const now = new Date();
|
|
12
|
+
const utcDay = now.getUTCDay();
|
|
13
|
+
const utcHour = now.getUTCHours();
|
|
14
|
+
|
|
15
|
+
const isDST = now.getTimezoneOffset() < Math.max(
|
|
16
|
+
new Date(now.getFullYear(), 0, 1).getTimezoneOffset(),
|
|
17
|
+
new Date(now.getFullYear(), 6, 1).getTimezoneOffset()
|
|
18
|
+
);
|
|
19
|
+
const ctOffset = isDST ? 5 : 6;
|
|
20
|
+
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
21
|
+
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
22
|
+
|
|
23
|
+
if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
|
|
24
|
+
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
25
|
+
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
26
|
+
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance (4:00-5:00 PM CT)' };
|
|
27
|
+
|
|
28
|
+
return { isOpen: true, message: 'Market is open' };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
module.exports = { checkMarketHours };
|