hedgequantx 2.6.163 → 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.
Files changed (146) hide show
  1. package/README.md +15 -88
  2. package/bin/cli.js +0 -11
  3. package/dist/lib/api.jsc +0 -0
  4. package/dist/lib/api2.jsc +0 -0
  5. package/dist/lib/core.jsc +0 -0
  6. package/dist/lib/core2.jsc +0 -0
  7. package/dist/lib/data.js +1 -1
  8. package/dist/lib/data.jsc +0 -0
  9. package/dist/lib/data2.jsc +0 -0
  10. package/dist/lib/decoder.jsc +0 -0
  11. package/dist/lib/m/mod1.jsc +0 -0
  12. package/dist/lib/m/mod2.jsc +0 -0
  13. package/dist/lib/n/r1.jsc +0 -0
  14. package/dist/lib/n/r2.jsc +0 -0
  15. package/dist/lib/n/r3.jsc +0 -0
  16. package/dist/lib/n/r4.jsc +0 -0
  17. package/dist/lib/n/r5.jsc +0 -0
  18. package/dist/lib/n/r6.jsc +0 -0
  19. package/dist/lib/n/r7.jsc +0 -0
  20. package/dist/lib/o/util1.jsc +0 -0
  21. package/dist/lib/o/util2.jsc +0 -0
  22. package/package.json +6 -3
  23. package/src/app.js +40 -162
  24. package/src/config/constants.js +31 -33
  25. package/src/config/propfirms.js +13 -217
  26. package/src/config/settings.js +0 -43
  27. package/src/lib/api.js +198 -0
  28. package/src/lib/api2.js +353 -0
  29. package/src/lib/core.js +539 -0
  30. package/src/lib/core2.js +341 -0
  31. package/src/lib/data.js +555 -0
  32. package/src/lib/data2.js +492 -0
  33. package/src/lib/decoder.js +599 -0
  34. package/src/lib/m/s1.js +804 -0
  35. package/src/lib/m/s2.js +34 -0
  36. package/src/lib/n/r1.js +454 -0
  37. package/src/lib/n/r2.js +514 -0
  38. package/src/lib/n/r3.js +631 -0
  39. package/src/lib/n/r4.js +401 -0
  40. package/src/lib/n/r5.js +335 -0
  41. package/src/lib/n/r6.js +425 -0
  42. package/src/lib/n/r7.js +530 -0
  43. package/src/lib/o/l1.js +44 -0
  44. package/src/lib/o/l2.js +427 -0
  45. package/src/lib/python-bridge.js +206 -0
  46. package/src/menus/connect.js +14 -176
  47. package/src/menus/dashboard.js +65 -110
  48. package/src/pages/accounts.js +18 -18
  49. package/src/pages/algo/copy-trading.js +210 -240
  50. package/src/pages/algo/index.js +41 -104
  51. package/src/pages/algo/one-account.js +386 -33
  52. package/src/pages/algo/ui.js +312 -151
  53. package/src/pages/orders.js +3 -3
  54. package/src/pages/positions.js +3 -3
  55. package/src/pages/stats/chart.js +74 -0
  56. package/src/pages/stats/display.js +228 -0
  57. package/src/pages/stats/index.js +236 -0
  58. package/src/pages/stats/metrics.js +213 -0
  59. package/src/pages/user.js +6 -6
  60. package/src/services/hqx-server/constants.js +55 -0
  61. package/src/services/hqx-server/index.js +401 -0
  62. package/src/services/hqx-server/latency.js +81 -0
  63. package/src/services/index.js +12 -3
  64. package/src/services/rithmic/accounts.js +7 -32
  65. package/src/services/rithmic/connection.js +1 -204
  66. package/src/services/rithmic/contracts.js +116 -99
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +63 -120
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -111
  71. package/src/services/rithmic/protobuf.js +384 -138
  72. package/src/services/session.js +22 -173
  73. package/src/ui/box.js +10 -18
  74. package/src/ui/index.js +1 -3
  75. package/src/ui/menu.js +1 -1
  76. package/src/utils/prompts.js +2 -2
  77. package/dist/lib/m/s1.js +0 -1
  78. package/src/menus/ai-agent-connect.js +0 -181
  79. package/src/menus/ai-agent-models.js +0 -219
  80. package/src/menus/ai-agent-oauth.js +0 -292
  81. package/src/menus/ai-agent-ui.js +0 -141
  82. package/src/menus/ai-agent.js +0 -484
  83. package/src/pages/algo/algo-config.js +0 -195
  84. package/src/pages/algo/algo-multi.js +0 -801
  85. package/src/pages/algo/algo-utils.js +0 -58
  86. package/src/pages/algo/copy-engine.js +0 -449
  87. package/src/pages/algo/custom-strategy.js +0 -459
  88. package/src/pages/algo/logger.js +0 -245
  89. package/src/pages/algo/smart-logs-data.js +0 -218
  90. package/src/pages/algo/smart-logs.js +0 -387
  91. package/src/pages/algo/ui-constants.js +0 -144
  92. package/src/pages/algo/ui-summary.js +0 -184
  93. package/src/pages/stats-calculations.js +0 -191
  94. package/src/pages/stats-ui.js +0 -381
  95. package/src/pages/stats.js +0 -339
  96. package/src/services/ai/client-analysis.js +0 -194
  97. package/src/services/ai/client-models.js +0 -333
  98. package/src/services/ai/client.js +0 -343
  99. package/src/services/ai/index.js +0 -384
  100. package/src/services/ai/oauth-anthropic.js +0 -265
  101. package/src/services/ai/oauth-gemini.js +0 -223
  102. package/src/services/ai/oauth-iflow.js +0 -269
  103. package/src/services/ai/oauth-openai.js +0 -233
  104. package/src/services/ai/oauth-qwen.js +0 -279
  105. package/src/services/ai/providers/direct-providers.js +0 -323
  106. package/src/services/ai/providers/index.js +0 -62
  107. package/src/services/ai/providers/other-providers.js +0 -104
  108. package/src/services/ai/proxy-install.js +0 -249
  109. package/src/services/ai/proxy-manager.js +0 -494
  110. package/src/services/ai/proxy-remote.js +0 -161
  111. package/src/services/ai/strategy-supervisor.js +0 -1312
  112. package/src/services/ai/supervisor-data.js +0 -195
  113. package/src/services/ai/supervisor-optimize.js +0 -215
  114. package/src/services/ai/supervisor-sync.js +0 -178
  115. package/src/services/ai/supervisor-utils.js +0 -158
  116. package/src/services/ai/supervisor.js +0 -484
  117. package/src/services/ai/validation.js +0 -250
  118. package/src/services/hqx-server-events.js +0 -110
  119. package/src/services/hqx-server-handlers.js +0 -217
  120. package/src/services/hqx-server-latency.js +0 -136
  121. package/src/services/hqx-server.js +0 -403
  122. package/src/services/position-constants.js +0 -28
  123. package/src/services/position-exit-logic.js +0 -174
  124. package/src/services/position-manager.js +0 -438
  125. package/src/services/position-momentum.js +0 -206
  126. package/src/services/projectx/accounts.js +0 -142
  127. package/src/services/projectx/index.js +0 -443
  128. package/src/services/projectx/market.js +0 -172
  129. package/src/services/projectx/stats.js +0 -110
  130. package/src/services/projectx/trading.js +0 -180
  131. package/src/services/rithmic/latency-tracker.js +0 -182
  132. package/src/services/rithmic/market-data-decoders.js +0 -229
  133. package/src/services/rithmic/market-data.js +0 -272
  134. package/src/services/rithmic/orders-fast.js +0 -246
  135. package/src/services/rithmic/proto-decoders.js +0 -403
  136. package/src/services/rithmic/specs.js +0 -146
  137. package/src/services/rithmic/trade-history.js +0 -254
  138. package/src/services/session-history.js +0 -475
  139. package/src/services/strategy/hft-signal-calc.js +0 -147
  140. package/src/services/strategy/hft-tick.js +0 -407
  141. package/src/services/strategy/recovery-math.js +0 -402
  142. package/src/services/tradovate/constants.js +0 -109
  143. package/src/services/tradovate/index.js +0 -392
  144. package/src/services/tradovate/market.js +0 -47
  145. package/src/services/tradovate/orders.js +0 -145
  146. package/src/services/tradovate/websocket.js +0 -97
