hedgequantx 2.6.161 → 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/menus/ai-agent-connect.js +181 -0
- package/src/menus/ai-agent-models.js +219 -0
- package/src/menus/ai-agent-oauth.js +292 -0
- package/src/menus/ai-agent-ui.js +141 -0
- package/src/menus/ai-agent.js +88 -1489
- package/src/pages/algo/copy-engine.js +449 -0
- package/src/pages/algo/copy-trading.js +11 -543
- package/src/pages/algo/smart-logs-data.js +218 -0
- package/src/pages/algo/smart-logs.js +9 -214
- package/src/pages/algo/ui-constants.js +144 -0
- package/src/pages/algo/ui-summary.js +184 -0
- package/src/pages/algo/ui.js +42 -526
- package/src/pages/stats-calculations.js +191 -0
- package/src/pages/stats-ui.js +381 -0
- package/src/pages/stats.js +14 -507
- package/src/services/ai/client-analysis.js +194 -0
- package/src/services/ai/client-models.js +333 -0
- package/src/services/ai/client.js +6 -489
- package/src/services/ai/index.js +2 -257
- 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/ai/proxy-install.js +249 -0
- package/src/services/ai/proxy-manager.js +29 -411
- package/src/services/ai/proxy-remote.js +161 -0
- package/src/services/ai/supervisor-optimize.js +215 -0
- package/src/services/ai/supervisor-sync.js +178 -0
- package/src/services/ai/supervisor.js +50 -515
- package/src/services/ai/validation.js +250 -0
- package/src/services/hqx-server-events.js +110 -0
- package/src/services/hqx-server-handlers.js +217 -0
- package/src/services/hqx-server-latency.js +136 -0
- package/src/services/hqx-server.js +51 -403
- package/src/services/position-constants.js +28 -0
- package/src/services/position-exit-logic.js +174 -0
- package/src/services/position-manager.js +90 -629
- package/src/services/position-momentum.js +206 -0
- package/src/services/projectx/accounts.js +142 -0
- package/src/services/projectx/index.js +40 -289
- package/src/services/projectx/trading.js +180 -0
- package/src/services/rithmic/contracts.js +218 -0
- package/src/services/rithmic/handlers.js +2 -208
- package/src/services/rithmic/index.js +28 -712
- package/src/services/rithmic/latency-tracker.js +182 -0
- 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/rithmic/specs.js +146 -0
- package/src/services/rithmic/trade-history.js +254 -0
- 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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rithmic Contracts Module
|
|
3
|
+
* @module services/rithmic/contracts
|
|
4
|
+
*
|
|
5
|
+
* Contract lookup and front month discovery
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { decodeFrontMonthContract } = require('./protobuf');
|
|
9
|
+
const { CME_CONTRACT_SPECS } = require('./specs');
|
|
10
|
+
const { TIMEOUTS, CACHE } = require('../../config/settings');
|
|
11
|
+
const { logger } = require('../../utils/logger');
|
|
12
|
+
|
|
13
|
+
const log = logger.scope('RithmicContracts');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decode product codes from raw protobuf buffer
|
|
17
|
+
* @param {Buffer} buffer
|
|
18
|
+
* @returns {Object}
|
|
19
|
+
*/
|
|
20
|
+
function decodeProductCodes(buffer) {
|
|
21
|
+
const result = {};
|
|
22
|
+
let offset = 0;
|
|
23
|
+
|
|
24
|
+
const readVarint = (buf, off) => {
|
|
25
|
+
let value = 0, shift = 0;
|
|
26
|
+
while (off < buf.length) {
|
|
27
|
+
const byte = buf[off++];
|
|
28
|
+
value |= (byte & 0x7F) << shift;
|
|
29
|
+
if (!(byte & 0x80)) break;
|
|
30
|
+
shift += 7;
|
|
31
|
+
}
|
|
32
|
+
return [value, off];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const readString = (buf, off) => {
|
|
36
|
+
const [len, newOff] = readVarint(buf, off);
|
|
37
|
+
return [buf.slice(newOff, newOff + len).toString('utf8'), newOff + len];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
while (offset < buffer.length) {
|
|
41
|
+
try {
|
|
42
|
+
const [tag, tagOff] = readVarint(buffer, offset);
|
|
43
|
+
const wireType = tag & 0x7;
|
|
44
|
+
const fieldNumber = tag >>> 3;
|
|
45
|
+
offset = tagOff;
|
|
46
|
+
|
|
47
|
+
if (wireType === 0) {
|
|
48
|
+
const [, newOff] = readVarint(buffer, offset);
|
|
49
|
+
offset = newOff;
|
|
50
|
+
} else if (wireType === 2) {
|
|
51
|
+
const [val, newOff] = readString(buffer, offset);
|
|
52
|
+
offset = newOff;
|
|
53
|
+
if (fieldNumber === 110101) result.exchange = val;
|
|
54
|
+
if (fieldNumber === 100749) result.productCode = val;
|
|
55
|
+
if (fieldNumber === 100003) result.productName = val;
|
|
56
|
+
} else {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
} catch { break; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Fetch all front month contracts from TICKER_PLANT
|
|
67
|
+
* @param {RithmicService} service
|
|
68
|
+
* @returns {Promise<Array>}
|
|
69
|
+
*/
|
|
70
|
+
async function fetchAllFrontMonths(service) {
|
|
71
|
+
if (!service.tickerConn) throw new Error('TICKER_PLANT not connected');
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
const contracts = new Map();
|
|
75
|
+
const productsToCheck = new Map();
|
|
76
|
+
|
|
77
|
+
const productHandler = (msg) => {
|
|
78
|
+
if (msg.templateId !== 112) return;
|
|
79
|
+
|
|
80
|
+
const decoded = decodeProductCodes(msg.data);
|
|
81
|
+
if (!decoded.productCode || !decoded.exchange) return;
|
|
82
|
+
|
|
83
|
+
const validExchanges = ['CME', 'CBOT', 'NYMEX', 'COMEX', 'NYBOT', 'CFE'];
|
|
84
|
+
if (!validExchanges.includes(decoded.exchange)) return;
|
|
85
|
+
|
|
86
|
+
const name = (decoded.productName || '').toLowerCase();
|
|
87
|
+
if (name.includes('option') || name.includes('swap') || name.includes('spread')) return;
|
|
88
|
+
|
|
89
|
+
const key = `${decoded.productCode}:${decoded.exchange}`;
|
|
90
|
+
if (!productsToCheck.has(key)) {
|
|
91
|
+
productsToCheck.set(key, {
|
|
92
|
+
productCode: decoded.productCode,
|
|
93
|
+
productName: decoded.productName || decoded.productCode,
|
|
94
|
+
exchange: decoded.exchange,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const frontMonthHandler = (msg) => {
|
|
100
|
+
if (msg.templateId !== 114) return;
|
|
101
|
+
|
|
102
|
+
const decoded = decodeFrontMonthContract(msg.data);
|
|
103
|
+
if (decoded.rpCode[0] === '0' && decoded.tradingSymbol) {
|
|
104
|
+
contracts.set(decoded.userMsg, {
|
|
105
|
+
symbol: decoded.tradingSymbol,
|
|
106
|
+
baseSymbol: decoded.userMsg,
|
|
107
|
+
exchange: decoded.exchange,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
service.tickerConn.on('message', productHandler);
|
|
113
|
+
service.tickerConn.on('message', frontMonthHandler);
|
|
114
|
+
|
|
115
|
+
service.tickerConn.send('RequestProductCodes', {
|
|
116
|
+
templateId: 111,
|
|
117
|
+
userMsg: ['get-products'],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
service.tickerConn.removeListener('message', productHandler);
|
|
122
|
+
log.debug('Collected products', { count: productsToCheck.size });
|
|
123
|
+
|
|
124
|
+
for (const product of productsToCheck.values()) {
|
|
125
|
+
service.tickerConn.send('RequestFrontMonthContract', {
|
|
126
|
+
templateId: 113,
|
|
127
|
+
userMsg: [product.productCode],
|
|
128
|
+
symbol: product.productCode,
|
|
129
|
+
exchange: product.exchange,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
service.tickerConn.removeListener('message', frontMonthHandler);
|
|
135
|
+
|
|
136
|
+
const results = [];
|
|
137
|
+
for (const [baseSymbol, contract] of contracts) {
|
|
138
|
+
const productKey = `${baseSymbol}:${contract.exchange}`;
|
|
139
|
+
const product = productsToCheck.get(productKey);
|
|
140
|
+
const specs = CME_CONTRACT_SPECS[baseSymbol] || null;
|
|
141
|
+
const productName = specs?.name || product?.productName || baseSymbol;
|
|
142
|
+
|
|
143
|
+
results.push({
|
|
144
|
+
symbol: contract.symbol,
|
|
145
|
+
baseSymbol,
|
|
146
|
+
name: contract.symbol,
|
|
147
|
+
description: productName,
|
|
148
|
+
exchange: contract.exchange,
|
|
149
|
+
tickSize: specs?.tickSize ?? null,
|
|
150
|
+
tickValue: specs?.tickValue ?? null,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
results.sort((a, b) => a.baseSymbol.localeCompare(b.baseSymbol));
|
|
155
|
+
log.debug('Got contracts from API', { count: results.length });
|
|
156
|
+
resolve(results);
|
|
157
|
+
}, TIMEOUTS.RITHMIC_PRODUCTS);
|
|
158
|
+
}, TIMEOUTS.RITHMIC_CONTRACTS);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get contracts with caching
|
|
164
|
+
* @param {RithmicService} service
|
|
165
|
+
* @returns {Promise<Object>}
|
|
166
|
+
*/
|
|
167
|
+
async function getContracts(service) {
|
|
168
|
+
if (service._contractsCache && Date.now() - service._contractsCacheTime < CACHE.CONTRACTS_TTL) {
|
|
169
|
+
return { success: true, contracts: service._contractsCache, source: 'cache' };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!service.credentials) return { success: false, error: 'Not logged in' };
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
if (!service.tickerConn) {
|
|
176
|
+
const connected = await service.connectTicker(service.credentials.username, service.credentials.password);
|
|
177
|
+
if (!connected) return { success: false, error: 'Failed to connect to TICKER_PLANT' };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
service.tickerConn.setMaxListeners(5000);
|
|
181
|
+
log.debug('Fetching contracts from Rithmic API');
|
|
182
|
+
const contracts = await fetchAllFrontMonths(service);
|
|
183
|
+
|
|
184
|
+
if (!contracts.length) return { success: false, error: 'No tradeable contracts found' };
|
|
185
|
+
|
|
186
|
+
service._contractsCache = contracts;
|
|
187
|
+
service._contractsCacheTime = Date.now();
|
|
188
|
+
|
|
189
|
+
return { success: true, contracts, source: 'api' };
|
|
190
|
+
} catch (err) {
|
|
191
|
+
log.error('getContracts error', { error: err.message });
|
|
192
|
+
return { success: false, error: err.message };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Search contracts by text
|
|
198
|
+
* @param {RithmicService} service
|
|
199
|
+
* @param {string} searchText
|
|
200
|
+
* @returns {Promise<Array>}
|
|
201
|
+
*/
|
|
202
|
+
async function searchContracts(service, searchText) {
|
|
203
|
+
const result = await getContracts(service);
|
|
204
|
+
if (!searchText || !result.success) return result.contracts || [];
|
|
205
|
+
|
|
206
|
+
const search = searchText.toUpperCase();
|
|
207
|
+
return result.contracts.filter(c =>
|
|
208
|
+
c.symbol.toUpperCase().includes(search) ||
|
|
209
|
+
c.name.toUpperCase().includes(search)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
decodeProductCodes,
|
|
215
|
+
fetchAllFrontMonths,
|
|
216
|
+
getContracts,
|
|
217
|
+
searchContracts,
|
|
218
|
+
};
|
|
@@ -14,151 +14,12 @@
|
|
|
14
14
|
const { proto, decodeAccountPnL, decodeInstrumentPnL } = require('./protobuf');
|
|
15
15
|
const { RES, STREAM } = require('./constants');
|
|
16
16
|
const { performance } = require('perf_hooks');
|
|
17
|
+
const { LatencyTracker, FillInfoPool } = require('./latency-tracker');
|
|
17
18
|
|
|
18
19
|
// Debug mode - use no-op function when disabled for zero overhead
|
|
19
20
|
const DEBUG = process.env.HQX_DEBUG === '1';
|
|
20
21
|
const debug = DEBUG ? (...args) => console.log('[Rithmic:Handler]', ...args) : () => {};
|
|
21
22
|
|
|
22
|
-
// ==================== HIGH-RESOLUTION TIMING ====================
|
|
23
|
-
// Use process.hrtime.bigint for sub-millisecond precision
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Get high-resolution timestamp in nanoseconds
|
|
27
|
-
* @returns {bigint}
|
|
28
|
-
*/
|
|
29
|
-
const hrNow = () => process.hrtime.bigint();
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Convert nanoseconds to milliseconds with precision
|
|
33
|
-
* @param {bigint} ns
|
|
34
|
-
* @returns {number}
|
|
35
|
-
*/
|
|
36
|
-
const nsToMs = (ns) => Number(ns) / 1_000_000;
|
|
37
|
-
|
|
38
|
-
// ==================== LATENCY TRACKING ====================
|
|
39
|
-
// Track order-to-fill latency for performance monitoring
|
|
40
|
-
// OPTIMIZED: Circular buffer (no array.shift), high-resolution timing
|
|
41
|
-
|
|
42
|
-
const LatencyTracker = {
|
|
43
|
-
_pending: new Map(), // orderTag -> entryTime (bigint nanoseconds)
|
|
44
|
-
_samples: null, // Pre-allocated Float64Array circular buffer
|
|
45
|
-
_maxSamples: 100,
|
|
46
|
-
_head: 0, // Next write position
|
|
47
|
-
_count: 0, // Number of valid samples
|
|
48
|
-
_initialized: false,
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Initialize circular buffer (lazy init)
|
|
52
|
-
* @private
|
|
53
|
-
*/
|
|
54
|
-
_init() {
|
|
55
|
-
if (this._initialized) return;
|
|
56
|
-
this._samples = new Float64Array(this._maxSamples);
|
|
57
|
-
this._initialized = true;
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Record order sent time with high-resolution timestamp
|
|
62
|
-
* @param {string} orderTag
|
|
63
|
-
* @param {number} entryTimeMs - Date.now() when order was sent (for compatibility)
|
|
64
|
-
*/
|
|
65
|
-
recordEntry(orderTag, entryTimeMs) {
|
|
66
|
-
// Store high-resolution time for precise measurement
|
|
67
|
-
this._pending.set(orderTag, hrNow());
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Record fill received, calculate latency with sub-ms precision
|
|
72
|
-
* @param {string} orderTag
|
|
73
|
-
* @returns {number|null} Round-trip latency in ms (with decimal precision), or null if not tracked
|
|
74
|
-
*/
|
|
75
|
-
recordFill(orderTag) {
|
|
76
|
-
const entryTime = this._pending.get(orderTag);
|
|
77
|
-
if (!entryTime) return null;
|
|
78
|
-
|
|
79
|
-
this._pending.delete(orderTag);
|
|
80
|
-
const latencyNs = hrNow() - entryTime;
|
|
81
|
-
const latencyMs = nsToMs(latencyNs);
|
|
82
|
-
|
|
83
|
-
// Store in circular buffer (no shift, O(1))
|
|
84
|
-
this._init();
|
|
85
|
-
this._samples[this._head] = latencyMs;
|
|
86
|
-
this._head = (this._head + 1) % this._maxSamples;
|
|
87
|
-
if (this._count < this._maxSamples) this._count++;
|
|
88
|
-
|
|
89
|
-
return latencyMs;
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Get average latency
|
|
94
|
-
* @returns {number|null}
|
|
95
|
-
*/
|
|
96
|
-
getAverage() {
|
|
97
|
-
if (this._count === 0) return null;
|
|
98
|
-
let sum = 0;
|
|
99
|
-
for (let i = 0; i < this._count; i++) {
|
|
100
|
-
sum += this._samples[i];
|
|
101
|
-
}
|
|
102
|
-
return sum / this._count;
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Get min/max/avg stats with high precision
|
|
107
|
-
* @returns {Object}
|
|
108
|
-
*/
|
|
109
|
-
getStats() {
|
|
110
|
-
if (this._count === 0) {
|
|
111
|
-
return { min: null, max: null, avg: null, p50: null, p99: null, samples: 0 };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Get valid samples
|
|
115
|
-
const valid = [];
|
|
116
|
-
for (let i = 0; i < this._count; i++) {
|
|
117
|
-
valid.push(this._samples[i]);
|
|
118
|
-
}
|
|
119
|
-
valid.sort((a, b) => a - b);
|
|
120
|
-
|
|
121
|
-
const sum = valid.reduce((a, b) => a + b, 0);
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
min: valid[0],
|
|
125
|
-
max: valid[valid.length - 1],
|
|
126
|
-
avg: sum / valid.length,
|
|
127
|
-
p50: valid[Math.floor(valid.length * 0.5)],
|
|
128
|
-
p99: valid[Math.floor(valid.length * 0.99)] || valid[valid.length - 1],
|
|
129
|
-
samples: this._count,
|
|
130
|
-
};
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Get last N latency samples
|
|
135
|
-
* @param {number} n
|
|
136
|
-
* @returns {number[]}
|
|
137
|
-
*/
|
|
138
|
-
getRecent(n = 10) {
|
|
139
|
-
if (this._count === 0) return [];
|
|
140
|
-
const result = [];
|
|
141
|
-
const start = this._count < this._maxSamples ? 0 : this._head;
|
|
142
|
-
for (let i = 0; i < Math.min(n, this._count); i++) {
|
|
143
|
-
const idx = (start + this._count - 1 - i + this._maxSamples) % this._maxSamples;
|
|
144
|
-
result.push(this._samples[idx]);
|
|
145
|
-
}
|
|
146
|
-
return result;
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Clear all tracking data
|
|
151
|
-
*/
|
|
152
|
-
clear() {
|
|
153
|
-
this._pending.clear();
|
|
154
|
-
this._head = 0;
|
|
155
|
-
this._count = 0;
|
|
156
|
-
if (this._samples) {
|
|
157
|
-
this._samples.fill(0);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
|
|
162
23
|
/**
|
|
163
24
|
* Create ORDER_PLANT message handler
|
|
164
25
|
* @param {RithmicService} service - The Rithmic service instance
|
|
@@ -436,74 +297,7 @@ const handleNewOrderResponse = (service, data) => {
|
|
|
436
297
|
}
|
|
437
298
|
};
|
|
438
299
|
|
|
439
|
-
//
|
|
440
|
-
// Reusable objects for hot path to avoid GC pressure
|
|
441
|
-
|
|
442
|
-
const FillInfoPool = {
|
|
443
|
-
// Pre-allocated fill info template
|
|
444
|
-
_template: {
|
|
445
|
-
orderTag: null,
|
|
446
|
-
basketId: null,
|
|
447
|
-
orderId: null,
|
|
448
|
-
status: null,
|
|
449
|
-
symbol: null,
|
|
450
|
-
exchange: null,
|
|
451
|
-
accountId: null,
|
|
452
|
-
fillQuantity: 0,
|
|
453
|
-
totalFillQuantity: 0,
|
|
454
|
-
remainingQuantity: 0,
|
|
455
|
-
avgFillPrice: 0,
|
|
456
|
-
lastFillPrice: 0,
|
|
457
|
-
transactionType: 0,
|
|
458
|
-
orderType: 0,
|
|
459
|
-
quantity: 0,
|
|
460
|
-
ssboe: 0,
|
|
461
|
-
usecs: 0,
|
|
462
|
-
localTimestamp: 0,
|
|
463
|
-
roundTripLatencyMs: null,
|
|
464
|
-
},
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Fill template with notification data
|
|
468
|
-
* @param {Object} notif - Decoded notification
|
|
469
|
-
* @param {number} receiveTime - Local receive timestamp
|
|
470
|
-
* @param {number|null} latency - Round-trip latency
|
|
471
|
-
* @returns {Object}
|
|
472
|
-
*/
|
|
473
|
-
fill(notif, receiveTime, latency) {
|
|
474
|
-
const o = this._template;
|
|
475
|
-
o.orderTag = notif.userTag || null; // userTag contains our order tag
|
|
476
|
-
o.basketId = notif.basketId;
|
|
477
|
-
o.orderId = notif.exchangeOrderId || notif.orderId;
|
|
478
|
-
o.status = notif.status;
|
|
479
|
-
o.symbol = notif.symbol;
|
|
480
|
-
o.exchange = notif.exchange;
|
|
481
|
-
o.accountId = notif.accountId;
|
|
482
|
-
// Proto uses totalFillSize, not fillQuantity
|
|
483
|
-
o.fillQuantity = notif.totalFillSize || notif.fillQuantity || 0;
|
|
484
|
-
o.totalFillQuantity = notif.totalFillSize || notif.totalFillQuantity || 0;
|
|
485
|
-
o.remainingQuantity = notif.totalUnfilledSize || notif.remainingQuantity || 0;
|
|
486
|
-
o.avgFillPrice = parseFloat(notif.avgFillPrice || 0);
|
|
487
|
-
o.lastFillPrice = parseFloat(notif.price || notif.fillPrice || 0);
|
|
488
|
-
o.transactionType = notif.transactionType;
|
|
489
|
-
o.orderType = notif.priceType || notif.orderType;
|
|
490
|
-
o.quantity = notif.quantity;
|
|
491
|
-
o.ssboe = notif.ssboe;
|
|
492
|
-
o.usecs = notif.usecs;
|
|
493
|
-
o.localTimestamp = receiveTime;
|
|
494
|
-
o.roundTripLatencyMs = latency;
|
|
495
|
-
return o;
|
|
496
|
-
},
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* Create a copy for async operations that need to keep the data
|
|
500
|
-
* @param {Object} fillInfo
|
|
501
|
-
* @returns {Object}
|
|
502
|
-
*/
|
|
503
|
-
clone(fillInfo) {
|
|
504
|
-
return { ...fillInfo };
|
|
505
|
-
}
|
|
506
|
-
};
|
|
300
|
+
// FillInfoPool imported from ./latency-tracker
|
|
507
301
|
|
|
508
302
|
/**
|
|
509
303
|
* Handle order notification (351) - CRITICAL for fill tracking
|