hedgequantx 2.6.163 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +15 -88
  2. package/bin/cli.js +0 -11
  3. package/dist/lib/api.jsc +0 -0
  4. package/dist/lib/api2.jsc +0 -0
  5. package/dist/lib/core.jsc +0 -0
  6. package/dist/lib/core2.jsc +0 -0
  7. package/dist/lib/data.js +1 -1
  8. package/dist/lib/data.jsc +0 -0
  9. package/dist/lib/data2.jsc +0 -0
  10. package/dist/lib/decoder.jsc +0 -0
  11. package/dist/lib/m/mod1.jsc +0 -0
  12. package/dist/lib/m/mod2.jsc +0 -0
  13. package/dist/lib/n/r1.jsc +0 -0
  14. package/dist/lib/n/r2.jsc +0 -0
  15. package/dist/lib/n/r3.jsc +0 -0
  16. package/dist/lib/n/r4.jsc +0 -0
  17. package/dist/lib/n/r5.jsc +0 -0
  18. package/dist/lib/n/r6.jsc +0 -0
  19. package/dist/lib/n/r7.jsc +0 -0
  20. package/dist/lib/o/util1.jsc +0 -0
  21. package/dist/lib/o/util2.jsc +0 -0
  22. package/package.json +6 -3
  23. package/src/app.js +40 -162
  24. package/src/config/constants.js +31 -33
  25. package/src/config/propfirms.js +13 -217
  26. package/src/config/settings.js +0 -43
  27. package/src/lib/api.js +198 -0
  28. package/src/lib/api2.js +353 -0
  29. package/src/lib/core.js +539 -0
  30. package/src/lib/core2.js +341 -0
  31. package/src/lib/data.js +555 -0
  32. package/src/lib/data2.js +492 -0
  33. package/src/lib/decoder.js +599 -0
  34. package/src/lib/m/s1.js +804 -0
  35. package/src/lib/m/s2.js +34 -0
  36. package/src/lib/n/r1.js +454 -0
  37. package/src/lib/n/r2.js +514 -0
  38. package/src/lib/n/r3.js +631 -0
  39. package/src/lib/n/r4.js +401 -0
  40. package/src/lib/n/r5.js +335 -0
  41. package/src/lib/n/r6.js +425 -0
  42. package/src/lib/n/r7.js +530 -0
  43. package/src/lib/o/l1.js +44 -0
  44. package/src/lib/o/l2.js +427 -0
  45. package/src/lib/python-bridge.js +206 -0
  46. package/src/menus/connect.js +14 -176
  47. package/src/menus/dashboard.js +65 -110
  48. package/src/pages/accounts.js +18 -18
  49. package/src/pages/algo/copy-trading.js +210 -240
  50. package/src/pages/algo/index.js +41 -104
  51. package/src/pages/algo/one-account.js +386 -33
  52. package/src/pages/algo/ui.js +312 -151
  53. package/src/pages/orders.js +3 -3
  54. package/src/pages/positions.js +3 -3
  55. package/src/pages/stats/chart.js +74 -0
  56. package/src/pages/stats/display.js +228 -0
  57. package/src/pages/stats/index.js +236 -0
  58. package/src/pages/stats/metrics.js +213 -0
  59. package/src/pages/user.js +6 -6
  60. package/src/services/hqx-server/constants.js +55 -0
  61. package/src/services/hqx-server/index.js +401 -0
  62. package/src/services/hqx-server/latency.js +81 -0
  63. package/src/services/index.js +12 -3
  64. package/src/services/rithmic/accounts.js +7 -32
  65. package/src/services/rithmic/connection.js +1 -204
  66. package/src/services/rithmic/contracts.js +116 -99
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +63 -120
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -111
  71. package/src/services/rithmic/protobuf.js +384 -138
  72. package/src/services/session.js +22 -173
  73. package/src/ui/box.js +10 -18
  74. package/src/ui/index.js +1 -3
  75. package/src/ui/menu.js +1 -1
  76. package/src/utils/prompts.js +2 -2
  77. package/dist/lib/m/s1.js +0 -1
  78. package/src/menus/ai-agent-connect.js +0 -181
  79. package/src/menus/ai-agent-models.js +0 -219
  80. package/src/menus/ai-agent-oauth.js +0 -292
  81. package/src/menus/ai-agent-ui.js +0 -141
  82. package/src/menus/ai-agent.js +0 -484
  83. package/src/pages/algo/algo-config.js +0 -195
  84. package/src/pages/algo/algo-multi.js +0 -801
  85. package/src/pages/algo/algo-utils.js +0 -58
  86. package/src/pages/algo/copy-engine.js +0 -449
  87. package/src/pages/algo/custom-strategy.js +0 -459
  88. package/src/pages/algo/logger.js +0 -245
  89. package/src/pages/algo/smart-logs-data.js +0 -218
  90. package/src/pages/algo/smart-logs.js +0 -387
  91. package/src/pages/algo/ui-constants.js +0 -144
  92. package/src/pages/algo/ui-summary.js +0 -184
  93. package/src/pages/stats-calculations.js +0 -191
  94. package/src/pages/stats-ui.js +0 -381
  95. package/src/pages/stats.js +0 -339
  96. package/src/services/ai/client-analysis.js +0 -194
  97. package/src/services/ai/client-models.js +0 -333
  98. package/src/services/ai/client.js +0 -343
  99. package/src/services/ai/index.js +0 -384
  100. package/src/services/ai/oauth-anthropic.js +0 -265
  101. package/src/services/ai/oauth-gemini.js +0 -223
  102. package/src/services/ai/oauth-iflow.js +0 -269
  103. package/src/services/ai/oauth-openai.js +0 -233
  104. package/src/services/ai/oauth-qwen.js +0 -279
  105. package/src/services/ai/providers/direct-providers.js +0 -323
  106. package/src/services/ai/providers/index.js +0 -62
  107. package/src/services/ai/providers/other-providers.js +0 -104
  108. package/src/services/ai/proxy-install.js +0 -249
  109. package/src/services/ai/proxy-manager.js +0 -494
  110. package/src/services/ai/proxy-remote.js +0 -161
  111. package/src/services/ai/strategy-supervisor.js +0 -1312
  112. package/src/services/ai/supervisor-data.js +0 -195
  113. package/src/services/ai/supervisor-optimize.js +0 -215
  114. package/src/services/ai/supervisor-sync.js +0 -178
  115. package/src/services/ai/supervisor-utils.js +0 -158
  116. package/src/services/ai/supervisor.js +0 -484
  117. package/src/services/ai/validation.js +0 -250
  118. package/src/services/hqx-server-events.js +0 -110
  119. package/src/services/hqx-server-handlers.js +0 -217
  120. package/src/services/hqx-server-latency.js +0 -136
  121. package/src/services/hqx-server.js +0 -403
  122. package/src/services/position-constants.js +0 -28
  123. package/src/services/position-exit-logic.js +0 -174
  124. package/src/services/position-manager.js +0 -438
  125. package/src/services/position-momentum.js +0 -206
  126. package/src/services/projectx/accounts.js +0 -142
  127. package/src/services/projectx/index.js +0 -443
  128. package/src/services/projectx/market.js +0 -172
  129. package/src/services/projectx/stats.js +0 -110
  130. package/src/services/projectx/trading.js +0 -180
  131. package/src/services/rithmic/latency-tracker.js +0 -182
  132. package/src/services/rithmic/market-data-decoders.js +0 -229
  133. package/src/services/rithmic/market-data.js +0 -272
  134. package/src/services/rithmic/orders-fast.js +0 -246
  135. package/src/services/rithmic/proto-decoders.js +0 -403
  136. package/src/services/rithmic/specs.js +0 -146
  137. package/src/services/rithmic/trade-history.js +0 -254
  138. package/src/services/session-history.js +0 -475
  139. package/src/services/strategy/hft-signal-calc.js +0 -147
  140. package/src/services/strategy/hft-tick.js +0 -407
  141. package/src/services/strategy/recovery-math.js +0 -402
  142. package/src/services/tradovate/constants.js +0 -109
  143. package/src/services/tradovate/index.js +0 -392
  144. package/src/services/tradovate/market.js +0 -47
  145. package/src/services/tradovate/orders.js +0 -145
  146. package/src/services/tradovate/websocket.js +0 -97