@@ -1,79 +1,92 @@
1
1
  /**
2
- * Rithmic Contracts Module
2
+ * @fileoverview Rithmic contract methods
3
3
  * @module services/rithmic/contracts
4
4
  *
5
- * Contract lookup and front month discovery
5
+ * NO FAKE DATA - Only real values from Rithmic API
6
6
  */
7
7
 
8
8
  const { decodeFrontMonthContract } = require('./protobuf');
9
- const { CME_CONTRACT_SPECS } = require('./specs');
10
9
  const { TIMEOUTS, CACHE } = require('../../config/settings');
11
10
  const { logger } = require('../../utils/logger');
12
11
 
13
- const log = logger.scope('RithmicContracts');
12
+ const log = logger.scope('Rithmic:Contracts');
14
13
 
15
14
  /**
16
- * Decode product codes from raw protobuf buffer
17
- * @param {Buffer} buffer
18
- * @returns {Object}
15
+ * Get all available contracts from Rithmic API
16
+ * @param {RithmicService} service - Service instance
17
+ * @returns {Promise<{success: boolean, contracts: Array, source?: string, error?: string}>}
19
18
  */
20
- function decodeProductCodes(buffer) {
21
- const result = {};
22
- let offset = 0;
19
+ const getContracts = async (service) => {
20
+ // Check cache
21
+ if (service._contractsCache && Date.now() - service._contractsCacheTime < CACHE.CONTRACTS_TTL) {
22
+ return { success: true, contracts: service._contractsCache, source: 'cache' };
23
+ }
23
24
 
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;
25
+ if (!service.credentials) {
26
+ return { success: false, error: 'Not logged in' };
27
+ }
28
+
29
+ try {
30
+ // Connect to TICKER_PLANT if needed
31
+ if (!service.tickerConn) {
32
+ const connected = await service.connectTicker(service.credentials.username, service.credentials.password);
33
+ if (!connected) {
34
+ return { success: false, error: 'Failed to connect to TICKER_PLANT' };
35
+ }
31
36
  }
32
- return [value, off];
33
- };
34
37
 
35
- const readString = (buf, off) => {
36
- const [len, newOff] = readVarint(buf, off);
37
- return [buf.slice(newOff, newOff + len).toString('utf8'), newOff + len];
38
- };
38
+ service.tickerConn.setMaxListeners(5000);
39
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;
40
+ log.debug('Fetching contracts from Rithmic API');
41
+ const contracts = await fetchAllFrontMonths(service);
46
42
 
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; }
43
+ if (!contracts.length) {
44
+ return { success: false, error: 'No tradeable contracts found' };
45
+ }
46
+
47
+ // Cache results
48
+ service._contractsCache = contracts;
49
+ service._contractsCacheTime = Date.now();
50
+
51
+ return { success: true, contracts, source: 'api' };
52
+ } catch (err) {
53
+ log.error('getContracts error', { error: err.message });
54
+ return { success: false, error: err.message };
60
55
  }
