hedgequantx 2.9.236 → 2.9.238
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/dist/lib/m/s1-models.js +281 -94
- package/package.json +2 -1
- package/src/lib/hft/index.js +648 -0
- package/src/pages/algo/algo-executor.js +29 -10
- package/src/services/rithmic/handlers.js +93 -54
- package/src/services/rithmic/orders.js +73 -95
- package/src/services/rithmic/protobuf-decoders.js +237 -241
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Protobuf Decoders - Decode Rithmic protobuf messages
|
|
3
3
|
* @module services/rithmic/protobuf-decoders
|
|
4
|
+
*
|
|
5
|
+
* HFT-GRADE OPTIMIZATIONS:
|
|
6
|
+
* - Lookup tables for O(1) field dispatch (replaces O(n) switch)
|
|
7
|
+
* - Pre-built decoder maps at module load time
|
|
8
|
+
* - Zero branch misprediction in hot path
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
const { readVarint, readLengthDelimited, skipField } = require('./protobuf-utils');
|
|
@@ -70,241 +75,243 @@ const INSTRUMENT_PNL_FIELDS = {
|
|
|
70
75
|
USECS: 150101,
|
|
71
76
|
};
|
|
72
77
|
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// HFT: PRE-BUILT LOOKUP TABLES FOR O(1) FIELD DISPATCH
|
|
80
|
+
// =============================================================================
|
|
81
|
+
|
|
82
|
+
// Decoder types: 0=skip, 1=varint, 2=string, 3=bool, 4=double
|
|
83
|
+
const DECODE_SKIP = 0;
|
|
84
|
+
const DECODE_VARINT = 1;
|
|
85
|
+
const DECODE_STRING = 2;
|
|
86
|
+
const DECODE_BOOL = 3;
|
|
87
|
+
const DECODE_DOUBLE = 4;
|
|
88
|
+
|
|
73
89
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* @param {
|
|
77
|
-
* @returns {
|
|
90
|
+
* Build lookup table from field definitions
|
|
91
|
+
* @param {Object} fields - Field ID mapping
|
|
92
|
+
* @param {Object} fieldTypes - Map of field name to [type, resultKey]
|
|
93
|
+
* @returns {Map} Lookup table: fieldNumber -> [type, resultKey]
|
|
78
94
|
*/
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
function buildLookupTable(fields, fieldTypes) {
|
|
96
|
+
const table = new Map();
|
|
97
|
+
for (const [fieldName, fieldNumber] of Object.entries(fields)) {
|
|
98
|
+
if (fieldTypes[fieldName]) {
|
|
99
|
+
table.set(fieldNumber, fieldTypes[fieldName]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return table;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// PnL field types: [decoderType, resultKey]
|
|
106
|
+
const PNL_FIELD_TYPES = {
|
|
107
|
+
TEMPLATE_ID: [DECODE_VARINT, 'templateId'],
|
|
108
|
+
IS_SNAPSHOT: [DECODE_BOOL, 'isSnapshot'],
|
|
109
|
+
FCM_ID: [DECODE_STRING, 'fcmId'],
|
|
110
|
+
IB_ID: [DECODE_STRING, 'ibId'],
|
|
111
|
+
ACCOUNT_ID: [DECODE_STRING, 'accountId'],
|
|
112
|
+
ACCOUNT_BALANCE: [DECODE_STRING, 'accountBalance'],
|
|
113
|
+
CASH_ON_HAND: [DECODE_STRING, 'cashOnHand'],
|
|
114
|
+
MARGIN_BALANCE: [DECODE_STRING, 'marginBalance'],
|
|
115
|
+
MIN_ACCOUNT_BALANCE: [DECODE_STRING, 'minAccountBalance'],
|
|
116
|
+
OPEN_POSITION_PNL: [DECODE_STRING, 'openPositionPnl'],
|
|
117
|
+
CLOSED_POSITION_PNL: [DECODE_STRING, 'closedPositionPnl'],
|
|
118
|
+
DAY_PNL: [DECODE_STRING, 'dayPnl'],
|
|
119
|
+
DAY_OPEN_PNL: [DECODE_STRING, 'dayOpenPnl'],
|
|
120
|
+
DAY_CLOSED_PNL: [DECODE_STRING, 'dayClosedPnl'],
|
|
121
|
+
AVAILABLE_BUYING_POWER: [DECODE_STRING, 'availableBuyingPower'],
|
|
122
|
+
SSBOE: [DECODE_VARINT, 'ssboe'],
|
|
123
|
+
USECS: [DECODE_VARINT, 'usecs'],
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Instrument PnL field types
|
|
127
|
+
const INSTRUMENT_PNL_FIELD_TYPES = {
|
|
128
|
+
TEMPLATE_ID: [DECODE_VARINT, 'templateId'],
|
|
129
|
+
IS_SNAPSHOT: [DECODE_BOOL, 'isSnapshot'],
|
|
130
|
+
FCM_ID: [DECODE_STRING, 'fcmId'],
|
|
131
|
+
IB_ID: [DECODE_STRING, 'ibId'],
|
|
132
|
+
ACCOUNT_ID: [DECODE_STRING, 'accountId'],
|
|
133
|
+
SYMBOL: [DECODE_STRING, 'symbol'],
|
|
134
|
+
EXCHANGE: [DECODE_STRING, 'exchange'],
|
|
135
|
+
PRODUCT_CODE: [DECODE_STRING, 'productCode'],
|
|
136
|
+
BUY_QTY: [DECODE_VARINT, 'buyQty'],
|
|
137
|
+
SELL_QTY: [DECODE_VARINT, 'sellQty'],
|
|
138
|
+
FILL_BUY_QTY: [DECODE_VARINT, 'fillBuyQty'],
|
|
139
|
+
FILL_SELL_QTY: [DECODE_VARINT, 'fillSellQty'],
|
|
140
|
+
NET_QUANTITY: [DECODE_VARINT, 'netQuantity'],
|
|
141
|
+
OPEN_POSITION_QUANTITY: [DECODE_VARINT, 'openPositionQuantity'],
|
|
142
|
+
AVG_OPEN_FILL_PRICE: [DECODE_DOUBLE, 'avgOpenFillPrice'],
|
|
143
|
+
OPEN_POSITION_PNL: [DECODE_STRING, 'openPositionPnl'],
|
|
144
|
+
CLOSED_POSITION_PNL: [DECODE_STRING, 'closedPositionPnl'],
|
|
145
|
+
DAY_PNL: [DECODE_STRING, 'dayPnl'],
|
|
146
|
+
DAY_OPEN_PNL: [DECODE_STRING, 'dayOpenPnl'],
|
|
147
|
+
DAY_CLOSED_PNL: [DECODE_STRING, 'dayClosedPnl'],
|
|
148
|
+
SSBOE: [DECODE_VARINT, 'ssboe'],
|
|
149
|
+
USECS: [DECODE_VARINT, 'usecs'],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Symbol/Product codes field types
|
|
153
|
+
const SYMBOL_FIELD_TYPES = {
|
|
154
|
+
TEMPLATE_ID: [DECODE_VARINT, 'templateId'],
|
|
155
|
+
RP_CODE: [DECODE_STRING, 'rpCode'], // Array field - handled specially
|
|
156
|
+
EXCHANGE: [DECODE_STRING, 'exchange'],
|
|
157
|
+
PRODUCT_CODE: [DECODE_STRING, 'productCode'],
|
|
158
|
+
PRODUCT_NAME: [DECODE_STRING, 'productName'],
|
|
159
|
+
SYMBOL: [DECODE_STRING, 'symbol'],
|
|
160
|
+
TRADING_SYMBOL: [DECODE_STRING, 'tradingSymbol'],
|
|
161
|
+
DESCRIPTION: [DECODE_STRING, 'description'],
|
|
162
|
+
USER_MSG: [DECODE_STRING, 'userMsg'],
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Pre-build lookup tables at module load time (O(1) access in hot path)
|
|
166
|
+
const PNL_LOOKUP = buildLookupTable(PNL_FIELDS, PNL_FIELD_TYPES);
|
|
167
|
+
const INSTRUMENT_PNL_LOOKUP = buildLookupTable(INSTRUMENT_PNL_FIELDS, INSTRUMENT_PNL_FIELD_TYPES);
|
|
168
|
+
const SYMBOL_LOOKUP = buildLookupTable(SYMBOL_FIELDS, SYMBOL_FIELD_TYPES);
|
|
169
|
+
|
|
170
|
+
// =============================================================================
|
|
171
|
+
// HFT: GENERIC DECODER WITH LOOKUP TABLE - O(1) FIELD DISPATCH
|
|
172
|
+
// =============================================================================
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* HFT-optimized generic decoder using lookup table
|
|
176
|
+
* Replaces O(n) switch statements with O(1) Map lookup
|
|
177
|
+
* @param {Buffer} data - Raw protobuf data (without length prefix)
|
|
178
|
+
* @param {Map} lookup - Pre-built field lookup table
|
|
179
|
+
* @param {Object} result - Pre-allocated result object (optional)
|
|
180
|
+
* @returns {Object} Decoded data
|
|
181
|
+
*/
|
|
182
|
+
function decodeWithLookup(data, lookup, result = {}) {
|
|
84
183
|
let offset = 0;
|
|
184
|
+
const len = data.length;
|
|
85
185
|
|
|
86
|
-
while (offset <
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
186
|
+
while (offset < len) {
|
|
187
|
+
// Read tag (varint) - inlined for performance
|
|
188
|
+
let tag = 0;
|
|
189
|
+
let shift = 0;
|
|
190
|
+
let byte;
|
|
191
|
+
do {
|
|
192
|
+
byte = data[offset++];
|
|
193
|
+
tag |= (byte & 0x7f) << shift;
|
|
194
|
+
shift += 7;
|
|
195
|
+
} while (byte & 0x80);
|
|
92
196
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
break;
|
|
138
|
-
case PNL_FIELDS.AVAILABLE_BUYING_POWER:
|
|
139
|
-
[result.availableBuyingPower, offset] = readLengthDelimited(data, offset);
|
|
140
|
-
break;
|
|
141
|
-
case PNL_FIELDS.SSBOE:
|
|
142
|
-
[result.ssboe, offset] = readVarint(data, offset);
|
|
143
|
-
break;
|
|
144
|
-
case PNL_FIELDS.USECS:
|
|
145
|
-
[result.usecs, offset] = readVarint(data, offset);
|
|
146
|
-
break;
|
|
147
|
-
default:
|
|
197
|
+
const wireType = tag & 0x7;
|
|
198
|
+
const fieldNumber = tag >>> 3;
|
|
199
|
+
|
|
200
|
+
// O(1) lookup instead of O(n) switch
|
|
201
|
+
const fieldInfo = lookup.get(fieldNumber);
|
|
202
|
+
|
|
203
|
+
if (fieldInfo) {
|
|
204
|
+
const [decodeType, key] = fieldInfo;
|
|
205
|
+
|
|
206
|
+
// Decode based on type (minimal branching)
|
|
207
|
+
if (decodeType === DECODE_VARINT) {
|
|
208
|
+
let value = 0;
|
|
209
|
+
shift = 0;
|
|
210
|
+
do {
|
|
211
|
+
byte = data[offset++];
|
|
212
|
+
value |= (byte & 0x7f) << shift;
|
|
213
|
+
shift += 7;
|
|
214
|
+
} while (byte & 0x80);
|
|
215
|
+
result[key] = value;
|
|
216
|
+
} else if (decodeType === DECODE_STRING) {
|
|
217
|
+
// Read length
|
|
218
|
+
let strLen = 0;
|
|
219
|
+
shift = 0;
|
|
220
|
+
do {
|
|
221
|
+
byte = data[offset++];
|
|
222
|
+
strLen |= (byte & 0x7f) << shift;
|
|
223
|
+
shift += 7;
|
|
224
|
+
} while (byte & 0x80);
|
|
225
|
+
result[key] = data.toString('utf8', offset, offset + strLen);
|
|
226
|
+
offset += strLen;
|
|
227
|
+
} else if (decodeType === DECODE_BOOL) {
|
|
228
|
+
let value = 0;
|
|
229
|
+
shift = 0;
|
|
230
|
+
do {
|
|
231
|
+
byte = data[offset++];
|
|
232
|
+
value |= (byte & 0x7f) << shift;
|
|
233
|
+
shift += 7;
|
|
234
|
+
} while (byte & 0x80);
|
|
235
|
+
result[key] = value !== 0;
|
|
236
|
+
} else if (decodeType === DECODE_DOUBLE) {
|
|
237
|
+
if (wireType === 1) {
|
|
238
|
+
result[key] = data.readDoubleLE(offset);
|
|
239
|
+
offset += 8;
|
|
240
|
+
} else {
|
|
148
241
|
offset = skipField(data, offset, wireType);
|
|
242
|
+
}
|
|
149
243
|
}
|
|
150
|
-
}
|
|
151
|
-
|
|
244
|
+
} else {
|
|
245
|
+
// Unknown field - skip efficiently
|
|
246
|
+
offset = skipField(data, offset, wireType);
|
|
152
247
|
}
|
|
153
248
|
}
|
|
154
249
|
|
|
155
250
|
return result;
|
|
156
251
|
}
|
|
157
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Manually decode AccountPnL from raw bytes
|
|
255
|
+
* HFT: Uses O(1) lookup table instead of O(n) switch
|
|
256
|
+
* @param {Buffer} buffer - Raw protobuf buffer
|
|
257
|
+
* @returns {Object} Decoded account PnL data
|
|
258
|
+
*/
|
|
259
|
+
function decodeAccountPnL(buffer) {
|
|
260
|
+
// Skip 4-byte length prefix
|
|
261
|
+
const data = buffer.length > 4 ? buffer.subarray(4) : buffer;
|
|
262
|
+
return decodeWithLookup(data, PNL_LOOKUP);
|
|
263
|
+
}
|
|
264
|
+
|
|
158
265
|
/**
|
|
159
266
|
* Manually decode InstrumentPnLPositionUpdate from raw bytes
|
|
160
|
-
*
|
|
267
|
+
* HFT: Uses O(1) lookup table instead of O(n) switch
|
|
161
268
|
* @param {Buffer} buffer - Raw protobuf buffer
|
|
162
269
|
* @returns {Object} Decoded instrument PnL data
|
|
163
270
|
*/
|
|
164
271
|
function decodeInstrumentPnL(buffer) {
|
|
165
|
-
// Skip 4-byte length prefix
|
|
166
|
-
const data = buffer.length > 4 ? buffer.
|
|
167
|
-
|
|
168
|
-
const result = {};
|
|
169
|
-
let offset = 0;
|
|
170
|
-
|
|
171
|
-
while (offset < data.length) {
|
|
172
|
-
try {
|
|
173
|
-
const [tag, tagOffset] = readVarint(data, offset);
|
|
174
|
-
const wireType = tag & 0x7;
|
|
175
|
-
const fieldNumber = tag >>> 3;
|
|
176
|
-
offset = tagOffset;
|
|
177
|
-
|
|
178
|
-
switch (fieldNumber) {
|
|
179
|
-
case INSTRUMENT_PNL_FIELDS.TEMPLATE_ID:
|
|
180
|
-
[result.templateId, offset] = readVarint(data, offset);
|
|
181
|
-
break;
|
|
182
|
-
case INSTRUMENT_PNL_FIELDS.IS_SNAPSHOT:
|
|
183
|
-
const [isSnap, snapOffset] = readVarint(data, offset);
|
|
184
|
-
result.isSnapshot = isSnap !== 0;
|
|
185
|
-
offset = snapOffset;
|
|
186
|
-
break;
|
|
187
|
-
case INSTRUMENT_PNL_FIELDS.FCM_ID:
|
|
188
|
-
[result.fcmId, offset] = readLengthDelimited(data, offset);
|
|
189
|
-
break;
|
|
190
|
-
case INSTRUMENT_PNL_FIELDS.IB_ID:
|
|
191
|
-
[result.ibId, offset] = readLengthDelimited(data, offset);
|
|
192
|
-
break;
|
|
193
|
-
case INSTRUMENT_PNL_FIELDS.ACCOUNT_ID:
|
|
194
|
-
[result.accountId, offset] = readLengthDelimited(data, offset);
|
|
195
|
-
break;
|
|
196
|
-
case INSTRUMENT_PNL_FIELDS.SYMBOL:
|
|
197
|
-
[result.symbol, offset] = readLengthDelimited(data, offset);
|
|
198
|
-
break;
|
|
199
|
-
case INSTRUMENT_PNL_FIELDS.EXCHANGE:
|
|
200
|
-
[result.exchange, offset] = readLengthDelimited(data, offset);
|
|
201
|
-
break;
|
|
202
|
-
case INSTRUMENT_PNL_FIELDS.PRODUCT_CODE:
|
|
203
|
-
[result.productCode, offset] = readLengthDelimited(data, offset);
|
|
204
|
-
break;
|
|
205
|
-
case INSTRUMENT_PNL_FIELDS.BUY_QTY:
|
|
206
|
-
[result.buyQty, offset] = readVarint(data, offset);
|
|
207
|
-
break;
|
|
208
|
-
case INSTRUMENT_PNL_FIELDS.SELL_QTY:
|
|
209
|
-
[result.sellQty, offset] = readVarint(data, offset);
|
|
210
|
-
break;
|
|
211
|
-
case INSTRUMENT_PNL_FIELDS.FILL_BUY_QTY:
|
|
212
|
-
[result.fillBuyQty, offset] = readVarint(data, offset);
|
|
213
|
-
break;
|
|
214
|
-
case INSTRUMENT_PNL_FIELDS.FILL_SELL_QTY:
|
|
215
|
-
[result.fillSellQty, offset] = readVarint(data, offset);
|
|
216
|
-
break;
|
|
217
|
-
case INSTRUMENT_PNL_FIELDS.NET_QUANTITY:
|
|
218
|
-
[result.netQuantity, offset] = readVarint(data, offset);
|
|
219
|
-
break;
|
|
220
|
-
case INSTRUMENT_PNL_FIELDS.OPEN_POSITION_QUANTITY:
|
|
221
|
-
[result.openPositionQuantity, offset] = readVarint(data, offset);
|
|
222
|
-
break;
|
|
223
|
-
case INSTRUMENT_PNL_FIELDS.AVG_OPEN_FILL_PRICE:
|
|
224
|
-
// Double is 64-bit fixed
|
|
225
|
-
if (wireType === 1) {
|
|
226
|
-
result.avgOpenFillPrice = data.readDoubleLE(offset);
|
|
227
|
-
offset += 8;
|
|
228
|
-
} else {
|
|
229
|
-
offset = skipField(data, offset, wireType);
|
|
230
|
-
}
|
|
231
|
-
break;
|
|
232
|
-
case INSTRUMENT_PNL_FIELDS.OPEN_POSITION_PNL:
|
|
233
|
-
[result.openPositionPnl, offset] = readLengthDelimited(data, offset);
|
|
234
|
-
break;
|
|
235
|
-
case INSTRUMENT_PNL_FIELDS.CLOSED_POSITION_PNL:
|
|
236
|
-
[result.closedPositionPnl, offset] = readLengthDelimited(data, offset);
|
|
237
|
-
break;
|
|
238
|
-
case INSTRUMENT_PNL_FIELDS.DAY_PNL:
|
|
239
|
-
[result.dayPnl, offset] = readLengthDelimited(data, offset);
|
|
240
|
-
break;
|
|
241
|
-
case INSTRUMENT_PNL_FIELDS.DAY_OPEN_PNL:
|
|
242
|
-
[result.dayOpenPnl, offset] = readLengthDelimited(data, offset);
|
|
243
|
-
break;
|
|
244
|
-
case INSTRUMENT_PNL_FIELDS.DAY_CLOSED_PNL:
|
|
245
|
-
[result.dayClosedPnl, offset] = readLengthDelimited(data, offset);
|
|
246
|
-
break;
|
|
247
|
-
case INSTRUMENT_PNL_FIELDS.SSBOE:
|
|
248
|
-
[result.ssboe, offset] = readVarint(data, offset);
|
|
249
|
-
break;
|
|
250
|
-
case INSTRUMENT_PNL_FIELDS.USECS:
|
|
251
|
-
[result.usecs, offset] = readVarint(data, offset);
|
|
252
|
-
break;
|
|
253
|
-
default:
|
|
254
|
-
offset = skipField(data, offset, wireType);
|
|
255
|
-
}
|
|
256
|
-
} catch (error) {
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return result;
|
|
272
|
+
// Skip 4-byte length prefix - use subarray (no copy) instead of slice
|
|
273
|
+
const data = buffer.length > 4 ? buffer.subarray(4) : buffer;
|
|
274
|
+
return decodeWithLookup(data, INSTRUMENT_PNL_LOOKUP);
|
|
262
275
|
}
|
|
263
276
|
|
|
264
277
|
/**
|
|
265
278
|
* Decode ResponseProductCodes (template 112) - list of available symbols
|
|
279
|
+
* Note: Not hot-path (initialization only), uses switch for rpCode array handling
|
|
266
280
|
* @param {Buffer} buffer - Raw protobuf buffer (with 4-byte length prefix)
|
|
267
281
|
* @returns {Object} Decoded product codes
|
|
268
282
|
*/
|
|
269
283
|
function decodeProductCodes(buffer) {
|
|
270
|
-
//
|
|
271
|
-
const data = buffer.length > 4 ? buffer.
|
|
284
|
+
// HFT: Use subarray (zero-copy) instead of slice
|
|
285
|
+
const data = buffer.length > 4 ? buffer.subarray(4) : buffer;
|
|
272
286
|
const result = { rpCode: [] };
|
|
273
287
|
let offset = 0;
|
|
288
|
+
const len = data.length;
|
|
274
289
|
|
|
275
|
-
while (offset <
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
offset = tagOffset;
|
|
290
|
+
while (offset < len) {
|
|
291
|
+
const [tag, tagOffset] = readVarint(data, offset);
|
|
292
|
+
const wireType = tag & 0x7;
|
|
293
|
+
const fieldNumber = tag >>> 3;
|
|
294
|
+
offset = tagOffset;
|
|
281
295
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
break;
|
|
297
|
-
case SYMBOL_FIELDS.PRODUCT_NAME:
|
|
298
|
-
[result.productName, offset] = readLengthDelimited(data, offset);
|
|
299
|
-
break;
|
|
300
|
-
case SYMBOL_FIELDS.USER_MSG:
|
|
301
|
-
[result.userMsg, offset] = readLengthDelimited(data, offset);
|
|
302
|
-
break;
|
|
303
|
-
default:
|
|
304
|
-
offset = skipField(data, offset, wireType);
|
|
296
|
+
// Use lookup for most fields, special case for rpCode array
|
|
297
|
+
const fieldInfo = SYMBOL_LOOKUP.get(fieldNumber);
|
|
298
|
+
if (fieldInfo) {
|
|
299
|
+
const [, key] = fieldInfo;
|
|
300
|
+
if (key === 'rpCode') {
|
|
301
|
+
// Array field - push to existing array
|
|
302
|
+
let rpCode;
|
|
303
|
+
[rpCode, offset] = readLengthDelimited(data, offset);
|
|
304
|
+
result.rpCode.push(rpCode);
|
|
305
|
+
} else {
|
|
306
|
+
// Regular field
|
|
307
|
+
let value;
|
|
308
|
+
[value, offset] = readLengthDelimited(data, offset);
|
|
309
|
+
result[key] = value;
|
|
305
310
|
}
|
|
306
|
-
}
|
|
307
|
-
|
|
311
|
+
} else if (fieldNumber === SYMBOL_FIELDS.TEMPLATE_ID) {
|
|
312
|
+
[result.templateId, offset] = readVarint(data, offset);
|
|
313
|
+
} else {
|
|
314
|
+
offset = skipField(data, offset, wireType);
|
|
308
315
|
}
|
|
309
316
|
}
|
|
310
317
|
|
|
@@ -313,53 +320,42 @@ function decodeProductCodes(buffer) {
|
|
|
313
320
|
|
|
314
321
|
/**
|
|
315
322
|
* Decode ResponseFrontMonthContract (template 114) - current tradeable contract
|
|
316
|
-
*
|
|
323
|
+
* Note: Not hot-path (initialization only), uses switch for rpCode array handling
|
|
317
324
|
* @param {Buffer} buffer - Raw protobuf buffer
|
|
318
325
|
* @returns {Object} Decoded front month contract
|
|
319
326
|
*/
|
|
320
327
|
function decodeFrontMonthContract(buffer) {
|
|
321
|
-
//
|
|
322
|
-
const data = buffer.length > 4 ? buffer.
|
|
323
|
-
|
|
328
|
+
// HFT: Use subarray (zero-copy) instead of slice
|
|
329
|
+
const data = buffer.length > 4 ? buffer.subarray(4) : buffer;
|
|
324
330
|
const result = { rpCode: [] };
|
|
325
331
|
let offset = 0;
|
|
332
|
+
const len = data.length;
|
|
326
333
|
|
|
327
|
-
while (offset <
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
offset = tagOffset;
|
|
334
|
+
while (offset < len) {
|
|
335
|
+
const [tag, tagOffset] = readVarint(data, offset);
|
|
336
|
+
const wireType = tag & 0x7;
|
|
337
|
+
const fieldNumber = tag >>> 3;
|
|
338
|
+
offset = tagOffset;
|
|
333
339
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
break;
|
|
349
|
-
case SYMBOL_FIELDS.TRADING_SYMBOL:
|
|
350
|
-
[result.tradingSymbol, offset] = readLengthDelimited(data, offset);
|
|
351
|
-
break;
|
|
352
|
-
case SYMBOL_FIELDS.DESCRIPTION:
|
|
353
|
-
[result.description, offset] = readLengthDelimited(data, offset);
|
|
354
|
-
break;
|
|
355
|
-
case SYMBOL_FIELDS.USER_MSG:
|
|
356
|
-
[result.userMsg, offset] = readLengthDelimited(data, offset);
|
|
357
|
-
break;
|
|
358
|
-
default:
|
|
359
|
-
offset = skipField(data, offset, wireType);
|
|
340
|
+
// Use lookup for most fields, special case for rpCode array
|
|
341
|
+
const fieldInfo = SYMBOL_LOOKUP.get(fieldNumber);
|
|
342
|
+
if (fieldInfo) {
|
|
343
|
+
const [, key] = fieldInfo;
|
|
344
|
+
if (key === 'rpCode') {
|
|
345
|
+
// Array field - push to existing array
|
|
346
|
+
let rpCode;
|
|
347
|
+
[rpCode, offset] = readLengthDelimited(data, offset);
|
|
348
|
+
result.rpCode.push(rpCode);
|
|
349
|
+
} else {
|
|
350
|
+
// Regular field
|
|
351
|
+
let value;
|
|
352
|
+
[value, offset] = readLengthDelimited(data, offset);
|
|
353
|
+
result[key] = value;
|
|
360
354
|
}
|
|
361
|
-
}
|
|
362
|
-
|
|
355
|
+
} else if (fieldNumber === SYMBOL_FIELDS.TEMPLATE_ID) {
|
|
356
|
+
[result.templateId, offset] = readVarint(data, offset);
|
|
357
|
+
} else {
|
|
358
|
+
offset = skipField(data, offset, wireType);
|
|
363
359
|
}
|
|
364
360
|
}
|
|
365
361
|
|