hedgequantx 2.6.162 → 2.6.163

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.
@@ -20,6 +20,7 @@
20
20
 
21
21
  const EventEmitter = require('events');
22
22
  const { logger } = require('../../utils/logger');
23
+ const { calculateSignal, buildSignal } = require('./hft-signal-calc');
23
24
 
24
25
  const log = logger.scope('HFT');
25
26
 
@@ -306,23 +307,49 @@ class HFTTickStrategy extends EventEmitter {
306
307
  * Check for trading signal
307
308
  */
308
309
  _checkSignal(tick) {
309
- // Need minimum data
310
310
  if (this.tickCount < 50) return;
311
311
 
312
- // Cooldown check
313
312
  const now = Date.now();
314
313
  if (now - this.lastSignalTime < this.cooldownMs) return;
315
314
 
316
- // Calculate composite score
317
- const { direction, confidence, scores } = this._calculateSignal();
315
+ const state = {
316
+ ofiValue: this.ofiValue,
317
+ zscore: this.zscore,
318
+ momentum: this.momentum,
319
+ buyVolume: this.buyVolume,
320
+ sellVolume: this.sellVolume,
321
+ cumulativeDelta: this.cumulativeDelta,
322
+ };
323
+
324
+ const config = {
325
+ ofiThreshold: this.ofiThreshold,
326
+ zscoreThreshold: this.zscoreThreshold,
327
+ momentumThreshold: this.momentumThreshold,
328
+ minConfidence: this.minConfidence,
329
+ };
330
+
331
+ const { direction, confidence, scores } = calculateSignal(state, config);
318
332
 
319
333
  if (direction === 'none' || confidence < this.minConfidence) return;
320
334
 
321
- // Generate signal
322
335
  this.signalCount++;
323
336
  this.lastSignalTime = now;
324
337
 
325
- const signal = this._buildSignal(direction, confidence, scores, tick);
338
+ const signalState = {
339
+ ...state,
340
+ std: this.std,
341
+ tickCount: this.tickCount,
342
+ signalCount: this.signalCount,
343
+ contractId: this.contractId,
344
+ tickSize: this.tickSize,
345
+ };
346
+
347
+ const signalConfig = {
348
+ baseStopTicks: this.baseStopTicks,
349
+ baseTargetTicks: this.baseTargetTicks,
350
+ };
351
+
352
+ const signal = buildSignal(direction, confidence, scores, tick, signalState, signalConfig);
326
353
 
327
354
  log.info(`SIGNAL #${this.signalCount}: ${direction.toUpperCase()} @ ${tick.price} | ` +
328
355
  `OFI=${this.ofiValue.toFixed(2)} Z=${this.zscore.toFixed(2)} Mom=${this.momentum.toFixed(1)} | ` +
@@ -331,133 +358,6 @@ class HFTTickStrategy extends EventEmitter {
331
358
  this.emit('signal', signal);
332
359
  }
333
360
 
334
- /**
335
- * Calculate trading signal from all models
336
- */
337
- _calculateSignal() {
338
- let direction = 'none';
339
- let confidence = 0;
340
-
341
- const scores = {
342
- ofi: 0,
343
- zscore: 0,
344
- momentum: 0,
345
- delta: 0,
346
- composite: 0,
347
- };
348
-
349
- // === MODEL 1: OFI ===
350
- const absOfi = Math.abs(this.ofiValue);
351
- if (absOfi > this.ofiThreshold) {
352
- scores.ofi = Math.min(1.0, absOfi / 0.6);
353
- }
354
-
355
- // === MODEL 2: Z-Score Mean Reversion ===
356
- const absZ = Math.abs(this.zscore);
357
- if (absZ > this.zscoreThreshold) {
358
- scores.zscore = Math.min(1.0, absZ / 3.0);
359
- }
360
-
361
- // === MODEL 3: Momentum ===
362
- const absMom = Math.abs(this.momentum);
363
- if (absMom > this.momentumThreshold) {
364
- scores.momentum = Math.min(1.0, absMom / 3.0);
365
- }
366
-
367
- // === MODEL 4: Delta ===
368
- const totalVol = this.buyVolume + this.sellVolume;
369
- if (totalVol > 0) {
370
- const deltaRatio = this.cumulativeDelta / totalVol;
371
- scores.delta = Math.min(1.0, Math.abs(deltaRatio) * 2);
372
- }
373
-
374
- // === COMPOSITE SCORE ===
375
- scores.composite =
376
- scores.ofi * 0.35 + // OFI: 35%
377
- scores.zscore * 0.25 + // Z-Score: 25%
378
- scores.momentum * 0.20 + // Momentum: 20%
379
- scores.delta * 0.20; // Delta: 20%
380
-
381
- confidence = scores.composite;
382
-
383
- // === DETERMINE DIRECTION ===
384
- // Mean reversion: go opposite to z-score
385
- // Momentum: confirm with OFI and delta
386
-
387
- if (scores.composite >= this.minConfidence) {
388
- // Primary: Mean reversion
389
- if (absZ > this.zscoreThreshold) {
390
- direction = this.zscore > 0 ? 'short' : 'long';
391
-
392
- // Confirm with OFI
393
- const ofiConfirms =
394
- (direction === 'long' && this.ofiValue > 0) ||
395
- (direction === 'short' && this.ofiValue < 0);
396
-
397
- if (ofiConfirms) {
398
- confidence += 0.1;
399
- } else if (Math.abs(this.ofiValue) > 0.2) {
400
- // OFI contradicts - reduce confidence
401
- confidence -= 0.15;
402
- }
403
- }
404
- // Fallback: Momentum breakout
405
- else if (absMom > this.momentumThreshold * 2 && absOfi > this.ofiThreshold) {
406
- direction = this.momentum > 0 ? 'long' : 'short';
407
- // Must be confirmed by OFI
408
- if ((direction === 'long' && this.ofiValue < 0) ||
409
- (direction === 'short' && this.ofiValue > 0)) {
410
- direction = 'none';
411
- }
412
- }
413
- }
414
-
415
- confidence = Math.min(1.0, Math.max(0, confidence));
416
-
417
- return { direction, confidence, scores };
418
- }
419
-
420
- /**
421
- * Build signal object
422
- */
423
- _buildSignal(direction, confidence, scores, tick) {
424
- const entry = tick.price;
425
- const isLong = direction === 'long';
426
-
427
- // Adaptive stops based on volatility (std dev)
428
- const volMult = Math.max(0.5, Math.min(2.0, this.std / this.tickSize / 4));
429
- const stopTicks = Math.round(this.baseStopTicks * volMult);
430
- const targetTicks = Math.round(this.baseTargetTicks * volMult);
431
-
432
- const stopLoss = isLong
433
- ? entry - stopTicks * this.tickSize
434
- : entry + stopTicks * this.tickSize;
435
-
436
- const takeProfit = isLong
437
- ? entry + targetTicks * this.tickSize
438
- : entry - targetTicks * this.tickSize;
439
-
440
- return {
441
- id: `hft-${Date.now()}-${this.signalCount}`,
442
- timestamp: Date.now(),
443
- contractId: this.contractId,
444
- direction,
445
- side: isLong ? 0 : 1,
446
- entry,
447
- stopLoss,
448
- takeProfit,
449
- confidence,
450
- scores,
451
- // Model values for logging
452
- ofi: this.ofiValue,
453
- zscore: this.zscore,
454
- momentum: this.momentum,
455
- delta: this.cumulativeDelta,
456
- // Tick count
457
- tickCount: this.tickCount,
458
- };
459
- }
460
-
461
361
  /**
462
362
  * Get current model values for monitoring
463
363
  */
@@ -13,6 +13,7 @@ const { TIMEOUTS } = require('../../config/settings');
13
13
  const { TRADOVATE_URLS, API_PATHS, getBaseUrl, getTradingWebSocketUrl } = require('./constants');
14
14
  const { checkMarketHours, isDST } = require('./market');
15
15
  const { connectWebSocket, wsSend, disconnectWebSocket } = require('./websocket');
16
+ const { getOrders, placeOrder, cancelOrder, closePosition, getOrderHistory } = require('./orders');
16
17
  const { logger } = require('../../utils/logger');
17
18
 
18
19
  const log = logger.scope('Tradovate');
@@ -252,112 +253,10 @@ class TradovateService extends EventEmitter {
252
253
 
253
254
  // ==================== ORDERS ====================
254
255
 
255
- /**
256
- * Get orders
257
- * @param {number} [accountId] - Optional account filter
258
- * @returns {Promise<{success: boolean, orders: Array, error?: string}>}
259
- */
260
- async getOrders(accountId) {
261
- try {
262
- const result = await this._request(API_PATHS.ORDER_LIST, 'GET');
263
- const orders = Array.isArray(result.data) ? result.data : [];
264
-
265
- const filtered = accountId
266
- ? orders.filter(o => o.accountId === accountId)
267
- : orders;
268
-
269
- return {
270
- success: true,
271
- orders: filtered.map(o => ({
272
- orderId: o.id,
273
- accountId: o.accountId,
274
- symbol: o.contractId,
275
- side: o.action === 'Buy' ? 0 : 1,
276
- quantity: o.orderQty,
277
- filledQuantity: o.filledQty || 0,
278
- price: o.price,
279
- status: o.ordStatus === 'Working' ? 1 : (o.ordStatus === 'Filled' ? 2 : 0),
280
- orderType: o.orderType,
281
- })),
282
- };
283
- } catch (err) {
284
- return { success: false, error: err.message, orders: [] };
285
- }
286
- }
287
-
288
- /**
289
- * Place an order
290
- * @param {Object} orderData - Order details
291
- * @returns {Promise<{success: boolean, orderId?: number, error?: string}>}
292
- */
293
- async placeOrder(orderData) {
294
- try {
295
- const result = await this._request(API_PATHS.ORDER_PLACE, 'POST', {
296
- accountId: orderData.accountId,
297
- action: orderData.side === 0 ? 'Buy' : 'Sell',
298
- symbol: orderData.symbol,
299
- orderQty: orderData.size,
300
- orderType: orderData.type === 2 ? 'Market' : 'Limit',
301
- price: orderData.price,
302
- isAutomated: true,
303
- });
304
-
305
- if (result.data.errorText || result.data.failureReason) {
306
- return { success: false, error: result.data.errorText || result.data.failureText };
307
- }
308
-
309
- log.info('Order placed', { orderId: result.data.orderId });
310
- return { success: true, orderId: result.data.orderId };
311
- } catch (err) {
312
- return { success: false, error: err.message };
313
- }
314
- }
315
-
316
- /**
317
- * Cancel an order
318
- * @param {number} orderId - Order ID
319
- * @returns {Promise<{success: boolean, error?: string}>}
320
- */
321
- async cancelOrder(orderId) {
322
- try {
323
- const result = await this._request(API_PATHS.ORDER_CANCEL, 'POST', {
324
- orderId,
325
- isAutomated: true,
326
- });
327
-
328
- if (result.data.errorText) {
329
- return { success: false, error: result.data.errorText };
330
- }
331
-
332
- return { success: true };
333
- } catch (err) {
334
- return { success: false, error: err.message };
335
- }
336
- }
337
-
338
- /**
339
- * Close a position
340
- * @param {number} accountId - Account ID
341
- * @param {number} contractId - Contract ID
342
- * @returns {Promise<{success: boolean, error?: string}>}
343
- */
344
- async closePosition(accountId, contractId) {
345
- try {
346
- const result = await this._request(API_PATHS.ORDER_LIQUIDATE_POSITION, 'POST', {
347
- accountId,
348
- contractId,
349
- isAutomated: true,
350
- });
351
-
352
- if (result.data.errorText) {
353
- return { success: false, error: result.data.errorText };
354
- }
355
-
356
- return { success: true };
357
- } catch (err) {
358
- return { success: false, error: err.message };
359
- }
360
- }
256
+ async getOrders(accountId) { return getOrders(this, accountId); }
257
+ async placeOrder(orderData) { return placeOrder(this, orderData); }
258
+ async cancelOrder(orderId) { return cancelOrder(this, orderId); }
259
+ async closePosition(accountId, contractId) { return closePosition(this, accountId, contractId); }
361
260
 
362
261
  // ==================== TRADES ====================
363
262
 
@@ -404,19 +303,7 @@ class TradovateService extends EventEmitter {
404
303
  }
405
304
  }
406
305
 
407
- /**
408
- * Get order history
409
- * @param {number} [days=30] - Days of history
410
- * @returns {Promise<{success: boolean, orders: Array, error?: string}>}
411
- */
412
- async getOrderHistory(days = 30) {
413
- try {
414
- const result = await this._request(API_PATHS.ORDER_LIST, 'GET');
415
- return { success: true, orders: result.data || [] };
416
- } catch (err) {
417
- return { success: false, error: err.message, orders: [] };
418
- }
419
- }
306
+ async getOrderHistory(days = 30) { return getOrderHistory(this, days); }
420
307
 
421
308
  // ==================== CONTRACTS ====================
422
309
 
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Tradovate Orders Module
3
+ * @module services/tradovate/orders
4
+ *
5
+ * Order placement, cancellation, and queries
6
+ */
7
+
8
+ const { API_PATHS } = require('./constants');
9
+ const { logger } = require('../../utils/logger');
10
+
11
+ const log = logger.scope('TradovateOrders');
12
+
13
+ /**
14
+ * Get orders
15
+ * @param {TradovateService} service
16
+ * @param {number} [accountId]
17
+ * @returns {Promise<{success: boolean, orders: Array, error?: string}>}
18
+ */
19
+ async function getOrders(service, accountId) {
20
+ try {
21
+ const result = await service._request(API_PATHS.ORDER_LIST, 'GET');
22
+ const orders = Array.isArray(result.data) ? result.data : [];
23
+
24
+ const filtered = accountId
25
+ ? orders.filter(o => o.accountId === accountId)
26
+ : orders;
27
+
28
+ return {
29
+ success: true,
30
+ orders: filtered.map(o => ({
31
+ orderId: o.id,
32
+ accountId: o.accountId,
33
+ symbol: o.contractId,
34
+ side: o.action === 'Buy' ? 0 : 1,
35
+ quantity: o.orderQty,
36
+ filledQuantity: o.filledQty || 0,
37
+ price: o.price,
38
+ status: o.ordStatus === 'Working' ? 1 : (o.ordStatus === 'Filled' ? 2 : 0),
39
+ orderType: o.orderType,
40
+ })),
41
+ };
42
+ } catch (err) {
43
+ return { success: false, error: err.message, orders: [] };
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Place an order
49
+ * @param {TradovateService} service
50
+ * @param {Object} orderData
51
+ * @returns {Promise<{success: boolean, orderId?: number, error?: string}>}
52
+ */
53
+ async function placeOrder(service, orderData) {
54
+ try {
55
+ const result = await service._request(API_PATHS.ORDER_PLACE, 'POST', {
56
+ accountId: orderData.accountId,
57
+ action: orderData.side === 0 ? 'Buy' : 'Sell',
58
+ symbol: orderData.symbol,
59
+ orderQty: orderData.size,
60
+ orderType: orderData.type === 2 ? 'Market' : 'Limit',
61
+ price: orderData.price,
62
+ isAutomated: true,
63
+ });
64
+
65
+ if (result.data.errorText || result.data.failureReason) {
66
+ return { success: false, error: result.data.errorText || result.data.failureText };
67
+ }
68
+
69
+ log.info('Order placed', { orderId: result.data.orderId });
70
+ return { success: true, orderId: result.data.orderId };
71
+ } catch (err) {
72
+ return { success: false, error: err.message };
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Cancel an order
78
+ * @param {TradovateService} service
79
+ * @param {number} orderId
80
+ * @returns {Promise<{success: boolean, error?: string}>}
81
+ */
82
+ async function cancelOrder(service, orderId) {
83
+ try {
84
+ const result = await service._request(API_PATHS.ORDER_CANCEL, 'POST', {
85
+ orderId,
86
+ isAutomated: true,
87
+ });
88
+
89
+ if (result.data.errorText) {
90
+ return { success: false, error: result.data.errorText };
91
+ }
92
+
93
+ return { success: true };
94
+ } catch (err) {
95
+ return { success: false, error: err.message };
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Close a position
101
+ * @param {TradovateService} service
102
+ * @param {number} accountId
103
+ * @param {number} contractId
104
+ * @returns {Promise<{success: boolean, error?: string}>}
105
+ */
106
+ async function closePosition(service, accountId, contractId) {
107
+ try {
108
+ const result = await service._request(API_PATHS.ORDER_LIQUIDATE_POSITION, 'POST', {
109
+ accountId,
110
+ contractId,
111
+ isAutomated: true,
112
+ });
113
+
114
+ if (result.data.errorText) {
115
+ return { success: false, error: result.data.errorText };
116
+ }
117
+
118
+ return { success: true };
119
+ } catch (err) {
120
+ return { success: false, error: err.message };
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Get order history
126
+ * @param {TradovateService} service
127
+ * @param {number} [days=30]
128
+ * @returns {Promise<{success: boolean, orders: Array, error?: string}>}
129
+ */
130
+ async function getOrderHistory(service, days = 30) {
131
+ try {
132
+ const result = await service._request(API_PATHS.ORDER_LIST, 'GET');
133
+ return { success: true, orders: result.data || [] };
134
+ } catch (err) {
135
+ return { success: false, error: err.message, orders: [] };
136
+ }
137
+ }
138
+
139
+ module.exports = {
140
+ getOrders,
141
+ placeOrder,
142
+ cancelOrder,
143
+ closePosition,
144
+ getOrderHistory,
145
+ };