56
+ };
61
57
 
62
- return result;
63
- }
58
+ /**
59
+ * Search contracts
60
+ * @param {RithmicService} service - Service instance
61
+ * @param {string} searchText - Search text
62
+ * @returns {Promise<Array>}
63
+ */
64
+ const searchContracts = async (service, searchText) => {
65
+ const result = await getContracts(service);
66
+ if (!searchText || !result.success) return result.contracts || [];
67
+
68
+ const search = searchText.toUpperCase();
69
+ return result.contracts.filter(c =>
70
+ c.symbol.toUpperCase().includes(search) ||
71
+ c.name.toUpperCase().includes(search)
72
+ );
73
+ };
64
74
 
65
75
  /**
66
- * Fetch all front month contracts from TICKER_PLANT
67
- * @param {RithmicService} service
76
+ * Fetch all front month contracts from API
77
+ * @param {RithmicService} service - Service instance
68
78
  * @returns {Promise<Array>}
69
79
  */
70
- async function fetchAllFrontMonths(service) {
71
- if (!service.tickerConn) throw new Error('TICKER_PLANT not connected');
80
+ const fetchAllFrontMonths = (service) => {
81
+ if (!service.tickerConn) {
82
+ throw new Error('TICKER_PLANT not connected');
83
+ }
72
84
 
73
85
  return new Promise((resolve) => {
74
86
  const contracts = new Map();
75
87
  const productsToCheck = new Map();
76
88
 
89
+ // Handler for ProductCodes responses
77
90
  const productHandler = (msg) => {
78
91
  if (msg.templateId !== 112) return;
79
92
 
@@ -96,6 +109,7 @@ async function fetchAllFrontMonths(service) {
96
109
  }
97
110
  };
98
111
 
112
+ // Handler for FrontMonth responses
99
113
  const frontMonthHandler = (msg) => {
100
114
  if (msg.templateId !== 114) return;
101
115
 
@@ -112,11 +126,13 @@ async function fetchAllFrontMonths(service) {
112
126
  service.tickerConn.on('message', productHandler);
113
127
  service.tickerConn.on('message', frontMonthHandler);
114
128
 
129
+ // Request all product codes
115
130
  service.tickerConn.send('RequestProductCodes', {
116
131
  templateId: 111,
117
132
  userMsg: ['get-products'],
118
133
  });
119
134
 
135
+ // After timeout, request front months
120
136
  setTimeout(() => {
121
137
  service.tickerConn.removeListener('message', productHandler);
122
138
  log.debug('Collected products', { count: productsToCheck.size });
@@ -130,6 +146,7 @@ async function fetchAllFrontMonths(service) {
130
146
  });
131
147
  }
132
148
 
149
+ // Collect results after timeout
133
150
  setTimeout(() => {
134
151
  service.tickerConn.removeListener('message', frontMonthHandler);
135
152
 
@@ -137,82 +154,82 @@ async function fetchAllFrontMonths(service) {
137
154
  for (const [baseSymbol, contract] of contracts) {
138
155
  const productKey = `${baseSymbol}:${contract.exchange}`;
139
156
  const product = productsToCheck.get(productKey);
140
- const specs = CME_CONTRACT_SPECS[baseSymbol] || null;
141
- const productName = specs?.name || product?.productName || baseSymbol;
142
-
157
+
158
+ // 100% API data - no static symbol info
143
159
  results.push({
144
160
  symbol: contract.symbol,
145
161
  baseSymbol,
146
- name: contract.symbol,
147
- description: productName,
162
+ name: product?.productName || baseSymbol,
148
163
  exchange: contract.exchange,
149
- tickSize: specs?.tickSize ?? null,
150
- tickValue: specs?.tickValue ?? null,
151
164
  });
152
165
  }
153
166
 
167
+ // Sort alphabetically by base symbol
154
168
  results.sort((a, b) => a.baseSymbol.localeCompare(b.baseSymbol));
169
+
155
170
  log.debug('Got contracts from API', { count: results.length });
156
171
  resolve(results);
157
172
  }, TIMEOUTS.RITHMIC_PRODUCTS);
158
173
  }, TIMEOUTS.RITHMIC_CONTRACTS);
159
174
  });
160
- }
175
+ };
161
176
 
162
177
  /**
163
- * Get contracts with caching
164
- * @param {RithmicService} service
165
- * @returns {Promise<Object>}
178
+ * Decode ProductCodes response
179
+ * @param {Buffer} buffer - Protobuf buffer
180
+ * @returns {Object} Decoded product data
166
181
  */
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' };
182
+ const decodeProductCodes = (buffer) => {
183
+ const result = {};
184
+ let offset = 0;
173
185
 
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' };
186
+ const readVarint = (buf, off) => {
187
+ let value = 0;
188
+ let shift = 0;
189
+ while (off < buf.length) {
190
+ const byte = buf[off++];
191
+ value |= (byte & 0x7F) << shift;
192
+ if (!(byte & 0x80)) break;
193
+ shift += 7;
178
194
  }
195
+ return [value, off];
196
+ };
179
197
 
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' };
198
+ const readString = (buf, off) => {
199
+ const [len, newOff] = readVarint(buf, off);
200
+ return [buf.slice(newOff, newOff + len).toString('utf8'), newOff + len];
201
+ };
185
202
 
186
- service._contractsCache = contracts;
187
- service._contractsCacheTime = Date.now();
203
+ while (offset < buffer.length) {
204
+ try {
205
+ const [tag, tagOff] = readVarint(buffer, offset);
206
+ const wireType = tag & 0x7;
207
+ const fieldNumber = tag >>> 3;
208
+ offset = tagOff;
188
209
 
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 };
210
+ if (wireType === 0) {
211
+ const [, newOff] = readVarint(buffer, offset);
212
+ offset = newOff;
213
+ } else if (wireType === 2) {
214
+ const [val, newOff] = readString(buffer, offset);
215
+ offset = newOff;
216
+ if (fieldNumber === 110101) result.exchange = val;
217
+ if (fieldNumber === 100749) result.productCode = val;
218
+ if (fieldNumber === 100003) result.productName = val;
219
+ } else {
220
+ break;
221
+ }
222
+ } catch {
223
+ break;
224
+ }
193
225
  }
194
- }
195
226
 
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
- }
227
+ return result;
228
+ };
212
229
 
