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
|
@@ -1,264 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rithmic Orders Module
|
|
3
3
|
* Order placement, cancellation, and history
|
|
4
|
-
*
|
|
5
|
-
* FAST SCALPING: fastEntry() and fastExit() for ultra-low latency execution
|
|
6
|
-
* Target: < 5ms local processing (network latency separate)
|
|
7
|
-
*
|
|
8
|
-
* OPTIMIZATIONS:
|
|
9
|
-
* - Pre-allocated order template objects
|
|
10
|
-
* - Fast orderTag generation (no Date.now in hot path)
|
|
11
|
-
* - Direct proto encoding with cached types
|
|
12
|
-
* - Minimal object creation
|
|
13
4
|
*/
|
|
14
5
|
|
|
15
6
|
const { REQ } = require('./constants');
|
|
16
|
-
const { proto } = require('./protobuf');
|
|
17
|
-
const { LatencyTracker } = require('./handlers');
|
|
18
|
-
const { performance } = require('perf_hooks');
|
|
19
7
|
const { logger } = require('../../utils/logger');
|
|
8
|
+
const { fastEntry, fastExit, getEffectiveLoginInfo } = require('./orders-fast');
|
|
20
9
|
|
|
21
10
|
const log = logger.scope('RithmicOrders');
|
|
22
11
|
|
|
23
|
-
// Debug mode - use no-op function when disabled for zero overhead
|
|
24
|
-
const DEBUG = process.env.HQX_DEBUG === '1';
|
|
25
|
-
const debug = DEBUG ? (...args) => console.log('[Rithmic:Orders]', ...args) : () => {};
|
|
26
|
-
|
|
27
|
-
// ==================== FAST ORDER TAG ====================
|
|
28
|
-
// Pre-generate prefix once at module load (not per-order)
|
|
29
|
-
const ORDER_TAG_PREFIX = `HQX${process.pid}-`;
|
|
30
|
-
let orderIdCounter = 0;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Ultra-fast order tag generation
|
|
34
|
-
* Avoids Date.now() and string interpolation in hot path
|
|
35
|
-
* @returns {string}
|
|
36
|
-
*/
|
|
37
|
-
const generateOrderTag = () => ORDER_TAG_PREFIX + (++orderIdCounter);
|
|
38
|
-
|
|
39
|
-
// ==================== PRE-ALLOCATED ORDER TEMPLATES ====================
|
|
40
|
-
// Reusable order object to minimize GC pressure
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Order object pool for zero-allocation hot path
|
|
44
|
-
*/
|
|
45
|
-
const OrderPool = {
|
|
46
|
-
// Pre-allocated order template
|
|
47
|
-
_template: {
|
|
48
|
-
templateId: REQ.NEW_ORDER,
|
|
49
|
-
userMsg: [''],
|
|
50
|
-
userTag: '', // Our order tag - returned in RithmicOrderNotification
|
|
51
|
-
fcmId: '',
|
|
52
|
-
ibId: '',
|
|
53
|
-
accountId: '',
|
|
54
|
-
symbol: '',
|
|
55
|
-
exchange: 'CME',
|
|
56
|
-
quantity: 0,
|
|
57
|
-
transactionType: 1,
|
|
58
|
-
duration: 1,
|
|
59
|
-
priceType: 2, // priceType 2 = MARKET order
|
|
60
|
-
manualOrAuto: 2,
|
|
61
|
-
tradeRoute: '', // Required by Rithmic API - fetched from RequestTradeRoutes
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get order object with values filled in
|
|
66
|
-
* Reuses same object to avoid allocation
|
|
67
|
-
* @param {string} orderTag - Unique order tag
|
|
68
|
-
* @param {Object} loginInfo - { fcmId, ibId }
|
|
69
|
-
* @param {Object} orderData - { accountId, symbol, exchange, size, side, tradeRoute }
|
|
70
|
-
*/
|
|
71
|
-
fill(orderTag, loginInfo, orderData) {
|
|
72
|
-
const o = this._template;
|
|
73
|
-
o.userMsg[0] = orderTag;
|
|
74
|
-
o.userTag = orderTag; // Set userTag for notification tracking
|
|
75
|
-
o.fcmId = loginInfo.fcmId;
|
|
76
|
-
o.ibId = loginInfo.ibId;
|
|
77
|
-
o.accountId = orderData.accountId;
|
|
78
|
-
o.symbol = orderData.symbol;
|
|
79
|
-
o.exchange = orderData.exchange || 'CME';
|
|
80
|
-
o.quantity = orderData.size;
|
|
81
|
-
o.transactionType = orderData.side === 0 ? 1 : 2;
|
|
82
|
-
o.tradeRoute = orderData.tradeRoute || ''; // From API via service.getTradeRoute()
|
|
83
|
-
return o;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Ultra-fast market order entry - HOT PATH
|
|
89
|
-
* NO SL/TP, NO await confirmation, fire-and-forget
|
|
90
|
-
* Target latency: < 5ms local processing
|
|
91
|
-
*
|
|
92
|
-
* OPTIMIZATIONS:
|
|
93
|
-
* - Reuses pre-allocated order object
|
|
94
|
-
* - Fast orderTag (no Date.now)
|
|
95
|
-
* - Uses fastEncode for cached protobuf type
|
|
96
|
-
* - Minimal branching
|
|
97
|
-
*
|
|
98
|
-
* @param {RithmicService} service - The Rithmic service instance
|
|
99
|
-
* @param {Object} orderData - { accountId, symbol, exchange, size, side }
|
|
100
|
-
* @returns {{ success: boolean, orderTag: string, entryTime: number, latencyMs: number }}
|
|
101
|
-
*/
|
|
102
|
-
const fastEntry = (service, orderData) => {
|
|
103
|
-
const startTime = performance.now();
|
|
104
|
-
const orderTag = generateOrderTag();
|
|
105
|
-
const entryTime = Date.now();
|
|
106
|
-
|
|
107
|
-
// Fast connection check
|
|
108
|
-
if (!service.orderConn?.isConnected || !service.loginInfo) {
|
|
109
|
-
return {
|
|
110
|
-
success: false,
|
|
111
|
-
error: 'Not connected',
|
|
112
|
-
orderTag,
|
|
113
|
-
entryTime,
|
|
114
|
-
latencyMs: performance.now() - startTime,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
// FIXED: Use account-specific fcmId/ibId if available (some prop firms have different IDs per account)
|
|
120
|
-
const account = service.accounts?.find(a =>
|
|
121
|
-
a.accountId === orderData.accountId || a.rithmicAccountId === orderData.accountId
|
|
122
|
-
);
|
|
123
|
-
const effectiveLoginInfo = {
|
|
124
|
-
fcmId: account?.fcmId || service.loginInfo.fcmId,
|
|
125
|
-
ibId: account?.ibId || service.loginInfo.ibId,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Get trade route from API (required by Rithmic)
|
|
129
|
-
const exchange = orderData.exchange || 'CME';
|
|
130
|
-
const tradeRoute = service.getTradeRoute?.(exchange);
|
|
131
|
-
if (!tradeRoute) {
|
|
132
|
-
return {
|
|
133
|
-
success: false,
|
|
134
|
-
error: `No trade route for exchange ${exchange}`,
|
|
135
|
-
orderTag,
|
|
136
|
-
entryTime,
|
|
137
|
-
latencyMs: performance.now() - startTime,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// OPTIMIZED: Use pre-allocated order object
|
|
142
|
-
const orderWithRoute = { ...orderData, tradeRoute };
|
|
143
|
-
const order = OrderPool.fill(orderTag, effectiveLoginInfo, orderWithRoute);
|
|
144
|
-
|
|
145
|
-
// Debug only - UI handles display via events
|
|
146
|
-
debug('ORDER Sending:', orderTag, orderData.side === 0 ? 'BUY' : 'SELL', orderData.size, 'x', orderData.symbol, 'acct:', orderData.accountId, 'route:', tradeRoute);
|
|
147
|
-
|
|
148
|
-
// OPTIMIZED: Use fastEncode with cached type
|
|
149
|
-
const buffer = proto.fastEncode('RequestNewOrder', order);
|
|
150
|
-
|
|
151
|
-
// ULTRA-OPTIMIZED: Try direct socket write first, fallback to fastSend
|
|
152
|
-
const sent = service.orderConn.ultraSend
|
|
153
|
-
? service.orderConn.ultraSend(buffer)
|
|
154
|
-
: (service.orderConn.fastSend(buffer), true);
|
|
155
|
-
|
|
156
|
-
if (!sent) {
|
|
157
|
-
service.orderConn.fastSend(buffer);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
debug('ORDER Sent to Rithmic:', orderTag, 'buffer:', buffer.length, 'bytes');
|
|
161
|
-
|
|
162
|
-
// Track for round-trip latency measurement
|
|
163
|
-
LatencyTracker.recordEntry(orderTag, entryTime);
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
success: true,
|
|
167
|
-
orderTag,
|
|
168
|
-
entryTime,
|
|
169
|
-
latencyMs: performance.now() - startTime,
|
|
170
|
-
};
|
|
171
|
-
} catch (error) {
|
|
172
|
-
return {
|
|
173
|
-
success: false,
|
|
174
|
-
error: error.message,
|
|
175
|
-
orderTag,
|
|
176
|
-
entryTime,
|
|
177
|
-
latencyMs: performance.now() - startTime,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Ultra-fast market exit - for position closing
|
|
184
|
-
* Fire-and-forget like fastEntry
|
|
185
|
-
* Same optimizations as fastEntry
|
|
186
|
-
*
|
|
187
|
-
* @param {RithmicService} service - The Rithmic service instance
|
|
188
|
-
* @param {Object} orderData - { accountId, symbol, exchange, size, side }
|
|
189
|
-
* @returns {{ success: boolean, orderTag: string, exitTime: number, latencyMs: number }}
|
|
190
|
-
*/
|
|
191
|
-
const fastExit = (service, orderData) => {
|
|
192
|
-
const startTime = performance.now();
|
|
193
|
-
const orderTag = generateOrderTag();
|
|
194
|
-
const exitTime = Date.now();
|
|
195
|
-
|
|
196
|
-
if (!service.orderConn?.isConnected || !service.loginInfo) {
|
|
197
|
-
return {
|
|
198
|
-
success: false,
|
|
199
|
-
error: 'Not connected',
|
|
200
|
-
orderTag,
|
|
201
|
-
exitTime,
|
|
202
|
-
latencyMs: performance.now() - startTime,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
// FIXED: Use account-specific fcmId/ibId if available
|
|
208
|
-
const account = service.accounts?.find(a =>
|
|
209
|
-
a.accountId === orderData.accountId || a.rithmicAccountId === orderData.accountId
|
|
210
|
-
);
|
|
211
|
-
const effectiveLoginInfo = {
|
|
212
|
-
fcmId: account?.fcmId || service.loginInfo.fcmId,
|
|
213
|
-
ibId: account?.ibId || service.loginInfo.ibId,
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// Get trade route from API (required by Rithmic)
|
|
217
|
-
const exchange = orderData.exchange || 'CME';
|
|
218
|
-
const tradeRoute = service.getTradeRoute?.(exchange);
|
|
219
|
-
if (!tradeRoute) {
|
|
220
|
-
return {
|
|
221
|
-
success: false,
|
|
222
|
-
error: `No trade route for exchange ${exchange}`,
|
|
223
|
-
orderTag,
|
|
224
|
-
exitTime,
|
|
225
|
-
latencyMs: performance.now() - startTime,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// OPTIMIZED: Use pre-allocated order object
|
|
230
|
-
const orderWithRoute = { ...orderData, tradeRoute };
|
|
231
|
-
const order = OrderPool.fill(orderTag, effectiveLoginInfo, orderWithRoute);
|
|
232
|
-
|
|
233
|
-
// OPTIMIZED: Use fastEncode with cached type
|
|
234
|
-
const buffer = proto.fastEncode('RequestNewOrder', order);
|
|
235
|
-
|
|
236
|
-
// ULTRA-OPTIMIZED: Try direct socket write first, fallback to fastSend
|
|
237
|
-
const sent = service.orderConn.ultraSend
|
|
238
|
-
? service.orderConn.ultraSend(buffer)
|
|
239
|
-
: (service.orderConn.fastSend(buffer), true);
|
|
240
|
-
|
|
241
|
-
if (!sent) {
|
|
242
|
-
service.orderConn.fastSend(buffer);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
success: true,
|
|
247
|
-
orderTag,
|
|
248
|
-
exitTime,
|
|
249
|
-
latencyMs: performance.now() - startTime,
|
|
250
|
-
};
|
|
251
|
-
} catch (error) {
|
|
252
|
-
return {
|
|
253
|
-
success: false,
|
|
254
|
-
error: error.message,
|
|
255
|
-
orderTag,
|
|
256
|
-
exitTime,
|
|
257
|
-
latencyMs: performance.now() - startTime,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
|
|
262
12
|
/**
|
|
263
13
|
* Place order via ORDER_PLANT
|
|
264
14
|
* @param {RithmicService} service - The Rithmic service instance
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rithmic Protobuf Manual Decoders
|
|
3
|
+
* @module services/rithmic/proto-decoders
|
|
4
|
+
*
|
|
5
|
+
* Hand-optimized decoders for Rithmic's large field ID messages.
|
|
6
|
+
* Faster than generic protobuf parsing for hot path.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// PnL field IDs (Rithmic uses very large field IDs)
|
|
10
|
+
const PNL_FIELDS = {
|
|
11
|
+
TEMPLATE_ID: 154467,
|
|
12
|
+
IS_SNAPSHOT: 110121,
|
|
13
|
+
FCM_ID: 154013,
|
|
14
|
+
IB_ID: 154014,
|
|
15
|
+
ACCOUNT_ID: 154008,
|
|
16
|
+
ACCOUNT_BALANCE: 156970,
|
|
17
|
+
CASH_ON_HAND: 156971,
|
|
18
|
+
MARGIN_BALANCE: 156977,
|
|
19
|
+
MIN_ACCOUNT_BALANCE: 156968,
|
|
20
|
+
OPEN_POSITION_PNL: 156961,
|
|
21
|
+
CLOSED_POSITION_PNL: 156963,
|
|
22
|
+
DAY_PNL: 157956,
|
|
23
|
+
DAY_OPEN_PNL: 157954,
|
|
24
|
+
DAY_CLOSED_PNL: 157955,
|
|
25
|
+
AVAILABLE_BUYING_POWER: 157015,
|
|
26
|
+
SSBOE: 150100,
|
|
27
|
+
USECS: 150101,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Symbol/Contract field IDs
|
|
31
|
+
const SYMBOL_FIELDS = {
|
|
32
|
+
TEMPLATE_ID: 154467,
|
|
33
|
+
RP_CODE: 132766,
|
|
34
|
+
EXCHANGE: 110101,
|
|
35
|
+
PRODUCT_CODE: 110102,
|
|
36
|
+
PRODUCT_NAME: 110103,
|
|
37
|
+
SYMBOL: 110100,
|
|
38
|
+
TRADING_SYMBOL: 157095,
|
|
39
|
+
DESCRIPTION: 110114,
|
|
40
|
+
USER_MSG: 132760,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Instrument PnL Position Update field IDs
|
|
44
|
+
const INSTRUMENT_PNL_FIELDS = {
|
|
45
|
+
TEMPLATE_ID: 154467,
|
|
46
|
+
IS_SNAPSHOT: 110121,
|
|
47
|
+
FCM_ID: 154013,
|
|
48
|
+
IB_ID: 154014,
|
|
49
|
+
ACCOUNT_ID: 154008,
|
|
50
|
+
SYMBOL: 110100,
|
|
51
|
+
EXCHANGE: 110101,
|
|
52
|
+
PRODUCT_CODE: 100749,
|
|
53
|
+
INSTRUMENT_TYPE: 110116,
|
|
54
|
+
FILL_BUY_QTY: 154041,
|
|
55
|
+
FILL_SELL_QTY: 154042,
|
|
56
|
+
ORDER_BUY_QTY: 154037,
|
|
57
|
+
ORDER_SELL_QTY: 154038,
|
|
58
|
+
BUY_QTY: 154260,
|
|
59
|
+
SELL_QTY: 154261,
|
|
60
|
+
AVG_OPEN_FILL_PRICE: 154434,
|
|
61
|
+
DAY_OPEN_PNL: 157954,
|
|
62
|
+
DAY_CLOSED_PNL: 157955,
|
|
63
|
+
DAY_PNL: 157956,
|
|
64
|
+
OPEN_POSITION_PNL: 156961,
|
|
65
|
+
OPEN_POSITION_QUANTITY: 156962,
|
|
66
|
+
CLOSED_POSITION_PNL: 156963,
|
|
67
|
+
CLOSED_POSITION_QUANTITY: 156964,
|
|
68
|
+
NET_QUANTITY: 156967,
|
|
69
|
+
SSBOE: 150100,
|
|
70
|
+
USECS: 150101,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Read a varint from buffer */
|
|
74
|
+
function readVarint(buffer, offset) {
|
|
75
|
+
let result = BigInt(0);
|
|
76
|
+
let shift = BigInt(0);
|
|
77
|
+
let pos = offset;
|
|
78
|
+
|
|
79
|
+
while (pos < buffer.length) {
|
|
80
|
+
const byte = buffer[pos++];
|
|
81
|
+
result |= BigInt(byte & 0x7f) << shift;
|
|
82
|
+
if ((byte & 0x80) === 0) {
|
|
83
|
+
return [Number(result), pos];
|
|
84
|
+
}
|
|
85
|
+
shift += BigInt(7);
|
|
86
|
+
if (shift > BigInt(63)) throw new Error('Varint too large');
|
|
87
|
+
}
|
|
88
|
+
throw new Error('Incomplete varint');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Read a length-delimited field (string/bytes) */
|
|
92
|
+
function readLengthDelimited(buffer, offset) {
|
|
93
|
+
const [length, newOffset] = readVarint(buffer, offset);
|
|
94
|
+
const value = buffer.slice(newOffset, newOffset + length).toString('utf8');
|
|
95
|
+
return [value, newOffset + length];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Skip a field based on wire type */
|
|
99
|
+
function skipField(buffer, offset, wireType) {
|
|
100
|
+
switch (wireType) {
|
|
101
|
+
case 0:
|
|
102
|
+
const [, newOffset] = readVarint(buffer, offset);
|
|
103
|
+
return newOffset;
|
|
104
|
+
case 1:
|
|
105
|
+
return offset + 8;
|
|
106
|
+
case 2:
|
|
107
|
+
const [length, lenOffset] = readVarint(buffer, offset);
|
|
108
|
+
return lenOffset + length;
|
|
109
|
+
case 5:
|
|
110
|
+
return offset + 4;
|
|
111
|
+
default:
|
|
112
|
+
throw new Error(`Unknown wire type: ${wireType}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Manually decode AccountPnL from raw bytes */
|
|
117
|
+
function decodeAccountPnL(buffer) {
|
|
118
|
+
const result = {};
|
|
119
|
+
let offset = 0;
|
|
120
|
+
|
|
121
|
+
while (offset < buffer.length) {
|
|
122
|
+
try {
|
|
123
|
+
const [tag, tagOffset] = readVarint(buffer, offset);
|
|
124
|
+
const wireType = tag & 0x7;
|
|
125
|
+
const fieldNumber = tag >>> 3;
|
|
126
|
+
offset = tagOffset;
|
|
127
|
+
|
|
128
|
+
switch (fieldNumber) {
|
|
129
|
+
case PNL_FIELDS.TEMPLATE_ID:
|
|
130
|
+
[result.templateId, offset] = readVarint(buffer, offset);
|
|
131
|
+
break;
|
|
132
|
+
case PNL_FIELDS.IS_SNAPSHOT:
|
|
133
|
+
const [isSnap, snapOffset] = readVarint(buffer, offset);
|
|
134
|
+
result.isSnapshot = isSnap !== 0;
|
|
135
|
+
offset = snapOffset;
|
|
136
|
+
break;
|
|
137
|
+
case PNL_FIELDS.FCM_ID:
|
|
138
|
+
[result.fcmId, offset] = readLengthDelimited(buffer, offset);
|
|
139
|
+
break;
|
|
140
|
+
case PNL_FIELDS.IB_ID:
|
|
141
|
+
[result.ibId, offset] = readLengthDelimited(buffer, offset);
|
|
142
|
+
break;
|
|
143
|
+
case PNL_FIELDS.ACCOUNT_ID:
|
|
144
|
+
[result.accountId, offset] = readLengthDelimited(buffer, offset);
|
|
145
|
+
break;
|
|
146
|
+
case PNL_FIELDS.ACCOUNT_BALANCE:
|
|
147
|
+
[result.accountBalance, offset] = readLengthDelimited(buffer, offset);
|
|
148
|
+
break;
|
|
149
|
+
case PNL_FIELDS.CASH_ON_HAND:
|
|
150
|
+
[result.cashOnHand, offset] = readLengthDelimited(buffer, offset);
|
|
151
|
+
break;
|
|
152
|
+
case PNL_FIELDS.MARGIN_BALANCE:
|
|
153
|
+
[result.marginBalance, offset] = readLengthDelimited(buffer, offset);
|
|
154
|
+
break;
|
|
155
|
+
case PNL_FIELDS.MIN_ACCOUNT_BALANCE:
|
|
156
|
+
[result.minAccountBalance, offset] = readLengthDelimited(buffer, offset);
|
|
157
|
+
break;
|
|
158
|
+
case PNL_FIELDS.OPEN_POSITION_PNL:
|
|
159
|
+
[result.openPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
160
|
+
break;
|
|
161
|
+
case PNL_FIELDS.CLOSED_POSITION_PNL:
|
|
162
|
+
[result.closedPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
163
|
+
break;
|
|
164
|
+
case PNL_FIELDS.DAY_PNL:
|
|
165
|
+
[result.dayPnl, offset] = readLengthDelimited(buffer, offset);
|
|
166
|
+
break;
|
|
167
|
+
case PNL_FIELDS.DAY_OPEN_PNL:
|
|
168
|
+
[result.dayOpenPnl, offset] = readLengthDelimited(buffer, offset);
|
|
169
|
+
break;
|
|
170
|
+
case PNL_FIELDS.DAY_CLOSED_PNL:
|
|
171
|
+
[result.dayClosedPnl, offset] = readLengthDelimited(buffer, offset);
|
|
172
|
+
break;
|
|
173
|
+
case PNL_FIELDS.AVAILABLE_BUYING_POWER:
|
|
174
|
+
[result.availableBuyingPower, offset] = readLengthDelimited(buffer, offset);
|
|
175
|
+
break;
|
|
176
|
+
case PNL_FIELDS.SSBOE:
|
|
177
|
+
[result.ssboe, offset] = readVarint(buffer, offset);
|
|
178
|
+
break;
|
|
179
|
+
case PNL_FIELDS.USECS:
|
|
180
|
+
[result.usecs, offset] = readVarint(buffer, offset);
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
offset = skipField(buffer, offset, wireType);
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Manually decode InstrumentPnLPositionUpdate from raw bytes */
|
|
193
|
+
function decodeInstrumentPnL(buffer) {
|
|
194
|
+
const result = {};
|
|
195
|
+
let offset = 0;
|
|
196
|
+
|
|
197
|
+
while (offset < buffer.length) {
|
|
198
|
+
try {
|
|
199
|
+
const [tag, tagOffset] = readVarint(buffer, offset);
|
|
200
|
+
const wireType = tag & 0x7;
|
|
201
|
+
const fieldNumber = tag >>> 3;
|
|
202
|
+
offset = tagOffset;
|
|
203
|
+
|
|
204
|
+
switch (fieldNumber) {
|
|
205
|
+
case INSTRUMENT_PNL_FIELDS.TEMPLATE_ID:
|
|
206
|
+
[result.templateId, offset] = readVarint(buffer, offset);
|
|
207
|
+
break;
|
|
208
|
+
case INSTRUMENT_PNL_FIELDS.IS_SNAPSHOT:
|
|
209
|
+
const [isSnap, snapOffset] = readVarint(buffer, offset);
|
|
210
|
+
result.isSnapshot = isSnap !== 0;
|
|
211
|
+
offset = snapOffset;
|
|
212
|
+
break;
|
|
213
|
+
case INSTRUMENT_PNL_FIELDS.FCM_ID:
|
|
214
|
+
[result.fcmId, offset] = readLengthDelimited(buffer, offset);
|
|
215
|
+
break;
|
|
216
|
+
case INSTRUMENT_PNL_FIELDS.IB_ID:
|
|
217
|
+
[result.ibId, offset] = readLengthDelimited(buffer, offset);
|
|
218
|
+
break;
|
|
219
|
+
case INSTRUMENT_PNL_FIELDS.ACCOUNT_ID:
|
|
220
|
+
[result.accountId, offset] = readLengthDelimited(buffer, offset);
|
|
221
|
+
break;
|
|
222
|
+
case INSTRUMENT_PNL_FIELDS.SYMBOL:
|
|
223
|
+
[result.symbol, offset] = readLengthDelimited(buffer, offset);
|
|
224
|
+
break;
|
|
225
|
+
case INSTRUMENT_PNL_FIELDS.EXCHANGE:
|
|
226
|
+
[result.exchange, offset] = readLengthDelimited(buffer, offset);
|
|
227
|
+
break;
|
|
228
|
+
case INSTRUMENT_PNL_FIELDS.PRODUCT_CODE:
|
|
229
|
+
[result.productCode, offset] = readLengthDelimited(buffer, offset);
|
|
230
|
+
break;
|
|
231
|
+
case INSTRUMENT_PNL_FIELDS.BUY_QTY:
|
|
232
|
+
[result.buyQty, offset] = readVarint(buffer, offset);
|
|
233
|
+
break;
|
|
234
|
+
case INSTRUMENT_PNL_FIELDS.SELL_QTY:
|
|
235
|
+
[result.sellQty, offset] = readVarint(buffer, offset);
|
|
236
|
+
break;
|
|
237
|
+
case INSTRUMENT_PNL_FIELDS.FILL_BUY_QTY:
|
|
238
|
+
[result.fillBuyQty, offset] = readVarint(buffer, offset);
|
|
239
|
+
break;
|
|
240
|
+
case INSTRUMENT_PNL_FIELDS.FILL_SELL_QTY:
|
|
241
|
+
[result.fillSellQty, offset] = readVarint(buffer, offset);
|
|
242
|
+
break;
|
|
243
|
+
case INSTRUMENT_PNL_FIELDS.NET_QUANTITY:
|
|
244
|
+
[result.netQuantity, offset] = readVarint(buffer, offset);
|
|
245
|
+
break;
|
|
246
|
+
case INSTRUMENT_PNL_FIELDS.OPEN_POSITION_QUANTITY:
|
|
247
|
+
[result.openPositionQuantity, offset] = readVarint(buffer, offset);
|
|
248
|
+
break;
|
|
249
|
+
case INSTRUMENT_PNL_FIELDS.AVG_OPEN_FILL_PRICE:
|
|
250
|
+
if (wireType === 1) {
|
|
251
|
+
result.avgOpenFillPrice = buffer.readDoubleLE(offset);
|
|
252
|
+
offset += 8;
|
|
253
|
+
} else {
|
|
254
|
+
offset = skipField(buffer, offset, wireType);
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
case INSTRUMENT_PNL_FIELDS.OPEN_POSITION_PNL:
|
|
258
|
+
[result.openPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
259
|
+
break;
|
|
260
|
+
case INSTRUMENT_PNL_FIELDS.CLOSED_POSITION_PNL:
|
|
261
|
+
[result.closedPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
262
|
+
break;
|
|
263
|
+
case INSTRUMENT_PNL_FIELDS.DAY_PNL:
|
|
264
|
+
if (wireType === 1) {
|
|
265
|
+
result.dayPnl = buffer.readDoubleLE(offset);
|
|
266
|
+
offset += 8;
|
|
267
|
+
} else {
|
|
268
|
+
[result.dayPnl, offset] = readLengthDelimited(buffer, offset);
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
case INSTRUMENT_PNL_FIELDS.DAY_OPEN_PNL:
|
|
272
|
+
if (wireType === 1) {
|
|
273
|
+
result.dayOpenPnl = buffer.readDoubleLE(offset);
|
|
274
|
+
offset += 8;
|
|
275
|
+
} else {
|
|
276
|
+
[result.dayOpenPnl, offset] = readLengthDelimited(buffer, offset);
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
case INSTRUMENT_PNL_FIELDS.DAY_CLOSED_PNL:
|
|
280
|
+
if (wireType === 1) {
|
|
281
|
+
result.dayClosedPnl = buffer.readDoubleLE(offset);
|
|
282
|
+
offset += 8;
|
|
283
|
+
} else {
|
|
284
|
+
[result.dayClosedPnl, offset] = readLengthDelimited(buffer, offset);
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
case INSTRUMENT_PNL_FIELDS.SSBOE:
|
|
288
|
+
[result.ssboe, offset] = readVarint(buffer, offset);
|
|
289
|
+
break;
|
|
290
|
+
case INSTRUMENT_PNL_FIELDS.USECS:
|
|
291
|
+
[result.usecs, offset] = readVarint(buffer, offset);
|
|
292
|
+
break;
|
|
293
|
+
default:
|
|
294
|
+
offset = skipField(buffer, offset, wireType);
|
|
295
|
+
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Decode ResponseProductCodes (template 112) */
|
|
304
|
+
function decodeProductCodes(buffer) {
|
|
305
|
+
const result = { rpCode: [] };
|
|
306
|
+
let offset = 0;
|
|
307
|
+
|
|
308
|
+
while (offset < buffer.length) {
|
|
309
|
+
try {
|
|
310
|
+
const [tag, tagOffset] = readVarint(buffer, offset);
|
|
311
|
+
const wireType = tag & 0x7;
|
|
312
|
+
const fieldNumber = tag >>> 3;
|
|
313
|
+
offset = tagOffset;
|
|
314
|
+
|
|
315
|
+
switch (fieldNumber) {
|
|
316
|
+
case SYMBOL_FIELDS.TEMPLATE_ID:
|
|
317
|
+
[result.templateId, offset] = readVarint(buffer, offset);
|
|
318
|
+
break;
|
|
319
|
+
case SYMBOL_FIELDS.RP_CODE:
|
|
320
|
+
let rpCode;
|
|
321
|
+
[rpCode, offset] = readLengthDelimited(buffer, offset);
|
|
322
|
+
result.rpCode.push(rpCode);
|
|
323
|
+
break;
|
|
324
|
+
case SYMBOL_FIELDS.EXCHANGE:
|
|
325
|
+
[result.exchange, offset] = readLengthDelimited(buffer, offset);
|
|
326
|
+
break;
|
|
327
|
+
case SYMBOL_FIELDS.PRODUCT_CODE:
|
|
328
|
+
[result.productCode, offset] = readLengthDelimited(buffer, offset);
|
|
329
|
+
break;
|
|
330
|
+
case SYMBOL_FIELDS.PRODUCT_NAME:
|
|
331
|
+
[result.productName, offset] = readLengthDelimited(buffer, offset);
|
|
332
|
+
break;
|
|
333
|
+
case SYMBOL_FIELDS.USER_MSG:
|
|
334
|
+
[result.userMsg, offset] = readLengthDelimited(buffer, offset);
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
offset = skipField(buffer, offset, wireType);
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Decode ResponseFrontMonthContract (template 114) */
|
|
347
|
+
function decodeFrontMonthContract(buffer) {
|
|
348
|
+
const result = { rpCode: [] };
|
|
349
|
+
let offset = 0;
|
|
350
|
+
|
|
351
|
+
while (offset < buffer.length) {
|
|
352
|
+
try {
|
|
353
|
+
const [tag, tagOffset] = readVarint(buffer, offset);
|
|
354
|
+
const wireType = tag & 0x7;
|
|
355
|
+
const fieldNumber = tag >>> 3;
|
|
356
|
+
offset = tagOffset;
|
|
357
|
+
|
|
358
|
+
switch (fieldNumber) {
|
|
359
|
+
case SYMBOL_FIELDS.TEMPLATE_ID:
|
|
360
|
+
[result.templateId, offset] = readVarint(buffer, offset);
|
|
361
|
+
break;
|
|
362
|
+
case SYMBOL_FIELDS.RP_CODE:
|
|
363
|
+
let rpCode;
|
|
364
|
+
[rpCode, offset] = readLengthDelimited(buffer, offset);
|
|
365
|
+
result.rpCode.push(rpCode);
|
|
366
|
+
break;
|
|
367
|
+
case SYMBOL_FIELDS.SYMBOL:
|
|
368
|
+
[result.symbol, offset] = readLengthDelimited(buffer, offset);
|
|
369
|
+
break;
|
|
370
|
+
case SYMBOL_FIELDS.EXCHANGE:
|
|
371
|
+
[result.exchange, offset] = readLengthDelimited(buffer, offset);
|
|
372
|
+
break;
|
|
373
|
+
case SYMBOL_FIELDS.TRADING_SYMBOL:
|
|
374
|
+
[result.tradingSymbol, offset] = readLengthDelimited(buffer, offset);
|
|
375
|
+
break;
|
|
376
|
+
case SYMBOL_FIELDS.DESCRIPTION:
|
|
377
|
+
[result.description, offset] = readLengthDelimited(buffer, offset);
|
|
378
|
+
break;
|
|
379
|
+
case SYMBOL_FIELDS.USER_MSG:
|
|
380
|
+
[result.userMsg, offset] = readLengthDelimited(buffer, offset);
|
|
381
|
+
break;
|
|
382
|
+
default:
|
|
383
|
+
offset = skipField(buffer, offset, wireType);
|
|
384
|
+
}
|
|
385
|
+
} catch (error) {
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
module.exports = {
|
|
393
|
+
PNL_FIELDS,
|
|
394
|
+
SYMBOL_FIELDS,
|
|
395
|
+
INSTRUMENT_PNL_FIELDS,
|
|
396
|
+
readVarint,
|
|
397
|
+
readLengthDelimited,
|
|
398
|
+
skipField,
|
|
399
|
+
decodeAccountPnL,
|
|
400
|
+
decodeInstrumentPnL,
|
|
401
|
+
decodeProductCodes,
|
|
402
|
+
decodeFrontMonthContract,
|
|
403
|
+
};
|