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.
Files changed (138) 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 +235 -0
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +60 -291
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -361
  71. package/src/services/rithmic/protobuf.js +5 -195
  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/index.js +0 -526
  106. package/src/services/ai/proxy-install.js +0 -249
  107. package/src/services/ai/proxy-manager.js +0 -494
  108. package/src/services/ai/proxy-remote.js +0 -161
  109. package/src/services/ai/strategy-supervisor.js +0 -1312
  110. package/src/services/ai/supervisor-data.js +0 -195
  111. package/src/services/ai/supervisor-optimize.js +0 -215
  112. package/src/services/ai/supervisor-sync.js +0 -178
  113. package/src/services/ai/supervisor-utils.js +0 -158
  114. package/src/services/ai/supervisor.js +0 -484
  115. package/src/services/ai/validation.js +0 -250
  116. package/src/services/hqx-server-events.js +0 -110
  117. package/src/services/hqx-server-handlers.js +0 -217
  118. package/src/services/hqx-server-latency.js +0 -136
  119. package/src/services/hqx-server.js +0 -403
  120. package/src/services/position-constants.js +0 -28
  121. package/src/services/position-manager.js +0 -528
  122. package/src/services/position-momentum.js +0 -206
  123. package/src/services/projectx/accounts.js +0 -142
  124. package/src/services/projectx/index.js +0 -443
  125. package/src/services/projectx/market.js +0 -172
  126. package/src/services/projectx/stats.js +0 -110
  127. package/src/services/projectx/trading.js +0 -180
  128. package/src/services/rithmic/latency-tracker.js +0 -182
  129. package/src/services/rithmic/market-data.js +0 -549
  130. package/src/services/rithmic/specs.js +0 -146
  131. package/src/services/rithmic/trade-history.js +0 -254
  132. package/src/services/session-history.js +0 -475
  133. package/src/services/strategy/hft-tick.js +0 -507
  134. package/src/services/strategy/recovery-math.js +0 -402
  135. package/src/services/tradovate/constants.js +0 -109
  136. package/src/services/tradovate/index.js +0 -505
  137. package/src/services/tradovate/market.js +0 -47
  138. 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 };