@@ -0,0 +1,539 @@
1
+ /**
2
+ * Core Module
3
+ */
4
+
5
+ const { M1 } = require('./m/s1');
6
+ const { L2 } = require('./o/l2');
7
+ const L1 = require('./o/l1');
8
+ const { getB, closeB } = require('./python-bridge');
9
+ const {
10
+ RithmicAdapter: N1,
11
+ getCurrentFrontMonth: getFM
12
+ } = require('./n/r1');
13
+
14
+ class C1 {
15
+ constructor(sessionId, config, callbacks) {
16
+ this.sessionId = sessionId;
17
+ this.config = config;
18
+ this.callbacks = callbacks;
19
+ this.running = false;
20
+ this.rithmicAdapter = null;
21
+ this.strategy = null;
22
+ this.pythonBridge = null; // For Python/Cython strategy mode
23
+ this.usePythonStrategy = config.usePythonStrategy || false;
24
+ this.symbol = null;
25
+ this.exchange = 'CME';
26
+
27
+ // Account info
28
+ this.rithmicAccountId = null;
29
+ this.accountDisplayName = null;
30
+
31
+ // Position & P&L from Rithmic
32
+ this.rithmicData = {
33
+ pnl: { open: 0, closed: 0, total: 0, dayPnl: 0 },
34
+ position: null,
35
+ lastPrice: 0,
36
+ lastBid: 0,
37
+ lastAsk: 0
38
+ };
39
+
40
+ // Stats
41
+ this.stats = {
42
+ trades: 0,
43
+ wins: 0,
44
+ losses: 0,
45
+ startTime: null,
46
+ lastClosedPnl: 0 // Track P&L at last position close
47
+ };
48
+
49
+ // Order management - CRITICAL
50
+ this.lastOrderTime = 0;
51
+ this.orderCooldownMs = 30000; // 30 seconds minimum between entries
52
+ this.pendingOrder = false;
53
+ this.hasOpenPosition = false;
54
+ this.lastEntryPrice = 0;
55
+ this.lastEntrySide = null;
56
+ this.entryTime = 0;
57
+ this.minHoldTimeMs = 10000; // MINIMUM 10 seconds hold time per trade
58
+
59
+ // Smart logs state tracking
60
+ this.lastMarketCondition = null;
61
+ this.lastLoggedPrice = 0;
62
+ this.lastDelta = 0;
63
+ this.logThrottleMs = 5000; // Min 5s between market flow logs
64
+ this.lastFlowLogTime = 0;
65
+ }
66
+
67
+ async start() {
68
+ this.running = true;
69
+ this.stats.startTime = Date.now();
70
+
71
+ const propfirm = this.config.propfirm || 'apex';
72
+ const rithmicCredentials = this.config.rithmicCredentials || {};
73
+
74
+ if (!rithmicCredentials.userId || !rithmicCredentials.password) {
75
+ this._log('error', 'Rithmic credentials required');
76
+ throw new Error('Rithmic credentials required');
77
+ }
78
+
79
+ // Initialize smart logger
80
+ L2.reset();
81
+ L2.algoStarting('Rithmic', this.config.symbol || 'MNQ');
82
+
83
+ this._log('info', 'Connecting to Rithmic...');
84
+
85
+ try {
86
+ // Connect to Rithmic
87
+ this.rithmicAdapter = new N1({ debug: false, usePool: false });
88
+
89
+ await this.rithmicAdapter.connect({
90
+ userId: rithmicCredentials.userId,
91
+ password: rithmicCredentials.password,
92
+ propfirm: propfirm
93
+ }, {
94
+ marketData: true,
95
+ trading: true,
96
+ pnl: true
97
+ });
98
+
99
+ // Get account
100
+ const accounts = this.rithmicAdapter.getAccounts();
101
+ if (accounts.length === 0) throw new Error('No Rithmic accounts found');
102
+
103
+ this.rithmicAccountId = accounts[0].accountId;
104
+ this.accountDisplayName = this.rithmicAccountId; // Real Rithmic ID like APEX-130042-62
105
+ this._log('info', `Account: ${this.accountDisplayName}`);
106
+
107
+ // Resolve symbol
108
+ this.symbol = this._getSymbol(this.config.symbol);
109
+ this.exchange = this._getExchange(this.symbol);
110
+
111
+ // Subscribe market data
112
+ await this.rithmicAdapter.subscribe(this.symbol, this.exchange);
113
+ this._log('info', `Symbol: ${this.symbol}`);
114
+
115
+ // Subscribe P&L
116
+ await this.rithmicAdapter.subscribePnL(this.rithmicAccountId);
117
+
118
+ // Event handlers
119
+ this.rithmicAdapter.on('quote', (q) => this._onQuote(q));
120
+ this.rithmicAdapter.on('trade', (t) => this._onTrade(t));
121
+ this.rithmicAdapter.on('fill', (f) => this._onFill(f));
122
+ this.rithmicAdapter.on('reject', (r) => this._onReject(r));
123
+ this.rithmicAdapter.on('accountPnL', (p) => this._onAccountPnL(p));
124
+ this.rithmicAdapter.on('instrumentPnL', (p) => this._onPositionPnL(p));
125
+
126
+ // Initialize strategy
127
+ // Contract specs MUST come from API via config - NO defaults
128
+ // P&L comes from PNL_PLANT API - never calculated locally
129
+ if (!this.config.tickSize) {
130
+ this._log('warn', 'tickSize not provided - using API data only for P&L');
131
+ }
132
+ const tickSize = this.config.tickSize || 0.25; // Only for stop/target calculations in ticks
133
+ const tickValue = 0; // NOT USED - P&L comes from API
134
+
135
+ if (this.usePythonStrategy) {
136
+ // Use Python/Cython compiled strategy
137
+ this._log('info', 'Initializing Python strategy bridge...');
138
+ try {
139
+ this.pythonBridge = await getB();
140
+ await this.pythonBridge.initialize(this.symbol, tickSize);
141
+
142
+ // Listen for signals from Python bridge
143
+ this.pythonBridge.on('signal', (signal) => {
144
+ if (signal && signal.confidence >= 0.5) {
145
+ this._executeSignal(signal);
146
+ }
147
+ });
148
+
149
+ // Listen for smart logs from Python
150
+ this.pythonBridge.on('log', (logData) => {
151
+ if (logData && logData.message) {
152
+ this._log('info', `[PY] ${logData.message}${logData.details ? ` | ${logData.details}` : ''}`);
153
+ }
154
+ });
155
+
156
+ this._log('success', 'Python strategy bridge connected');
157
+ } catch (error) {
158
+ this._log('error', `Python bridge failed: ${error.message}, falling back to JS strategy`);
159
+ this.usePythonStrategy = false;
160
+ }
161
+ }
162
+
163
+ if (!this.usePythonStrategy) {
164
+ // Use JavaScript strategy - P&L from API, not calculated
165
+ this.strategy = new M1({
166
+ tickSize: tickSize
167
+ });
168
+ this.strategy.initialize(this.symbol, tickSize);
169
+
170
+ // Forward strategy signals to smart logger
171
+ this.strategy.on('signal', (signal) => {
172
+ L2.strategySignal(
173
+ signal.direction,
174
+ signal.zScore || 0,
175
+ signal.confidence,
176
+ signal.stopTicks,
177
+ signal.targetTicks
178
+ );
179
+ });
180
+ }
181
+
182
+ L2.algoRunning();
183
+ const strategyMode = this.usePythonStrategy ? 'Python/Cython' : 'JavaScript';
184
+ this._log('success', `Engine started [${strategyMode}]`);
185
+
186
+ // Stats interval
187
+ this.statsInterval = setInterval(() => this._sendStats(), 2000);
188
+
189
+ } catch (error) {
190
+ this._log('error', `Connection failed: ${error.message}`);
191
+ throw error;
192
+ }
193
+ }
194
+
195
+ async stop() {
196
+ L2.algoStopping();
197
+ this._log('info', 'Stopping algo...');
198
+ this.running = false;
199
+
200
+ if (this.statsInterval) {
201
+ clearInterval(this.statsInterval);
202
+ this.statsInterval = null;
203
+ }
204
+
205
+ // CRITICAL: Cancel all orders and flatten positions
206
+ if (this.rithmicAdapter && this.rithmicAccountId) {
207
+ try {
208
+ // 1. Cancel all open orders
209
+ this._log('info', 'Cancelling orders...');
210
+ await this.rithmicAdapter.cancelAllOrders(this.rithmicAccountId);
211
+ await new Promise(r => setTimeout(r, 500));
212
+
213
+ // 2. Flatten position if any
214
+ if (this.hasOpenPosition && this.rithmicData.position) {
215
+ const pos = this.rithmicData.position;
216
+ const qty = Math.abs(pos.qty || 0);
217
+ if (qty > 0) {
218
+ const side = pos.qty > 0 ? 'sell' : 'buy';
219
+ this._log('info', `Flatten: ${side.toUpperCase()} ${qty}x @MKT`);
220
+ await this.rithmicAdapter.placeMarketOrder(
221
+ this.rithmicAccountId,
222
+ this.symbol,
223
+ side,
224
+ qty,
225
+ this.exchange
226
+ );
227
+ await new Promise(r => setTimeout(r, 2000));
228
+ }
229
+ }
230
+
231
+ this._log('success', 'Positions closed');
232
+ } catch (error) {
233
+ this._log('error', `Cleanup error: ${error.message}`);
234
+ }
235
+ }
236
+
237
+ if (this.rithmicAdapter) {
238
+ this.rithmicAdapter.disconnect();
239
+ this.rithmicAdapter = null;
240
+ }
241
+
242
+ // Close Python bridge if using it
243
+ if (this.pythonBridge) {
244
+ closeB();
245
+ this.pythonBridge = null;
246
+ }
247
+
248
+ this._log('info', 'ALGO STOPPED');
249
+ }
250
+
251
+ // Quote handler - ONLY process valid signals
252
+ async _onQuote(quote) {
253
+ if (!this.running) return;
254
+
255
+ if (quote.bid > 0) this.rithmicData.lastBid = quote.bid;
256
+ if (quote.ask > 0) this.rithmicData.lastAsk = quote.ask;
257
+ if (quote.price > 0) this.rithmicData.lastPrice = quote.price;
258
+
259
+ const tick = {
260
+ contract_id: this.symbol,
261
+ price: this.rithmicData.lastPrice,
262
+ bid: this.rithmicData.lastBid,
263
+ ask: this.rithmicData.lastAsk,
264
+ volume: 1,
265
+ timestamp: Date.now()
266
+ };
267
+
268
+ // Process tick with appropriate strategy
269
+ if (this.usePythonStrategy && this.pythonBridge && this.rithmicData.lastPrice > 0) {
270
+ // Python/Cython strategy path
271
+ try {
272
+ const result = await this.pythonBridge.processTick(tick);
273
+ if (result && result.signal) {
274
+ // ONLY execute if: no position, no pending order, cooldown passed
275
+ if (!this.hasOpenPosition && !this.pendingOrder && this._canOrder()) {
276
+ if (result.signal.confidence >= 0.5) {
277
+ this._executeSignal(result.signal);
278
+ }
279
+ }
280
+ }
281
+ } catch (error) {
282
+ // Silent fail - don't spam logs
283
+ }
284
+ } else if (this.strategy && this.rithmicData.lastPrice > 0) {
285
+ // JavaScript strategy path
286
+ this.strategy.processTick(tick);
287
+
288
+ // ONLY generate signal if:
289
+ // 1. No open position
290
+ // 2. No pending order
291
+ // 3. Cooldown passed
292
+ if (!this.hasOpenPosition && !this.pendingOrder && this._canOrder()) {
293
+ const signal = this.strategy.generateSignal(this.rithmicData.lastPrice);
294
+ if (signal && signal.confidence >= 0.5) {
295
+ this._executeSignal(signal);
296
+ }
297
+ }
298
+ }
299
+
300
+ // Check risk limits
301
+ this._checkRiskLimits();
302
+ }
303
+
304
+ // Trade handler - pass to strategy for order flow
305
+ async _onTrade(trade) {
306
+ if (!this.running) return;
307
+
308
+ this.rithmicData.lastPrice = trade.price;
309
+
310
+ const tradeData = {
311
+ contract_id: this.symbol,
312
+ price: trade.price,
313
+ size: trade.size || trade.volume || 1,
314
+ volume: trade.size || trade.volume || 1,
315
+ side: trade.side,
316
+ timestamp: trade.timestamp || Date.now()
317
+ };
318
+
319
+ if (this.usePythonStrategy && this.pythonBridge) {
320
+ // Python strategy processes trades as bars
321
+ try {
322
+ await this.pythonBridge.processTick(tradeData);
323
+ } catch (error) {
324
+ // Silent fail
325
+ }
326
+ } else if (this.strategy) {
327
+ this.strategy.onTrade(tradeData);
328
+ }
329
+ }
330
+
331
+ // Fill handler
332
+ async _onFill(fill) {
333
+ if (!this.running) return;
334
+
335
+ const side = fill.side === 'buy' ? 'BUY' : 'SELL';
336
+ const price = fill.fillPrice || fill.avgFillPrice || fill.price;
337
+ const qty = fill.fillSize || fill.totalFillSize || 1;
338
+
339
+ // Determine if this is entry or exit
340
+ const wasFlat = !this.hasOpenPosition;
341
+
342
+ if (wasFlat) {
343
+ // Entry fill
344
+ this.hasOpenPosition = true;
345
+ this.lastEntryPrice = price;
346
+ this.lastEntrySide = side.toLowerCase();
347
+ this.entryTime = Date.now(); // Track entry time for minimum hold
348
+ this.pendingOrder = false;
349
+ this._log('trade', `ENTRY ${side} ${qty}x @${price}`);
350
+ } else {
351
+ // Exit fill - P&L comes from API (PNL_PLANT), not calculated locally
352
+ // Use closedPnL delta from rithmicData which is updated by _onAccountPnL
353
+ const currentClosedPnl = this.rithmicData.pnl.closed || 0;
354
+ const tradePnl = currentClosedPnl - this.stats.lastClosedPnl;
355
+ this.stats.lastClosedPnl = currentClosedPnl;
356
+
357
+ // Calculate hold time
358
+ const holdTimeMs = Date.now() - this.entryTime;
359
+ const holdTimeSec = Math.round(holdTimeMs / 1000);
360
+
361
+ if (tradePnl > 0) {
362
+ this.stats.wins++;
363
+ this._log('fill_win', `EXIT ${side} ${qty}x @${price} | +$${tradePnl.toFixed(2)} | ${holdTimeSec}s`);
364
+ } else {
365
+ this.stats.losses++;
366
+ this._log('fill_loss', `EXIT ${side} ${qty}x @${price} | -$${Math.abs(tradePnl).toFixed(2)} | ${holdTimeSec}s`);
367
+ }
368
+
369
+ // Record trade result in strategy for performance tracking
370
+ if (this.usePythonStrategy && this.pythonBridge) {
371
+ try {
372
+ await this.pythonBridge.recordTradeResult(tradePnl);
373
+ } catch (error) {
374
+ // Silent fail
375
+ }
376
+ } else if (this.strategy && this.strategy.recordTradeResult) {
377
+ this.strategy.recordTradeResult(tradePnl);
378
+ }
379
+
380
+ this.hasOpenPosition = false;
381
+ this.pendingOrder = false;
382
+ this.lastEntryPrice = 0;
383
+ this.lastEntrySide = null;
384
+ this.entryTime = 0;
385
+ }
386
+
387
+ this.stats.trades++;
388
+ this._sendStats();
389
+ }
390
+
391
+ // Reject handler
392
+ _onReject(reject) {
393
+ this.pendingOrder = false;
394
+ this._log('error', `REJECTED: ${reject.reason || reject.text || 'Unknown'}`);
395
+ }
396
+
397
+ // Account P&L from Rithmic
398
+ _onAccountPnL(pnl) {
399
+ if (!this.running) return;
400
+
401
+ this.rithmicData.pnl.open = parseFloat(pnl.openPositionPnl || pnl.openPositionPnL || 0);
402
+ this.rithmicData.pnl.closed = parseFloat(pnl.closedPositionPnl || pnl.closedPositionPnL || 0);
403
+ this.rithmicData.pnl.dayPnl = parseFloat(pnl.dayPnl || 0);
404
+ this.rithmicData.pnl.total = this.rithmicData.pnl.open + this.rithmicData.pnl.closed;
405
+ }
406
+
407
+ // Position P&L from Rithmic
408
+ _onPositionPnL(pnl) {
409
+ if (!this.running) return;
410
+
411
+ const netQty = pnl.netQuantity || 0;
412
+
413
+ if (netQty !== 0) {
414
+ this.rithmicData.position = {
415
+ symbol: pnl.symbol,
416
+ qty: netQty,
417
+ side: netQty > 0 ? 'long' : 'short',
418
+ avgPrice: pnl.avgOpenFillPrice || 0,
419
+ pnl: parseFloat(pnl.openPositionPnl || pnl.openPositionPnL || 0)
420
+ };
421
+ this.hasOpenPosition = true;
422
+ } else {
423
+ // Position closed
424
+ if (this.hasOpenPosition && this.rithmicData.position) {
425
+ const closedPnl = this.rithmicData.pnl.closed - this.stats.lastClosedPnl;
426
+ this.stats.lastClosedPnl = this.rithmicData.pnl.closed;
427
+ }
428
+ this.rithmicData.position = null;
429
+ this.hasOpenPosition = false;
430
+ }
431
+ }
432
+
433
+ // Execute signal
434
+ async _executeSignal(signal) {
435
+ if (!this.rithmicAdapter || !this.running) return;
436
+ if (this.hasOpenPosition || this.pendingOrder) return;
437
+
438
+ const side = signal.direction === 'long' ? 'buy' : 'sell';
439
+ const qty = this.config.contracts || 1;
440
+
441
+ this.pendingOrder = true;
442
+ this.lastOrderTime = Date.now();
443
+
444
+ const confidence = (signal.confidence * 100).toFixed(0);
445
+ this._log('info', `SIGNAL: ${side.toUpperCase()} (${confidence}% conf)`);
446
+
447
+ try {
448
+ await this.rithmicAdapter.placeMarketOrder(
449
+ this.rithmicAccountId,
450
+ this.symbol,
451
+ side,
452
+ qty,
453
+ this.exchange
454
+ );
455
+ } catch (error) {
456
+ this.pendingOrder = false;
457
+ this._log('error', `ORDER FAILED: ${error.message}`);
458
+ }
459
+ }
460
+
461
+ // Check daily risk limits
462
+ _checkRiskLimits() {
463
+ const target = this.config.dailyTarget || 200;
464
+ const maxRisk = this.config.maxRisk || 100;
465
+ const totalPnl = this.rithmicData.pnl.total;
466
+
467
+ if (totalPnl >= target) {
468
+ this._log('success', `TARGET REACHED: +$${totalPnl.toFixed(2)}`);
469
+ this.stop();
470
+ }
471
+
472
+ if (totalPnl <= -maxRisk) {
473
+ this._log('error', `MAX LOSS: -$${Math.abs(totalPnl).toFixed(2)}`);
474
+ this.stop();
475
+ }
476
+ }
477
+
478
+ _canOrder() {
479
+ return Date.now() - this.lastOrderTime >= this.orderCooldownMs;
480
+ }
481
+
482
+ _canExit() {
483
+ // Minimum 10 seconds hold time before allowing exit
484
+ if (!this.hasOpenPosition || this.entryTime === 0) return true;
485
+ const holdTimeMs = Date.now() - this.entryTime;
486
+ return holdTimeMs >= this.minHoldTimeMs;
487
+ }
488
+
489
+ _getHoldTime() {
490
+ if (!this.entryTime) return 0;
491
+ return Math.round((Date.now() - this.entryTime) / 1000);
492
+ }
493
+
494
+ _sendStats() {
495
+ const totalTrades = this.stats.wins + this.stats.losses;
496
+ const winRate = totalTrades > 0 ? (this.stats.wins / totalTrades * 100) : 0;
497
+
498
+ this.callbacks.onStats({
499
+ accountName: this.accountDisplayName,
500
+ realTimePnL: {
501
+ openPnL: this.rithmicData.pnl.open,
502
+ closedPnL: this.rithmicData.pnl.closed,
503
+ totalPnL: this.rithmicData.pnl.total,
504
+ dayPnL: this.rithmicData.pnl.dayPnl
505
+ },
506
+ position: this.rithmicData.position,
507
+ hasPosition: this.hasOpenPosition,
508
+ holdTime: this._getHoldTime(),
509
+ canExit: this._canExit(),
510
+ trades: this.stats.trades,
511
+ wins: this.stats.wins,
512
+ losses: this.stats.losses,
513
+ winRate: winRate.toFixed(1),
514
+ lastPrice: this.rithmicData.lastPrice
515
+ });
516
+ }
517
+
518
+ _log(type, message) {
519
+ this.callbacks.onLog({ type, message, timestamp: Date.now() });
520
+ }
521
+
522
+ _getSymbol(symbol) {
523
+ if (/^[A-Z]{2,4}[FGHJKMNQUVXZ]\d{1,2}$/.test(symbol?.toUpperCase())) {
524
+ return symbol.toUpperCase();
525
+ }
526
+ const base = symbol?.replace(/[^A-Z]/gi, '').toUpperCase() || 'MNQ';
527
+ return getFM(base);
528
+ }
529
+
530
+ _getExchange(symbol) {
531
+ const base = symbol?.replace(/[FGHJKMNQUVXZ]\d{1,2}$/, '').toUpperCase();
532
+ if (['ZB', 'ZN', 'ZF', 'ZT', 'ZC', 'ZS', 'ZW', 'ZL', 'ZM', 'ZO', 'YM', 'MYM', 'TN', 'ZQ'].includes(base)) return 'CBOT';
533
+ if (['CL', 'MCL', 'NG', 'HO', 'RB', 'PA', 'PL', 'BZ'].includes(base)) return 'NYMEX';
534
+ if (['GC', 'MGC', 'SI', 'SIL', 'HG', 'MHG', '1OZ'].includes(base)) return 'COMEX';
535
+ return 'CME';
536
+ }
537
+ }
538
+
539
+ module.exports = { C1 };