hedgequantx 2.7.14 → 2.7.16
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/lib/data.js +245 -471
- package/src/lib/m/s1-models.js +173 -0
- package/src/lib/m/s1.js +354 -735
- package/src/services/rithmic/accounts.js +22 -7
- 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/core.js
DELETED
|
@@ -1,539 +0,0 @@
|
|
|
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 };
|