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
|
@@ -1,549 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* =============================================================================
|
|
3
|
-
* Rithmic Market Data Feed
|
|
4
|
-
* =============================================================================
|
|
5
|
-
* Handles real-time quotes and trades streaming via Rithmic WebSocket
|
|
6
|
-
*
|
|
7
|
-
* Based on HQX-TG RithmicMarketData implementation
|
|
8
|
-
*
|
|
9
|
-
* IMPORTANT: Use continuous/front-month symbols for subscriptions:
|
|
10
|
-
* - ES, NQ, MES, MNQ (NOT ESH25, NQH25, etc.)
|
|
11
|
-
* - Rithmic automatically routes to the current front-month contract
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const EventEmitter = require('events');
|
|
15
|
-
const { logger } = require('../../utils/logger');
|
|
16
|
-
|
|
17
|
-
const log = logger.scope('RithmicMD');
|
|
18
|
-
|
|
19
|
-
// Template IDs for Rithmic messages
|
|
20
|
-
const TEMPLATE_IDS = {
|
|
21
|
-
// Request
|
|
22
|
-
MARKET_DATA_UPDATE: 100,
|
|
23
|
-
// Response/Stream
|
|
24
|
-
LAST_TRADE: 150,
|
|
25
|
-
BEST_BID_OFFER: 151,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Rithmic field IDs for LastTrade (from protobuf)
|
|
29
|
-
const LAST_TRADE_FIELDS = {
|
|
30
|
-
TEMPLATE_ID: 154467,
|
|
31
|
-
SYMBOL: 110100,
|
|
32
|
-
EXCHANGE: 110101,
|
|
33
|
-
TRADE_PRICE: 100006,
|
|
34
|
-
TRADE_SIZE: 100178,
|
|
35
|
-
AGGRESSOR: 112003, // 1=BUY, 2=SELL
|
|
36
|
-
SSBOE: 150100,
|
|
37
|
-
USECS: 150101,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Rithmic field IDs for BestBidOffer (from protobuf)
|
|
41
|
-
const BBO_FIELDS = {
|
|
42
|
-
TEMPLATE_ID: 154467,
|
|
43
|
-
SYMBOL: 110100,
|
|
44
|
-
EXCHANGE: 110101,
|
|
45
|
-
BID_PRICE: 100022,
|
|
46
|
-
BID_SIZE: 100030,
|
|
47
|
-
ASK_PRICE: 100025,
|
|
48
|
-
ASK_SIZE: 100031,
|
|
49
|
-
SSBOE: 150100,
|
|
50
|
-
USECS: 150101,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Read a varint from buffer starting at offset
|
|
55
|
-
* Uses BigInt internally to handle large field IDs correctly
|
|
56
|
-
* @param {Buffer} buffer
|
|
57
|
-
* @param {number} offset
|
|
58
|
-
* @returns {[number, number]} [value, newOffset]
|
|
59
|
-
*/
|
|
60
|
-
function readVarint(buffer, offset) {
|
|
61
|
-
let result = BigInt(0);
|
|
62
|
-
let shift = BigInt(0);
|
|
63
|
-
let pos = offset;
|
|
64
|
-
|
|
65
|
-
while (pos < buffer.length) {
|
|
66
|
-
const byte = buffer[pos++];
|
|
67
|
-
result |= BigInt(byte & 0x7f) << shift;
|
|
68
|
-
if ((byte & 0x80) === 0) {
|
|
69
|
-
return [Number(result), pos];
|
|
70
|
-
}
|
|
71
|
-
shift += BigInt(7);
|
|
72
|
-
if (shift > BigInt(63)) {
|
|
73
|
-
throw new Error('Varint too large');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
throw new Error('Incomplete varint');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Read a length-delimited field (string/bytes)
|
|
82
|
-
* @param {Buffer} buffer
|
|
83
|
-
* @param {number} offset
|
|
84
|
-
* @returns {[string, number]} [value, newOffset]
|
|
85
|
-
*/
|
|
86
|
-
function readLengthDelimited(buffer, offset) {
|
|
87
|
-
const [length, newOffset] = readVarint(buffer, offset);
|
|
88
|
-
const value = buffer.slice(newOffset, newOffset + length).toString('utf8');
|
|
89
|
-
return [value, newOffset + length];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Skip a field based on wire type
|
|
94
|
-
* @param {Buffer} buffer
|
|
95
|
-
* @param {number} offset
|
|
96
|
-
* @param {number} wireType
|
|
97
|
-
* @returns {number} newOffset
|
|
98
|
-
*/
|
|
99
|
-
function skipField(buffer, offset, wireType) {
|
|
100
|
-
switch (wireType) {
|
|
101
|
-
case 0: // Varint
|
|
102
|
-
const [, newOffset] = readVarint(buffer, offset);
|
|
103
|
-
return newOffset;
|
|
104
|
-
case 1: // 64-bit
|
|
105
|
-
return offset + 8;
|
|
106
|
-
case 2: // Length-delimited
|
|
107
|
-
const [length, lenOffset] = readVarint(buffer, offset);
|
|
108
|
-
return lenOffset + length;
|
|
109
|
-
case 5: // 32-bit
|
|
110
|
-
return offset + 4;
|
|
111
|
-
default:
|
|
112
|
-
throw new Error(`Unknown wire type: ${wireType}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Manually decode LastTrade message from Rithmic
|
|
118
|
-
* Required because protobufjs can't handle field IDs > 100000
|
|
119
|
-
* @param {Buffer} buffer
|
|
120
|
-
* @returns {Object}
|
|
121
|
-
*/
|
|
122
|
-
function decodeLastTrade(buffer) {
|
|
123
|
-
const result = {};
|
|
124
|
-
let offset = 0;
|
|
125
|
-
|
|
126
|
-
while (offset < buffer.length) {
|
|
127
|
-
try {
|
|
128
|
-
const [tag, newOffset] = readVarint(buffer, offset);
|
|
129
|
-
const fieldNumber = tag >>> 3;
|
|
130
|
-
const wireType = tag & 0x7;
|
|
131
|
-
offset = newOffset;
|
|
132
|
-
|
|
133
|
-
switch (fieldNumber) {
|
|
134
|
-
case LAST_TRADE_FIELDS.SYMBOL:
|
|
135
|
-
if (wireType === 2) {
|
|
136
|
-
const [val, next] = readLengthDelimited(buffer, offset);
|
|
137
|
-
result.symbol = val;
|
|
138
|
-
offset = next;
|
|
139
|
-
} else {
|
|
140
|
-
offset = skipField(buffer, offset, wireType);
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
case LAST_TRADE_FIELDS.EXCHANGE:
|
|
144
|
-
if (wireType === 2) {
|
|
145
|
-
const [val, next] = readLengthDelimited(buffer, offset);
|
|
146
|
-
result.exchange = val;
|
|
147
|
-
offset = next;
|
|
148
|
-
} else {
|
|
149
|
-
offset = skipField(buffer, offset, wireType);
|
|
150
|
-
}
|
|
151
|
-
break;
|
|
152
|
-
case LAST_TRADE_FIELDS.TRADE_PRICE:
|
|
153
|
-
if (wireType === 1) {
|
|
154
|
-
result.tradePrice = buffer.readDoubleLE(offset);
|
|
155
|
-
offset += 8;
|
|
156
|
-
} else {
|
|
157
|
-
offset = skipField(buffer, offset, wireType);
|
|
158
|
-
}
|
|
159
|
-
break;
|
|
160
|
-
case LAST_TRADE_FIELDS.TRADE_SIZE:
|
|
161
|
-
if (wireType === 0) {
|
|
162
|
-
const [val, next] = readVarint(buffer, offset);
|
|
163
|
-
result.tradeSize = val;
|
|
164
|
-
offset = next;
|
|
165
|
-
} else {
|
|
166
|
-
offset = skipField(buffer, offset, wireType);
|
|
167
|
-
}
|
|
168
|
-
break;
|
|
169
|
-
case LAST_TRADE_FIELDS.AGGRESSOR:
|
|
170
|
-
if (wireType === 0) {
|
|
171
|
-
const [val, next] = readVarint(buffer, offset);
|
|
172
|
-
result.aggressor = val;
|
|
173
|
-
offset = next;
|
|
174
|
-
} else {
|
|
175
|
-
offset = skipField(buffer, offset, wireType);
|
|
176
|
-
}
|
|
177
|
-
break;
|
|
178
|
-
case LAST_TRADE_FIELDS.SSBOE:
|
|
179
|
-
if (wireType === 0) {
|
|
180
|
-
const [val, next] = readVarint(buffer, offset);
|
|
181
|
-
result.ssboe = val;
|
|
182
|
-
offset = next;
|
|
183
|
-
} else {
|
|
184
|
-
offset = skipField(buffer, offset, wireType);
|
|
185
|
-
}
|
|
186
|
-
break;
|
|
187
|
-
case LAST_TRADE_FIELDS.USECS:
|
|
188
|
-
if (wireType === 0) {
|
|
189
|
-
const [val, next] = readVarint(buffer, offset);
|
|
190
|
-
result.usecs = val;
|
|
191
|
-
offset = next;
|
|
192
|
-
} else {
|
|
193
|
-
offset = skipField(buffer, offset, wireType);
|
|
194
|
-
}
|
|
195
|
-
break;
|
|
196
|
-
default:
|
|
197
|
-
offset = skipField(buffer, offset, wireType);
|
|
198
|
-
}
|
|
199
|
-
} catch {
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return result;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Manually decode BestBidOffer message from Rithmic
|
|
209
|
-
* Required because protobufjs can't handle field IDs > 100000
|
|
210
|
-
* @param {Buffer} buffer
|
|
211
|
-
* @returns {Object}
|
|
212
|
-
*/
|
|
213
|
-
function decodeBestBidOffer(buffer) {
|
|
214
|
-
const result = {};
|
|
215
|
-
let offset = 0;
|
|
216
|
-
|
|
217
|
-
while (offset < buffer.length) {
|
|
218
|
-
try {
|
|
219
|
-
const [tag, newOffset] = readVarint(buffer, offset);
|
|
220
|
-
const fieldNumber = tag >>> 3;
|
|
221
|
-
const wireType = tag & 0x7;
|
|
222
|
-
offset = newOffset;
|
|
223
|
-
|
|
224
|
-
switch (fieldNumber) {
|
|
225
|
-
case BBO_FIELDS.SYMBOL:
|
|
226
|
-
if (wireType === 2) {
|
|
227
|
-
const [val, next] = readLengthDelimited(buffer, offset);
|
|
228
|
-
result.symbol = val;
|
|
229
|
-
offset = next;
|
|
230
|
-
} else {
|
|
231
|
-
offset = skipField(buffer, offset, wireType);
|
|
232
|
-
}
|
|
233
|
-
break;
|
|
234
|
-
case BBO_FIELDS.EXCHANGE:
|
|
235
|
-
if (wireType === 2) {
|
|
236
|
-
const [val, next] = readLengthDelimited(buffer, offset);
|
|
237
|
-
result.exchange = val;
|
|
238
|
-
offset = next;
|
|
239
|
-
} else {
|
|
240
|
-
offset = skipField(buffer, offset, wireType);
|
|
241
|
-
}
|
|
242
|
-
break;
|
|
243
|
-
case BBO_FIELDS.BID_PRICE:
|
|
244
|
-
if (wireType === 1) {
|
|
245
|
-
result.bidPrice = buffer.readDoubleLE(offset);
|
|
246
|
-
offset += 8;
|
|
247
|
-
} else {
|
|
248
|
-
offset = skipField(buffer, offset, wireType);
|
|
249
|
-
}
|
|
250
|
-
break;
|
|
251
|
-
case BBO_FIELDS.BID_SIZE:
|
|
252
|
-
if (wireType === 0) {
|
|
253
|
-
const [val, next] = readVarint(buffer, offset);
|
|
254
|
-
result.bidSize = val;
|
|
255
|
-
offset = next;
|
|
256
|
-
} else {
|
|
257
|
-
offset = skipField(buffer, offset, wireType);
|
|
258
|
-
}
|
|
259
|
-
break;
|
|
260
|
-
case BBO_FIELDS.ASK_PRICE:
|
|
261
|
-
if (wireType === 1) {
|
|
262
|
-
result.askPrice = buffer.readDoubleLE(offset);
|
|
263
|
-
offset += 8;
|
|
264
|
-
} else {
|
|
265
|
-
offset = skipField(buffer, offset, wireType);
|
|
266
|
-
}
|
|
267
|
-
break;
|
|
268
|
-
case BBO_FIELDS.ASK_SIZE:
|
|
269
|
-
if (wireType === 0) {
|
|
270
|
-
const [val, next] = readVarint(buffer, offset);
|
|
271
|
-
result.askSize = val;
|
|
272
|
-
offset = next;
|
|
273
|
-
} else {
|
|
274
|
-
offset = skipField(buffer, offset, wireType);
|
|
275
|
-
}
|
|
276
|
-
break;
|
|
277
|
-
case BBO_FIELDS.SSBOE:
|
|
278
|
-
if (wireType === 0) {
|
|
279
|
-
const [val, next] = readVarint(buffer, offset);
|
|
280
|
-
result.ssboe = val;
|
|
281
|
-
offset = next;
|
|
282
|
-
} else {
|
|
283
|
-
offset = skipField(buffer, offset, wireType);
|
|
284
|
-
}
|
|
285
|
-
break;
|
|
286
|
-
case BBO_FIELDS.USECS:
|
|
287
|
-
if (wireType === 0) {
|
|
288
|
-
const [val, next] = readVarint(buffer, offset);
|
|
289
|
-
result.usecs = val;
|
|
290
|
-
offset = next;
|
|
291
|
-
} else {
|
|
292
|
-
offset = skipField(buffer, offset, wireType);
|
|
293
|
-
}
|
|
294
|
-
break;
|
|
295
|
-
default:
|
|
296
|
-
offset = skipField(buffer, offset, wireType);
|
|
297
|
-
}
|
|
298
|
-
} catch {
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return result;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Rithmic Market Data Feed
|
|
308
|
-
* Provides real-time market data via Rithmic WebSocket connection
|
|
309
|
-
*/
|
|
310
|
-
class RithmicMarketDataFeed extends EventEmitter {
|
|
311
|
-
constructor(rithmicService) {
|
|
312
|
-
super();
|
|
313
|
-
this.service = rithmicService;
|
|
314
|
-
this.subscriptions = new Set();
|
|
315
|
-
this.connected = false;
|
|
316
|
-
this.messageHandler = null;
|
|
317
|
-
|
|
318
|
-
// Stats for debugging
|
|
319
|
-
this.tradeCount = 0;
|
|
320
|
-
this.quoteCount = 0;
|
|
321
|
-
this.lastLogTime = 0;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Check if connected
|
|
326
|
-
* @returns {boolean}
|
|
327
|
-
*/
|
|
328
|
-
isConnected() {
|
|
329
|
-
return this.connected && this.service?.tickerConn?.isConnected;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Connect to market data (uses existing tickerConn from RithmicService)
|
|
334
|
-
* Will attempt to reconnect if tickerConn is not connected
|
|
335
|
-
* @returns {Promise<boolean>}
|
|
336
|
-
*/
|
|
337
|
-
async connect() {
|
|
338
|
-
if (!this.service) {
|
|
339
|
-
throw new Error('RithmicService not available');
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Check if ticker connection is ready, reconnect if needed
|
|
343
|
-
if (!this.service.tickerConn?.isConnected) {
|
|
344
|
-
log.info('Ticker connection not ready, attempting to reconnect...');
|
|
345
|
-
|
|
346
|
-
// Try to reconnect using stored credentials
|
|
347
|
-
if (this.service.credentials) {
|
|
348
|
-
try {
|
|
349
|
-
const connected = await this.service.connectTicker(
|
|
350
|
-
this.service.credentials.username,
|
|
351
|
-
this.service.credentials.password
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
if (!connected || !this.service.tickerConn?.isConnected) {
|
|
355
|
-
throw new Error('Failed to reconnect to TICKER_PLANT');
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
log.info('Ticker connection re-established');
|
|
359
|
-
} catch (err) {
|
|
360
|
-
throw new Error(`Ticker reconnection failed: ${err.message}`);
|
|
361
|
-
}
|
|
362
|
-
} else {
|
|
363
|
-
throw new Error('Ticker connection not established and no credentials available');
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Setup message handler
|
|
368
|
-
this.messageHandler = ({ templateId, data }) => {
|
|
369
|
-
if (templateId === TEMPLATE_IDS.LAST_TRADE) {
|
|
370
|
-
this._handleTrade(data);
|
|
371
|
-
} else if (templateId === TEMPLATE_IDS.BEST_BID_OFFER) {
|
|
372
|
-
this._handleQuote(data);
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
this.service.tickerConn.on('message', this.messageHandler);
|
|
377
|
-
this.connected = true;
|
|
378
|
-
|
|
379
|
-
log.info('Market data feed connected');
|
|
380
|
-
this.emit('connected');
|
|
381
|
-
|
|
382
|
-
return true;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Disconnect from market data
|
|
387
|
-
*/
|
|
388
|
-
disconnect() {
|
|
389
|
-
if (this.messageHandler && this.service?.tickerConn) {
|
|
390
|
-
this.service.tickerConn.removeListener('message', this.messageHandler);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Unsubscribe all
|
|
394
|
-
for (const key of this.subscriptions) {
|
|
395
|
-
const [exchange, symbol] = key.split(':');
|
|
396
|
-
this._sendUnsubscribe(exchange, symbol);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
this.subscriptions.clear();
|
|
400
|
-
this.connected = false;
|
|
401
|
-
this.messageHandler = null;
|
|
402
|
-
|
|
403
|
-
log.info('Market data feed disconnected');
|
|
404
|
-
this.emit('disconnected');
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Subscribe to market data for a symbol
|
|
409
|
-
* @param {string} symbol - Trading symbol (e.g., ESH6, NQH6)
|
|
410
|
-
* @param {string} [exchange='CME'] - Exchange
|
|
411
|
-
*/
|
|
412
|
-
subscribe(symbol, exchange = 'CME') {
|
|
413
|
-
const key = `${exchange}:${symbol}`;
|
|
414
|
-
if (this.subscriptions.has(key)) return;
|
|
415
|
-
|
|
416
|
-
if (!this.service?.tickerConn?.isConnected) {
|
|
417
|
-
log.warn('Cannot subscribe - ticker connection not ready');
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Extract base symbol for subscription (ES from ESH6)
|
|
422
|
-
// Rithmic uses continuous symbols for subscriptions
|
|
423
|
-
const baseSymbol = symbol.replace(/[A-Z]\d+$/, '');
|
|
424
|
-
|
|
425
|
-
this.service.tickerConn.send('RequestMarketDataUpdate', {
|
|
426
|
-
templateId: TEMPLATE_IDS.MARKET_DATA_UPDATE,
|
|
427
|
-
userMsg: ['HQX-CLI'],
|
|
428
|
-
symbol: baseSymbol,
|
|
429
|
-
exchange,
|
|
430
|
-
request: 1, // SUBSCRIBE
|
|
431
|
-
updateBits: 3, // LAST_TRADE | BBO
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
this.subscriptions.add(key);
|
|
435
|
-
log.info(`Subscribed to ${key} (base: ${baseSymbol})`);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Unsubscribe from market data for a symbol
|
|
440
|
-
* @param {string} symbol
|
|
441
|
-
* @param {string} [exchange='CME']
|
|
442
|
-
*/
|
|
443
|
-
unsubscribe(symbol, exchange = 'CME') {
|
|
444
|
-
const key = `${exchange}:${symbol}`;
|
|
445
|
-
if (!this.subscriptions.has(key)) return;
|
|
446
|
-
|
|
447
|
-
this._sendUnsubscribe(exchange, symbol);
|
|
448
|
-
this.subscriptions.delete(key);
|
|
449
|
-
log.info(`Unsubscribed from ${key}`);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Send unsubscribe request
|
|
454
|
-
* @private
|
|
455
|
-
*/
|
|
456
|
-
_sendUnsubscribe(exchange, symbol) {
|
|
457
|
-
if (!this.service?.tickerConn?.isConnected) return;
|
|
458
|
-
|
|
459
|
-
const baseSymbol = symbol.replace(/[A-Z]\d+$/, '');
|
|
460
|
-
|
|
461
|
-
this.service.tickerConn.send('RequestMarketDataUpdate', {
|
|
462
|
-
templateId: TEMPLATE_IDS.MARKET_DATA_UPDATE,
|
|
463
|
-
userMsg: ['HQX-CLI'],
|
|
464
|
-
symbol: baseSymbol,
|
|
465
|
-
exchange,
|
|
466
|
-
request: 2, // UNSUBSCRIBE
|
|
467
|
-
updateBits: 3,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Handle incoming trade data
|
|
473
|
-
* @private
|
|
474
|
-
*/
|
|
475
|
-
_handleTrade(data) {
|
|
476
|
-
const t = decodeLastTrade(data);
|
|
477
|
-
|
|
478
|
-
if (!t.symbol || t.tradePrice === undefined || t.tradeSize === undefined) {
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
this.tradeCount++;
|
|
483
|
-
|
|
484
|
-
// Log periodically
|
|
485
|
-
const now = Date.now();
|
|
486
|
-
if (now - this.lastLogTime > 30000) {
|
|
487
|
-
this.lastLogTime = now;
|
|
488
|
-
log.debug(`Stats: ${this.tradeCount} trades, ${this.quoteCount} quotes`);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// First trade and every 500th
|
|
492
|
-
if (this.tradeCount === 1 || this.tradeCount % 500 === 0) {
|
|
493
|
-
log.info(`Trade #${this.tradeCount}: ${t.symbol} ${t.tradeSize}@${t.tradePrice}`);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const trade = {
|
|
497
|
-
symbol: t.symbol,
|
|
498
|
-
exchange: t.exchange || 'CME',
|
|
499
|
-
price: t.tradePrice,
|
|
500
|
-
lastPrice: t.tradePrice,
|
|
501
|
-
size: t.tradeSize,
|
|
502
|
-
volume: t.tradeSize,
|
|
503
|
-
side: t.aggressor === 1 ? 'BUY' : 'SELL',
|
|
504
|
-
lastTradeSide: t.aggressor === 1 ? 'BUY' : 'SELL',
|
|
505
|
-
timestamp: (t.ssboe || 0) * 1000 + Math.floor((t.usecs || 0) / 1000),
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
this.emit('tick', trade);
|
|
509
|
-
this.emit('trade', trade);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Handle incoming quote data
|
|
514
|
-
* @private
|
|
515
|
-
*/
|
|
516
|
-
_handleQuote(data) {
|
|
517
|
-
const q = decodeBestBidOffer(data);
|
|
518
|
-
|
|
519
|
-
if (!q.symbol) return;
|
|
520
|
-
|
|
521
|
-
this.quoteCount++;
|
|
522
|
-
|
|
523
|
-
const quote = {
|
|
524
|
-
symbol: q.symbol,
|
|
525
|
-
exchange: q.exchange || 'CME',
|
|
526
|
-
bid: q.bidPrice || 0,
|
|
527
|
-
bidPrice: q.bidPrice || 0,
|
|
528
|
-
bidSize: q.bidSize || 0,
|
|
529
|
-
ask: q.askPrice || 0,
|
|
530
|
-
askPrice: q.askPrice || 0,
|
|
531
|
-
askSize: q.askSize || 0,
|
|
532
|
-
timestamp: (q.ssboe || 0) * 1000 + Math.floor((q.usecs || 0) / 1000),
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
// Emit as tick with mid price
|
|
536
|
-
if (quote.bid && quote.ask) {
|
|
537
|
-
const tick = {
|
|
538
|
-
...quote,
|
|
539
|
-
price: (quote.bid + quote.ask) / 2,
|
|
540
|
-
lastPrice: (quote.bid + quote.ask) / 2,
|
|
541
|
-
};
|
|
542
|
-
this.emit('tick', tick);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
this.emit('quote', quote);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
module.exports = { RithmicMarketDataFeed };
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Rithmic Specifications and Configurations
|
|
3
|
-
* CME Contract Specifications and PropFirm configurations
|
|
4
|
-
*
|
|
5
|
-
* NO FAKE DATA - These are official exchange constants
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS } = require('./constants');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* CME Contract Specifications - Official exchange tick sizes, values, and names
|
|
12
|
-
* These are technical constants defined by the exchange, not market data.
|
|
13
|
-
* Source: CME Group contract specifications
|
|
14
|
-
*/
|
|
15
|
-
const CME_CONTRACT_SPECS = {
|
|
16
|
-
// E-mini Index Futures (CME)
|
|
17
|
-
ES: { tickSize: 0.25, tickValue: 12.50, name: 'E-mini S&P 500' },
|
|
18
|
-
NQ: { tickSize: 0.25, tickValue: 5.00, name: 'E-mini NASDAQ-100' },
|
|
19
|
-
RTY: { tickSize: 0.10, tickValue: 5.00, name: 'E-mini Russell 2000' },
|
|
20
|
-
YM: { tickSize: 1.00, tickValue: 5.00, name: 'E-mini Dow' },
|
|
21
|
-
|
|
22
|
-
// Micro Index Futures (CME)
|
|
23
|
-
MES: { tickSize: 0.25, tickValue: 1.25, name: 'Micro E-mini S&P 500' },
|
|
24
|
-
MNQ: { tickSize: 0.25, tickValue: 0.50, name: 'Micro E-mini NASDAQ-100' },
|
|
25
|
-
M2K: { tickSize: 0.10, tickValue: 0.50, name: 'Micro E-mini Russell 2000' },
|
|
26
|
-
MYM: { tickSize: 1.00, tickValue: 0.50, name: 'Micro E-mini Dow' },
|
|
27
|
-
|
|
28
|
-
// Energy Futures (NYMEX)
|
|
29
|
-
CL: { tickSize: 0.01, tickValue: 10.00, name: 'Crude Oil' },
|
|
30
|
-
QM: { tickSize: 0.025, tickValue: 12.50, name: 'E-mini Crude Oil' },
|
|
31
|
-
MCL: { tickSize: 0.01, tickValue: 1.00, name: 'Micro Crude Oil' },
|
|
32
|
-
NG: { tickSize: 0.001, tickValue: 10.00, name: 'Natural Gas' },
|
|
33
|
-
QG: { tickSize: 0.005, tickValue: 12.50, name: 'E-mini Natural Gas' },
|
|
34
|
-
|
|
35
|
-
// Metal Futures (COMEX)
|
|
36
|
-
GC: { tickSize: 0.10, tickValue: 10.00, name: 'Gold' },
|
|
37
|
-
MGC: { tickSize: 0.10, tickValue: 1.00, name: 'Micro Gold' },
|
|
38
|
-
SI: { tickSize: 0.005, tickValue: 25.00, name: 'Silver' },
|
|
39
|
-
SIL: { tickSize: 0.005, tickValue: 2.50, name: '1000oz Silver' },
|
|
40
|
-
HG: { tickSize: 0.0005, tickValue: 12.50, name: 'Copper' },
|
|
41
|
-
MHG: { tickSize: 0.0005, tickValue: 1.25, name: 'Micro Copper' },
|
|
42
|
-
|
|
43
|
-
// Treasury Futures (CBOT)
|
|
44
|
-
ZB: { tickSize: 0.03125, tickValue: 31.25, name: '30-Year T-Bond' },
|
|
45
|
-
ZN: { tickSize: 0.015625, tickValue: 15.625, name: '10-Year T-Note' },
|
|
46
|
-
ZF: { tickSize: 0.0078125, tickValue: 7.8125, name: '5-Year T-Note' },
|
|
47
|
-
ZT: { tickSize: 0.0078125, tickValue: 15.625, name: '2-Year T-Note' },
|
|
48
|
-
|
|
49
|
-
// Agricultural Futures (CBOT)
|
|
50
|
-
ZC: { tickSize: 0.25, tickValue: 12.50, name: 'Corn' },
|
|
51
|
-
ZS: { tickSize: 0.25, tickValue: 12.50, name: 'Soybeans' },
|
|
52
|
-
ZW: { tickSize: 0.25, tickValue: 12.50, name: 'Wheat' },
|
|
53
|
-
ZL: { tickSize: 0.01, tickValue: 6.00, name: 'Soybean Oil' },
|
|
54
|
-
ZM: { tickSize: 0.10, tickValue: 10.00, name: 'Soybean Meal' },
|
|
55
|
-
|
|
56
|
-
// Currency Futures (CME)
|
|
57
|
-
'6E': { tickSize: 0.00005, tickValue: 6.25, name: 'Euro FX' },
|
|
58
|
-
'6J': { tickSize: 0.0000005, tickValue: 6.25, name: 'Japanese Yen' },
|
|
59
|
-
'6B': { tickSize: 0.0001, tickValue: 6.25, name: 'British Pound' },
|
|
60
|
-
'6A': { tickSize: 0.0001, tickValue: 10.00, name: 'Australian Dollar' },
|
|
61
|
-
'6C': { tickSize: 0.00005, tickValue: 5.00, name: 'Canadian Dollar' },
|
|
62
|
-
'6M': { tickSize: 0.0001, tickValue: 5.00, name: 'Mexican Peso' },
|
|
63
|
-
|
|
64
|
-
// Nikkei (CME)
|
|
65
|
-
NKD: { tickSize: 5.0, tickValue: 25.00, name: 'Nikkei 225' },
|
|
66
|
-
|
|
67
|
-
// VIX Futures (CFE)
|
|
68
|
-
VX: { tickSize: 0.05, tickValue: 50.00, name: 'VIX Futures' },
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* PropFirm configurations
|
|
73
|
-
*/
|
|
74
|
-
const PROPFIRM_CONFIGS = {
|
|
75
|
-
apex: { name: 'Apex Trader Funding', systemName: 'Apex', gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
76
|
-
apex_rithmic: { name: 'Apex Trader Funding', systemName: 'Apex', gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
77
|
-
topstep_r: { name: 'Topstep (Rithmic)', systemName: RITHMIC_SYSTEMS.TOPSTEP, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
78
|
-
bulenox_r: { name: 'Bulenox (Rithmic)', systemName: RITHMIC_SYSTEMS.BULENOX, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
79
|
-
earn2trade: { name: 'Earn2Trade', systemName: RITHMIC_SYSTEMS.EARN_2_TRADE, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
80
|
-
mescapital: { name: 'MES Capital', systemName: RITHMIC_SYSTEMS.MES_CAPITAL, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
81
|
-
tradefundrr: { name: 'TradeFundrr', systemName: RITHMIC_SYSTEMS.TRADEFUNDRR, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
82
|
-
thetradingpit: { name: 'The Trading Pit', systemName: RITHMIC_SYSTEMS.THE_TRADING_PIT, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
83
|
-
fundedfutures: { name: 'Funded Futures Network', systemName: RITHMIC_SYSTEMS.FUNDED_FUTURES_NETWORK, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
84
|
-
propshop: { name: 'PropShop Trader', systemName: RITHMIC_SYSTEMS.PROPSHOP_TRADER, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
85
|
-
'4proptrader': { name: '4PropTrader', systemName: RITHMIC_SYSTEMS.FOUR_PROP_TRADER, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
86
|
-
daytraders: { name: 'DayTraders.com', systemName: RITHMIC_SYSTEMS.DAY_TRADERS, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
87
|
-
'10xfutures': { name: '10X Futures', systemName: RITHMIC_SYSTEMS.TEN_X_FUTURES, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
88
|
-
lucidtrading: { name: 'Lucid Trading', systemName: RITHMIC_SYSTEMS.LUCID_TRADING, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
89
|
-
thrivetrading: { name: 'Thrive Trading', systemName: RITHMIC_SYSTEMS.THRIVE_TRADING, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
90
|
-
legendstrading: { name: 'Legends Trading', systemName: RITHMIC_SYSTEMS.LEGENDS_TRADING, gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Get tick multiplier for P&L calculation
|
|
95
|
-
* @param {string} symbol - Trading symbol
|
|
96
|
-
* @returns {number} Multiplier for P&L calculation
|
|
97
|
-
*/
|
|
98
|
-
const getTickMultiplier = (symbol) => {
|
|
99
|
-
const sym = (symbol || '').toUpperCase();
|
|
100
|
-
if (sym.startsWith('ES')) return 50; // E-mini S&P 500: $50 per point
|
|
101
|
-
if (sym.startsWith('NQ')) return 20; // E-mini Nasdaq: $20 per point
|
|
102
|
-
if (sym.startsWith('YM')) return 5; // E-mini Dow: $5 per point
|
|
103
|
-
if (sym.startsWith('RTY')) return 50; // E-mini Russell: $50 per point
|
|
104
|
-
if (sym.startsWith('MES')) return 5; // Micro E-mini S&P: $5 per point
|
|
105
|
-
if (sym.startsWith('MNQ')) return 2; // Micro E-mini Nasdaq: $2 per point
|
|
106
|
-
if (sym.startsWith('GC')) return 100; // Gold: $100 per point
|
|
107
|
-
if (sym.startsWith('SI')) return 5000; // Silver: $5000 per point
|
|
108
|
-
if (sym.startsWith('CL')) return 1000; // Crude Oil: $1000 per point
|
|
109
|
-
if (sym.startsWith('NG')) return 10000; // Natural Gas: $10000 per point
|
|
110
|
-
if (sym.startsWith('ZB') || sym.startsWith('ZN')) return 1000; // Bonds
|
|
111
|
-
if (sym.startsWith('6E')) return 125000; // Euro FX
|
|
112
|
-
if (sym.startsWith('6J')) return 12500000; // Japanese Yen
|
|
113
|
-
return 1; // Default
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Check market hours
|
|
118
|
-
* @returns {{isOpen: boolean, message: string}}
|
|
119
|
-
*/
|
|
120
|
-
const checkMarketHours = () => {
|
|
121
|
-
const now = new Date();
|
|
122
|
-
const utcDay = now.getUTCDay();
|
|
123
|
-
const utcHour = now.getUTCHours();
|
|
124
|
-
|
|
125
|
-
const isDST = now.getTimezoneOffset() < Math.max(
|
|
126
|
-
new Date(now.getFullYear(), 0, 1).getTimezoneOffset(),
|
|
127
|
-
new Date(now.getFullYear(), 6, 1).getTimezoneOffset()
|
|
128
|
-
);
|
|
129
|
-
const ctOffset = isDST ? 5 : 6;
|
|
130
|
-
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
131
|
-
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
132
|
-
|
|
133
|
-
if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
|
|
134
|
-
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
135
|
-
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
136
|
-
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance (4:00-5:00 PM CT)' };
|
|
137
|
-
|
|
138
|
-
return { isOpen: true, message: 'Market is open' };
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
module.exports = {
|
|
142
|
-
CME_CONTRACT_SPECS,
|
|
143
|
-
PROPFIRM_CONFIGS,
|
|
144
|
-
getTickMultiplier,
|
|
145
|
-
checkMarketHours,
|
|
146
|
-
};
|