hedgequantx 2.6.163 → 2.7.1

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 +8 -5
  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
@@ -0,0 +1,631 @@
1
+ /**
2
+ * Rithmic Trading API
3
+ * Handles order execution via ORDER_PLANT
4
+ * Supports: Market, Limit, Stop Market, Stop Limit orders
5
+ */
6
+
7
+ const EventEmitter = require('events');
8
+ const { RithmicConnection } = require('./r2');
9
+ const { TEMPLATE_IDS, INFRA_TYPES, ORDER_TYPES, NOTIFY_TYPES, RITHMIC_NOTIFY_TYPES } = require('./r7');
10
+
11
+ class RithmicTrading extends EventEmitter {
12
+ constructor(options = {}) {
13
+ super();
14
+
15
+ this.connection = null;
16
+ this.config = null;
17
+
18
+ // Login info from ORDER_PLANT
19
+ this.fcmId = null;
20
+ this.ibId = null;
21
+
22
+ // Trade routes cache
23
+ this.tradeRoutes = new Map(); // exchange -> tradeRoute
24
+
25
+ // Accounts cache
26
+ this.accounts = new Map(); // accountId -> account info
27
+
28
+ // Orders tracking
29
+ this.pendingOrders = new Map(); // basketId -> { resolve, reject, order }
30
+ this.openOrders = new Map(); // basketId -> order info
31
+ this.orderHistory = [];
32
+
33
+ // Positions tracking
34
+ this.positions = new Map(); // symbol -> position
35
+
36
+ // Order ID counter
37
+ this._orderIdCounter = 0;
38
+
39
+ // Options
40
+ this.debug = options.debug || false;
41
+ }
42
+
43
+ /**
44
+ * Connect to Rithmic ORDER_PLANT
45
+ * @param {Object} credentials - { userId, password, systemName, gateway }
46
+ */
47
+ async connect(credentials) {
48
+ this.config = credentials;
49
+
50
+ this.connection = new RithmicConnection({
51
+ debug: this.debug,
52
+ maxReconnectAttempts: 5
53
+ });
54
+
55
+ // Forward connection events
56
+ this.connection.on('error', (error) => {
57
+ this.emit('error', error);
58
+ });
59
+
60
+ this.connection.on('disconnected', (data) => {
61
+ this.emit('disconnected', data);
62
+ });
63
+
64
+ // Handle order notifications
65
+ // Handle both notification types - RithmicOrderNotification (351) and ExchangeOrderNotification (352)
66
+ this.connection.on('RithmicOrderNotification', (notif) => this._handleOrderNotification(notif));
67
+ this.connection.on('ExchangeOrderNotification', (notif) => this._handleOrderNotification(notif));
68
+ this.connection.on('ResponseNewOrder', (resp) => this._handleOrderResponse(resp));
69
+ this.connection.on('ResponseTradeRoutes', (resp) => this._handleTradeRoutes(resp));
70
+ this.connection.on('ResponseAccountList', (resp) => this._handleAccountList(resp));
71
+
72
+ // Connect and login
73
+ const loginData = await this.connection.connect({
74
+ userId: credentials.userId,
75
+ password: credentials.password,
76
+ systemName: credentials.systemName,
77
+ gateway: credentials.gateway,
78
+ infraType: INFRA_TYPES.ORDER_PLANT
79
+ });
80
+
81
+ this.fcmId = loginData.fcmId;
82
+ this.ibId = loginData.ibId;
83
+
84
+ // Load trade routes and accounts BEFORE signaling ready
85
+ await this._loadTradeRoutes();
86
+ await this._loadAccounts();
87
+ await this._subscribeOrderUpdates();
88
+
89
+ this.emit('connected', loginData);
90
+
91
+ return true;
92
+ }
93
+
94
+ /**
95
+ * Load trade routes
96
+ */
97
+ async _loadTradeRoutes() {
98
+ try {
99
+ this.connection.send('RequestTradeRoutes', {
100
+ templateId: TEMPLATE_IDS.REQUEST_TRADE_ROUTES,
101
+ subscribeForUpdates: false
102
+ });
103
+
104
+ // Wait for response
105
+ await new Promise(resolve => setTimeout(resolve, 1000));
106
+ } catch (error) {
107
+ // Silently fail - trade routes not critical
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Load accounts
113
+ */
114
+ async _loadAccounts() {
115
+ try {
116
+ this.connection.send('RequestAccountList', {
117
+ templateId: TEMPLATE_IDS.REQUEST_ACCOUNT_LIST,
118
+ fcmId: this.fcmId,
119
+ ibId: this.ibId,
120
+ userType: 3 // USER_TYPE_TRADER
121
+ });
122
+
123
+ // Wait for response
124
+ await new Promise(resolve => setTimeout(resolve, 1000));
125
+ } catch (error) {
126
+ // Silently fail - accounts loaded via events
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Subscribe to order updates
132
+ */
133
+ async _subscribeOrderUpdates() {
134
+ try {
135
+ // Subscribe for all accounts
136
+ for (const accountId of this.accounts.keys()) {
137
+ this.connection.send('RequestSubscribeForOrderUpdates', {
138
+ templateId: TEMPLATE_IDS.REQUEST_SUBSCRIBE_FOR_ORDER_UPDATES,
139
+ fcmId: this.fcmId,
140
+ ibId: this.ibId,
141
+ accountId: accountId
142
+ });
143
+ }
144
+
145
+
146
+
147
+ } catch (error) {
148
+ console.error('[RITHMIC:Trading] Failed to subscribe to order updates:', error.message);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Handle trade routes response
154
+ */
155
+ _handleTradeRoutes(response) {
156
+ if (response.exchange && response.tradeRoute) {
157
+ this.tradeRoutes.set(response.exchange, {
158
+ tradeRoute: response.tradeRoute,
159
+ status: response.status,
160
+ isDefault: response.isDefault
161
+ });
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Handle account list response
167
+ */
168
+ _handleAccountList(response) {
169
+ if (response.accountId) {
170
+ this.accounts.set(response.accountId, {
171
+ accountId: response.accountId,
172
+ accountName: response.accountName,
173
+ currency: response.accountCurrency,
174
+ fcmId: response.fcmId,
175
+ ibId: response.ibId
176
+ });
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Handle order response (acknowledgment)
182
+ */
183
+ _handleOrderResponse(response) {
184
+ // Get rpCode from either rpCode or rqHandlerRpCode (Rithmic sends both)
185
+ const rpCodeArr = response.rpCode || response.rqHandlerRpCode;
186
+ const rpCode = Array.isArray(rpCodeArr) ? rpCodeArr[0] : rpCodeArr;
187
+ const userMsgs = response.userMsg || [];
188
+
189
+ // Only process responses with success code or error code
190
+ // Skip if we don't have any response code yet
191
+ if (!rpCode && rpCode !== '0') {
192
+ return;
193
+ }
194
+
195
+ // Find pending order by userMsg
196
+ for (const [basketId, pending] of this.pendingOrders) {
197
+ // Check if any userMsg matches the basketId
198
+ const matches = userMsgs.some(msg => msg === basketId || msg.includes(basketId) || basketId.includes(msg));
199
+
200
+ if (matches) {
201
+ if (rpCode === '0') {
202
+ // Success! Store the Rithmic basket ID for tracking
203
+ const rithmicBasketId = response.basketId || basketId;
204
+ pending.resolve({ success: true, basketId: rithmicBasketId, orderId: rithmicBasketId, response });
205
+ } else {
206
+ // Error - get error text from rpCode array (second element often contains message)
207
+ const errorText = Array.isArray(rpCodeArr) && rpCodeArr.length > 1
208
+ ? rpCodeArr.slice(1).join(' ')
209
+ : (response.textMsg || response.text || `Error code: ${rpCode}`);
210
+ console.log(`[RITHMIC] ORDER REJECTED: ${errorText}`);
211
+ pending.reject(new Error(errorText));
212
+ }
213
+
214
+ this.pendingOrders.delete(basketId);
215
+ return;
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Handle order notification (fill, cancel, etc.)
222
+ * Handles both RithmicOrderNotification (351) and ExchangeOrderNotification (352)
223
+ * Uses deduplication to avoid double-emitting fills
224
+ */
225
+ _handleOrderNotification(notif) {
226
+ const notifyType = notif.notifyType;
227
+ const status = notif.status || '';
228
+ const symbol = notif.symbol;
229
+ const basketId = notif.basketId;
230
+
231
+ // Deduplicate fills - use basketId + totalFillSize as unique key
232
+ const fillKey = `${basketId}-${notif.totalFillSize || 0}-${notif.fillPrice || notif.avgFillPrice || 0}`;
233
+ if (!this._processedFills) this._processedFills = new Set();
234
+
235
+ // Skip if already processed (within 2 seconds window)
236
+ if (this._processedFills.has(fillKey)) {
237
+ return;
238
+ }
239
+ this._processedFills.add(fillKey);
240
+
241
+ // Cleanup old fills after 5 seconds
242
+ setTimeout(() => this._processedFills.delete(fillKey), 5000);
243
+
244
+ // Determine event type from notify_type and status
245
+ // RithmicOrderNotification uses: COMPLETE=15, OPEN=13, etc.
246
+ // ExchangeOrderNotification uses: FILL=5, STATUS=1, etc.
247
+ let eventType = 'status';
248
+
249
+ // Check by numeric notifyType first (most reliable)
250
+ if (notifyType === RITHMIC_NOTIFY_TYPES.COMPLETE || notifyType === NOTIFY_TYPES.FILL) {
251
+ eventType = 'fill';
252
+ } else if (notifyType === RITHMIC_NOTIFY_TYPES.CANCELLATION_FAILED || notifyType === NOTIFY_TYPES.NOT_CANCELLED) {
253
+ eventType = 'cancel_failed';
254
+ } else if (notifyType === RITHMIC_NOTIFY_TYPES.MODIFICATION_FAILED) {
255
+ eventType = 'modify_failed';
256
+ } else if (status.includes('cancel') || status === 'cancelled') {
257
+ eventType = 'cancel';
258
+ } else if (status.includes('reject') || notifyType === NOTIFY_TYPES.REJECT) {
259
+ eventType = 'reject';
260
+ } else if (notifyType === RITHMIC_NOTIFY_TYPES.OPEN) {
261
+ eventType = 'open';
262
+ } else if (status === 'complete') {
263
+ // Fallback for status string
264
+ eventType = 'fill';
265
+ }
266
+
267
+ // Debug logging for significant events
268
+ if (this.debug && (eventType === 'fill' || eventType === 'reject' || eventType === 'cancel' || eventType === 'open')) {
269
+ console.log(`[RITHMIC] ${eventType.toUpperCase()}: ${symbol} type=${notifyType} status="${status}" fill=${notif.totalFillSize || 0}`);
270
+ }
271
+
272
+ const orderInfo = {
273
+ type: eventType,
274
+ basketId: basketId,
275
+ symbol: symbol,
276
+ exchange: notif.exchange,
277
+ accountId: notif.accountId,
278
+ side: notif.transactionType === 1 ? 'buy' : 'sell',
279
+ quantity: notif.quantity,
280
+ price: notif.price,
281
+ triggerPrice: notif.triggerPrice,
282
+ priceType: notif.priceType,
283
+ status: notif.status,
284
+
285
+ // Fill info
286
+ fillPrice: notif.fillPrice,
287
+ fillSize: notif.fillSize,
288
+ avgFillPrice: notif.avgFillPrice,
289
+ totalFillSize: notif.totalFillSize,
290
+ totalUnfilledSize: notif.totalUnfilledSize,
291
+
292
+ // Timestamps
293
+ confirmedTime: notif.confirmedTime,
294
+ fillTime: notif.fillTime,
295
+
296
+ // Messages
297
+ text: notif.text,
298
+ reportText: notif.reportText
299
+ };
300
+
301
+ // Log based on event type
302
+ if (eventType === 'fill') {
303
+ // Use fillSize/fillPrice if available, otherwise use totalFillSize/avgFillPrice
304
+ const fillSize = notif.fillSize || notif.totalFillSize || 0;
305
+ const fillPrice = notif.fillPrice || notif.avgFillPrice || 0;
306
+ const sideStr = notif.transactionType === 'BUY' || notif.transactionType === 1 ? 'BUY' : 'SELL';
307
+
308
+ // Update orderInfo with resolved values
309
+ orderInfo.fillSize = fillSize;
310
+ orderInfo.fillPrice = fillPrice;
311
+
312
+ this.emit('fill', orderInfo);
313
+
314
+ // Update position
315
+ this._updatePosition(symbol, notif);
316
+
317
+ } else if (eventType === 'reject') {
318
+ console.log(`[RITHMIC] REJECT: ${symbol} - ${notif.text || 'Unknown reason'}`);
319
+ this.emit('reject', orderInfo);
320
+
321
+ // Reject pending order
322
+ const pending = this.pendingOrders.get(basketId);
323
+ if (pending) {
324
+ pending.reject(new Error(notif.text || 'Order rejected'));
325
+ this.pendingOrders.delete(basketId);
326
+ }
327
+
328
+ } else if (eventType === 'cancel') {
329
+ this.emit('cancel', orderInfo);
330
+ this.openOrders.delete(basketId);
331
+ }
332
+
333
+ // Emit generic order event
334
+ this.emit('order', orderInfo);
335
+
336
+ // Store order info
337
+ if (eventType !== 'cancel' && eventType !== 'reject') {
338
+ this.openOrders.set(basketId, orderInfo);
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Update position based on fill
344
+ */
345
+ _updatePosition(symbol, notif) {
346
+ const current = this.positions.get(symbol) || { symbol, netQty: 0, avgPrice: 0 };
347
+
348
+ // Use fillSize/fillPrice if available, otherwise use totalFillSize/avgFillPrice
349
+ const fillQty = notif.fillSize || notif.totalFillSize || 0;
350
+ const fillPrice = notif.fillPrice || notif.avgFillPrice || 0;
351
+ // Handle both numeric and string transactionType
352
+ const isBuy = notif.transactionType === 1 || notif.transactionType === 'BUY';
353
+ const side = isBuy ? 1 : -1; // 1 = buy, -1 = sell
354
+
355
+ const newQty = current.netQty + (fillQty * side);
356
+
357
+ if (Math.abs(newQty) > Math.abs(current.netQty)) {
358
+ // Adding to position
359
+ const totalCost = (current.netQty * current.avgPrice) + (fillQty * fillPrice * side);
360
+ current.avgPrice = newQty !== 0 ? totalCost / newQty : 0;
361
+ }
362
+
363
+ current.netQty = newQty;
364
+ current.lastFillPrice = fillPrice;
365
+ current.lastFillTime = Date.now();
366
+
367
+ this.positions.set(symbol, current);
368
+ this.emit('position', current);
369
+ }
370
+
371
+ /**
372
+ * Generate unique basket ID
373
+ */
374
+ _generateBasketId() {
375
+ return `X_${Date.now()}_${++this._orderIdCounter}`;
376
+ }
377
+
378
+ /**
379
+ * Get trade route for exchange
380
+ */
381
+ _getTradeRoute(exchange) {
382
+ const route = this.tradeRoutes.get(exchange);
383
+ return route ? route.tradeRoute : 'DEFAULT';
384
+ }
385
+
386
+ /**
387
+ * Place a MARKET order
388
+ */
389
+ async placeMarketOrder(accountId, symbol, exchange, side, quantity) {
390
+ return this._placeOrder({
391
+ accountId,
392
+ symbol,
393
+ exchange,
394
+ side,
395
+ quantity,
396
+ priceType: ORDER_TYPES.PRICE_TYPE.MARKET
397
+ });
398
+ }
399
+
400
+ /**
401
+ * Place a LIMIT order
402
+ */
403
+ async placeLimitOrder(accountId, symbol, exchange, side, quantity, price) {
404
+ return this._placeOrder({
405
+ accountId,
406
+ symbol,
407
+ exchange,
408
+ side,
409
+ quantity,
410
+ price,
411
+ priceType: ORDER_TYPES.PRICE_TYPE.LIMIT
412
+ });
413
+ }
414
+
415
+ /**
416
+ * Place a STOP MARKET order
417
+ */
418
+ async placeStopMarketOrder(accountId, symbol, exchange, side, quantity, stopPrice) {
419
+ return this._placeOrder({
420
+ accountId,
421
+ symbol,
422
+ exchange,
423
+ side,
424
+ quantity,
425
+ triggerPrice: stopPrice,
426
+ priceType: ORDER_TYPES.PRICE_TYPE.STOP_MARKET
427
+ });
428
+ }
429
+
430
+ /**
431
+ * Place a STOP LIMIT order
432
+ */
433
+ async placeStopLimitOrder(accountId, symbol, exchange, side, quantity, stopPrice, limitPrice) {
434
+ return this._placeOrder({
435
+ accountId,
436
+ symbol,
437
+ exchange,
438
+ side,
439
+ quantity,
440
+ price: limitPrice,
441
+ triggerPrice: stopPrice,
442
+ priceType: ORDER_TYPES.PRICE_TYPE.STOP_LIMIT
443
+ });
444
+ }
445
+
446
+ /**
447
+ * Internal order placement
448
+ */
449
+ async _placeOrder(params) {
450
+ if (!this.connection || !this.connection.isReady) {
451
+ throw new Error('Not connected');
452
+ }
453
+
454
+ const {
455
+ accountId,
456
+ symbol,
457
+ exchange = 'CME',
458
+ side,
459
+ quantity,
460
+ price,
461
+ triggerPrice,
462
+ priceType = ORDER_TYPES.PRICE_TYPE.MARKET,
463
+ duration = ORDER_TYPES.DURATION.DAY
464
+ } = params;
465
+
466
+ const basketId = this._generateBasketId();
467
+ const transactionType = side.toLowerCase() === 'buy'
468
+ ? ORDER_TYPES.TRANSACTION_TYPE.BUY
469
+ : ORDER_TYPES.TRANSACTION_TYPE.SELL;
470
+
471
+ const tradeRoute = this._getTradeRoute(exchange);
472
+
473
+ const orderRequest = {
474
+ templateId: TEMPLATE_IDS.REQUEST_NEW_ORDER,
475
+ userMsg: [basketId],
476
+ fcmId: this.fcmId,
477
+ ibId: this.ibId,
478
+ accountId: accountId.toString(),
479
+ symbol: symbol,
480
+ exchange: exchange,
481
+ quantity: quantity,
482
+ transactionType: transactionType,
483
+ duration: duration,
484
+ priceType: priceType,
485
+ tradeRoute: tradeRoute,
486
+ manualOrAuto: ORDER_TYPES.ORDER_PLACEMENT.AUTO
487
+ };
488
+
489
+ // Add price for limit orders
490
+ if (price && (priceType === ORDER_TYPES.PRICE_TYPE.LIMIT || priceType === ORDER_TYPES.PRICE_TYPE.STOP_LIMIT)) {
491
+ orderRequest.price = price;
492
+ }
493
+
494
+ // Add trigger price for stop orders
495
+ if (triggerPrice && (priceType === ORDER_TYPES.PRICE_TYPE.STOP_MARKET || priceType === ORDER_TYPES.PRICE_TYPE.STOP_LIMIT)) {
496
+ orderRequest.triggerPrice = triggerPrice;
497
+ }
498
+
499
+ return new Promise((resolve, reject) => {
500
+ this.pendingOrders.set(basketId, { resolve, reject, order: orderRequest });
501
+
502
+ // Timeout
503
+ setTimeout(() => {
504
+ if (this.pendingOrders.has(basketId)) {
505
+ this.pendingOrders.delete(basketId);
506
+ reject(new Error('Order timeout'));
507
+ }
508
+ }, 15000);
509
+
510
+ try {
511
+ this.connection.send('RequestNewOrder', orderRequest);
512
+ } catch (error) {
513
+ this.pendingOrders.delete(basketId);
514
+ reject(error);
515
+ }
516
+ });
517
+ }
518
+
519
+ /**
520
+ * Cancel an order
521
+ */
522
+ async cancelOrder(basketId) {
523
+ if (!this.connection || !this.connection.isReady) {
524
+ throw new Error('Not connected');
525
+ }
526
+
527
+ const order = this.openOrders.get(basketId);
528
+ if (!order) {
529
+ throw new Error('Order not found');
530
+ }
531
+
532
+ this.connection.send('RequestCancelOrder', {
533
+ templateId: TEMPLATE_IDS.REQUEST_CANCEL_ORDER,
534
+ userMsg: [basketId],
535
+ fcmId: this.fcmId,
536
+ ibId: this.ibId,
537
+ accountId: order.accountId,
538
+ basketId: basketId
539
+ });
540
+
541
+ return { success: true, basketId };
542
+ }
543
+
544
+ /**
545
+ * Cancel all orders for an account
546
+ */
547
+ async cancelAllOrders(accountId) {
548
+ if (!this.connection || !this.connection.isReady) {
549
+ throw new Error('Not connected');
550
+ }
551
+
552
+ this.connection.send('RequestCancelAllOrders', {
553
+ templateId: TEMPLATE_IDS.REQUEST_CANCEL_ALL_ORDERS,
554
+ fcmId: this.fcmId,
555
+ ibId: this.ibId,
556
+ accountId: accountId.toString()
557
+ });
558
+
559
+ return { success: true };
560
+ }
561
+
562
+ /**
563
+ * Close a position (flatten)
564
+ */
565
+ async closePosition(accountId, symbol, exchange = 'CME') {
566
+ const position = this.positions.get(symbol);
567
+
568
+ if (!position || position.netQty === 0) {
569
+ return { success: true, message: 'No position' };
570
+ }
571
+
572
+ // Place opposite order to flatten
573
+ const side = position.netQty > 0 ? 'sell' : 'buy';
574
+ const quantity = Math.abs(position.netQty);
575
+
576
+ return this.placeMarketOrder(accountId, symbol, exchange, side, quantity);
577
+ }
578
+
579
+ /**
580
+ * Get position for symbol
581
+ */
582
+ getPosition(symbol) {
583
+ return this.positions.get(symbol) || null;
584
+ }
585
+
586
+ /**
587
+ * Get all positions
588
+ */
589
+ getPositions() {
590
+ return Array.from(this.positions.values());
591
+ }
592
+
593
+ /**
594
+ * Get open orders
595
+ */
596
+ getOpenOrders() {
597
+ return Array.from(this.openOrders.values());
598
+ }
599
+
600
+ /**
601
+ * Get accounts
602
+ */
603
+ getAccounts() {
604
+ return Array.from(this.accounts.values());
605
+ }
606
+
607
+ /**
608
+ * Check if connected
609
+ */
610
+ get isConnected() {
611
+ return this.connection && this.connection.isReady;
612
+ }
613
+
614
+ /**
615
+ * Disconnect
616
+ */
617
+ async disconnect() {
618
+ if (this.connection) {
619
+ await this.connection.disconnect();
620
+ this.connection = null;
621
+ }
622
+
623
+ this.pendingOrders.clear();
624
+ this.openOrders.clear();
625
+ this.positions.clear();
626
+ this.tradeRoutes.clear();
627
+ this.accounts.clear();
628
+ }
629
+ }
630
+
631
+ module.exports = { RithmicTrading };