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.
- package/package.json +1 -1
- package/src/services/ai/providers/direct-providers.js +323 -0
- package/src/services/ai/providers/index.js +8 -472
- package/src/services/ai/providers/other-providers.js +104 -0
- package/src/services/position-exit-logic.js +174 -0
- package/src/services/position-manager.js +18 -108
- package/src/services/rithmic/contracts.js +218 -0
- package/src/services/rithmic/index.js +5 -179
- package/src/services/rithmic/market-data-decoders.js +229 -0
- package/src/services/rithmic/market-data.js +1 -278
- package/src/services/rithmic/orders-fast.js +246 -0
- package/src/services/rithmic/orders.js +1 -251
- package/src/services/rithmic/proto-decoders.js +403 -0
- package/src/services/rithmic/protobuf.js +7 -443
- package/src/services/strategy/hft-signal-calc.js +147 -0
- package/src/services/strategy/hft-tick.js +33 -133
- package/src/services/tradovate/index.js +6 -119
- package/src/services/tradovate/orders.js +145 -0
|
@@ -6,15 +6,12 @@
|
|
|
6
6
|
const protobuf = require('protobufjs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { PROTO_FILES } = require('./constants');
|
|
9
|
+
const {
|
|
10
|
+
readVarint, readLengthDelimited, skipField,
|
|
11
|
+
decodeAccountPnL, decodeInstrumentPnL, decodeProductCodes, decodeFrontMonthContract
|
|
12
|
+
} = require('./proto-decoders');
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
// Pre-allocated buffer pool for zero-allocation hot path
|
|
12
|
-
// Avoids GC pressure during high-frequency trading
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* High-performance buffer pool for zero-allocation encoding
|
|
16
|
-
* Uses ring buffer pattern for O(1) acquire/release
|
|
17
|
-
*/
|
|
14
|
+
/** High-performance buffer pool for zero-allocation encoding */
|
|
18
15
|
class BufferPool {
|
|
19
16
|
constructor(poolSize = 16, bufferSize = 512) {
|
|
20
17
|
this._pool = new Array(poolSize);
|
|
@@ -24,370 +21,32 @@ class BufferPool {
|
|
|
24
21
|
this._head = 0;
|
|
25
22
|
this._tail = 0;
|
|
26
23
|
this._count = poolSize;
|
|
27
|
-
|
|
28
|
-
// Pre-allocate all buffers
|
|
29
24
|
for (let i = 0; i < poolSize; i++) {
|
|
30
25
|
this._pool[i] = Buffer.allocUnsafe(bufferSize);
|
|
31
26
|
this._available[i] = i;
|
|
32
27
|
}
|
|
33
28
|
}
|
|
34
29
|
|
|
35
|
-
/**
|
|
36
|
-
* Acquire a buffer from the pool
|
|
37
|
-
* @returns {Buffer|null} Buffer or null if pool exhausted
|
|
38
|
-
*/
|
|
39
30
|
acquire() {
|
|
40
|
-
if (this._count === 0)
|
|
41
|
-
// Pool exhausted - allocate new (fallback)
|
|
42
|
-
return Buffer.allocUnsafe(this._bufferSize);
|
|
43
|
-
}
|
|
31
|
+
if (this._count === 0) return Buffer.allocUnsafe(this._bufferSize);
|
|
44
32
|
const idx = this._available[this._head];
|
|
45
33
|
this._head = (this._head + 1) % this._size;
|
|
46
34
|
this._count--;
|
|
47
35
|
return this._pool[idx];
|
|
48
36
|
}
|
|
49
37
|
|
|
50
|
-
/**
|
|
51
|
-
* Release a buffer back to pool
|
|
52
|
-
* Only releases buffers that belong to the pool
|
|
53
|
-
* @param {Buffer} buffer
|
|
54
|
-
*/
|
|
55
38
|
release(buffer) {
|
|
56
|
-
// Find if this buffer is from our pool
|
|
57
39
|
const idx = this._pool.indexOf(buffer);
|
|
58
40
|
if (idx !== -1 && this._count < this._size) {
|
|
59
41
|
this._available[this._tail] = idx;
|
|
60
42
|
this._tail = (this._tail + 1) % this._size;
|
|
61
43
|
this._count++;
|
|
62
44
|
}
|
|
63
|
-
// If not from pool, let GC handle it
|
|
64
45
|
}
|
|
65
46
|
|
|
66
|
-
/**
|
|
67
|
-
* Get pool stats
|
|
68
|
-
*/
|
|
69
47
|
getStats() {
|
|
70
|
-
return {
|
|
71
|
-
size: this._size,
|
|
72
|
-
available: this._count,
|
|
73
|
-
bufferSize: this._bufferSize,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// PnL field IDs (Rithmic uses very large field IDs)
|
|
79
|
-
const PNL_FIELDS = {
|
|
80
|
-
TEMPLATE_ID: 154467,
|
|
81
|
-
IS_SNAPSHOT: 110121,
|
|
82
|
-
FCM_ID: 154013,
|
|
83
|
-
IB_ID: 154014,
|
|
84
|
-
ACCOUNT_ID: 154008,
|
|
85
|
-
ACCOUNT_BALANCE: 156970,
|
|
86
|
-
CASH_ON_HAND: 156971,
|
|
87
|
-
MARGIN_BALANCE: 156977,
|
|
88
|
-
MIN_ACCOUNT_BALANCE: 156968,
|
|
89
|
-
OPEN_POSITION_PNL: 156961,
|
|
90
|
-
CLOSED_POSITION_PNL: 156963,
|
|
91
|
-
DAY_PNL: 157956,
|
|
92
|
-
DAY_OPEN_PNL: 157954,
|
|
93
|
-
DAY_CLOSED_PNL: 157955,
|
|
94
|
-
AVAILABLE_BUYING_POWER: 157015,
|
|
95
|
-
SSBOE: 150100,
|
|
96
|
-
USECS: 150101,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Symbol/Contract field IDs (ResponseProductCodes, ResponseFrontMonthContract)
|
|
100
|
-
const SYMBOL_FIELDS = {
|
|
101
|
-
TEMPLATE_ID: 154467,
|
|
102
|
-
RP_CODE: 132766,
|
|
103
|
-
EXCHANGE: 110101,
|
|
104
|
-
PRODUCT_CODE: 110102, // Base symbol (ES, NQ, MNQ)
|
|
105
|
-
PRODUCT_NAME: 110103, // Product name
|
|
106
|
-
SYMBOL: 110100, // Full contract symbol (ESH26)
|
|
107
|
-
TRADING_SYMBOL: 157095, // Trading symbol
|
|
108
|
-
DESCRIPTION: 110114, // Contract description
|
|
109
|
-
USER_MSG: 132760,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Instrument PnL Position Update field IDs
|
|
113
|
-
const INSTRUMENT_PNL_FIELDS = {
|
|
114
|
-
TEMPLATE_ID: 154467,
|
|
115
|
-
IS_SNAPSHOT: 110121,
|
|
116
|
-
FCM_ID: 154013,
|
|
117
|
-
IB_ID: 154014,
|
|
118
|
-
ACCOUNT_ID: 154008,
|
|
119
|
-
SYMBOL: 110100,
|
|
120
|
-
EXCHANGE: 110101,
|
|
121
|
-
PRODUCT_CODE: 100749,
|
|
122
|
-
INSTRUMENT_TYPE: 110116,
|
|
123
|
-
FILL_BUY_QTY: 154041,
|
|
124
|
-
FILL_SELL_QTY: 154042,
|
|
125
|
-
ORDER_BUY_QTY: 154037,
|
|
126
|
-
ORDER_SELL_QTY: 154038,
|
|
127
|
-
BUY_QTY: 154260,
|
|
128
|
-
SELL_QTY: 154261,
|
|
129
|
-
AVG_OPEN_FILL_PRICE: 154434,
|
|
130
|
-
DAY_OPEN_PNL: 157954,
|
|
131
|
-
DAY_CLOSED_PNL: 157955,
|
|
132
|
-
DAY_PNL: 157956,
|
|
133
|
-
OPEN_POSITION_PNL: 156961,
|
|
134
|
-
OPEN_POSITION_QUANTITY: 156962,
|
|
135
|
-
CLOSED_POSITION_PNL: 156963,
|
|
136
|
-
CLOSED_POSITION_QUANTITY: 156964,
|
|
137
|
-
NET_QUANTITY: 156967,
|
|
138
|
-
SSBOE: 150100,
|
|
139
|
-
USECS: 150101,
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Read a varint from buffer
|
|
144
|
-
*/
|
|
145
|
-
function readVarint(buffer, offset) {
|
|
146
|
-
let result = BigInt(0);
|
|
147
|
-
let shift = BigInt(0);
|
|
148
|
-
let pos = offset;
|
|
149
|
-
|
|
150
|
-
while (pos < buffer.length) {
|
|
151
|
-
const byte = buffer[pos++];
|
|
152
|
-
result |= BigInt(byte & 0x7f) << shift;
|
|
153
|
-
if ((byte & 0x80) === 0) {
|
|
154
|
-
return [Number(result), pos];
|
|
155
|
-
}
|
|
156
|
-
shift += BigInt(7);
|
|
157
|
-
if (shift > BigInt(63)) {
|
|
158
|
-
throw new Error('Varint too large');
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
throw new Error('Incomplete varint');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Read a length-delimited field (string/bytes)
|
|
166
|
-
*/
|
|
167
|
-
function readLengthDelimited(buffer, offset) {
|
|
168
|
-
const [length, newOffset] = readVarint(buffer, offset);
|
|
169
|
-
const value = buffer.slice(newOffset, newOffset + length).toString('utf8');
|
|
170
|
-
return [value, newOffset + length];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Skip a field based on wire type
|
|
175
|
-
*/
|
|
176
|
-
function skipField(buffer, offset, wireType) {
|
|
177
|
-
switch (wireType) {
|
|
178
|
-
case 0: // Varint
|
|
179
|
-
const [, newOffset] = readVarint(buffer, offset);
|
|
180
|
-
return newOffset;
|
|
181
|
-
case 1: // 64-bit
|
|
182
|
-
return offset + 8;
|
|
183
|
-
case 2: // Length-delimited
|
|
184
|
-
const [length, lenOffset] = readVarint(buffer, offset);
|
|
185
|
-
return lenOffset + length;
|
|
186
|
-
case 5: // 32-bit
|
|
187
|
-
return offset + 4;
|
|
188
|
-
default:
|
|
189
|
-
throw new Error(`Unknown wire type: ${wireType}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Manually decode AccountPnL from raw bytes
|
|
195
|
-
*/
|
|
196
|
-
function decodeAccountPnL(buffer) {
|
|
197
|
-
const result = {};
|
|
198
|
-
let offset = 0;
|
|
199
|
-
|
|
200
|
-
while (offset < buffer.length) {
|
|
201
|
-
try {
|
|
202
|
-
const [tag, tagOffset] = readVarint(buffer, offset);
|
|
203
|
-
const wireType = tag & 0x7;
|
|
204
|
-
const fieldNumber = tag >>> 3;
|
|
205
|
-
offset = tagOffset;
|
|
206
|
-
|
|
207
|
-
switch (fieldNumber) {
|
|
208
|
-
case PNL_FIELDS.TEMPLATE_ID:
|
|
209
|
-
[result.templateId, offset] = readVarint(buffer, offset);
|
|
210
|
-
break;
|
|
211
|
-
case PNL_FIELDS.IS_SNAPSHOT:
|
|
212
|
-
const [isSnap, snapOffset] = readVarint(buffer, offset);
|
|
213
|
-
result.isSnapshot = isSnap !== 0;
|
|
214
|
-
offset = snapOffset;
|
|
215
|
-
break;
|
|
216
|
-
case PNL_FIELDS.FCM_ID:
|
|
217
|
-
[result.fcmId, offset] = readLengthDelimited(buffer, offset);
|
|
218
|
-
break;
|
|
219
|
-
case PNL_FIELDS.IB_ID:
|
|
220
|
-
[result.ibId, offset] = readLengthDelimited(buffer, offset);
|
|
221
|
-
break;
|
|
222
|
-
case PNL_FIELDS.ACCOUNT_ID:
|
|
223
|
-
[result.accountId, offset] = readLengthDelimited(buffer, offset);
|
|
224
|
-
break;
|
|
225
|
-
case PNL_FIELDS.ACCOUNT_BALANCE:
|
|
226
|
-
[result.accountBalance, offset] = readLengthDelimited(buffer, offset);
|
|
227
|
-
break;
|
|
228
|
-
case PNL_FIELDS.CASH_ON_HAND:
|
|
229
|
-
[result.cashOnHand, offset] = readLengthDelimited(buffer, offset);
|
|
230
|
-
break;
|
|
231
|
-
case PNL_FIELDS.MARGIN_BALANCE:
|
|
232
|
-
[result.marginBalance, offset] = readLengthDelimited(buffer, offset);
|
|
233
|
-
break;
|
|
234
|
-
case PNL_FIELDS.MIN_ACCOUNT_BALANCE:
|
|
235
|
-
[result.minAccountBalance, offset] = readLengthDelimited(buffer, offset);
|
|
236
|
-
break;
|
|
237
|
-
case PNL_FIELDS.OPEN_POSITION_PNL:
|
|
238
|
-
[result.openPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
239
|
-
break;
|
|
240
|
-
case PNL_FIELDS.CLOSED_POSITION_PNL:
|
|
241
|
-
[result.closedPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
242
|
-
break;
|
|
243
|
-
case PNL_FIELDS.DAY_PNL:
|
|
244
|
-
[result.dayPnl, offset] = readLengthDelimited(buffer, offset);
|
|
245
|
-
break;
|
|
246
|
-
case PNL_FIELDS.DAY_OPEN_PNL:
|
|
247
|
-
[result.dayOpenPnl, offset] = readLengthDelimited(buffer, offset);
|
|
248
|
-
break;
|
|
249
|
-
case PNL_FIELDS.DAY_CLOSED_PNL:
|
|
250
|
-
[result.dayClosedPnl, offset] = readLengthDelimited(buffer, offset);
|
|
251
|
-
break;
|
|
252
|
-
case PNL_FIELDS.AVAILABLE_BUYING_POWER:
|
|
253
|
-
[result.availableBuyingPower, offset] = readLengthDelimited(buffer, offset);
|
|
254
|
-
break;
|
|
255
|
-
case PNL_FIELDS.SSBOE:
|
|
256
|
-
[result.ssboe, offset] = readVarint(buffer, offset);
|
|
257
|
-
break;
|
|
258
|
-
case PNL_FIELDS.USECS:
|
|
259
|
-
[result.usecs, offset] = readVarint(buffer, offset);
|
|
260
|
-
break;
|
|
261
|
-
default:
|
|
262
|
-
offset = skipField(buffer, offset, wireType);
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
48
|
+
return { size: this._size, available: this._count, bufferSize: this._bufferSize };
|
|
267
49
|
}
|
|
268
|
-
|
|
269
|
-
return result;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Manually decode InstrumentPnLPositionUpdate from raw bytes
|
|
274
|
-
*/
|
|
275
|
-
function decodeInstrumentPnL(buffer) {
|
|
276
|
-
const result = {};
|
|
277
|
-
let offset = 0;
|
|
278
|
-
|
|
279
|
-
while (offset < buffer.length) {
|
|
280
|
-
try {
|
|
281
|
-
const [tag, tagOffset] = readVarint(buffer, offset);
|
|
282
|
-
const wireType = tag & 0x7;
|
|
283
|
-
const fieldNumber = tag >>> 3;
|
|
284
|
-
offset = tagOffset;
|
|
285
|
-
|
|
286
|
-
switch (fieldNumber) {
|
|
287
|
-
case INSTRUMENT_PNL_FIELDS.TEMPLATE_ID:
|
|
288
|
-
[result.templateId, offset] = readVarint(buffer, offset);
|
|
289
|
-
break;
|
|
290
|
-
case INSTRUMENT_PNL_FIELDS.IS_SNAPSHOT:
|
|
291
|
-
const [isSnap, snapOffset] = readVarint(buffer, offset);
|
|
292
|
-
result.isSnapshot = isSnap !== 0;
|
|
293
|
-
offset = snapOffset;
|
|
294
|
-
break;
|
|
295
|
-
case INSTRUMENT_PNL_FIELDS.FCM_ID:
|
|
296
|
-
[result.fcmId, offset] = readLengthDelimited(buffer, offset);
|
|
297
|
-
break;
|
|
298
|
-
case INSTRUMENT_PNL_FIELDS.IB_ID:
|
|
299
|
-
[result.ibId, offset] = readLengthDelimited(buffer, offset);
|
|
300
|
-
break;
|
|
301
|
-
case INSTRUMENT_PNL_FIELDS.ACCOUNT_ID:
|
|
302
|
-
[result.accountId, offset] = readLengthDelimited(buffer, offset);
|
|
303
|
-
break;
|
|
304
|
-
case INSTRUMENT_PNL_FIELDS.SYMBOL:
|
|
305
|
-
[result.symbol, offset] = readLengthDelimited(buffer, offset);
|
|
306
|
-
break;
|
|
307
|
-
case INSTRUMENT_PNL_FIELDS.EXCHANGE:
|
|
308
|
-
[result.exchange, offset] = readLengthDelimited(buffer, offset);
|
|
309
|
-
break;
|
|
310
|
-
case INSTRUMENT_PNL_FIELDS.PRODUCT_CODE:
|
|
311
|
-
[result.productCode, offset] = readLengthDelimited(buffer, offset);
|
|
312
|
-
break;
|
|
313
|
-
case INSTRUMENT_PNL_FIELDS.BUY_QTY:
|
|
314
|
-
[result.buyQty, offset] = readVarint(buffer, offset);
|
|
315
|
-
break;
|
|
316
|
-
case INSTRUMENT_PNL_FIELDS.SELL_QTY:
|
|
317
|
-
[result.sellQty, offset] = readVarint(buffer, offset);
|
|
318
|
-
break;
|
|
319
|
-
case INSTRUMENT_PNL_FIELDS.FILL_BUY_QTY:
|
|
320
|
-
[result.fillBuyQty, offset] = readVarint(buffer, offset);
|
|
321
|
-
break;
|
|
322
|
-
case INSTRUMENT_PNL_FIELDS.FILL_SELL_QTY:
|
|
323
|
-
[result.fillSellQty, offset] = readVarint(buffer, offset);
|
|
324
|
-
break;
|
|
325
|
-
case INSTRUMENT_PNL_FIELDS.NET_QUANTITY:
|
|
326
|
-
[result.netQuantity, offset] = readVarint(buffer, offset);
|
|
327
|
-
break;
|
|
328
|
-
case INSTRUMENT_PNL_FIELDS.OPEN_POSITION_QUANTITY:
|
|
329
|
-
[result.openPositionQuantity, offset] = readVarint(buffer, offset);
|
|
330
|
-
break;
|
|
331
|
-
case INSTRUMENT_PNL_FIELDS.AVG_OPEN_FILL_PRICE:
|
|
332
|
-
// Double is 64-bit fixed
|
|
333
|
-
if (wireType === 1) {
|
|
334
|
-
result.avgOpenFillPrice = buffer.readDoubleLE(offset);
|
|
335
|
-
offset += 8;
|
|
336
|
-
} else {
|
|
337
|
-
offset = skipField(buffer, offset, wireType);
|
|
338
|
-
}
|
|
339
|
-
break;
|
|
340
|
-
case INSTRUMENT_PNL_FIELDS.OPEN_POSITION_PNL:
|
|
341
|
-
[result.openPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
342
|
-
break;
|
|
343
|
-
case INSTRUMENT_PNL_FIELDS.CLOSED_POSITION_PNL:
|
|
344
|
-
[result.closedPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
345
|
-
break;
|
|
346
|
-
case INSTRUMENT_PNL_FIELDS.DAY_PNL:
|
|
347
|
-
// DAY_PNL is a double (wireType 1 = 64-bit fixed)
|
|
348
|
-
if (wireType === 1) {
|
|
349
|
-
result.dayPnl = buffer.readDoubleLE(offset);
|
|
350
|
-
offset += 8;
|
|
351
|
-
} else {
|
|
352
|
-
// Fallback: try string
|
|
353
|
-
[result.dayPnl, offset] = readLengthDelimited(buffer, offset);
|
|
354
|
-
}
|
|
355
|
-
break;
|
|
356
|
-
case INSTRUMENT_PNL_FIELDS.DAY_OPEN_PNL:
|
|
357
|
-
// DAY_OPEN_PNL is a double (wireType 1 = 64-bit fixed)
|
|
358
|
-
if (wireType === 1) {
|
|
359
|
-
result.dayOpenPnl = buffer.readDoubleLE(offset);
|
|
360
|
-
offset += 8;
|
|
361
|
-
} else {
|
|
362
|
-
// Fallback: try string
|
|
363
|
-
[result.dayOpenPnl, offset] = readLengthDelimited(buffer, offset);
|
|
364
|
-
}
|
|
365
|
-
break;
|
|
366
|
-
case INSTRUMENT_PNL_FIELDS.DAY_CLOSED_PNL:
|
|
367
|
-
// DAY_CLOSED_PNL is a double (wireType 1 = 64-bit fixed)
|
|
368
|
-
if (wireType === 1) {
|
|
369
|
-
result.dayClosedPnl = buffer.readDoubleLE(offset);
|
|
370
|
-
offset += 8;
|
|
371
|
-
} else {
|
|
372
|
-
// Fallback: try string
|
|
373
|
-
[result.dayClosedPnl, offset] = readLengthDelimited(buffer, offset);
|
|
374
|
-
}
|
|
375
|
-
break;
|
|
376
|
-
case INSTRUMENT_PNL_FIELDS.SSBOE:
|
|
377
|
-
[result.ssboe, offset] = readVarint(buffer, offset);
|
|
378
|
-
break;
|
|
379
|
-
case INSTRUMENT_PNL_FIELDS.USECS:
|
|
380
|
-
[result.usecs, offset] = readVarint(buffer, offset);
|
|
381
|
-
break;
|
|
382
|
-
default:
|
|
383
|
-
offset = skipField(buffer, offset, wireType);
|
|
384
|
-
}
|
|
385
|
-
} catch (error) {
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
return result;
|
|
391
50
|
}
|
|
392
51
|
|
|
393
52
|
/**
|
|
@@ -580,101 +239,6 @@ class ProtobufHandler {
|
|
|
580
239
|
}
|
|
581
240
|
}
|
|
582
241
|
|
|
583
|
-
/**
|
|
584
|
-
* Decode ResponseProductCodes (template 112) - list of available symbols
|
|
585
|
-
*/
|
|
586
|
-
function decodeProductCodes(buffer) {
|
|
587
|
-
const result = { rpCode: [] };
|
|
588
|
-
let offset = 0;
|
|
589
|
-
|
|
590
|
-
while (offset < buffer.length) {
|
|
591
|
-
try {
|
|
592
|
-
const [tag, tagOffset] = readVarint(buffer, offset);
|
|
593
|
-
const wireType = tag & 0x7;
|
|
594
|
-
const fieldNumber = tag >>> 3;
|
|
595
|
-
offset = tagOffset;
|
|
596
|
-
|
|
597
|
-
switch (fieldNumber) {
|
|
598
|
-
case SYMBOL_FIELDS.TEMPLATE_ID:
|
|
599
|
-
[result.templateId, offset] = readVarint(buffer, offset);
|
|
600
|
-
break;
|
|
601
|
-
case SYMBOL_FIELDS.RP_CODE:
|
|
602
|
-
let rpCode;
|
|
603
|
-
[rpCode, offset] = readLengthDelimited(buffer, offset);
|
|
604
|
-
result.rpCode.push(rpCode);
|
|
605
|
-
break;
|
|
606
|
-
case SYMBOL_FIELDS.EXCHANGE:
|
|
607
|
-
[result.exchange, offset] = readLengthDelimited(buffer, offset);
|
|
608
|
-
break;
|
|
609
|
-
case SYMBOL_FIELDS.PRODUCT_CODE:
|
|
610
|
-
[result.productCode, offset] = readLengthDelimited(buffer, offset);
|
|
611
|
-
break;
|
|
612
|
-
case SYMBOL_FIELDS.PRODUCT_NAME:
|
|
613
|
-
[result.productName, offset] = readLengthDelimited(buffer, offset);
|
|
614
|
-
break;
|
|
615
|
-
case SYMBOL_FIELDS.USER_MSG:
|
|
616
|
-
[result.userMsg, offset] = readLengthDelimited(buffer, offset);
|
|
617
|
-
break;
|
|
618
|
-
default:
|
|
619
|
-
offset = skipField(buffer, offset, wireType);
|
|
620
|
-
}
|
|
621
|
-
} catch (error) {
|
|
622
|
-
break;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
return result;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* Decode ResponseFrontMonthContract (template 114) - current tradeable contract
|
|
631
|
-
*/
|
|
632
|
-
function decodeFrontMonthContract(buffer) {
|
|
633
|
-
const result = { rpCode: [] };
|
|
634
|
-
let offset = 0;
|
|
635
|
-
|
|
636
|
-
while (offset < buffer.length) {
|
|
637
|
-
try {
|
|
638
|
-
const [tag, tagOffset] = readVarint(buffer, offset);
|
|
639
|
-
const wireType = tag & 0x7;
|
|
640
|
-
const fieldNumber = tag >>> 3;
|
|
641
|
-
offset = tagOffset;
|
|
642
|
-
|
|
643
|
-
switch (fieldNumber) {
|
|
644
|
-
case SYMBOL_FIELDS.TEMPLATE_ID:
|
|
645
|
-
[result.templateId, offset] = readVarint(buffer, offset);
|
|
646
|
-
break;
|
|
647
|
-
case SYMBOL_FIELDS.RP_CODE:
|
|
648
|
-
let rpCode;
|
|
649
|
-
[rpCode, offset] = readLengthDelimited(buffer, offset);
|
|
650
|
-
result.rpCode.push(rpCode);
|
|
651
|
-
break;
|
|
652
|
-
case SYMBOL_FIELDS.SYMBOL:
|
|
653
|
-
[result.symbol, offset] = readLengthDelimited(buffer, offset);
|
|
654
|
-
break;
|
|
655
|
-
case SYMBOL_FIELDS.EXCHANGE:
|
|
656
|
-
[result.exchange, offset] = readLengthDelimited(buffer, offset);
|
|
657
|
-
break;
|
|
658
|
-
case SYMBOL_FIELDS.TRADING_SYMBOL:
|
|
659
|
-
[result.tradingSymbol, offset] = readLengthDelimited(buffer, offset);
|
|
660
|
-
break;
|
|
661
|
-
case SYMBOL_FIELDS.DESCRIPTION:
|
|
662
|
-
[result.description, offset] = readLengthDelimited(buffer, offset);
|
|
663
|
-
break;
|
|
664
|
-
case SYMBOL_FIELDS.USER_MSG:
|
|
665
|
-
[result.userMsg, offset] = readLengthDelimited(buffer, offset);
|
|
666
|
-
break;
|
|
667
|
-
default:
|
|
668
|
-
offset = skipField(buffer, offset, wireType);
|
|
669
|
-
}
|
|
670
|
-
} catch (error) {
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
return result;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
242
|
// Singleton
|
|
679
243
|
const proto = new ProtobufHandler();
|
|
680
244
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HFT Signal Calculation
|
|
3
|
+
* @module services/strategy/hft-signal-calc
|
|
4
|
+
*
|
|
5
|
+
* Signal generation and building logic for HFT strategy
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Calculate trading signal from all models
|
|
10
|
+
* @param {Object} state - Strategy state
|
|
11
|
+
* @param {Object} config - Strategy config
|
|
12
|
+
* @returns {Object} {direction, confidence, scores}
|
|
13
|
+
*/
|
|
14
|
+
function calculateSignal(state, config) {
|
|
15
|
+
let direction = 'none';
|
|
16
|
+
let confidence = 0;
|
|
17
|
+
|
|
18
|
+
const { ofiValue, zscore, momentum, buyVolume, sellVolume, cumulativeDelta } = state;
|
|
19
|
+
const { ofiThreshold, zscoreThreshold, momentumThreshold, minConfidence } = config;
|
|
20
|
+
|
|
21
|
+
const scores = {
|
|
22
|
+
ofi: 0,
|
|
23
|
+
zscore: 0,
|
|
24
|
+
momentum: 0,
|
|
25
|
+
delta: 0,
|
|
26
|
+
composite: 0,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// === MODEL 1: OFI ===
|
|
30
|
+
const absOfi = Math.abs(ofiValue);
|
|
31
|
+
if (absOfi > ofiThreshold) {
|
|
32
|
+
scores.ofi = Math.min(1.0, absOfi / 0.6);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// === MODEL 2: Z-Score Mean Reversion ===
|
|
36
|
+
const absZ = Math.abs(zscore);
|
|
37
|
+
if (absZ > zscoreThreshold) {
|
|
38
|
+
scores.zscore = Math.min(1.0, absZ / 3.0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// === MODEL 3: Momentum ===
|
|
42
|
+
const absMom = Math.abs(momentum);
|
|
43
|
+
if (absMom > momentumThreshold) {
|
|
44
|
+
scores.momentum = Math.min(1.0, absMom / 3.0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// === MODEL 4: Delta ===
|
|
48
|
+
const totalVol = buyVolume + sellVolume;
|
|
49
|
+
if (totalVol > 0) {
|
|
50
|
+
const deltaRatio = cumulativeDelta / totalVol;
|
|
51
|
+
scores.delta = Math.min(1.0, Math.abs(deltaRatio) * 2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// === COMPOSITE SCORE ===
|
|
55
|
+
scores.composite =
|
|
56
|
+
scores.ofi * 0.35 + // OFI: 35%
|
|
57
|
+
scores.zscore * 0.25 + // Z-Score: 25%
|
|
58
|
+
scores.momentum * 0.20 + // Momentum: 20%
|
|
59
|
+
scores.delta * 0.20; // Delta: 20%
|
|
60
|
+
|
|
61
|
+
confidence = scores.composite;
|
|
62
|
+
|
|
63
|
+
// === DETERMINE DIRECTION ===
|
|
64
|
+
if (scores.composite >= minConfidence) {
|
|
65
|
+
// Primary: Mean reversion
|
|
66
|
+
if (absZ > zscoreThreshold) {
|
|
67
|
+
direction = zscore > 0 ? 'short' : 'long';
|
|
68
|
+
|
|
69
|
+
// Confirm with OFI
|
|
70
|
+
const ofiConfirms =
|
|
71
|
+
(direction === 'long' && ofiValue > 0) ||
|
|
72
|
+
(direction === 'short' && ofiValue < 0);
|
|
73
|
+
|
|
74
|
+
if (ofiConfirms) {
|
|
75
|
+
confidence += 0.1;
|
|
76
|
+
} else if (Math.abs(ofiValue) > 0.2) {
|
|
77
|
+
confidence -= 0.15;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Fallback: Momentum breakout
|
|
81
|
+
else if (absMom > momentumThreshold * 2 && absOfi > ofiThreshold) {
|
|
82
|
+
direction = momentum > 0 ? 'long' : 'short';
|
|
83
|
+
if ((direction === 'long' && ofiValue < 0) ||
|
|
84
|
+
(direction === 'short' && ofiValue > 0)) {
|
|
85
|
+
direction = 'none';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
confidence = Math.min(1.0, Math.max(0, confidence));
|
|
91
|
+
|
|
92
|
+
return { direction, confidence, scores };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build signal object
|
|
97
|
+
* @param {string} direction
|
|
98
|
+
* @param {number} confidence
|
|
99
|
+
* @param {Object} scores
|
|
100
|
+
* @param {Object} tick
|
|
101
|
+
* @param {Object} state
|
|
102
|
+
* @param {Object} config
|
|
103
|
+
* @returns {Object} Signal object
|
|
104
|
+
*/
|
|
105
|
+
function buildSignal(direction, confidence, scores, tick, state, config) {
|
|
106
|
+
const entry = tick.price;
|
|
107
|
+
const isLong = direction === 'long';
|
|
108
|
+
|
|
109
|
+
const { std, ofiValue, zscore, momentum, cumulativeDelta, tickCount, signalCount, contractId, tickSize } = state;
|
|
110
|
+
const { baseStopTicks, baseTargetTicks } = config;
|
|
111
|
+
|
|
112
|
+
// Adaptive stops based on volatility
|
|
113
|
+
const volMult = Math.max(0.5, Math.min(2.0, std / tickSize / 4));
|
|
114
|
+
const stopTicks = Math.round(baseStopTicks * volMult);
|
|
115
|
+
const targetTicks = Math.round(baseTargetTicks * volMult);
|
|
116
|
+
|
|
117
|
+
const stopLoss = isLong
|
|
118
|
+
? entry - stopTicks * tickSize
|
|
119
|
+
: entry + stopTicks * tickSize;
|
|
120
|
+
|
|
121
|
+
const takeProfit = isLong
|
|
122
|
+
? entry + targetTicks * tickSize
|
|
123
|
+
: entry - targetTicks * tickSize;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
id: `hft-${Date.now()}-${signalCount}`,
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
contractId,
|
|
129
|
+
direction,
|
|
130
|
+
side: isLong ? 0 : 1,
|
|
131
|
+
entry,
|
|
132
|
+
stopLoss,
|
|
133
|
+
takeProfit,
|
|
134
|
+
confidence,
|
|
135
|
+
scores,
|
|
136
|
+
ofi: ofiValue,
|
|
137
|
+
zscore,
|
|
138
|
+
momentum,
|
|
139
|
+
delta: cumulativeDelta,
|
|
140
|
+
tickCount,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
calculateSignal,
|
|
146
|
+
buildSignal,
|
|
147
|
+
};
|