213
230
  module.exports = {
214
- decodeProductCodes,
215
- fetchAllFrontMonths,
216
231
  getContracts,
217
232
  searchContracts,
233
+ fetchAllFrontMonths,
234
+ decodeProductCodes,
218
235
  };
@@ -1,24 +1,14 @@
1
1
  /**
2
2
  * Rithmic Message Handlers
3
3
  * Handles ORDER_PLANT and PNL_PLANT messages
4
- *
5
- * FAST SCALPING: Handles order fill notifications (351) for position tracking
6
- *
7
- * OPTIMIZED FOR LOW LATENCY:
8
- * - Fast path for order notifications (351)
9
- * - Minimal object creation in hot path
10
- * - Template ID check before proto decode
11
- * - Latency tracking for fills
12
4
  */
13
5
 
14
6
  const { proto, decodeAccountPnL, decodeInstrumentPnL } = require('./protobuf');
15
7
  const { RES, STREAM } = require('./constants');
16
- const { performance } = require('perf_hooks');
17
- const { LatencyTracker, FillInfoPool } = require('./latency-tracker');
18
8
 
19
- // Debug mode - use no-op function when disabled for zero overhead
9
+ // Debug mode
20
10
  const DEBUG = process.env.HQX_DEBUG === '1';
21
- const debug = DEBUG ? (...args) => console.log('[Rithmic:Handler]', ...args) : () => {};
11
+ const debug = (...args) => DEBUG && console.log('[Rithmic:Handler]', ...args);
22
12
 
