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.
- 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 +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- 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/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- 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-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- 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-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- 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-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -1,79 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Rithmic
|
|
2
|
+
* @fileoverview Rithmic contract methods
|
|
3
3
|
* @module services/rithmic/contracts
|
|
4
4
|
*
|
|
5
|
-
*
|
|
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('
|
|
12
|
+
const log = logger.scope('Rithmic:Contracts');
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
|
-
*
|
|
17
|
-
* @param {
|
|
18
|
-
* @returns {
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
71
|
-
if (!service.tickerConn)
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
|
|
158
|
+
// 100% API data - no static symbol info
|
|
143
159
|
results.push({
|
|
144
160
|
symbol: contract.symbol,
|
|
145
161
|
baseSymbol,
|
|
146
|
-
name:
|
|
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
|
-
*
|
|
164
|
-
* @param {
|
|
165
|
-
* @returns {
|
|
178
|
+
* Decode ProductCodes response
|
|
179
|
+
* @param {Buffer} buffer - Protobuf buffer
|
|
180
|
+
* @returns {Object} Decoded product data
|
|
166
181
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
|
9
|
+
// Debug mode
|
|
20
10
|
const DEBUG = process.env.HQX_DEBUG === '1';
|
|
21
|
-
const debug =
|
|
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
|
-
|
|
54
|
-
handleExchangeNotification(service, data);
|
|
39
|
+
service.emit('exchangeNotification', data);
|
|
55
40
|
break;
|
|
56
41
|
case STREAM.ORDER_NOTIFICATION:
|
|
57
|
-
|
|
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',
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|