hedgequantx 2.6.163 → 2.7.1
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/README.md +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +8 -5
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HQX Server Service - Ultra Low Latency WebSocket for Scalping
|
|
3
|
-
* Optimized for sub-ms message handling with TCP_NODELAY and binary format
|
|
4
|
-
* @module services/hqx-server
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const WebSocket = require('ws');
|
|
8
|
-
const crypto = require('crypto');
|
|
9
|
-
const os = require('os');
|
|
10
|
-
const { request } = require('../utils/http');
|
|
11
|
-
const { HQX_SERVER, TIMEOUTS, SECURITY } = require('../config/settings');
|
|
12
|
-
const { logger } = require('../utils/logger');
|
|
13
|
-
const latencyMgr = require('./hqx-server-latency');
|
|
14
|
-
const eventMgr = require('./hqx-server-events');
|
|
15
|
-
const handlers = require('./hqx-server-handlers');
|
|
16
|
-
|
|
17
|
-
const log = logger.scope('HQX');
|
|
18
|
-
|
|
19
|
-
// Re-export MSG_TYPE from handlers
|
|
20
|
-
const { MSG_TYPE } = handlers;
|
|
21
|
-
|
|
22
|
-
/** Pre-allocated ping buffer */
|
|
23
|
-
const PING_BUFFER = Buffer.alloc(9);
|
|
24
|
-
PING_BUFFER.writeUInt8(MSG_TYPE.PING, 0);
|
|
25
|
-
|
|
26
|
-
/** Fast JSON stringify */
|
|
27
|
-
const fastStringify = (obj) => {
|
|
28
|
-
if (obj === null) return 'null';
|
|
29
|
-
if (typeof obj !== 'object') return JSON.stringify(obj);
|
|
30
|
-
return JSON.stringify(obj);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/** HQX Server Service - Ultra Low Latency */
|
|
34
|
-
class HQXServerService {
|
|
35
|
-
constructor() {
|
|
36
|
-
// Connection
|
|
37
|
-
this.ws = null;
|
|
38
|
-
this.connected = false;
|
|
39
|
-
this.reconnecting = false;
|
|
40
|
-
this.reconnectAttempts = 0;
|
|
41
|
-
|
|
42
|
-
// Auth
|
|
43
|
-
this.token = null;
|
|
44
|
-
this.refreshToken = null;
|
|
45
|
-
this.apiKey = null;
|
|
46
|
-
this.sessionId = null;
|
|
47
|
-
|
|
48
|
-
// Performance
|
|
49
|
-
this.latency = 0;
|
|
50
|
-
this.minLatency = Infinity;
|
|
51
|
-
this.maxLatency = 0;
|
|
52
|
-
this.avgLatency = 0;
|
|
53
|
-
this.latencySamples = [];
|
|
54
|
-
this.lastPingTime = 0;
|
|
55
|
-
this.pingInterval = null;
|
|
56
|
-
this.adaptiveHeartbeat = 1000; // Start at 1s, adapt based on connection
|
|
57
|
-
|
|
58
|
-
// Message handling
|
|
59
|
-
this.listeners = new Map();
|
|
60
|
-
this.messageQueue = [];
|
|
61
|
-
this.sendBuffer = Buffer.alloc(4096); // Pre-allocated send buffer
|
|
62
|
-
|
|
63
|
-
// Device
|
|
64
|
-
this._deviceId = null;
|
|
65
|
-
|
|
66
|
-
// Stats
|
|
67
|
-
this.messagesSent = 0;
|
|
68
|
-
this.messagesReceived = 0;
|
|
69
|
-
this.bytesReceived = 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Get cached device fingerprint */
|
|
73
|
-
_getDeviceId() {
|
|
74
|
-
if (this._deviceId) return this._deviceId;
|
|
75
|
-
|
|
76
|
-
const data = `${os.hostname()}-${os.platform()}-${os.arch()}-${os.cpus()[0]?.model || 'cpu'}`;
|
|
77
|
-
this._deviceId = crypto.createHash('sha256').update(data).digest('hex').slice(0, 32);
|
|
78
|
-
return this._deviceId;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Authenticate with HQX Server */
|
|
82
|
-
async authenticate(userId, propfirm = 'unknown') {
|
|
83
|
-
const start = process.hrtime.bigint();
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const deviceId = this._getDeviceId();
|
|
87
|
-
const url = `http://${HQX_SERVER.host}:${HQX_SERVER.port}/${HQX_SERVER.VERSION}/auth/token`;
|
|
88
|
-
|
|
89
|
-
const response = await request(url, {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
body: {
|
|
92
|
-
userId: userId || deviceId,
|
|
93
|
-
deviceId,
|
|
94
|
-
propfirm,
|
|
95
|
-
timestamp: Date.now(),
|
|
96
|
-
},
|
|
97
|
-
timeout: 5000, // Fast timeout for auth
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
if (response.statusCode === 200 && response.data?.success) {
|
|
101
|
-
const { token, refreshToken, apiKey, sessionId } = response.data.data;
|
|
102
|
-
this.token = token;
|
|
103
|
-
this.refreshToken = refreshToken;
|
|
104
|
-
this.apiKey = apiKey;
|
|
105
|
-
this.sessionId = sessionId;
|
|
106
|
-
|
|
107
|
-
const elapsed = Number(process.hrtime.bigint() - start) / 1e6;
|
|
108
|
-
log.info('Authenticated', { sessionId, latency: `${elapsed.toFixed(1)}ms` });
|
|
109
|
-
|
|
110
|
-
return { success: true, sessionId, apiKey };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { success: false, error: response.data?.error || 'Authentication failed' };
|
|
114
|
-
} catch (err) {
|
|
115
|
-
log.error('Auth error', { error: err.message });
|
|
116
|
-
return { success: false, error: err.message };
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Connect with ultra-low latency settings */
|
|
121
|
-
async connect() {
|
|
122
|
-
if (!this.token) {
|
|
123
|
-
return { success: false, error: 'Not authenticated' };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return new Promise((resolve) => {
|
|
127
|
-
const wsUrl = `${HQX_SERVER.wsUrl}?token=${this.token}&session=${this.sessionId}`;
|
|
128
|
-
|
|
129
|
-
log.debug('Connecting', { url: HQX_SERVER.wsUrl });
|
|
130
|
-
|
|
131
|
-
this.ws = new WebSocket(wsUrl, {
|
|
132
|
-
headers: {
|
|
133
|
-
'X-Device-Id': this._getDeviceId(),
|
|
134
|
-
'X-API-Key': this.apiKey,
|
|
135
|
-
},
|
|
136
|
-
// Performance options
|
|
137
|
-
perMessageDeflate: false, // Disable compression for speed
|
|
138
|
-
maxPayload: 64 * 1024, // 64KB max payload
|
|
139
|
-
handshakeTimeout: 5000, // Fast handshake
|
|
140
|
-
// TCP optimizations applied after open
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Binary mode for speed
|
|
144
|
-
this.ws.binaryType = 'nodebuffer';
|
|
145
|
-
|
|
146
|
-
const connectTimeout = setTimeout(() => {
|
|
147
|
-
if (!this.connected) {
|
|
148
|
-
this.ws?.terminate();
|
|
149
|
-
resolve({ success: false, error: 'Connection timeout' });
|
|
150
|
-
}
|
|
151
|
-
}, 5000);
|
|
152
|
-
|
|
153
|
-
this.ws.on('open', () => {
|
|
154
|
-
clearTimeout(connectTimeout);
|
|
155
|
-
this.connected = true;
|
|
156
|
-
this.reconnectAttempts = 0;
|
|
157
|
-
|
|
158
|
-
// Apply TCP_NODELAY for lowest latency
|
|
159
|
-
this._optimizeSocket();
|
|
160
|
-
|
|
161
|
-
// Start adaptive heartbeat
|
|
162
|
-
this._startHeartbeat();
|
|
163
|
-
|
|
164
|
-
// Flush queued messages
|
|
165
|
-
this._flushQueue();
|
|
166
|
-
|
|
167
|
-
this._emit('connected', { sessionId: this.sessionId });
|
|
168
|
-
log.info('Connected with TCP_NODELAY');
|
|
169
|
-
|
|
170
|
-
resolve({ success: true });
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
this.ws.on('message', (data) => {
|
|
174
|
-
this._handleMessage(data);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
this.ws.on('close', (code, reason) => {
|
|
178
|
-
clearTimeout(connectTimeout);
|
|
179
|
-
this.connected = false;
|
|
180
|
-
this._stopHeartbeat();
|
|
181
|
-
|
|
182
|
-
log.info('Disconnected', { code });
|
|
183
|
-
this._emit('disconnected', { code, reason: reason?.toString() });
|
|
184
|
-
|
|
185
|
-
if (!this.reconnecting) {
|
|
186
|
-
this._attemptReconnect();
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
this.ws.on('error', (err) => {
|
|
191
|
-
log.error('WebSocket error', { error: err.message });
|
|
192
|
-
this._emit('error', { message: err.message });
|
|
193
|
-
|
|
194
|
-
if (!this.connected) {
|
|
195
|
-
clearTimeout(connectTimeout);
|
|
196
|
-
resolve({ success: false, error: err.message });
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** Apply TCP socket optimizations */
|
|
203
|
-
_optimizeSocket() {
|
|
204
|
-
try {
|
|
205
|
-
const socket = this.ws._socket;
|
|
206
|
-
if (socket) {
|
|
207
|
-
// Disable Nagle's algorithm - critical for low latency
|
|
208
|
-
socket.setNoDelay(true);
|
|
209
|
-
|
|
210
|
-
// Keep connection alive
|
|
211
|
-
socket.setKeepAlive(true, 10000);
|
|
212
|
-
|
|
213
|
-
// Increase buffer sizes for throughput
|
|
214
|
-
if (socket.setRecvBufferSize) socket.setRecvBufferSize(65536);
|
|
215
|
-
if (socket.setSendBufferSize) socket.setSendBufferSize(65536);
|
|
216
|
-
|
|
217
|
-
log.debug('Socket optimized: TCP_NODELAY enabled');
|
|
218
|
-
}
|
|
219
|
-
} catch (err) {
|
|
220
|
-
log.warn('Socket optimization failed', { error: err.message });
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** Ultra-fast message handler */
|
|
225
|
-
_handleMessage(data) {
|
|
226
|
-
handlers.processMessage(
|
|
227
|
-
this,
|
|
228
|
-
data,
|
|
229
|
-
(event, payload) => this._emit(event, payload),
|
|
230
|
-
(latency) => this._updateLatency(latency)
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Update latency statistics */
|
|
235
|
-
_updateLatency(latency) {
|
|
236
|
-
latencyMgr.updateLatency.call(this, latency);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/** Send message with minimal overhead */
|
|
240
|
-
send(type, data) {
|
|
241
|
-
const message = {
|
|
242
|
-
type,
|
|
243
|
-
data,
|
|
244
|
-
ts: Date.now(), // Short key for speed
|
|
245
|
-
sid: this.sessionId,
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
if (this.connected && this.ws?.readyState === WebSocket.OPEN) {
|
|
249
|
-
this._sendRaw(fastStringify(message));
|
|
250
|
-
this.messagesSent++;
|
|
251
|
-
} else {
|
|
252
|
-
this.messageQueue.push(message);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/** Send raw data */
|
|
257
|
-
_sendRaw(data) {
|
|
258
|
-
try {
|
|
259
|
-
this.ws.send(data);
|
|
260
|
-
} catch (err) {
|
|
261
|
-
log.warn('Send error', { error: err.message });
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/** Send binary ping for lowest latency measurement */
|
|
266
|
-
_sendBinaryPing() {
|
|
267
|
-
if (!this.connected || this.ws?.readyState !== WebSocket.OPEN) return;
|
|
268
|
-
|
|
269
|
-
this.lastPingTime = process.hrtime.bigint();
|
|
270
|
-
|
|
271
|
-
// Write timestamp to pre-allocated buffer
|
|
272
|
-
PING_BUFFER.writeBigInt64LE(this.lastPingTime, 1);
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
this.ws.send(PING_BUFFER);
|
|
276
|
-
} catch {
|
|
277
|
-
// Ignore ping errors
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/** Flush message queue */
|
|
282
|
-
_flushQueue() {
|
|
283
|
-
while (this.messageQueue.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
|
|
284
|
-
const message = this.messageQueue.shift();
|
|
285
|
-
this._sendRaw(fastStringify(message));
|
|
286
|
-
this.messagesSent++;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** Start algo trading session */
|
|
291
|
-
startAlgo(config) {
|
|
292
|
-
log.info('Starting algo', { symbol: config.symbol, contracts: config.contracts });
|
|
293
|
-
this.send('start_algo', handlers.buildAlgoPayload(config));
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/** Stop algo trading */
|
|
297
|
-
stopAlgo() {
|
|
298
|
-
log.info('Stopping algo');
|
|
299
|
-
this.send('stop_algo', {});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/** Start copy trading */
|
|
303
|
-
startCopyTrading(config) {
|
|
304
|
-
log.info('Starting copy trading');
|
|
305
|
-
this.send('start_copy_trading', handlers.buildCopyTradingPayload(config));
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/** Register event listener */
|
|
309
|
-
on(event, callback) {
|
|
310
|
-
eventMgr.on(this.listeners, event, callback);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/** Remove event listener */
|
|
314
|
-
off(event, callback) {
|
|
315
|
-
eventMgr.off(this.listeners, event, callback);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** Emit event */
|
|
319
|
-
_emit(event, data) {
|
|
320
|
-
eventMgr.emit(this.listeners, event, data);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/** Start adaptive heartbeat */
|
|
324
|
-
_startHeartbeat() {
|
|
325
|
-
latencyMgr.startHeartbeat.call(this, () => this._sendBinaryPing());
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/** Stop heartbeat */
|
|
329
|
-
_stopHeartbeat() {
|
|
330
|
-
latencyMgr.stopHeartbeat.call(this);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** Attempt reconnection with exponential backoff */
|
|
334
|
-
_attemptReconnect() {
|
|
335
|
-
if (this.reconnectAttempts >= SECURITY.MAX_RECONNECT_ATTEMPTS) {
|
|
336
|
-
log.error('Max reconnect attempts reached');
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
this.reconnecting = true;
|
|
341
|
-
this.reconnectAttempts++;
|
|
342
|
-
|
|
343
|
-
// Fast initial reconnect, then backoff
|
|
344
|
-
const delay = Math.min(
|
|
345
|
-
100 * Math.pow(2, this.reconnectAttempts - 1), // Start at 100ms
|
|
346
|
-
10000 // Max 10s
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
log.info('Reconnecting', { attempt: this.reconnectAttempts, delay });
|
|
350
|
-
|
|
351
|
-
setTimeout(async () => {
|
|
352
|
-
try {
|
|
353
|
-
await this.connect();
|
|
354
|
-
} catch (err) {
|
|
355
|
-
log.error('Reconnect failed', { error: err.message });
|
|
356
|
-
}
|
|
357
|
-
this.reconnecting = false;
|
|
358
|
-
}, delay);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/** Get latency statistics */
|
|
362
|
-
getLatencyStats() {
|
|
363
|
-
return latencyMgr.getLatencyStats.call(this);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/** Get connection statistics */
|
|
367
|
-
getStats() {
|
|
368
|
-
return latencyMgr.getConnectionStats.call(this);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/** Get current latency */
|
|
372
|
-
getLatency() {
|
|
373
|
-
return this.latency;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/** Disconnect and cleanup */
|
|
377
|
-
disconnect() {
|
|
378
|
-
log.info('Disconnecting');
|
|
379
|
-
|
|
380
|
-
this._stopHeartbeat();
|
|
381
|
-
|
|
382
|
-
if (this.ws) {
|
|
383
|
-
this.ws.close(1000, 'Client disconnect');
|
|
384
|
-
this.ws = null;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
this.connected = false;
|
|
388
|
-
this.token = null;
|
|
389
|
-
this.sessionId = null;
|
|
390
|
-
this.messageQueue = [];
|
|
391
|
-
eventMgr.clearAll(this.listeners);
|
|
392
|
-
|
|
393
|
-
// Reset stats
|
|
394
|
-
latencyMgr.resetLatencyStats.call(this);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/** Check if connected */
|
|
398
|
-
isConnected() {
|
|
399
|
-
return this.connected && this.ws?.readyState === WebSocket.OPEN;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
module.exports = { HQXServerService, HQX_SERVER, MSG_TYPE };
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Position Manager Constants
|
|
3
|
-
* Momentum thresholds and weights for position management
|
|
4
|
-
*
|
|
5
|
-
* Data source: Calibrated from backtesting and live trading analysis
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// =============================================================================
|
|
9
|
-
// MOMENTUM THRESHOLDS (from analysis)
|
|
10
|
-
// =============================================================================
|
|
11
|
-
const MOMENTUM = {
|
|
12
|
-
STRONG_FAVORABLE: 0.5, // momentum > 0.5 + profit → HOLD
|
|
13
|
-
WEAK_THRESHOLD: 0.2, // momentum < 0.2 + profit → EXIT with profit
|
|
14
|
-
ADVERSE_THRESHOLD: -0.3, // momentum < -0.3 → EXIT immediately
|
|
15
|
-
VPIN_DANGER: 0.7, // VPIN > 0.7 → informed traders = EXIT
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Momentum weights
|
|
19
|
-
const WEIGHTS = {
|
|
20
|
-
OFI: 0.50, // Order Flow Imbalance - 50%
|
|
21
|
-
KALMAN: 0.25, // Kalman Velocity - 25%
|
|
22
|
-
ZSCORE: 0.25, // Z-Score progression - 25%
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
module.exports = {
|
|
26
|
-
MOMENTUM,
|
|
27
|
-
WEIGHTS,
|
|
28
|
-
};
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Position Exit Logic
|
|
3
|
-
* @module services/position-exit-logic
|
|
4
|
-
*
|
|
5
|
-
* Exit condition evaluation for fast scalping positions
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { FAST_SCALPING } = require('../config/settings');
|
|
9
|
-
const { MOMENTUM, WEIGHTS } = require('./position-constants');
|
|
10
|
-
const { calculateMomentum, getVPIN } = require('./position-momentum');
|
|
11
|
-
const { logger } = require('../utils/logger');
|
|
12
|
-
|
|
13
|
-
const log = logger.scope('PositionExit');
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Check all exit conditions for a position
|
|
17
|
-
* @param {Object} params
|
|
18
|
-
* @param {Object} params.position - Position data
|
|
19
|
-
* @param {number} params.currentPrice - Current market price
|
|
20
|
-
* @param {number} params.pnlTicks - Current P&L in ticks
|
|
21
|
-
* @param {number} params.holdDuration - Time held in ms
|
|
22
|
-
* @param {Object} params.strategy - Strategy instance
|
|
23
|
-
* @param {Function} params.getTickSize - Function to get tick size
|
|
24
|
-
* @param {Map} params.latestPrices - Latest prices map
|
|
25
|
-
* @returns {Object|null} - Exit reason or null
|
|
26
|
-
*/
|
|
27
|
-
function checkExitConditions({
|
|
28
|
-
position,
|
|
29
|
-
currentPrice,
|
|
30
|
-
pnlTicks,
|
|
31
|
-
holdDuration,
|
|
32
|
-
strategy,
|
|
33
|
-
getTickSize,
|
|
34
|
-
latestPrices,
|
|
35
|
-
}) {
|
|
36
|
-
if (pnlTicks === null) return null;
|
|
37
|
-
|
|
38
|
-
const targetTicks = FAST_SCALPING.TARGET_TICKS;
|
|
39
|
-
const stopTicks = FAST_SCALPING.STOP_TICKS;
|
|
40
|
-
|
|
41
|
-
// 1. TARGET HIT
|
|
42
|
-
if (pnlTicks >= targetTicks) {
|
|
43
|
-
return { type: 'target', reason: 'Target reached', pnlTicks };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 2. BREAKEVEN CHECK
|
|
47
|
-
if (position.breakevenActive && position.breakevenPrice !== null) {
|
|
48
|
-
const tickSize = getTickSize(position);
|
|
49
|
-
if (tickSize) {
|
|
50
|
-
if (position.side === 0 && currentPrice <= position.breakevenPrice) {
|
|
51
|
-
return { type: 'breakeven', reason: 'Breakeven stop hit', pnlTicks };
|
|
52
|
-
} else if (position.side === 1 && currentPrice >= position.breakevenPrice) {
|
|
53
|
-
return { type: 'breakeven', reason: 'Breakeven stop hit', pnlTicks };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 3. STOP HIT (only if BE not active)
|
|
59
|
-
if (!position.breakevenActive && pnlTicks <= -stopTicks) {
|
|
60
|
-
return { type: 'stop', reason: 'Stop loss hit', pnlTicks };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 4. VPIN DANGER
|
|
64
|
-
const vpin = getVPIN(strategy, position.contractId || position.symbol);
|
|
65
|
-
if (vpin !== null && vpin > MOMENTUM.VPIN_DANGER) {
|
|
66
|
-
return { type: 'vpin', reason: `VPIN spike ${(vpin * 100).toFixed(0)}% - informed traders`, pnlTicks, vpin };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 5. TRAILING STOP
|
|
70
|
-
if (pnlTicks >= FAST_SCALPING.TRAILING_ACTIVATION_TICKS) {
|
|
71
|
-
const trailingPnl = calculateTrailingPnl(position, currentPrice, getTickSize);
|
|
72
|
-
if (trailingPnl !== null && trailingPnl <= -FAST_SCALPING.TRAILING_DISTANCE_TICKS) {
|
|
73
|
-
return { type: 'trailing', reason: 'Trailing stop triggered', pnlTicks, trailingPnl };
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 6. MOMENTUM-BASED EXIT
|
|
78
|
-
const momentum = calculateMomentum({
|
|
79
|
-
strategy: strategy,
|
|
80
|
-
contractId: position.contractId || position.symbol,
|
|
81
|
-
side: position.side,
|
|
82
|
-
currentPrice: latestPrices.get(position.symbol),
|
|
83
|
-
tickSize: getTickSize(position),
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (momentum !== null) {
|
|
87
|
-
if (momentum > MOMENTUM.STRONG_FAVORABLE && pnlTicks > 4) {
|
|
88
|
-
return null; // Don't exit - momentum is strong
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (momentum < MOMENTUM.WEAK_THRESHOLD && pnlTicks > 0) {
|
|
92
|
-
return { type: 'momentum_weak', reason: 'Weak momentum - securing profit', pnlTicks, momentum };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (momentum < MOMENTUM.ADVERSE_THRESHOLD) {
|
|
96
|
-
return { type: 'momentum_adverse', reason: 'Adverse momentum detected', pnlTicks, momentum };
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Check and activate breakeven if conditions met
|
|
105
|
-
* @param {Object} position
|
|
106
|
-
* @param {number} pnlTicks
|
|
107
|
-
* @param {Function} getTickSize
|
|
108
|
-
* @returns {Object|null} - Breakeven activation info or null
|
|
109
|
-
*/
|
|
110
|
-
function checkBreakevenActivation(position, pnlTicks, getTickSize) {
|
|
111
|
-
if (position.breakevenActive) return null;
|
|
112
|
-
if (pnlTicks < FAST_SCALPING.BREAKEVEN_ACTIVATION_TICKS) return null;
|
|
113
|
-
|
|
114
|
-
const tickSize = getTickSize(position);
|
|
115
|
-
if (!tickSize || !position.entryPrice) return null;
|
|
116
|
-
|
|
117
|
-
const offset = FAST_SCALPING.BREAKEVEN_OFFSET_TICKS * tickSize;
|
|
118
|
-
const breakevenPrice = position.side === 0
|
|
119
|
-
? position.entryPrice + offset
|
|
120
|
-
: position.entryPrice - offset;
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
breakevenPrice,
|
|
124
|
-
pnlTicks,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Calculate trailing P&L from high/low water mark
|
|
130
|
-
* @param {Object} position
|
|
131
|
-
* @param {number} currentPrice
|
|
132
|
-
* @param {Function} getTickSize
|
|
133
|
-
* @returns {number|null}
|
|
134
|
-
*/
|
|
135
|
-
function calculateTrailingPnl(position, currentPrice, getTickSize) {
|
|
136
|
-
const tickSize = getTickSize(position);
|
|
137
|
-
if (tickSize === null) return null;
|
|
138
|
-
|
|
139
|
-
if (position.side === 0) {
|
|
140
|
-
if (position.highWaterMark === null) return null;
|
|
141
|
-
const dropFromHigh = position.highWaterMark - currentPrice;
|
|
142
|
-
return -Math.round(dropFromHigh / tickSize);
|
|
143
|
-
} else {
|
|
144
|
-
if (position.lowWaterMark === null) return null;
|
|
145
|
-
const riseFromLow = currentPrice - position.lowWaterMark;
|
|
146
|
-
return -Math.round(riseFromLow / tickSize);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Calculate P&L in ticks
|
|
152
|
-
* @param {Object} position
|
|
153
|
-
* @param {number} currentPrice
|
|
154
|
-
* @param {Function} getTickSize
|
|
155
|
-
* @returns {number|null}
|
|
156
|
-
*/
|
|
157
|
-
function calculatePnlTicks(position, currentPrice, getTickSize) {
|
|
158
|
-
if (position.entryPrice === null || currentPrice === null) return null;
|
|
159
|
-
|
|
160
|
-
const tickSize = getTickSize(position);
|
|
161
|
-
if (tickSize === null) return null;
|
|
162
|
-
|
|
163
|
-
const priceDiff = currentPrice - position.entryPrice;
|
|
164
|
-
const signedDiff = position.side === 0 ? priceDiff : -priceDiff;
|
|
165
|
-
|
|
166
|
-
return Math.round(signedDiff / tickSize);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
module.exports = {
|
|
170
|
-
checkExitConditions,
|
|
171
|
-
checkBreakevenActivation,
|
|
172
|
-
calculateTrailingPnl,
|
|
173
|
-
calculatePnlTicks,
|
|
174
|
-
};
|