hedgequantx 2.6.28 → 2.6.29
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
CHANGED
|
@@ -16,12 +16,16 @@ const { checkMarketHours } = require('../../services/projectx/market');
|
|
|
16
16
|
const { FAST_SCALPING } = require('../../config/settings');
|
|
17
17
|
const { PositionManager } = require('../../services/position-manager');
|
|
18
18
|
|
|
19
|
-
// Strategy & Market Data
|
|
19
|
+
// Strategy & Market Data
|
|
20
20
|
const { M1 } = require('../../../dist/lib/m/s1');
|
|
21
|
+
const { hftStrategy } = require('../../services/strategy/hft-tick');
|
|
21
22
|
const { MarketDataFeed } = require('../../../dist/lib/data');
|
|
22
23
|
const { RithmicMarketDataFeed } = require('../../services/rithmic/market-data');
|
|
23
24
|
const { algoLogger } = require('./logger');
|
|
24
25
|
|
|
26
|
+
// Use HFT tick-based strategy for Rithmic (fast path), M1 for ProjectX
|
|
27
|
+
const USE_HFT_STRATEGY = true;
|
|
28
|
+
|
|
25
29
|
// AI Strategy Supervisor - observes, learns, and optimizes the strategy
|
|
26
30
|
const aiService = require('../../services/ai');
|
|
27
31
|
const StrategySupervisor = require('../../services/ai/strategy-supervisor');
|
|
@@ -263,13 +267,15 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
263
267
|
let lastTradeCount = 0; // Track number of trades from API
|
|
264
268
|
let lastPositionQty = 0; // Track position changes
|
|
265
269
|
|
|
266
|
-
// Initialize Strategy
|
|
267
|
-
//
|
|
268
|
-
const strategy = M1;
|
|
270
|
+
// Initialize Strategy
|
|
271
|
+
// Use HFT tick-based strategy for Rithmic (fast path), M1 for ProjectX
|
|
272
|
+
const strategy = (USE_HFT_STRATEGY && useFastPath) ? hftStrategy : M1;
|
|
273
|
+
const strategyName = (USE_HFT_STRATEGY && useFastPath) ? 'HFT-TICK' : 'M1';
|
|
269
274
|
|
|
270
|
-
//
|
|
275
|
+
// Initialize strategy with tick specs
|
|
271
276
|
if (tickSize !== null && tickValue !== null) {
|
|
272
277
|
strategy.initialize(contractId, tickSize, tickValue);
|
|
278
|
+
algoLogger.info(ui, 'STRATEGY', `${strategyName} initialized | tick=${tickSize} value=$${tickValue}`);
|
|
273
279
|
} else {
|
|
274
280
|
algoLogger.warning(ui, 'WARNING', 'Tick size/value not available from API');
|
|
275
281
|
}
|
|
@@ -588,15 +594,19 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
588
594
|
// Heartbeat every 30 seconds - show strategy model values
|
|
589
595
|
if (Date.now() - lastHeartbeat > 30000) {
|
|
590
596
|
// Try to get model values from strategy
|
|
591
|
-
const modelValues = strategy.getModelValues?.(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
|
|
597
|
+
const modelValues = strategy.getModelValues?.() || strategy.getModelValues?.(contractId) || null;
|
|
598
|
+
|
|
599
|
+
if (modelValues && modelValues.ofi !== undefined) {
|
|
600
|
+
// HFT Strategy - pure tick values
|
|
601
|
+
const ofi = (modelValues.ofi || 0).toFixed(2);
|
|
602
|
+
const delta = modelValues.delta || 0;
|
|
603
|
+
const zscore = (modelValues.zscore || 0).toFixed(2);
|
|
604
|
+
const mom = (modelValues.momentum || 0).toFixed(1);
|
|
605
|
+
const signals = modelValues.signalCount || 0;
|
|
606
|
+
const deltaStr = delta >= 0 ? `+${delta}` : `${delta}`;
|
|
607
|
+
algoLogger.info(ui, strategyName, `OFI=${ofi} Z=${zscore} Mom=${mom} Δ=${deltaStr} | ${signals} signals | ${tps} t/30s`);
|
|
598
608
|
} else {
|
|
599
|
-
//
|
|
609
|
+
// Fallback
|
|
600
610
|
algoLogger.info(ui, 'SCANNING', `${tps} ticks/30s | ${tickCount} total | price=${tickData.price}`);
|
|
601
611
|
}
|
|
602
612
|
lastHeartbeat = Date.now();
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* HFT TICK-BY-TICK STRATEGY
|
|
4
|
+
* =============================================================================
|
|
5
|
+
* Pure tick-based strategy for high-frequency trading
|
|
6
|
+
* No bars, no candles - every tick counts
|
|
7
|
+
*
|
|
8
|
+
* MODELS:
|
|
9
|
+
* 1. Order Flow Imbalance (OFI) - Real-time buy/sell pressure
|
|
10
|
+
* 2. Cumulative Delta - Net volume direction
|
|
11
|
+
* 3. Bid/Ask Pressure - Spread and size analysis
|
|
12
|
+
* 4. Momentum - Price velocity and acceleration
|
|
13
|
+
* 5. Mean Reversion - Z-score on tick prices
|
|
14
|
+
*
|
|
15
|
+
* SIGNALS:
|
|
16
|
+
* - Generated on each tick when conditions align
|
|
17
|
+
* - Sub-second decision making
|
|
18
|
+
* - Designed for <50ms execution
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const EventEmitter = require('events');
|
|
22
|
+
const { logger } = require('../../utils/logger');
|
|
23
|
+
|
|
24
|
+
const log = logger.scope('HFT');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Circular buffer for efficient tick storage
|
|
28
|
+
*/
|
|
29
|
+
class CircularBuffer {
|
|
30
|
+
constructor(size) {
|
|
31
|
+
this.size = size;
|
|
32
|
+
this.buffer = new Array(size);
|
|
33
|
+
this.head = 0;
|
|
34
|
+
this.count = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
push(item) {
|
|
38
|
+
this.buffer[this.head] = item;
|
|
39
|
+
this.head = (this.head + 1) % this.size;
|
|
40
|
+
if (this.count < this.size) this.count++;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get(index) {
|
|
44
|
+
if (index >= this.count) return null;
|
|
45
|
+
const actualIndex = (this.head - this.count + index + this.size) % this.size;
|
|
46
|
+
return this.buffer[actualIndex];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getLast(n = 1) {
|
|
50
|
+
const result = [];
|
|
51
|
+
const count = Math.min(n, this.count);
|
|
52
|
+
for (let i = this.count - count; i < this.count; i++) {
|
|
53
|
+
result.push(this.get(i));
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getAll() {
|
|
59
|
+
return this.getLast(this.count);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get length() {
|
|
63
|
+
return this.count;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
clear() {
|
|
67
|
+
this.head = 0;
|
|
68
|
+
this.count = 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* HFT Tick Strategy
|
|
74
|
+
*/
|
|
75
|
+
class HFTTickStrategy extends EventEmitter {
|
|
76
|
+
constructor() {
|
|
77
|
+
super();
|
|
78
|
+
|
|
79
|
+
// Configuration
|
|
80
|
+
this.tickSize = 0.25;
|
|
81
|
+
this.tickValue = 5.0;
|
|
82
|
+
this.contractId = null;
|
|
83
|
+
this.initialized = false;
|
|
84
|
+
|
|
85
|
+
// Tick buffers (circular for memory efficiency)
|
|
86
|
+
this.tickBuffer = new CircularBuffer(1000); // Last 1000 ticks
|
|
87
|
+
this.priceBuffer = new CircularBuffer(500); // Last 500 prices
|
|
88
|
+
|
|
89
|
+
// Real-time metrics
|
|
90
|
+
this.cumulativeDelta = 0;
|
|
91
|
+
this.buyVolume = 0;
|
|
92
|
+
this.sellVolume = 0;
|
|
93
|
+
this.lastPrice = 0;
|
|
94
|
+
this.lastBid = 0;
|
|
95
|
+
this.lastAsk = 0;
|
|
96
|
+
|
|
97
|
+
// OFI calculation
|
|
98
|
+
this.ofiWindow = 50; // Ticks for OFI calculation
|
|
99
|
+
this.ofiValue = 0;
|
|
100
|
+
|
|
101
|
+
// Momentum
|
|
102
|
+
this.momentumWindow = 20;
|
|
103
|
+
this.momentum = 0;
|
|
104
|
+
this.acceleration = 0;
|
|
105
|
+
|
|
106
|
+
// Mean reversion
|
|
107
|
+
this.zscoreWindow = 100;
|
|
108
|
+
this.zscore = 0;
|
|
109
|
+
this.mean = 0;
|
|
110
|
+
this.std = 0;
|
|
111
|
+
|
|
112
|
+
// Signal thresholds
|
|
113
|
+
this.ofiThreshold = 0.3; // |OFI| > 0.3 for signal
|
|
114
|
+
this.zscoreThreshold = 1.5; // |Z| > 1.5 for mean reversion
|
|
115
|
+
this.momentumThreshold = 0.5; // Momentum confirmation
|
|
116
|
+
this.minConfidence = 0.6; // Minimum confidence for signal
|
|
117
|
+
|
|
118
|
+
// Trade parameters
|
|
119
|
+
this.baseStopTicks = 8;
|
|
120
|
+
this.baseTargetTicks = 12;
|
|
121
|
+
|
|
122
|
+
// State
|
|
123
|
+
this.tickCount = 0;
|
|
124
|
+
this.signalCount = 0;
|
|
125
|
+
this.lastSignalTime = 0;
|
|
126
|
+
this.cooldownMs = 5000; // 5 second cooldown between signals
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Initialize strategy for a contract
|
|
131
|
+
*/
|
|
132
|
+
initialize(contractId, tickSize = 0.25, tickValue = 5.0) {
|
|
133
|
+
this.contractId = contractId;
|
|
134
|
+
this.tickSize = tickSize;
|
|
135
|
+
this.tickValue = tickValue;
|
|
136
|
+
this.initialized = true;
|
|
137
|
+
|
|
138
|
+
// Reset state
|
|
139
|
+
this.tickBuffer.clear();
|
|
140
|
+
this.priceBuffer.clear();
|
|
141
|
+
this.cumulativeDelta = 0;
|
|
142
|
+
this.buyVolume = 0;
|
|
143
|
+
this.sellVolume = 0;
|
|
144
|
+
this.tickCount = 0;
|
|
145
|
+
|
|
146
|
+
log.info(`Initialized for ${contractId}: tick=${tickSize}, value=${tickValue}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Process a single tick - CORE HFT FUNCTION
|
|
151
|
+
* Called on every market tick
|
|
152
|
+
*/
|
|
153
|
+
processTick(tick) {
|
|
154
|
+
if (!this.initialized) return;
|
|
155
|
+
|
|
156
|
+
const { price, bid, ask, volume, side, timestamp } = tick;
|
|
157
|
+
|
|
158
|
+
// Store tick
|
|
159
|
+
this.tickBuffer.push({
|
|
160
|
+
price,
|
|
161
|
+
bid: bid || this.lastBid,
|
|
162
|
+
ask: ask || this.lastAsk,
|
|
163
|
+
volume: volume || 1,
|
|
164
|
+
side: side || this.inferSide(price, bid, ask),
|
|
165
|
+
timestamp: timestamp || Date.now(),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
this.priceBuffer.push(price);
|
|
169
|
+
this.tickCount++;
|
|
170
|
+
|
|
171
|
+
// Update last values
|
|
172
|
+
this.lastPrice = price;
|
|
173
|
+
if (bid) this.lastBid = bid;
|
|
174
|
+
if (ask) this.lastAsk = ask;
|
|
175
|
+
|
|
176
|
+
// Update real-time metrics
|
|
177
|
+
this._updateDelta(tick);
|
|
178
|
+
this._updateOFI();
|
|
179
|
+
this._updateMomentum();
|
|
180
|
+
this._updateZScore();
|
|
181
|
+
|
|
182
|
+
// Check for signal (every tick!)
|
|
183
|
+
this._checkSignal(tick);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Infer trade side from price position
|
|
188
|
+
*/
|
|
189
|
+
inferSide(price, bid, ask) {
|
|
190
|
+
if (!bid || !ask) return 'unknown';
|
|
191
|
+
const mid = (bid + ask) / 2;
|
|
192
|
+
return price >= mid ? 'BUY' : 'SELL';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Update cumulative delta
|
|
197
|
+
*/
|
|
198
|
+
_updateDelta(tick) {
|
|
199
|
+
const vol = tick.volume || 1;
|
|
200
|
+
const side = tick.side?.toUpperCase() || this.inferSide(tick.price, tick.bid, tick.ask);
|
|
201
|
+
|
|
202
|
+
if (side === 'BUY') {
|
|
203
|
+
this.buyVolume += vol;
|
|
204
|
+
this.cumulativeDelta += vol;
|
|
205
|
+
} else if (side === 'SELL') {
|
|
206
|
+
this.sellVolume += vol;
|
|
207
|
+
this.cumulativeDelta -= vol;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Calculate Order Flow Imbalance on recent ticks
|
|
213
|
+
*/
|
|
214
|
+
_updateOFI() {
|
|
215
|
+
const ticks = this.tickBuffer.getLast(this.ofiWindow);
|
|
216
|
+
if (ticks.length < 10) {
|
|
217
|
+
this.ofiValue = 0;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let buyPressure = 0;
|
|
222
|
+
let sellPressure = 0;
|
|
223
|
+
|
|
224
|
+
for (const tick of ticks) {
|
|
225
|
+
const vol = tick.volume || 1;
|
|
226
|
+
const side = (tick.side || '').toUpperCase();
|
|
227
|
+
|
|
228
|
+
if (side === 'BUY') {
|
|
229
|
+
buyPressure += vol;
|
|
230
|
+
} else if (side === 'SELL') {
|
|
231
|
+
sellPressure += vol;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const total = buyPressure + sellPressure;
|
|
236
|
+
if (total > 0) {
|
|
237
|
+
// OFI range: -1 (all sells) to +1 (all buys)
|
|
238
|
+
this.ofiValue = (buyPressure - sellPressure) / total;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Calculate price momentum
|
|
244
|
+
*/
|
|
245
|
+
_updateMomentum() {
|
|
246
|
+
const prices = this.priceBuffer.getLast(this.momentumWindow);
|
|
247
|
+
if (prices.length < 5) {
|
|
248
|
+
this.momentum = 0;
|
|
249
|
+
this.acceleration = 0;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Momentum = price change over window
|
|
254
|
+
const first = prices[0];
|
|
255
|
+
const last = prices[prices.length - 1];
|
|
256
|
+
this.momentum = (last - first) / this.tickSize;
|
|
257
|
+
|
|
258
|
+
// Acceleration = change in momentum
|
|
259
|
+
if (prices.length >= 10) {
|
|
260
|
+
const mid = prices[Math.floor(prices.length / 2)];
|
|
261
|
+
const firstHalf = (mid - first) / this.tickSize;
|
|
262
|
+
const secondHalf = (last - mid) / this.tickSize;
|
|
263
|
+
this.acceleration = secondHalf - firstHalf;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Calculate Z-Score for mean reversion
|
|
269
|
+
*/
|
|
270
|
+
_updateZScore() {
|
|
271
|
+
const prices = this.priceBuffer.getLast(this.zscoreWindow);
|
|
272
|
+
if (prices.length < 20) {
|
|
273
|
+
this.zscore = 0;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Calculate mean
|
|
278
|
+
this.mean = prices.reduce((a, b) => a + b, 0) / prices.length;
|
|
279
|
+
|
|
280
|
+
// Calculate standard deviation
|
|
281
|
+
const variance = prices.reduce((sum, p) => sum + Math.pow(p - this.mean, 2), 0) / prices.length;
|
|
282
|
+
this.std = Math.sqrt(variance);
|
|
283
|
+
|
|
284
|
+
// Z-Score
|
|
285
|
+
if (this.std > 0.0001) {
|
|
286
|
+
this.zscore = (this.lastPrice - this.mean) / this.std;
|
|
287
|
+
} else {
|
|
288
|
+
this.zscore = 0;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Check for trading signal
|
|
294
|
+
*/
|
|
295
|
+
_checkSignal(tick) {
|
|
296
|
+
// Need minimum data
|
|
297
|
+
if (this.tickCount < 50) return;
|
|
298
|
+
|
|
299
|
+
// Cooldown check
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
if (now - this.lastSignalTime < this.cooldownMs) return;
|
|
302
|
+
|
|
303
|
+
// Calculate composite score
|
|
304
|
+
const { direction, confidence, scores } = this._calculateSignal();
|
|
305
|
+
|
|
306
|
+
if (direction === 'none' || confidence < this.minConfidence) return;
|
|
307
|
+
|
|
308
|
+
// Generate signal
|
|
309
|
+
this.signalCount++;
|
|
310
|
+
this.lastSignalTime = now;
|
|
311
|
+
|
|
312
|
+
const signal = this._buildSignal(direction, confidence, scores, tick);
|
|
313
|
+
|
|
314
|
+
log.info(`SIGNAL #${this.signalCount}: ${direction.toUpperCase()} @ ${tick.price} | ` +
|
|
315
|
+
`OFI=${this.ofiValue.toFixed(2)} Z=${this.zscore.toFixed(2)} Mom=${this.momentum.toFixed(1)} | ` +
|
|
316
|
+
`Conf=${(confidence * 100).toFixed(0)}%`);
|
|
317
|
+
|
|
318
|
+
this.emit('signal', signal);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Calculate trading signal from all models
|
|
323
|
+
*/
|
|
324
|
+
_calculateSignal() {
|
|
325
|
+
let direction = 'none';
|
|
326
|
+
let confidence = 0;
|
|
327
|
+
|
|
328
|
+
const scores = {
|
|
329
|
+
ofi: 0,
|
|
330
|
+
zscore: 0,
|
|
331
|
+
momentum: 0,
|
|
332
|
+
delta: 0,
|
|
333
|
+
composite: 0,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// === MODEL 1: OFI ===
|
|
337
|
+
const absOfi = Math.abs(this.ofiValue);
|
|
338
|
+
if (absOfi > this.ofiThreshold) {
|
|
339
|
+
scores.ofi = Math.min(1.0, absOfi / 0.6);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// === MODEL 2: Z-Score Mean Reversion ===
|
|
343
|
+
const absZ = Math.abs(this.zscore);
|
|
344
|
+
if (absZ > this.zscoreThreshold) {
|
|
345
|
+
scores.zscore = Math.min(1.0, absZ / 3.0);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// === MODEL 3: Momentum ===
|
|
349
|
+
const absMom = Math.abs(this.momentum);
|
|
350
|
+
if (absMom > this.momentumThreshold) {
|
|
351
|
+
scores.momentum = Math.min(1.0, absMom / 3.0);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// === MODEL 4: Delta ===
|
|
355
|
+
const totalVol = this.buyVolume + this.sellVolume;
|
|
356
|
+
if (totalVol > 0) {
|
|
357
|
+
const deltaRatio = this.cumulativeDelta / totalVol;
|
|
358
|
+
scores.delta = Math.min(1.0, Math.abs(deltaRatio) * 2);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// === COMPOSITE SCORE ===
|
|
362
|
+
scores.composite =
|
|
363
|
+
scores.ofi * 0.35 + // OFI: 35%
|
|
364
|
+
scores.zscore * 0.25 + // Z-Score: 25%
|
|
365
|
+
scores.momentum * 0.20 + // Momentum: 20%
|
|
366
|
+
scores.delta * 0.20; // Delta: 20%
|
|
367
|
+
|
|
368
|
+
confidence = scores.composite;
|
|
369
|
+
|
|
370
|
+
// === DETERMINE DIRECTION ===
|
|
371
|
+
// Mean reversion: go opposite to z-score
|
|
372
|
+
// Momentum: confirm with OFI and delta
|
|
373
|
+
|
|
374
|
+
if (scores.composite >= this.minConfidence) {
|
|
375
|
+
// Primary: Mean reversion
|
|
376
|
+
if (absZ > this.zscoreThreshold) {
|
|
377
|
+
direction = this.zscore > 0 ? 'short' : 'long';
|
|
378
|
+
|
|
379
|
+
// Confirm with OFI
|
|
380
|
+
const ofiConfirms =
|
|
381
|
+
(direction === 'long' && this.ofiValue > 0) ||
|
|
382
|
+
(direction === 'short' && this.ofiValue < 0);
|
|
383
|
+
|
|
384
|
+
if (ofiConfirms) {
|
|
385
|
+
confidence += 0.1;
|
|
386
|
+
} else if (Math.abs(this.ofiValue) > 0.2) {
|
|
387
|
+
// OFI contradicts - reduce confidence
|
|
388
|
+
confidence -= 0.15;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Fallback: Momentum breakout
|
|
392
|
+
else if (absMom > this.momentumThreshold * 2 && absOfi > this.ofiThreshold) {
|
|
393
|
+
direction = this.momentum > 0 ? 'long' : 'short';
|
|
394
|
+
// Must be confirmed by OFI
|
|
395
|
+
if ((direction === 'long' && this.ofiValue < 0) ||
|
|
396
|
+
(direction === 'short' && this.ofiValue > 0)) {
|
|
397
|
+
direction = 'none';
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
confidence = Math.min(1.0, Math.max(0, confidence));
|
|
403
|
+
|
|
404
|
+
return { direction, confidence, scores };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Build signal object
|
|
409
|
+
*/
|
|
410
|
+
_buildSignal(direction, confidence, scores, tick) {
|
|
411
|
+
const entry = tick.price;
|
|
412
|
+
const isLong = direction === 'long';
|
|
413
|
+
|
|
414
|
+
// Adaptive stops based on volatility (std dev)
|
|
415
|
+
const volMult = Math.max(0.5, Math.min(2.0, this.std / this.tickSize / 4));
|
|
416
|
+
const stopTicks = Math.round(this.baseStopTicks * volMult);
|
|
417
|
+
const targetTicks = Math.round(this.baseTargetTicks * volMult);
|
|
418
|
+
|
|
419
|
+
const stopLoss = isLong
|
|
420
|
+
? entry - stopTicks * this.tickSize
|
|
421
|
+
: entry + stopTicks * this.tickSize;
|
|
422
|
+
|
|
423
|
+
const takeProfit = isLong
|
|
424
|
+
? entry + targetTicks * this.tickSize
|
|
425
|
+
: entry - targetTicks * this.tickSize;
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
id: `hft-${Date.now()}-${this.signalCount}`,
|
|
429
|
+
timestamp: Date.now(),
|
|
430
|
+
contractId: this.contractId,
|
|
431
|
+
direction,
|
|
432
|
+
side: isLong ? 0 : 1,
|
|
433
|
+
entry,
|
|
434
|
+
stopLoss,
|
|
435
|
+
takeProfit,
|
|
436
|
+
confidence,
|
|
437
|
+
scores,
|
|
438
|
+
// Model values for logging
|
|
439
|
+
ofi: this.ofiValue,
|
|
440
|
+
zscore: this.zscore,
|
|
441
|
+
momentum: this.momentum,
|
|
442
|
+
delta: this.cumulativeDelta,
|
|
443
|
+
// Tick count
|
|
444
|
+
tickCount: this.tickCount,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Get current model values for monitoring
|
|
450
|
+
*/
|
|
451
|
+
getModelValues() {
|
|
452
|
+
return {
|
|
453
|
+
ofi: this.ofiValue,
|
|
454
|
+
delta: this.cumulativeDelta,
|
|
455
|
+
zscore: this.zscore,
|
|
456
|
+
momentum: this.momentum,
|
|
457
|
+
mean: this.mean,
|
|
458
|
+
std: this.std,
|
|
459
|
+
buyVolume: this.buyVolume,
|
|
460
|
+
sellVolume: this.sellVolume,
|
|
461
|
+
tickCount: this.tickCount,
|
|
462
|
+
signalCount: this.signalCount,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get state for display
|
|
468
|
+
*/
|
|
469
|
+
getState() {
|
|
470
|
+
return this.getModelValues();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Reset strategy state
|
|
475
|
+
*/
|
|
476
|
+
reset() {
|
|
477
|
+
this.tickBuffer.clear();
|
|
478
|
+
this.priceBuffer.clear();
|
|
479
|
+
this.cumulativeDelta = 0;
|
|
480
|
+
this.buyVolume = 0;
|
|
481
|
+
this.sellVolume = 0;
|
|
482
|
+
this.tickCount = 0;
|
|
483
|
+
this.signalCount = 0;
|
|
484
|
+
this.ofiValue = 0;
|
|
485
|
+
this.zscore = 0;
|
|
486
|
+
this.momentum = 0;
|
|
487
|
+
log.info('Strategy reset');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Singleton instance
|
|
492
|
+
const hftStrategy = new HFTTickStrategy();
|
|
493
|
+
|
|
494
|
+
module.exports = { HFTTickStrategy, hftStrategy };
|