hedgequantx 2.5.44 → 2.6.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.
- package/bin/cli.js +11 -0
- package/package.json +1 -1
- package/src/app.js +11 -0
- package/src/config/settings.js +29 -0
- package/src/pages/algo/one-account.js +241 -47
- package/src/services/position-manager.js +927 -0
- package/src/services/rithmic/connection.js +201 -1
- package/src/services/rithmic/handlers.js +341 -5
- package/src/services/rithmic/index.js +75 -3
- package/src/services/rithmic/orders.js +193 -1
- package/src/services/rithmic/protobuf.js +171 -2
- package/src/services/session.js +27 -5
package/bin/cli.js
CHANGED
|
@@ -51,6 +51,17 @@ program
|
|
|
51
51
|
console.log(`HedgeQuantX CLI v${pkg.version}`);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
program
|
|
55
|
+
.command('test-latency')
|
|
56
|
+
.description('Test order latency (requires active Rithmic connection)')
|
|
57
|
+
.option('-s, --symbol <symbol>', 'Symbol to test (e.g., NQ, ES, MNQ)', 'NQ')
|
|
58
|
+
.option('-c, --count <count>', 'Number of test iterations', '10')
|
|
59
|
+
.option('-p, --propfirm <propfirm>', 'Propfirm to use', 'apex')
|
|
60
|
+
.action(async (options) => {
|
|
61
|
+
const { runLatencyTest } = require('../src/commands/test-latency');
|
|
62
|
+
await runLatencyTest(options);
|
|
63
|
+
});
|
|
64
|
+
|
|
54
65
|
// Handle -u flag before parsing commands
|
|
55
66
|
if (process.argv.includes('-u') || process.argv.includes('--update')) {
|
|
56
67
|
const { execSync } = require('child_process');
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -256,6 +256,17 @@ const run = async () => {
|
|
|
256
256
|
log.info('Starting HQX CLI');
|
|
257
257
|
await bannerClosed();
|
|
258
258
|
|
|
259
|
+
// PRE-LOAD: Initialize Rithmic protobuf definitions at startup
|
|
260
|
+
// This removes ~100ms latency from first order
|
|
261
|
+
const { proto } = require('./services/rithmic/protobuf');
|
|
262
|
+
const protoSpinner = ora({ text: 'INITIALIZING TRADING ENGINE...', color: 'cyan' }).start();
|
|
263
|
+
try {
|
|
264
|
+
await proto.load();
|
|
265
|
+
protoSpinner.succeed('TRADING ENGINE READY');
|
|
266
|
+
} catch (e) {
|
|
267
|
+
protoSpinner.warn('Trading engine partial init');
|
|
268
|
+
}
|
|
269
|
+
|
|
259
270
|
// Restore session
|
|
260
271
|
const spinner = ora({ text: 'RESTORING SESSION...', color: 'yellow' }).start();
|
|
261
272
|
const restored = await connections.restoreFromStorage();
|
package/src/config/settings.js
CHANGED
|
@@ -136,6 +136,34 @@ const CACHE = {
|
|
|
136
136
|
STATS_TTL: 60000, // 1 minute
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
+
// ==================== FAST SCALPING (Ultra-Low Latency) ====================
|
|
140
|
+
const FAST_SCALPING = {
|
|
141
|
+
// Hold constraints (prop firm rules - NON-NEGOTIABLE)
|
|
142
|
+
MIN_HOLD_MS: 10000, // 10 seconds minimum hold
|
|
143
|
+
MAX_HOLD_MS: 60000, // 60 seconds failsafe (force exit if stuck)
|
|
144
|
+
|
|
145
|
+
// Exit targets (in ticks) - defaults, override per symbol
|
|
146
|
+
TARGET_TICKS: 16, // Take profit target
|
|
147
|
+
STOP_TICKS: 20, // Stop loss
|
|
148
|
+
|
|
149
|
+
// Trailing stop (activates after MIN_HOLD + profit threshold)
|
|
150
|
+
TRAILING_ACTIVATION_TICKS: 8, // Start trailing after +8 ticks profit
|
|
151
|
+
TRAILING_DISTANCE_TICKS: 4, // Trail 4 ticks behind high/low
|
|
152
|
+
|
|
153
|
+
// Position monitoring
|
|
154
|
+
MONITOR_INTERVAL_MS: 100, // Check position every 100ms after hold
|
|
155
|
+
|
|
156
|
+
// Momentum thresholds (cumulative delta over 5 seconds)
|
|
157
|
+
MOMENTUM_STRONG_THRESHOLD: 50, // Delta > 50 = strong momentum, HOLD
|
|
158
|
+
MOMENTUM_WEAK_THRESHOLD: 20, // Delta < 20 = weak momentum, consider EXIT
|
|
159
|
+
MOMENTUM_WINDOW_MS: 5000, // 5 second window for momentum calc
|
|
160
|
+
|
|
161
|
+
// Latency monitoring
|
|
162
|
+
LOG_LATENCY: true,
|
|
163
|
+
LATENCY_TARGET_MS: 50, // Target entry latency
|
|
164
|
+
LATENCY_WARN_MS: 100, // Warn if entry takes > 100ms
|
|
165
|
+
};
|
|
166
|
+
|
|
139
167
|
// ==================== DEBUG ====================
|
|
140
168
|
const DEBUG = {
|
|
141
169
|
get enabled() {
|
|
@@ -152,4 +180,5 @@ module.exports = {
|
|
|
152
180
|
HQX_SERVER,
|
|
153
181
|
CACHE,
|
|
154
182
|
DEBUG,
|
|
183
|
+
FAST_SCALPING,
|
|
155
184
|
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One Account Mode - HQX Ultra Scalping
|
|
3
|
+
*
|
|
4
|
+
* FAST PATH: Rithmic direct execution (~10-50ms latency)
|
|
5
|
+
* SLOW PATH: ProjectX/Tradovate HTTP REST (~50-150ms latency)
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
const chalk = require('chalk');
|
|
@@ -10,6 +13,8 @@ const { connections } = require('../../services');
|
|
|
10
13
|
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
11
14
|
const { prompts } = require('../../utils');
|
|
12
15
|
const { checkMarketHours } = require('../../services/projectx/market');
|
|
16
|
+
const { FAST_SCALPING } = require('../../config/settings');
|
|
17
|
+
const { PositionManager } = require('../../services/position-manager');
|
|
13
18
|
|
|
14
19
|
// Strategy & Market Data (obfuscated)
|
|
15
20
|
const { M1 } = require('../../../dist/lib/m/s1');
|
|
@@ -168,23 +173,42 @@ const configureAlgo = async (account, contract) => {
|
|
|
168
173
|
return { contracts, dailyTarget, maxRisk, showName };
|
|
169
174
|
};
|
|
170
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Check if service supports fast path (Rithmic direct)
|
|
178
|
+
* @param {Object} service - Trading service
|
|
179
|
+
* @returns {boolean}
|
|
180
|
+
*/
|
|
181
|
+
const isRithmicFastPath = (service) => {
|
|
182
|
+
return typeof service.fastEntry === 'function' &&
|
|
183
|
+
typeof service.fastExit === 'function' &&
|
|
184
|
+
service.orderConn?.isConnected;
|
|
185
|
+
};
|
|
186
|
+
|
|
171
187
|
/**
|
|
172
188
|
* Launch algo trading - HQX Ultra Scalping Strategy
|
|
173
189
|
* Real-time market data + Strategy signals + Auto order execution
|
|
174
190
|
* AI Supervision: All connected agents monitor and supervise trading
|
|
191
|
+
*
|
|
192
|
+
* FAST PATH (Rithmic): Uses fastEntry() for ~10-50ms latency
|
|
193
|
+
* SLOW PATH (ProjectX): Uses placeOrder() for ~50-150ms latency
|
|
175
194
|
*/
|
|
176
195
|
const launchAlgo = async (service, account, contract, config) => {
|
|
177
196
|
const { contracts, dailyTarget, maxRisk, showName } = config;
|
|
178
197
|
|
|
179
|
-
// Use RAW API fields
|
|
198
|
+
// Use RAW API fields only - NO hardcoded fallbacks
|
|
180
199
|
const accountName = showName
|
|
181
200
|
? (account.accountName || account.rithmicAccountId || account.accountId)
|
|
182
201
|
: 'HQX *****';
|
|
183
202
|
const symbolName = contract.name;
|
|
184
203
|
const contractId = contract.id;
|
|
185
204
|
const connectionType = account.platform || 'ProjectX';
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
|
|
206
|
+
// Tick size/value from API - null if not available (RULES.md compliant)
|
|
207
|
+
const tickSize = contract.tickSize ?? null;
|
|
208
|
+
const tickValue = contract.tickValue ?? null;
|
|
209
|
+
|
|
210
|
+
// Determine execution path
|
|
211
|
+
const useFastPath = isRithmicFastPath(service);
|
|
188
212
|
|
|
189
213
|
const ui = new AlgoUI({ subtitle: 'HQX ULTRA SCALPING', mode: 'one-account' });
|
|
190
214
|
|
|
@@ -204,7 +228,12 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
204
228
|
connected: false,
|
|
205
229
|
startTime: Date.now(),
|
|
206
230
|
aiSupervision: false,
|
|
207
|
-
aiMode: null
|
|
231
|
+
aiMode: null,
|
|
232
|
+
// Fast path stats
|
|
233
|
+
fastPath: useFastPath,
|
|
234
|
+
avgEntryLatency: 0,
|
|
235
|
+
avgFillLatency: 0,
|
|
236
|
+
entryLatencies: [],
|
|
208
237
|
};
|
|
209
238
|
|
|
210
239
|
let running = true;
|
|
@@ -216,9 +245,76 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
216
245
|
let lastTradeCount = 0; // Track number of trades from API
|
|
217
246
|
let lastPositionQty = 0; // Track position changes
|
|
218
247
|
|
|
219
|
-
// Initialize Strategy (M1 is singleton instance)
|
|
248
|
+
// Initialize Strategy FIRST (M1 is singleton instance)
|
|
249
|
+
// Strategy needs to be initialized before PositionManager so it can access math models
|
|
220
250
|
const strategy = M1;
|
|
221
|
-
|
|
251
|
+
|
|
252
|
+
// Only initialize strategy if we have tick data from API
|
|
253
|
+
if (tickSize !== null && tickValue !== null) {
|
|
254
|
+
strategy.initialize(contractId, tickSize, tickValue);
|
|
255
|
+
} else {
|
|
256
|
+
algoLogger.warn(ui, 'WARNING', 'Tick size/value not available from API');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Initialize Position Manager for fast path
|
|
260
|
+
let positionManager = null;
|
|
261
|
+
if (useFastPath) {
|
|
262
|
+
// Pass strategy reference so PositionManager can access math models
|
|
263
|
+
positionManager = new PositionManager(service, strategy);
|
|
264
|
+
|
|
265
|
+
// Set contract info from API (NOT hardcoded)
|
|
266
|
+
if (tickSize !== null && tickValue !== null) {
|
|
267
|
+
positionManager.setContractInfo(symbolName, {
|
|
268
|
+
tickSize,
|
|
269
|
+
tickValue,
|
|
270
|
+
contractId,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
positionManager.start();
|
|
275
|
+
|
|
276
|
+
// Listen for position manager events
|
|
277
|
+
positionManager.on('entryFilled', ({ orderTag, position, fillLatencyMs }) => {
|
|
278
|
+
stats.entryLatencies.push(fillLatencyMs);
|
|
279
|
+
stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
|
|
280
|
+
algoLogger.info(ui, 'FAST FILL', `${fillLatencyMs}ms | avg=${stats.avgFillLatency.toFixed(1)}ms`);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
positionManager.on('exitFilled', ({ orderTag, exitPrice, pnlTicks, holdDurationMs }) => {
|
|
284
|
+
// Calculate PnL in dollars only if tickValue is available from API
|
|
285
|
+
if (pnlTicks !== null && tickValue !== null) {
|
|
286
|
+
const pnlDollars = pnlTicks * tickValue;
|
|
287
|
+
if (pnlDollars >= 0) {
|
|
288
|
+
stats.wins++;
|
|
289
|
+
algoLogger.targetHit(ui, symbolName, exitPrice, pnlDollars);
|
|
290
|
+
} else {
|
|
291
|
+
stats.losses++;
|
|
292
|
+
algoLogger.stopHit(ui, symbolName, exitPrice, Math.abs(pnlDollars));
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
// Log with ticks only if tickValue unavailable
|
|
296
|
+
if (pnlTicks !== null && pnlTicks >= 0) {
|
|
297
|
+
stats.wins++;
|
|
298
|
+
algoLogger.info(ui, 'TARGET', `+${pnlTicks} ticks`);
|
|
299
|
+
} else if (pnlTicks !== null) {
|
|
300
|
+
stats.losses++;
|
|
301
|
+
algoLogger.info(ui, 'STOP', `${pnlTicks} ticks`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
stats.trades++;
|
|
305
|
+
currentPosition = 0;
|
|
306
|
+
pendingOrder = false;
|
|
307
|
+
algoLogger.info(ui, 'HOLD TIME', `${(holdDurationMs / 1000).toFixed(1)}s`);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
positionManager.on('holdComplete', ({ orderTag, position }) => {
|
|
311
|
+
algoLogger.info(ui, 'HOLD COMPLETE', `${FAST_SCALPING.MIN_HOLD_MS / 1000}s minimum reached`);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
positionManager.on('exitOrderFired', ({ orderTag, exitReason, latencyMs }) => {
|
|
315
|
+
algoLogger.info(ui, 'EXIT FIRED', `${exitReason.reason} | ${latencyMs.toFixed(1)}ms`);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
222
318
|
|
|
223
319
|
// Initialize AI Strategy Supervisor - agents observe, learn & optimize
|
|
224
320
|
const aiAgents = aiService.getAgents();
|
|
@@ -245,10 +341,23 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
245
341
|
algoLogger.info(ui, 'AI SUPERVISION', `${aiAgents.length} agent(s) - ${stats.aiMode} mode - LEARNING ACTIVE`);
|
|
246
342
|
}
|
|
247
343
|
|
|
344
|
+
// Log execution path
|
|
345
|
+
if (useFastPath) {
|
|
346
|
+
algoLogger.info(ui, 'FAST PATH', `Rithmic direct | Target <${FAST_SCALPING.LATENCY_TARGET_MS}ms | Hold ${FAST_SCALPING.MIN_HOLD_MS / 1000}s`);
|
|
347
|
+
} else {
|
|
348
|
+
algoLogger.info(ui, 'SLOW PATH', `HTTP REST | Bracket orders enabled`);
|
|
349
|
+
}
|
|
350
|
+
|
|
248
351
|
// Handle strategy signals
|
|
249
352
|
strategy.on('signal', async (signal) => {
|
|
250
353
|
if (!running || pendingOrder || currentPosition !== 0) return;
|
|
251
354
|
|
|
355
|
+
// Fast path: check if position manager allows new entry
|
|
356
|
+
if (useFastPath && positionManager && !positionManager.canEnter(symbolName)) {
|
|
357
|
+
algoLogger.info(ui, 'BLOCKED', 'Existing position in symbol');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
252
361
|
const { side, direction, entry, stopLoss, takeProfit, confidence } = signal;
|
|
253
362
|
|
|
254
363
|
// Feed signal to AI supervisor (agents observe the signal)
|
|
@@ -282,58 +391,116 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
282
391
|
|
|
283
392
|
// Place order via API
|
|
284
393
|
pendingOrder = true;
|
|
394
|
+
const orderSide = direction === 'long' ? 0 : 1; // 0=Buy, 1=Sell
|
|
395
|
+
|
|
285
396
|
try {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
stats.trades++;
|
|
298
|
-
const sideStr = direction === 'long' ? 'BUY' : 'SELL';
|
|
299
|
-
const positionSide = direction === 'long' ? 'LONG' : 'SHORT';
|
|
397
|
+
// ═══════════════════════════════════════════════════════════════
|
|
398
|
+
// FAST PATH: Rithmic direct execution (~10-50ms)
|
|
399
|
+
// ═══════════════════════════════════════════════════════════════
|
|
400
|
+
if (useFastPath && positionManager) {
|
|
401
|
+
const orderData = {
|
|
402
|
+
accountId: account.accountId,
|
|
403
|
+
symbol: symbolName,
|
|
404
|
+
exchange: contract.exchange || 'CME',
|
|
405
|
+
size: contracts,
|
|
406
|
+
side: orderSide,
|
|
407
|
+
};
|
|
300
408
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
algoLogger.positionOpened(ui, symbolName, positionSide, contracts, entry);
|
|
304
|
-
algoLogger.entryConfirmed(ui, sideStr, contracts, symbolName, entry);
|
|
409
|
+
// Fire-and-forget entry (no await on fill)
|
|
410
|
+
const entryResult = service.fastEntry(orderData);
|
|
305
411
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
412
|
+
if (entryResult.success) {
|
|
413
|
+
// Register with position manager for lifecycle tracking
|
|
414
|
+
// Pass contract info from API (NOT hardcoded)
|
|
415
|
+
const contractInfo = {
|
|
416
|
+
tickSize,
|
|
417
|
+
tickValue,
|
|
418
|
+
contractId,
|
|
419
|
+
};
|
|
420
|
+
positionManager.registerEntry(entryResult, orderData, contractInfo);
|
|
421
|
+
|
|
422
|
+
currentPosition = direction === 'long' ? contracts : -contracts;
|
|
423
|
+
const sideStr = direction === 'long' ? 'BUY' : 'SELL';
|
|
317
424
|
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
size: contracts,
|
|
325
|
-
limitPrice: takeProfit
|
|
326
|
-
});
|
|
425
|
+
// Log with latency
|
|
426
|
+
const latencyColor = entryResult.latencyMs < FAST_SCALPING.LATENCY_TARGET_MS
|
|
427
|
+
? chalk.green
|
|
428
|
+
: entryResult.latencyMs < FAST_SCALPING.LATENCY_WARN_MS
|
|
429
|
+
? chalk.yellow
|
|
430
|
+
: chalk.red;
|
|
327
431
|
|
|
328
|
-
|
|
432
|
+
stats.avgEntryLatency = stats.entryLatencies.length > 0
|
|
433
|
+
? (stats.avgEntryLatency * stats.entryLatencies.length + entryResult.latencyMs) / (stats.entryLatencies.length + 1)
|
|
434
|
+
: entryResult.latencyMs;
|
|
435
|
+
|
|
436
|
+
algoLogger.info(ui, 'FAST ENTRY', `${sideStr} ${contracts}x ${symbolName} | ${latencyColor(entryResult.latencyMs.toFixed(2) + 'ms')}`);
|
|
437
|
+
algoLogger.info(ui, 'HOLD START', `Min ${FAST_SCALPING.MIN_HOLD_MS / 1000}s before exit`);
|
|
438
|
+
|
|
439
|
+
// Note: NO bracket orders in fast path
|
|
440
|
+
// PositionManager handles exit logic after 10s hold
|
|
441
|
+
|
|
442
|
+
} else {
|
|
443
|
+
algoLogger.orderRejected(ui, symbolName, entryResult.error || 'Fast entry failed');
|
|
444
|
+
pendingOrder = false;
|
|
329
445
|
}
|
|
446
|
+
|
|
447
|
+
// ═══════════════════════════════════════════════════════════════
|
|
448
|
+
// SLOW PATH: ProjectX/Tradovate HTTP REST (~50-150ms)
|
|
449
|
+
// ═══════════════════════════════════════════════════════════════
|
|
330
450
|
} else {
|
|
331
|
-
|
|
451
|
+
const orderResult = await service.placeOrder({
|
|
452
|
+
accountId: account.accountId,
|
|
453
|
+
contractId: contractId,
|
|
454
|
+
type: 2, // Market order
|
|
455
|
+
side: orderSide,
|
|
456
|
+
size: contracts
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
if (orderResult.success) {
|
|
460
|
+
currentPosition = direction === 'long' ? contracts : -contracts;
|
|
461
|
+
stats.trades++;
|
|
462
|
+
const sideStr = direction === 'long' ? 'BUY' : 'SELL';
|
|
463
|
+
const positionSide = direction === 'long' ? 'LONG' : 'SHORT';
|
|
464
|
+
|
|
465
|
+
algoLogger.orderSubmitted(ui, symbolName, sideStr, contracts, entry);
|
|
466
|
+
algoLogger.orderFilled(ui, symbolName, sideStr, contracts, entry);
|
|
467
|
+
algoLogger.positionOpened(ui, symbolName, positionSide, contracts, entry);
|
|
468
|
+
algoLogger.entryConfirmed(ui, sideStr, contracts, symbolName, entry);
|
|
469
|
+
|
|
470
|
+
// Place bracket orders (SL/TP) - SLOW PATH ONLY
|
|
471
|
+
if (stopLoss && takeProfit) {
|
|
472
|
+
// Stop Loss
|
|
473
|
+
await service.placeOrder({
|
|
474
|
+
accountId: account.accountId,
|
|
475
|
+
contractId: contractId,
|
|
476
|
+
type: 4, // Stop order
|
|
477
|
+
side: direction === 'long' ? 1 : 0, // Opposite side
|
|
478
|
+
size: contracts,
|
|
479
|
+
stopPrice: stopLoss
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Take Profit
|
|
483
|
+
await service.placeOrder({
|
|
484
|
+
accountId: account.accountId,
|
|
485
|
+
contractId: contractId,
|
|
486
|
+
type: 1, // Limit order
|
|
487
|
+
side: direction === 'long' ? 1 : 0,
|
|
488
|
+
size: contracts,
|
|
489
|
+
limitPrice: takeProfit
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
algoLogger.stopsSet(ui, stopLoss, takeProfit);
|
|
493
|
+
}
|
|
494
|
+
pendingOrder = false;
|
|
495
|
+
} else {
|
|
496
|
+
algoLogger.orderRejected(ui, symbolName, orderResult.error || 'Unknown error');
|
|
497
|
+
pendingOrder = false;
|
|
498
|
+
}
|
|
332
499
|
}
|
|
333
500
|
} catch (e) {
|
|
334
501
|
algoLogger.error(ui, 'ORDER ERROR', e.message);
|
|
502
|
+
pendingOrder = false;
|
|
335
503
|
}
|
|
336
|
-
pendingOrder = false;
|
|
337
504
|
});
|
|
338
505
|
|
|
339
506
|
// Handle market data ticks
|
|
@@ -368,6 +535,27 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
368
535
|
|
|
369
536
|
strategy.processTick(tickData);
|
|
370
537
|
|
|
538
|
+
// Feed price to position manager for exit monitoring (fast path)
|
|
539
|
+
if (useFastPath && positionManager) {
|
|
540
|
+
// Update latest price for position monitoring
|
|
541
|
+
service.emit('priceUpdate', {
|
|
542
|
+
symbol: symbolName,
|
|
543
|
+
price: tickData.price,
|
|
544
|
+
timestamp: tickData.timestamp,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Get momentum data from strategy if available
|
|
548
|
+
const modelValues = strategy.getModelValues?.(contractId);
|
|
549
|
+
if (modelValues) {
|
|
550
|
+
positionManager.updateMomentum(symbolName, {
|
|
551
|
+
ofi: modelValues.ofi || 0,
|
|
552
|
+
zscore: modelValues.zscore || 0,
|
|
553
|
+
delta: modelValues.delta || 0,
|
|
554
|
+
timestamp: tickData.timestamp,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
371
559
|
stats.latency = Date.now() - latencyStart;
|
|
372
560
|
|
|
373
561
|
// Heartbeat every 30 seconds (smart log instead of tick count)
|
|
@@ -576,6 +764,12 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
576
764
|
clearInterval(refreshInterval);
|
|
577
765
|
clearInterval(pnlInterval);
|
|
578
766
|
|
|
767
|
+
// Stop Position Manager (fast path)
|
|
768
|
+
if (positionManager) {
|
|
769
|
+
positionManager.stop();
|
|
770
|
+
positionManager = null;
|
|
771
|
+
}
|
|
772
|
+
|
|
579
773
|
// Stop AI Supervisor and get learning summary
|
|
580
774
|
if (stats.aiSupervision) {
|
|
581
775
|
const aiSummary = StrategySupervisor.stop();
|