23
13
  /**
24
14
  * Create ORDER_PLANT message handler
@@ -45,40 +35,16 @@ const createOrderHandler = (service) => {
45
35
  case RES.SHOW_ORDERS:
46
36
  handleShowOrdersResponse(service, data);
47
37
  break;
48
- case RES.NEW_ORDER:
49
- debug('Handling NEW_ORDER response (313)');
50
- handleNewOrderResponse(service, data);
51
- break;
52
38
  case STREAM.EXCHANGE_NOTIFICATION:
53
- debug('Handling EXCHANGE_NOTIFICATION (352)');
54
- handleExchangeNotification(service, data);
39
+ service.emit('exchangeNotification', data);
55
40
  break;
56
41
  case STREAM.ORDER_NOTIFICATION:
57
- debug('Handling ORDER_NOTIFICATION (351)');
58
- handleOrderNotification(service, data);
59
- break;
60
- case RES.SHOW_ORDER_HISTORY:
61
- debug('Handling SHOW_ORDER_HISTORY (325)');
62
- handleOrderHistoryResponse(service, data);
42
+ service.emit('orderNotification', data);
63
43
  break;
64
44
  }
65
45
  };
66
46
  };
67
47
 
68
- /**
69
- * Handle order history response (325) - signals end of history snapshot
70
- */
71
- const handleOrderHistoryResponse = (service, data) => {
72
- try {
73
- const res = proto.decode('ResponseShowOrderHistory', data);
74
- debug('Order history complete:', { userMsg: res.userMsg, rpCode: res.rpCode });
75
- service.emit('orderHistoryComplete', { userMsg: res.userMsg, rpCode: res.rpCode });
76
- } catch (e) {
77
- debug('Error decoding order history response:', e.message);
78
- service.emit('orderHistoryComplete', {});
79
- }
80
- };
81
-
82
48
  /**
83
49
  * Create PNL_PLANT message handler
84
50
  * @param {RithmicService} service - The Rithmic service instance
@@ -193,23 +159,17 @@ const handleAccountPnLUpdate = (service, data) => {
193
159
  debug('Decoded Account PNL:', JSON.stringify(pnl));
194
160
 
195
161
  if (pnl.accountId) {
196
- // Store ALL R Trader metrics from ACCOUNT_PNL_UPDATE (451)
197
162
  const pnlData = {
198
- // Core P&L
199
163
  accountBalance: parseFloat(pnl.accountBalance || 0),
164
+ cashOnHand: parseFloat(pnl.cashOnHand || 0),
165
+ marginBalance: parseFloat(pnl.marginBalance || 0),
200
166
  openPositionPnl: parseFloat(pnl.openPositionPnl || 0),
201
167
  closedPositionPnl: parseFloat(pnl.closedPositionPnl || 0),
202
168
  dayPnl: parseFloat(pnl.dayPnl || 0),
203
- // R Trader additional metrics
204
- availableBuyingPower: parseFloat(pnl.availableBuyingPower || 0),
205
- marginBalance: parseFloat(pnl.marginBalance || 0),
206
- cashOnHand: parseFloat(pnl.cashOnHand || 0),
207
- // Net Liquidation = Account Balance + Open P&L (same as R Trader)
208
- netLiquidation: parseFloat(pnl.accountBalance || 0) + parseFloat(pnl.openPositionPnl || 0),
209
169
  };
210
170
  debug('Storing PNL for account:', pnl.accountId, pnlData);
211
171
  service.accountPnL.set(pnl.accountId, pnlData);
212
- service.emit('pnlUpdate', { accountId: pnl.accountId, ...pnlData });
172
+ service.emit('pnlUpdate', pnl);
213
173
  } else {
214
174
  debug('No accountId in PNL response');
215
175
  }
@@ -220,8 +180,6 @@ const handleAccountPnLUpdate = (service, data) => {
220
180
 
221
181
  /**
222
182
  * Handle instrument PnL update (positions)
223
- * INSTRUMENT_PNL_UPDATE (450) - Real-time position and P&L per instrument
224
- * This is the PRIMARY source for unrealized P&L (same as R Trader Positions panel)
225
183
  */
