hedgequantx 2.7.15 → 2.7.17
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/package.json +1 -1
- package/src/app.js +10 -10
- package/src/lib/data.js +245 -471
- package/src/lib/m/s1-models.js +173 -0
- package/src/lib/m/s1.js +354 -735
- package/src/menus/dashboard.js +8 -5
- package/src/lib/api.js +0 -198
- package/src/lib/api2.js +0 -353
- package/src/lib/core.js +0 -539
- package/src/lib/core2.js +0 -341
- package/src/lib/data2.js +0 -492
- package/src/lib/decoder.js +0 -599
- package/src/lib/m/s2.js +0 -34
- package/src/lib/n/r1.js +0 -454
- package/src/lib/n/r2.js +0 -514
- package/src/lib/n/r3.js +0 -631
- package/src/lib/n/r4.js +0 -401
- package/src/lib/n/r5.js +0 -335
- package/src/lib/n/r6.js +0 -425
- package/src/lib/n/r7.js +0 -530
- package/src/lib/o/l1.js +0 -44
- package/src/lib/o/l2.js +0 -427
- package/src/lib/python-bridge.js +0 -206
package/src/lib/n/r4.js
DELETED
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rithmic Market Data Feed
|
|
3
|
-
* Handles real-time quotes and trades from TICKER_PLANT
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const EventEmitter = require('events');
|
|
7
|
-
const { RithmicConnection } = require('./r2');
|
|
8
|
-
const { TEMPLATE_IDS, INFRA_TYPES, getCurrentFrontMonth } = require('./r7');
|
|
9
|
-
|
|
10
|
-
class RithmicMarketData extends EventEmitter {
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
super();
|
|
13
|
-
|
|
14
|
-
this.connection = null;
|
|
15
|
-
this.config = null;
|
|
16
|
-
this.subscriptions = new Map(); // symbol -> { exchange, active }
|
|
17
|
-
|
|
18
|
-
// Current market data
|
|
19
|
-
this.quotes = new Map(); // symbol -> { bid, ask, bidSize, askSize, timestamp }
|
|
20
|
-
this.trades = new Map(); // symbol -> last trade
|
|
21
|
-
this.lastPrices = new Map(); // symbol -> last price
|
|
22
|
-
|
|
23
|
-
// Stats
|
|
24
|
-
this.quoteCount = 0;
|
|
25
|
-
this.tradeCount = 0;
|
|
26
|
-
|
|
27
|
-
// Options
|
|
28
|
-
this.debug = options.debug || false;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Connect to Rithmic TICKER_PLANT
|
|
33
|
-
* @param {Object} credentials - { userId, password, systemName, gateway }
|
|
34
|
-
*/
|
|
35
|
-
async connect(credentials) {
|
|
36
|
-
this.config = credentials;
|
|
37
|
-
|
|
38
|
-
this.connection = new RithmicConnection({
|
|
39
|
-
debug: this.debug,
|
|
40
|
-
maxReconnectAttempts: 5
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Forward connection events
|
|
44
|
-
this.connection.on('loggedIn', (data) => {
|
|
45
|
-
this.emit('connected', data);
|
|
46
|
-
|
|
47
|
-
// Resubscribe to all symbols after reconnect
|
|
48
|
-
for (const [symbol, sub] of this.subscriptions) {
|
|
49
|
-
if (sub.active) {
|
|
50
|
-
this._sendSubscribe(symbol, sub.exchange);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
this.connection.on('error', (error) => {
|
|
56
|
-
this.emit('error', error);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
this.connection.on('disconnected', (data) => {
|
|
60
|
-
this.emit('disconnected', data);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Handle market data messages
|
|
64
|
-
this.connection.on('LastTrade', (trade) => this._handleTrade(trade));
|
|
65
|
-
this.connection.on('BestBidOffer', (quote) => this._handleQuote(quote));
|
|
66
|
-
|
|
67
|
-
// Connect
|
|
68
|
-
await this.connection.connect({
|
|
69
|
-
userId: credentials.userId,
|
|
70
|
-
password: credentials.password,
|
|
71
|
-
systemName: credentials.systemName,
|
|
72
|
-
gateway: credentials.gateway,
|
|
73
|
-
infraType: INFRA_TYPES.TICKER_PLANT
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Subscribe to market data for a symbol
|
|
81
|
-
* @param {string} symbol - Rithmic symbol (e.g., 'MNQH5')
|
|
82
|
-
* @param {string} exchange - Exchange (e.g., 'CME')
|
|
83
|
-
*/
|
|
84
|
-
async subscribe(symbol, exchange = 'CME') {
|
|
85
|
-
if (!this.connection || !this.connection.isReady) {
|
|
86
|
-
throw new Error('Not connected');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Store subscription
|
|
90
|
-
this.subscriptions.set(symbol, { exchange, active: true });
|
|
91
|
-
|
|
92
|
-
// Send subscribe request
|
|
93
|
-
return this._sendSubscribe(symbol, exchange);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Internal subscribe
|
|
98
|
-
*/
|
|
99
|
-
_sendSubscribe(symbol, exchange) {
|
|
100
|
-
try {
|
|
101
|
-
this.connection.send('RequestMarketDataUpdate', {
|
|
102
|
-
templateId: TEMPLATE_IDS.REQUEST_MARKET_DATA_UPDATE,
|
|
103
|
-
symbol: symbol,
|
|
104
|
-
exchange: exchange,
|
|
105
|
-
request: 1, // Subscribe
|
|
106
|
-
updateBits: 3 // LAST_TRADE (1) + BBO (2)
|
|
107
|
-
});
|
|
108
|
-
this.emit('subscribed', { symbol, exchange });
|
|
109
|
-
return true;
|
|
110
|
-
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error(`[RITHMIC:MarketData] Subscribe error:`, error.message);
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Unsubscribe from market data
|
|
119
|
-
*/
|
|
120
|
-
async unsubscribe(symbol, exchange = 'CME') {
|
|
121
|
-
if (!this.connection || !this.connection.isReady) {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const sub = this.subscriptions.get(symbol);
|
|
126
|
-
if (sub) {
|
|
127
|
-
sub.active = false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
this.connection.send('RequestMarketDataUpdate', {
|
|
132
|
-
templateId: TEMPLATE_IDS.REQUEST_MARKET_DATA_UPDATE,
|
|
133
|
-
symbol: symbol,
|
|
134
|
-
exchange: exchange,
|
|
135
|
-
request: 2 // Unsubscribe
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
this.subscriptions.delete(symbol);
|
|
139
|
-
this.emit('unsubscribed', { symbol, exchange });
|
|
140
|
-
return true;
|
|
141
|
-
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.error(`[RITHMIC:MarketData] Unsubscribe error:`, error.message);
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Unsubscribe from all symbols
|
|
150
|
-
*/
|
|
151
|
-
async unsubscribeAll() {
|
|
152
|
-
for (const [symbol, sub] of this.subscriptions) {
|
|
153
|
-
await this.unsubscribe(symbol, sub.exchange);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Handle trade data
|
|
159
|
-
*/
|
|
160
|
-
_handleTrade(trade) {
|
|
161
|
-
this.tradeCount++;
|
|
162
|
-
|
|
163
|
-
const symbol = trade.symbol;
|
|
164
|
-
const price = trade.tradePrice || 0;
|
|
165
|
-
const size = trade.tradeSize || 1;
|
|
166
|
-
|
|
167
|
-
// Determine side from aggressor field
|
|
168
|
-
// aggressor: 1 = BUY (buyer aggressor), 2 = SELL (seller aggressor)
|
|
169
|
-
let side = 'unknown';
|
|
170
|
-
if (trade.aggressor === 1) side = 'buy';
|
|
171
|
-
else if (trade.aggressor === 2) side = 'sell';
|
|
172
|
-
|
|
173
|
-
if (price <= 0) return;
|
|
174
|
-
|
|
175
|
-
// Update last price
|
|
176
|
-
this.lastPrices.set(symbol, price);
|
|
177
|
-
|
|
178
|
-
// Store trade
|
|
179
|
-
const tradeData = {
|
|
180
|
-
type: 'trade',
|
|
181
|
-
symbol: symbol,
|
|
182
|
-
exchange: trade.exchange || 'CME',
|
|
183
|
-
price: price,
|
|
184
|
-
size: size,
|
|
185
|
-
side: side,
|
|
186
|
-
volume: size,
|
|
187
|
-
timestamp: Date.now(),
|
|
188
|
-
ssboe: trade.ssboe,
|
|
189
|
-
usecs: trade.usecs
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
this.trades.set(symbol, tradeData);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// Emit events
|
|
197
|
-
this.emit('trade', tradeData);
|
|
198
|
-
|
|
199
|
-
// Also emit tick for compatibility
|
|
200
|
-
const quote = this.quotes.get(symbol) || {};
|
|
201
|
-
this.emit('tick', {
|
|
202
|
-
symbol: symbol,
|
|
203
|
-
price: price,
|
|
204
|
-
bid: quote.bid || 0,
|
|
205
|
-
ask: quote.ask || 0,
|
|
206
|
-
spread: (quote.ask || 0) - (quote.bid || 0),
|
|
207
|
-
volume: size,
|
|
208
|
-
side: side,
|
|
209
|
-
timestamp: Date.now()
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Handle quote data (BBO)
|
|
215
|
-
*/
|
|
216
|
-
_handleQuote(quote) {
|
|
217
|
-
this.quoteCount++;
|
|
218
|
-
|
|
219
|
-
const symbol = quote.symbol;
|
|
220
|
-
const bid = quote.bidPrice || 0;
|
|
221
|
-
const ask = quote.askPrice || 0;
|
|
222
|
-
const bidSize = quote.bidSize || 0;
|
|
223
|
-
const askSize = quote.askSize || 0;
|
|
224
|
-
|
|
225
|
-
// Store quote
|
|
226
|
-
const quoteData = {
|
|
227
|
-
type: 'quote',
|
|
228
|
-
symbol: symbol,
|
|
229
|
-
exchange: quote.exchange || 'CME',
|
|
230
|
-
bid: bid,
|
|
231
|
-
ask: ask,
|
|
232
|
-
bidSize: bidSize,
|
|
233
|
-
askSize: askSize,
|
|
234
|
-
spread: ask > 0 && bid > 0 ? ask - bid : 0,
|
|
235
|
-
mid: bid > 0 && ask > 0 ? (bid + ask) / 2 : 0,
|
|
236
|
-
timestamp: Date.now(),
|
|
237
|
-
ssboe: quote.ssboe,
|
|
238
|
-
usecs: quote.usecs
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// Update stored quote
|
|
242
|
-
this.quotes.set(symbol, quoteData);
|
|
243
|
-
|
|
244
|
-
// Update last price if we don't have one
|
|
245
|
-
if (!this.lastPrices.has(symbol) && quoteData.mid > 0) {
|
|
246
|
-
this.lastPrices.set(symbol, quoteData.mid);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
// Emit events
|
|
252
|
-
this.emit('quote', quoteData);
|
|
253
|
-
|
|
254
|
-
// Also emit tick for compatibility
|
|
255
|
-
const lastPrice = this.lastPrices.get(symbol) || quoteData.mid;
|
|
256
|
-
this.emit('tick', {
|
|
257
|
-
symbol: symbol,
|
|
258
|
-
price: lastPrice,
|
|
259
|
-
bid: bid,
|
|
260
|
-
ask: ask,
|
|
261
|
-
spread: quoteData.spread,
|
|
262
|
-
bidSize: bidSize,
|
|
263
|
-
askSize: askSize,
|
|
264
|
-
timestamp: Date.now()
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Get last price for symbol
|
|
270
|
-
*/
|
|
271
|
-
getLastPrice(symbol) {
|
|
272
|
-
return this.lastPrices.get(symbol) || 0;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Get current quote for symbol
|
|
277
|
-
*/
|
|
278
|
-
getQuote(symbol) {
|
|
279
|
-
return this.quotes.get(symbol) || null;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Get last trade for symbol
|
|
284
|
-
*/
|
|
285
|
-
getLastTrade(symbol) {
|
|
286
|
-
return this.trades.get(symbol) || null;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Get all subscribed symbols
|
|
291
|
-
*/
|
|
292
|
-
getSubscriptions() {
|
|
293
|
-
return Array.from(this.subscriptions.entries()).map(([symbol, sub]) => ({
|
|
294
|
-
symbol,
|
|
295
|
-
exchange: sub.exchange,
|
|
296
|
-
active: sub.active
|
|
297
|
-
}));
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Check if connected
|
|
302
|
-
*/
|
|
303
|
-
get isConnected() {
|
|
304
|
-
return this.connection && this.connection.isReady;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Disconnect
|
|
309
|
-
*/
|
|
310
|
-
async disconnect() {
|
|
311
|
-
await this.unsubscribeAll();
|
|
312
|
-
|
|
313
|
-
if (this.connection) {
|
|
314
|
-
await this.connection.disconnect();
|
|
315
|
-
this.connection = null;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
this.subscriptions.clear();
|
|
319
|
-
this.quotes.clear();
|
|
320
|
-
this.trades.clear();
|
|
321
|
-
this.lastPrices.clear();
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Get stats
|
|
326
|
-
*/
|
|
327
|
-
getStats() {
|
|
328
|
-
return {
|
|
329
|
-
quoteCount: this.quoteCount,
|
|
330
|
-
tradeCount: this.tradeCount,
|
|
331
|
-
subscriptions: this.subscriptions.size,
|
|
332
|
-
connected: this.isConnected
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Get front month contract for a base symbol
|
|
338
|
-
* @param {string} baseSymbol - Base symbol (e.g., 'MNQ', 'ES')
|
|
339
|
-
* @param {string} exchange - Exchange (e.g., 'CME')
|
|
340
|
-
* @returns {Promise<string>} Full contract symbol (e.g., 'MNQH6')
|
|
341
|
-
*/
|
|
342
|
-
async getFrontMonthContract(baseSymbol, exchange = 'CME') {
|
|
343
|
-
if (!this.connection || !this.connection.isReady) {
|
|
344
|
-
throw new Error('Not connected');
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return new Promise((resolve, reject) => {
|
|
348
|
-
const timeout = setTimeout(() => {
|
|
349
|
-
reject(new Error('Front month request timeout'));
|
|
350
|
-
}, 10000);
|
|
351
|
-
|
|
352
|
-
// Listen for response
|
|
353
|
-
const handler = (msg) => {
|
|
354
|
-
if (msg.templateId === 114) { // ResponseFrontMonthContract
|
|
355
|
-
clearTimeout(timeout);
|
|
356
|
-
this.connection.removeListener('message', handler);
|
|
357
|
-
|
|
358
|
-
if (msg.rpCode && msg.rpCode[0] === '0') {
|
|
359
|
-
resolve(msg.symbol || msg.tradingSymbol);
|
|
360
|
-
} else {
|
|
361
|
-
reject(new Error(`Front month failed: ${msg.rpCode?.join(', ')}`));
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
this.connection.on('message', handler);
|
|
367
|
-
|
|
368
|
-
// Send request (Template ID 113)
|
|
369
|
-
try {
|
|
370
|
-
this.connection.send('RequestFrontMonthContract', {
|
|
371
|
-
templateId: 113,
|
|
372
|
-
symbol: baseSymbol,
|
|
373
|
-
exchange: exchange
|
|
374
|
-
});
|
|
375
|
-
} catch (error) {
|
|
376
|
-
clearTimeout(timeout);
|
|
377
|
-
this.connection.removeListener('message', handler);
|
|
378
|
-
reject(error);
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Subscribe to a base symbol by auto-detecting front month
|
|
385
|
-
* @param {string} baseSymbol - Base symbol (e.g., 'MNQ', 'ES')
|
|
386
|
-
* @param {string} exchange - Exchange (e.g., 'CME')
|
|
387
|
-
*/
|
|
388
|
-
async subscribeByBaseSymbol(baseSymbol, exchange = 'CME') {
|
|
389
|
-
try {
|
|
390
|
-
// Try to get front month from Rithmic
|
|
391
|
-
const fullSymbol = await this.getFrontMonthContract(baseSymbol, exchange);
|
|
392
|
-
return this.subscribe(fullSymbol, exchange);
|
|
393
|
-
} catch (error) {
|
|
394
|
-
// Fallback to calculated front month
|
|
395
|
-
const calculated = getCurrentFrontMonth(baseSymbol);
|
|
396
|
-
return this.subscribe(calculated, exchange);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
module.exports = { RithmicMarketData };
|