hedgequantx 2.6.161 → 2.6.163

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 (57) hide show
  1. package/package.json +1 -1
  2. package/src/menus/ai-agent-connect.js +181 -0
  3. package/src/menus/ai-agent-models.js +219 -0
  4. package/src/menus/ai-agent-oauth.js +292 -0
  5. package/src/menus/ai-agent-ui.js +141 -0
  6. package/src/menus/ai-agent.js +88 -1489
  7. package/src/pages/algo/copy-engine.js +449 -0
  8. package/src/pages/algo/copy-trading.js +11 -543
  9. package/src/pages/algo/smart-logs-data.js +218 -0
  10. package/src/pages/algo/smart-logs.js +9 -214
  11. package/src/pages/algo/ui-constants.js +144 -0
  12. package/src/pages/algo/ui-summary.js +184 -0
  13. package/src/pages/algo/ui.js +42 -526
  14. package/src/pages/stats-calculations.js +191 -0
  15. package/src/pages/stats-ui.js +381 -0
  16. package/src/pages/stats.js +14 -507
  17. package/src/services/ai/client-analysis.js +194 -0
  18. package/src/services/ai/client-models.js +333 -0
  19. package/src/services/ai/client.js +6 -489
  20. package/src/services/ai/index.js +2 -257
  21. package/src/services/ai/providers/direct-providers.js +323 -0
  22. package/src/services/ai/providers/index.js +8 -472
  23. package/src/services/ai/providers/other-providers.js +104 -0
  24. package/src/services/ai/proxy-install.js +249 -0
  25. package/src/services/ai/proxy-manager.js +29 -411
  26. package/src/services/ai/proxy-remote.js +161 -0
  27. package/src/services/ai/supervisor-optimize.js +215 -0
  28. package/src/services/ai/supervisor-sync.js +178 -0
  29. package/src/services/ai/supervisor.js +50 -515
  30. package/src/services/ai/validation.js +250 -0
  31. package/src/services/hqx-server-events.js +110 -0
  32. package/src/services/hqx-server-handlers.js +217 -0
  33. package/src/services/hqx-server-latency.js +136 -0
  34. package/src/services/hqx-server.js +51 -403
  35. package/src/services/position-constants.js +28 -0
  36. package/src/services/position-exit-logic.js +174 -0
  37. package/src/services/position-manager.js +90 -629
  38. package/src/services/position-momentum.js +206 -0
  39. package/src/services/projectx/accounts.js +142 -0
  40. package/src/services/projectx/index.js +40 -289
  41. package/src/services/projectx/trading.js +180 -0
  42. package/src/services/rithmic/contracts.js +218 -0
  43. package/src/services/rithmic/handlers.js +2 -208
  44. package/src/services/rithmic/index.js +28 -712
  45. package/src/services/rithmic/latency-tracker.js +182 -0
  46. package/src/services/rithmic/market-data-decoders.js +229 -0
  47. package/src/services/rithmic/market-data.js +1 -278
  48. package/src/services/rithmic/orders-fast.js +246 -0
  49. package/src/services/rithmic/orders.js +1 -251
  50. package/src/services/rithmic/proto-decoders.js +403 -0
  51. package/src/services/rithmic/protobuf.js +7 -443
  52. package/src/services/rithmic/specs.js +146 -0
  53. package/src/services/rithmic/trade-history.js +254 -0
  54. package/src/services/strategy/hft-signal-calc.js +147 -0
  55. package/src/services/strategy/hft-tick.js +33 -133
  56. package/src/services/tradovate/index.js +6 -119
  57. package/src/services/tradovate/orders.js +145 -0
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Rithmic Contracts Module
3
+ * @module services/rithmic/contracts
4
+ *
5
+ * Contract lookup and front month discovery
6
+ */
7
+
8
+ const { decodeFrontMonthContract } = require('./protobuf');
9
+ const { CME_CONTRACT_SPECS } = require('./specs');
10
+ const { TIMEOUTS, CACHE } = require('../../config/settings');
11
+ const { logger } = require('../../utils/logger');
12
+
13
+ const log = logger.scope('RithmicContracts');
14
+
15
+ /**
16
+ * Decode product codes from raw protobuf buffer
17
+ * @param {Buffer} buffer
18
+ * @returns {Object}
19
+ */
20
+ function decodeProductCodes(buffer) {
21
+ const result = {};
22
+ let offset = 0;
23
+
24
+ const readVarint = (buf, off) => {
25
+ let value = 0, shift = 0;
26
+ while (off < buf.length) {
27
+ const byte = buf[off++];
28
+ value |= (byte & 0x7F) << shift;
29
+ if (!(byte & 0x80)) break;
30
+ shift += 7;
31
+ }
32
+ return [value, off];
33
+ };
34
+
35
+ const readString = (buf, off) => {
36
+ const [len, newOff] = readVarint(buf, off);
37
+ return [buf.slice(newOff, newOff + len).toString('utf8'), newOff + len];
38
+ };
39
+
40
+ while (offset < buffer.length) {
41
+ try {
42
+ const [tag, tagOff] = readVarint(buffer, offset);
43
+ const wireType = tag & 0x7;
44
+ const fieldNumber = tag >>> 3;
45
+ offset = tagOff;
46
+
47
+ if (wireType === 0) {
48
+ const [, newOff] = readVarint(buffer, offset);
49
+ offset = newOff;
50
+ } else if (wireType === 2) {
51
+ const [val, newOff] = readString(buffer, offset);
52
+ offset = newOff;
53
+ if (fieldNumber === 110101) result.exchange = val;
54
+ if (fieldNumber === 100749) result.productCode = val;
55
+ if (fieldNumber === 100003) result.productName = val;
56
+ } else {
57
+ break;
58
+ }
59
+ } catch { break; }
60
+ }
61
+
62
+ return result;
63
+ }
64
+
65
+ /**
66
+ * Fetch all front month contracts from TICKER_PLANT
67
+ * @param {RithmicService} service
68
+ * @returns {Promise<Array>}
69
+ */
70
+ async function fetchAllFrontMonths(service) {
71
+ if (!service.tickerConn) throw new Error('TICKER_PLANT not connected');
72
+
73
+ return new Promise((resolve) => {
74
+ const contracts = new Map();
75
+ const productsToCheck = new Map();
76
+
77
+ const productHandler = (msg) => {
78
+ if (msg.templateId !== 112) return;
79
+
80
+ const decoded = decodeProductCodes(msg.data);
81
+ if (!decoded.productCode || !decoded.exchange) return;
82
+
83
+ const validExchanges = ['CME', 'CBOT', 'NYMEX', 'COMEX', 'NYBOT', 'CFE'];
84
+ if (!validExchanges.includes(decoded.exchange)) return;
85
+
86
+ const name = (decoded.productName || '').toLowerCase();
87
+ if (name.includes('option') || name.includes('swap') || name.includes('spread')) return;
88
+
89
+ const key = `${decoded.productCode}:${decoded.exchange}`;
90
+ if (!productsToCheck.has(key)) {
91
+ productsToCheck.set(key, {
92
+ productCode: decoded.productCode,
93
+ productName: decoded.productName || decoded.productCode,
94
+ exchange: decoded.exchange,
95
+ });
96
+ }
97
+ };
98
+
99
+ const frontMonthHandler = (msg) => {
100
+ if (msg.templateId !== 114) return;
101
+
102
+ const decoded = decodeFrontMonthContract(msg.data);
103
+ if (decoded.rpCode[0] === '0' && decoded.tradingSymbol) {
104
+ contracts.set(decoded.userMsg, {
105
+ symbol: decoded.tradingSymbol,
106
+ baseSymbol: decoded.userMsg,
107
+ exchange: decoded.exchange,
108
+ });
109
+ }
110
+ };
111
+
112
+ service.tickerConn.on('message', productHandler);
113
+ service.tickerConn.on('message', frontMonthHandler);
114
+
115
+ service.tickerConn.send('RequestProductCodes', {
116
+ templateId: 111,
117
+ userMsg: ['get-products'],
118
+ });
119
+
120
+ setTimeout(() => {
121
+ service.tickerConn.removeListener('message', productHandler);
122
+ log.debug('Collected products', { count: productsToCheck.size });
123
+
124
+ for (const product of productsToCheck.values()) {
125
+ service.tickerConn.send('RequestFrontMonthContract', {
126
+ templateId: 113,
127
+ userMsg: [product.productCode],
128
+ symbol: product.productCode,
129
+ exchange: product.exchange,
130
+ });
131
+ }
132
+
133
+ setTimeout(() => {
134
+ service.tickerConn.removeListener('message', frontMonthHandler);
135
+
136
+ const results = [];
137
+ for (const [baseSymbol, contract] of contracts) {
138
+ const productKey = `${baseSymbol}:${contract.exchange}`;
139
+ const product = productsToCheck.get(productKey);
140
+ const specs = CME_CONTRACT_SPECS[baseSymbol] || null;
141
+ const productName = specs?.name || product?.productName || baseSymbol;
142
+
143
+ results.push({
144
+ symbol: contract.symbol,
145
+ baseSymbol,
146
+ name: contract.symbol,
147
+ description: productName,
148
+ exchange: contract.exchange,
149
+ tickSize: specs?.tickSize ?? null,
150
+ tickValue: specs?.tickValue ?? null,
151
+ });
152
+ }
153
+
154
+ results.sort((a, b) => a.baseSymbol.localeCompare(b.baseSymbol));
155
+ log.debug('Got contracts from API', { count: results.length });
156
+ resolve(results);
157
+ }, TIMEOUTS.RITHMIC_PRODUCTS);
158
+ }, TIMEOUTS.RITHMIC_CONTRACTS);
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Get contracts with caching
164
+ * @param {RithmicService} service
165
+ * @returns {Promise<Object>}
166
+ */
167
+ async function getContracts(service) {
168
+ if (service._contractsCache && Date.now() - service._contractsCacheTime < CACHE.CONTRACTS_TTL) {
169
+ return { success: true, contracts: service._contractsCache, source: 'cache' };
170
+ }
171
+
172
+ if (!service.credentials) return { success: false, error: 'Not logged in' };
173
+
174
+ try {
175
+ if (!service.tickerConn) {
176
+ const connected = await service.connectTicker(service.credentials.username, service.credentials.password);
177
+ if (!connected) return { success: false, error: 'Failed to connect to TICKER_PLANT' };
178
+ }
179
+
180
+ service.tickerConn.setMaxListeners(5000);
181
+ log.debug('Fetching contracts from Rithmic API');
182
+ const contracts = await fetchAllFrontMonths(service);
183
+
184
+ if (!contracts.length) return { success: false, error: 'No tradeable contracts found' };
185
+
186
+ service._contractsCache = contracts;
187
+ service._contractsCacheTime = Date.now();
188
+
189
+ return { success: true, contracts, source: 'api' };
190
+ } catch (err) {
191
+ log.error('getContracts error', { error: err.message });
192
+ return { success: false, error: err.message };
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Search contracts by text
198
+ * @param {RithmicService} service
199
+ * @param {string} searchText
200
+ * @returns {Promise<Array>}
201
+ */
202
+ async function searchContracts(service, searchText) {
203
+ const result = await getContracts(service);
204
+ if (!searchText || !result.success) return result.contracts || [];
205
+
206
+ const search = searchText.toUpperCase();
207
+ return result.contracts.filter(c =>
208
+ c.symbol.toUpperCase().includes(search) ||
209
+ c.name.toUpperCase().includes(search)
210
+ );
211
+ }
212
+
213
+ module.exports = {
214
+ decodeProductCodes,
215
+ fetchAllFrontMonths,
216
+ getContracts,
217
+ searchContracts,
218
+ };
@@ -14,151 +14,12 @@
14
14
  const { proto, decodeAccountPnL, decodeInstrumentPnL } = require('./protobuf');
15
15
  const { RES, STREAM } = require('./constants');
16
16
  const { performance } = require('perf_hooks');
17
+ const { LatencyTracker, FillInfoPool } = require('./latency-tracker');
17
18
 
18
19
  // Debug mode - use no-op function when disabled for zero overhead
19
20
  const DEBUG = process.env.HQX_DEBUG === '1';
20
21
  const debug = DEBUG ? (...args) => console.log('[Rithmic:Handler]', ...args) : () => {};
21
22
 
22
- // ==================== HIGH-RESOLUTION TIMING ====================
23
- // Use process.hrtime.bigint for sub-millisecond precision
24
-
25
- /**
26
- * Get high-resolution timestamp in nanoseconds
27
- * @returns {bigint}
28
- */
29
- const hrNow = () => process.hrtime.bigint();
30
-
31
- /**
32
- * Convert nanoseconds to milliseconds with precision
33
- * @param {bigint} ns
34
- * @returns {number}
35
- */
36
- const nsToMs = (ns) => Number(ns) / 1_000_000;
37
-
38
- // ==================== LATENCY TRACKING ====================
39
- // Track order-to-fill latency for performance monitoring
40
- // OPTIMIZED: Circular buffer (no array.shift), high-resolution timing
41
-
42
- const LatencyTracker = {
43
- _pending: new Map(), // orderTag -> entryTime (bigint nanoseconds)
44
- _samples: null, // Pre-allocated Float64Array circular buffer
45
- _maxSamples: 100,
46
- _head: 0, // Next write position
47
- _count: 0, // Number of valid samples
48
- _initialized: false,
49
-
50
- /**
51
- * Initialize circular buffer (lazy init)
52
- * @private
53
- */
54
- _init() {
55
- if (this._initialized) return;
56
- this._samples = new Float64Array(this._maxSamples);
57
- this._initialized = true;
58
- },
59
-
60
- /**
61
- * Record order sent time with high-resolution timestamp
62
- * @param {string} orderTag
63
- * @param {number} entryTimeMs - Date.now() when order was sent (for compatibility)
64
- */
65
- recordEntry(orderTag, entryTimeMs) {
66
- // Store high-resolution time for precise measurement
67
- this._pending.set(orderTag, hrNow());
68
- },
69
-
70
- /**
71
- * Record fill received, calculate latency with sub-ms precision
72
- * @param {string} orderTag
73
- * @returns {number|null} Round-trip latency in ms (with decimal precision), or null if not tracked
74
- */
75
- recordFill(orderTag) {
76
- const entryTime = this._pending.get(orderTag);
77
- if (!entryTime) return null;
78
-
79
- this._pending.delete(orderTag);
80
- const latencyNs = hrNow() - entryTime;
81
- const latencyMs = nsToMs(latencyNs);
82
-
83
- // Store in circular buffer (no shift, O(1))
84
- this._init();
85
- this._samples[this._head] = latencyMs;
86
- this._head = (this._head + 1) % this._maxSamples;
87
- if (this._count < this._maxSamples) this._count++;
88
-
89
- return latencyMs;
90
- },
91
-
92
- /**
93
- * Get average latency
94
- * @returns {number|null}
95
- */
96
- getAverage() {
97
- if (this._count === 0) return null;
98
- let sum = 0;
99
- for (let i = 0; i < this._count; i++) {
100
- sum += this._samples[i];
101
- }
102
- return sum / this._count;
103
- },
104
-
105
- /**
106
- * Get min/max/avg stats with high precision
107
- * @returns {Object}
108
- */
109
- getStats() {
110
- if (this._count === 0) {
111
- return { min: null, max: null, avg: null, p50: null, p99: null, samples: 0 };
112
- }
113
-
114
- // Get valid samples
115
- const valid = [];
116
- for (let i = 0; i < this._count; i++) {
117
- valid.push(this._samples[i]);
118
- }
119
- valid.sort((a, b) => a - b);
120
-
121
- const sum = valid.reduce((a, b) => a + b, 0);
122
-
123
- return {
124
- min: valid[0],
125
- max: valid[valid.length - 1],
126
- avg: sum / valid.length,
127
- p50: valid[Math.floor(valid.length * 0.5)],
128
- p99: valid[Math.floor(valid.length * 0.99)] || valid[valid.length - 1],
129
- samples: this._count,
130
- };
131
- },
132
-
133
- /**
134
- * Get last N latency samples
135
- * @param {number} n
136
- * @returns {number[]}
137
- */
138
- getRecent(n = 10) {
139
- if (this._count === 0) return [];
140
- const result = [];
141
- const start = this._count < this._maxSamples ? 0 : this._head;
142
- for (let i = 0; i < Math.min(n, this._count); i++) {
143
- const idx = (start + this._count - 1 - i + this._maxSamples) % this._maxSamples;
144
- result.push(this._samples[idx]);
145
- }
146
- return result;
147
- },
148
-
149
- /**
150
- * Clear all tracking data
151
- */
152
- clear() {
153
- this._pending.clear();
154
- this._head = 0;
155
- this._count = 0;
156
- if (this._samples) {
157
- this._samples.fill(0);
158
- }
159
- }
160
- };
161
-
162
23
  /**
163
24
  * Create ORDER_PLANT message handler
164
25
  * @param {RithmicService} service - The Rithmic service instance
@@ -436,74 +297,7 @@ const handleNewOrderResponse = (service, data) => {
436
297
  }
437
298
  };
438
299
 
439
- // ==================== PRE-ALLOCATED OBJECTS ====================
440
- // Reusable objects for hot path to avoid GC pressure
441
-
442
- const FillInfoPool = {
443
- // Pre-allocated fill info template
444
- _template: {
445
- orderTag: null,
446
- basketId: null,
447
- orderId: null,
448
- status: null,
449
- symbol: null,
450
- exchange: null,
451
- accountId: null,
452
- fillQuantity: 0,
453
- totalFillQuantity: 0,
454
- remainingQuantity: 0,
455
- avgFillPrice: 0,
456
- lastFillPrice: 0,
457
- transactionType: 0,
458
- orderType: 0,
459
- quantity: 0,
460
- ssboe: 0,
461
- usecs: 0,
462
- localTimestamp: 0,
463
- roundTripLatencyMs: null,
464
- },
465
-
466
- /**
467
- * Fill template with notification data
468
- * @param {Object} notif - Decoded notification
469
- * @param {number} receiveTime - Local receive timestamp
470
- * @param {number|null} latency - Round-trip latency
471
- * @returns {Object}
472
- */
473
- fill(notif, receiveTime, latency) {
474
- const o = this._template;
475
- o.orderTag = notif.userTag || null; // userTag contains our order tag
476
- o.basketId = notif.basketId;
477
- o.orderId = notif.exchangeOrderId || notif.orderId;
478
- o.status = notif.status;
479
- o.symbol = notif.symbol;
480
- o.exchange = notif.exchange;
481
- o.accountId = notif.accountId;
482
- // Proto uses totalFillSize, not fillQuantity
483
- o.fillQuantity = notif.totalFillSize || notif.fillQuantity || 0;
484
- o.totalFillQuantity = notif.totalFillSize || notif.totalFillQuantity || 0;
485
- o.remainingQuantity = notif.totalUnfilledSize || notif.remainingQuantity || 0;
486
- o.avgFillPrice = parseFloat(notif.avgFillPrice || 0);
487
- o.lastFillPrice = parseFloat(notif.price || notif.fillPrice || 0);
488
- o.transactionType = notif.transactionType;
489
- o.orderType = notif.priceType || notif.orderType;
490
- o.quantity = notif.quantity;
491
- o.ssboe = notif.ssboe;
492
- o.usecs = notif.usecs;
493
- o.localTimestamp = receiveTime;
494
- o.roundTripLatencyMs = latency;
495
- return o;
496
- },
497
-
498
- /**
499
- * Create a copy for async operations that need to keep the data
500
- * @param {Object} fillInfo
501
- * @returns {Object}
502
- */
503
- clone(fillInfo) {
504
- return { ...fillInfo };
505
- }
506
- };
300
+ // FillInfoPool imported from ./latency-tracker
507
301
 
508
302
  /**
509
303
  * Handle order notification (351) - CRITICAL for fill tracking