226
184
  const handleInstrumentPnLUpdate = (service, data) => {
227
185
  try {
@@ -230,163 +188,30 @@ const handleInstrumentPnLUpdate = (service, data) => {
230
188
  const key = `${pos.accountId}:${pos.symbol}:${pos.exchange}`;
231
189
  const netQty = pos.netQuantity || pos.openPositionQuantity || ((pos.buyQty || 0) - (pos.sellQty || 0));
232
190
 
233
- // Build position data - ALWAYS emit, even when FLAT (netQty === 0)
234
- // This ensures Open P&L resets to 0 when position closes (like R Trader)
235
- //
236
- // OPEN PNL PRIORITY (real-time unrealized P&L):
237
- // 1. dayOpenPnl (double) - most reliable from INSTRUMENT_PNL_UPDATE
238
- // 2. openPositionPnl (string) - fallback
239
- // 3. Calculate from price if available
240
- const rawOpenPnl = pos.dayOpenPnl ?? pos.openPositionPnl;
241
- const rawClosedPnl = pos.dayClosedPnl ?? pos.closedPositionPnl;
242
-
243
- const positionData = {
244
- accountId: pos.accountId,
245
- symbol: pos.symbol,
246
- exchange: pos.exchange || 'CME',
247
- quantity: netQty,
248
- averagePrice: netQty !== 0 ? (pos.avgOpenFillPrice || 0) : 0,
249
- // Open P&L - parse as float, handle both double and string formats
250
- openPnl: typeof rawOpenPnl === 'number' ? rawOpenPnl : parseFloat(rawOpenPnl || 0),
251
- closedPnl: typeof rawClosedPnl === 'number' ? rawClosedPnl : parseFloat(rawClosedPnl || 0),
252
- dayPnl: typeof pos.dayPnl === 'number' ? pos.dayPnl : parseFloat(pos.dayPnl || 0),
253
- isSnapshot: pos.isSnapshot || false,
254
- };
255
-
256
191
  if (netQty !== 0) {
257
- service.positions.set(key, positionData);
192
+ service.positions.set(key, {
193
+ accountId: pos.accountId,
194
+ symbol: pos.symbol,
195
+ exchange: pos.exchange || 'CME',
196
+ quantity: netQty,
197
+ averagePrice: pos.avgOpenFillPrice || 0,
198
+ openPnl: parseFloat(pos.openPositionPnl || pos.dayOpenPnl || 0),
199
+ closedPnl: parseFloat(pos.closedPositionPnl || pos.dayClosedPnl || 0),
200
+ dayPnl: parseFloat(pos.dayPnl || 0),
201
+ isSnapshot: pos.isSnapshot || false,
202
+ });
258
203
  } else {
259
204
  service.positions.delete(key);
260
205
  }
261
206
 
262
- // ALWAYS emit positionUpdate - even when FLAT
263
- // This ensures UI updates Open P&L to 0 when position closes
264
- service.emit('positionUpdate', positionData);
207
+ service.emit('positionUpdate', service.positions.get(key));
265
208
  }
266
209
  } catch (e) {
267
- debug('Error decoding Instrument PNL:', e.message);
268
- }
269
- };
270
-
271
- /**
272
- * Handle new order response (313) - confirms order accepted
273
- */
274
- const handleNewOrderResponse = (service, data) => {
275
- try {
276
- const res = proto.decode('ResponseNewOrder', data);
277
- const orderTag = res.userMsg?.[0] || null;
278
- const timestamp = performance.now();
279
-
280
- debug('New order response:', {
281
- orderTag,
282
- rpCode: res.rpCode,
283
- basketId: res.basketId,
284
- ssboe: res.ssboe,
285
- usecs: res.usecs,
286
- });
287
-
288
- // Emit for position manager tracking
289
- service.emit('orderAccepted', {
290
- orderTag,
291
- basketId: res.basketId,
292
- rpCode: res.rpCode,
293
- timestamp,
294
- });
295
- } catch (e) {
296
- debug('Error decoding new order response:', e.message);
297
- }
298
- };
299
-
300
- // FillInfoPool imported from ./latency-tracker
301
-
302
- /**
303
- * Handle order notification (351) - CRITICAL for fill tracking
304
- * This is the primary notification for order status changes including FILLS
305
- *
306
- * ULTRA-OPTIMIZED:
307
- * - Pre-allocated fill info object (zero allocation in hot path)
308
- * - Fast path for fill detection
309
- * - High-resolution latency tracking
310
- */
311
- const handleOrderNotification = (service, data) => {
312
- const receiveTime = Date.now();
313
-
314
- try {
315
- const notif = proto.decode('RithmicOrderNotification', data);
316
- // userTag contains our order tag (userMsg is not in this proto)
317
- const orderTag = notif.userTag || null;
318
-
319
- // FAST PATH: Check for fill immediately
320
- // Proto uses total_fill_size (camelCase: totalFillSize), not fillQuantity
321
- const fillQty = notif.totalFillSize || notif.totalFillQuantity || notif.fillQuantity || 0;
322
- const isFill = fillQty > 0 || notif.status === 'complete';
323
-
324
- // Calculate round-trip latency if this is a fill we're tracking
325
- let roundTripLatency = null;
326
- if (isFill && orderTag) {
327
- roundTripLatency = LatencyTracker.recordFill(orderTag);
328
- }
329
-
330
- debug('Order notification:', {
331
- orderTag,
332
- status: notif.status,
333
- filledQty: fillQty,
334
- avgFillPrice: notif.avgFillPrice,
335
- roundTripLatency,
336
- });
337
-
338
- // OPTIMIZED: Use pre-allocated object
339
- const fillInfo = FillInfoPool.fill(notif, receiveTime, roundTripLatency);
340
-
341
- // Emit raw notification
342
- service.emit('orderNotification', fillInfo);
343
-
344
- // Emit fill event if this is a fill (status=complete means order is fully filled)
345
- if (isFill) {
346
- const actualFillQty = fillInfo.totalFillQuantity || fillInfo.fillQuantity || notif.quantity || 0;
347
- const fillPrice = fillInfo.avgFillPrice || fillInfo.lastFillPrice || 0;
348
-
349
- // Debug only - UI handles display via orderFilled event
350
- debug('FILL Received:', orderTag, fillInfo.transactionType === 1 ? 'BUY' : 'SELL', actualFillQty, '@', fillPrice, 'latency:', roundTripLatency, 'ms');
351
-
352
- // Clone for fill event (async handlers may need to keep the data)
353
- service.emit('orderFilled', FillInfoPool.clone(fillInfo));
354
- } else {
355
- // Debug only - UI handles display via orderNotification event
356
- debug('ORDER STATUS:', orderTag, 'status:', fillInfo.status, 'text:', fillInfo.text || 'N/A');
357
- }
358
- } catch (e) {
359
- debug('Error decoding order notification:', e.message);
360
- }
361
- };
362
-
363
- /**
364
- * Handle exchange notification (352) - exchange-level order updates
365
- */
366
- const handleExchangeNotification = (service, data) => {
367
- try {
368
- const notif = proto.decode('ExchangeOrderNotification', data);
369
- const timestamp = performance.now();
370
-
371
- debug('Exchange notification:', {
372
- orderTag: notif.userMsg?.[0],
373
- text: notif.text,
374
- reportType: notif.reportType,
375
- });
376
-
377
- service.emit('exchangeNotification', {
378
- orderTag: notif.userMsg?.[0] || null,
379
- text: notif.text,
380
- reportType: notif.reportType,
381
- timestamp,
382
- });
383
- } catch (e) {
384
- debug('Error decoding exchange notification:', e.message);
210
+ // Ignore decode errors
385
211
  }
386
212
  };
387
213
 
388
214
  module.exports = {
389
215
  createOrderHandler,
390
- createPnLHandler,
391
- LatencyTracker,
216
+ createPnLHandler
392
217
  };