hedgequantx 2.9.216 → 2.9.217
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/bin/cli.js +56 -7
- package/package.json +1 -1
- package/src/services/daemon/client.js +413 -0
- package/src/services/daemon/constants.js +104 -0
- package/src/services/daemon/handlers.js +381 -0
- package/src/services/daemon/index.js +258 -0
- package/src/services/daemon/protocol.js +197 -0
- package/src/services/daemon/proxy.js +408 -0
- package/src/services/daemon/server.js +340 -0
package/bin/cli.js
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* HedgeQuantX CLI - Entry Point
|
|
5
5
|
* Prop Futures Algo Trading with Protected Strategy
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* Modes:
|
|
8
|
+
* hqx - Start TUI (connects to daemon if available, or standalone)
|
|
9
|
+
* hqx --daemon - Start daemon in foreground (persistent Rithmic connection)
|
|
10
|
+
* hqx --stop - Stop running daemon
|
|
11
|
+
* hqx --status - Check daemon status
|
|
12
|
+
* hqx -u - Update HQX to latest version
|
|
7
13
|
*/
|
|
8
14
|
|
|
9
15
|
'use strict';
|
|
@@ -34,7 +40,10 @@ program
|
|
|
34
40
|
.name('hqx')
|
|
35
41
|
.description('HedgeQuantX - Prop Futures Algo Trading CLI')
|
|
36
42
|
.version(pkg.version)
|
|
37
|
-
.option('-u, --update', 'Update HQX to latest version')
|
|
43
|
+
.option('-u, --update', 'Update HQX to latest version')
|
|
44
|
+
.option('-d, --daemon', 'Start daemon (persistent Rithmic connection)')
|
|
45
|
+
.option('--stop', 'Stop running daemon')
|
|
46
|
+
.option('--status', 'Check daemon status');
|
|
38
47
|
|
|
39
48
|
program
|
|
40
49
|
.command('start', { isDefault: true })
|
|
@@ -51,12 +60,23 @@ program
|
|
|
51
60
|
console.log(`HedgeQuantX CLI v${pkg.version}`);
|
|
52
61
|
});
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
program
|
|
64
|
+
.command('daemon')
|
|
65
|
+
.description('Start daemon in foreground')
|
|
66
|
+
.action(async () => {
|
|
67
|
+
const { startDaemonForeground } = require('../src/services/daemon');
|
|
68
|
+
await startDaemonForeground();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Handle special flags before parsing
|
|
72
|
+
const args = process.argv;
|
|
73
|
+
|
|
74
|
+
// Handle -u flag
|
|
75
|
+
if (args.includes('-u') || args.includes('--update')) {
|
|
56
76
|
const { execSync } = require('child_process');
|
|
57
77
|
console.log('Updating HedgeQuantX...');
|
|
58
78
|
try {
|
|
59
|
-
execSync('npm
|
|
79
|
+
execSync('npm update -g hedgequantx', { stdio: 'inherit' });
|
|
60
80
|
console.log('Update complete! Run "hqx" to start.');
|
|
61
81
|
} catch (e) {
|
|
62
82
|
console.error('Update failed:', e.message);
|
|
@@ -64,5 +84,34 @@ if (process.argv.includes('-u') || process.argv.includes('--update')) {
|
|
|
64
84
|
process.exit(0);
|
|
65
85
|
}
|
|
66
86
|
|
|
67
|
-
//
|
|
68
|
-
|
|
87
|
+
// Handle --daemon flag
|
|
88
|
+
if (args.includes('-d') || args.includes('--daemon')) {
|
|
89
|
+
const { startDaemonForeground } = require('../src/services/daemon');
|
|
90
|
+
startDaemonForeground().catch((err) => {
|
|
91
|
+
console.error('Daemon error:', err.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Handle --stop flag
|
|
96
|
+
else if (args.includes('--stop')) {
|
|
97
|
+
const { stopDaemon } = require('../src/services/daemon');
|
|
98
|
+
stopDaemon();
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
// Handle --status flag
|
|
102
|
+
else if (args.includes('--status')) {
|
|
103
|
+
const { isDaemonRunning, getDaemonPid, SOCKET_PATH } = require('../src/services/daemon');
|
|
104
|
+
|
|
105
|
+
if (isDaemonRunning()) {
|
|
106
|
+
console.log('Daemon Status: RUNNING');
|
|
107
|
+
console.log(' PID:', getDaemonPid());
|
|
108
|
+
console.log(' Socket:', SOCKET_PATH);
|
|
109
|
+
} else {
|
|
110
|
+
console.log('Daemon Status: NOT RUNNING');
|
|
111
|
+
}
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
// Normal TUI startup
|
|
115
|
+
else {
|
|
116
|
+
program.parse(process.argv);
|
|
117
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Daemon Client - TUI connection to daemon
|
|
3
|
+
* @module services/daemon/client
|
|
4
|
+
*
|
|
5
|
+
* Connects to the HQX daemon via Unix socket.
|
|
6
|
+
* Provides async request/response API for TUI.
|
|
7
|
+
*
|
|
8
|
+
* NO MOCK DATA - All data comes from daemon (which gets it from Rithmic)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const net = require('net');
|
|
14
|
+
const EventEmitter = require('events');
|
|
15
|
+
const { SOCKET_PATH, MSG_TYPE, TIMEOUTS } = require('./constants');
|
|
16
|
+
const { createMessage, encode, MessageParser, RequestHandler } = require('./protocol');
|
|
17
|
+
const { logger } = require('../../utils/logger');
|
|
18
|
+
|
|
19
|
+
const log = logger.scope('DaemonClient');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Daemon Client for TUI
|
|
23
|
+
* Connects to daemon and provides async API
|
|
24
|
+
*/
|
|
25
|
+
class DaemonClient extends EventEmitter {
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
|
|
29
|
+
/** @type {net.Socket|null} */
|
|
30
|
+
this.socket = null;
|
|
31
|
+
|
|
32
|
+
/** @type {MessageParser} */
|
|
33
|
+
this.parser = new MessageParser();
|
|
34
|
+
|
|
35
|
+
/** @type {RequestHandler} */
|
|
36
|
+
this.requests = new RequestHandler();
|
|
37
|
+
|
|
38
|
+
/** @type {boolean} */
|
|
39
|
+
this.connected = false;
|
|
40
|
+
|
|
41
|
+
/** @type {NodeJS.Timeout|null} */
|
|
42
|
+
this.pingInterval = null;
|
|
43
|
+
|
|
44
|
+
/** @type {Object|null} Cached daemon info */
|
|
45
|
+
this.daemonInfo = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Connect to daemon
|
|
50
|
+
* @returns {Promise<boolean>}
|
|
51
|
+
*/
|
|
52
|
+
async connect() {
|
|
53
|
+
if (this.connected) return true;
|
|
54
|
+
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
this.socket = net.createConnection(SOCKET_PATH);
|
|
57
|
+
|
|
58
|
+
this.socket.on('connect', async () => {
|
|
59
|
+
log.debug('Connected to daemon');
|
|
60
|
+
this.connected = true;
|
|
61
|
+
|
|
62
|
+
// Perform handshake
|
|
63
|
+
try {
|
|
64
|
+
this.daemonInfo = await this._request(MSG_TYPE.HANDSHAKE, null, TIMEOUTS.HANDSHAKE);
|
|
65
|
+
log.debug('Handshake complete', this.daemonInfo);
|
|
66
|
+
|
|
67
|
+
// Start ping interval
|
|
68
|
+
this._startPing();
|
|
69
|
+
|
|
70
|
+
resolve(true);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
log.error('Handshake failed', { error: err.message });
|
|
73
|
+
this.disconnect();
|
|
74
|
+
resolve(false);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.socket.on('data', (data) => {
|
|
79
|
+
const messages = this.parser.feed(data);
|
|
80
|
+
for (const msg of messages) {
|
|
81
|
+
this._handleMessage(msg);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
this.socket.on('close', () => {
|
|
86
|
+
log.debug('Disconnected from daemon');
|
|
87
|
+
this._cleanup();
|
|
88
|
+
this.emit('disconnected');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this.socket.on('error', (err) => {
|
|
92
|
+
if (err.code === 'ENOENT') {
|
|
93
|
+
log.debug('Daemon not running');
|
|
94
|
+
} else if (err.code === 'ECONNREFUSED') {
|
|
95
|
+
log.debug('Daemon connection refused');
|
|
96
|
+
} else {
|
|
97
|
+
log.warn('Socket error', { error: err.message });
|
|
98
|
+
}
|
|
99
|
+
this._cleanup();
|
|
100
|
+
resolve(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if daemon is available
|
|
107
|
+
* @returns {Promise<boolean>}
|
|
108
|
+
*/
|
|
109
|
+
async isAvailable() {
|
|
110
|
+
const connected = await this.connect();
|
|
111
|
+
return connected;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Disconnect from daemon
|
|
116
|
+
*/
|
|
117
|
+
disconnect() {
|
|
118
|
+
if (this.socket) {
|
|
119
|
+
this.socket.destroy();
|
|
120
|
+
}
|
|
121
|
+
this._cleanup();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Cleanup state
|
|
126
|
+
*/
|
|
127
|
+
_cleanup() {
|
|
128
|
+
this.connected = false;
|
|
129
|
+
this.requests.clear();
|
|
130
|
+
this.parser.reset();
|
|
131
|
+
|
|
132
|
+
if (this.pingInterval) {
|
|
133
|
+
clearInterval(this.pingInterval);
|
|
134
|
+
this.pingInterval = null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.socket = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Start ping interval
|
|
142
|
+
*/
|
|
143
|
+
_startPing() {
|
|
144
|
+
this.pingInterval = setInterval(async () => {
|
|
145
|
+
try {
|
|
146
|
+
await this._request(MSG_TYPE.PING, null, TIMEOUTS.PING_TIMEOUT);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
log.warn('Ping failed, disconnecting');
|
|
149
|
+
this.disconnect();
|
|
150
|
+
}
|
|
151
|
+
}, TIMEOUTS.PING_INTERVAL);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Handle incoming message
|
|
156
|
+
* @param {Object} msg
|
|
157
|
+
*/
|
|
158
|
+
_handleMessage(msg) {
|
|
159
|
+
const { type, data, replyTo } = msg;
|
|
160
|
+
|
|
161
|
+
// Check if this is a response to a pending request
|
|
162
|
+
if (replyTo && this.requests.resolve(replyTo, data)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Handle push events from daemon
|
|
167
|
+
switch (type) {
|
|
168
|
+
case MSG_TYPE.EVENT_ORDER_UPDATE:
|
|
169
|
+
this.emit('orderUpdate', data);
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case MSG_TYPE.EVENT_POSITION_UPDATE:
|
|
173
|
+
this.emit('positionUpdate', data);
|
|
174
|
+
break;
|
|
175
|
+
|
|
176
|
+
case MSG_TYPE.EVENT_PNL_UPDATE:
|
|
177
|
+
this.emit('pnlUpdate', data);
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case MSG_TYPE.EVENT_FILL:
|
|
181
|
+
this.emit('fill', data);
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
case MSG_TYPE.EVENT_DISCONNECTED:
|
|
185
|
+
this.emit('rithmicDisconnected', data);
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case MSG_TYPE.EVENT_RECONNECTED:
|
|
189
|
+
this.emit('rithmicReconnected', data);
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case MSG_TYPE.MARKET_DATA:
|
|
193
|
+
case MSG_TYPE.TICK:
|
|
194
|
+
this.emit('marketData', data);
|
|
195
|
+
break;
|
|
196
|
+
|
|
197
|
+
case MSG_TYPE.ALGO_LOG:
|
|
198
|
+
this.emit('algoLog', data);
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case MSG_TYPE.PONG:
|
|
202
|
+
// Handled by request handler
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
default:
|
|
206
|
+
log.debug('Unhandled message', { type });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Send request and wait for response
|
|
212
|
+
* @param {string} type - Message type
|
|
213
|
+
* @param {any} data - Request data
|
|
214
|
+
* @param {number} [timeout] - Timeout in ms
|
|
215
|
+
* @returns {Promise<any>} Response data
|
|
216
|
+
*/
|
|
217
|
+
async _request(type, data, timeout = TIMEOUTS.REQUEST) {
|
|
218
|
+
if (!this.connected || !this.socket) {
|
|
219
|
+
throw new Error('Not connected to daemon');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const msg = createMessage(type, data);
|
|
223
|
+
const promise = this.requests.createRequest(msg.id, timeout);
|
|
224
|
+
|
|
225
|
+
this.socket.write(encode(msg));
|
|
226
|
+
|
|
227
|
+
return promise;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ==================== PUBLIC API ====================
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get daemon status
|
|
234
|
+
* @returns {Promise<Object>}
|
|
235
|
+
*/
|
|
236
|
+
async getStatus() {
|
|
237
|
+
return this._request(MSG_TYPE.GET_STATUS);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Login to Rithmic via daemon
|
|
242
|
+
* @param {string} propfirmKey
|
|
243
|
+
* @param {string} username
|
|
244
|
+
* @param {string} password
|
|
245
|
+
* @returns {Promise<Object>}
|
|
246
|
+
*/
|
|
247
|
+
async login(propfirmKey, username, password) {
|
|
248
|
+
return this._request(MSG_TYPE.LOGIN, { propfirmKey, username, password }, TIMEOUTS.LOGIN);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Restore session from storage
|
|
253
|
+
* @returns {Promise<Object>}
|
|
254
|
+
*/
|
|
255
|
+
async restoreSession() {
|
|
256
|
+
return this._request(MSG_TYPE.RESTORE_SESSION, null, TIMEOUTS.LOGIN);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Logout
|
|
261
|
+
* @returns {Promise<Object>}
|
|
262
|
+
*/
|
|
263
|
+
async logout() {
|
|
264
|
+
return this._request(MSG_TYPE.LOGOUT);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get trading accounts
|
|
269
|
+
* @returns {Promise<Object>}
|
|
270
|
+
*/
|
|
271
|
+
async getTradingAccounts() {
|
|
272
|
+
return this._request(MSG_TYPE.GET_ACCOUNTS);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get positions
|
|
277
|
+
* @returns {Promise<Object>}
|
|
278
|
+
*/
|
|
279
|
+
async getPositions() {
|
|
280
|
+
return this._request(MSG_TYPE.GET_POSITIONS);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get orders
|
|
285
|
+
* @returns {Promise<Object>}
|
|
286
|
+
*/
|
|
287
|
+
async getOrders() {
|
|
288
|
+
return this._request(MSG_TYPE.GET_ORDERS);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get P&L for account
|
|
293
|
+
* @param {string} accountId
|
|
294
|
+
* @returns {Promise<Object>}
|
|
295
|
+
*/
|
|
296
|
+
async getPnL(accountId) {
|
|
297
|
+
return this._request(MSG_TYPE.GET_PNL, { accountId });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Place order
|
|
302
|
+
* @param {Object} orderData
|
|
303
|
+
* @returns {Promise<Object>}
|
|
304
|
+
*/
|
|
305
|
+
async placeOrder(orderData) {
|
|
306
|
+
return this._request(MSG_TYPE.PLACE_ORDER, orderData);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Cancel order
|
|
311
|
+
* @param {string} orderId
|
|
312
|
+
* @returns {Promise<Object>}
|
|
313
|
+
*/
|
|
314
|
+
async cancelOrder(orderId) {
|
|
315
|
+
return this._request(MSG_TYPE.CANCEL_ORDER, { orderId });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Cancel all orders for account
|
|
320
|
+
* @param {string} accountId
|
|
321
|
+
* @returns {Promise<Object>}
|
|
322
|
+
*/
|
|
323
|
+
async cancelAllOrders(accountId) {
|
|
324
|
+
return this._request(MSG_TYPE.CANCEL_ALL, { accountId });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Close position
|
|
329
|
+
* @param {string} accountId
|
|
330
|
+
* @param {string} symbol
|
|
331
|
+
* @returns {Promise<Object>}
|
|
332
|
+
*/
|
|
333
|
+
async closePosition(accountId, symbol) {
|
|
334
|
+
return this._request(MSG_TYPE.CLOSE_POSITION, { accountId, symbol });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get contracts
|
|
339
|
+
* @returns {Promise<Object>}
|
|
340
|
+
*/
|
|
341
|
+
async getContracts() {
|
|
342
|
+
return this._request(MSG_TYPE.GET_CONTRACTS);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Search contracts
|
|
347
|
+
* @param {string} search
|
|
348
|
+
* @returns {Promise<Object>}
|
|
349
|
+
*/
|
|
350
|
+
async searchContracts(search) {
|
|
351
|
+
return this._request(MSG_TYPE.SEARCH_CONTRACTS, { search });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Subscribe to market data
|
|
356
|
+
* @param {string} symbol
|
|
357
|
+
* @returns {Promise<Object>}
|
|
358
|
+
*/
|
|
359
|
+
async subscribeMarket(symbol) {
|
|
360
|
+
return this._request(MSG_TYPE.SUBSCRIBE_MARKET, { symbol });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Unsubscribe from market data
|
|
365
|
+
* @param {string} symbol
|
|
366
|
+
* @returns {Promise<Object>}
|
|
367
|
+
*/
|
|
368
|
+
async unsubscribeMarket(symbol) {
|
|
369
|
+
return this._request(MSG_TYPE.UNSUBSCRIBE_MARKET, { symbol });
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Start algo trading
|
|
374
|
+
* @param {Object} config
|
|
375
|
+
* @returns {Promise<Object>}
|
|
376
|
+
*/
|
|
377
|
+
async startAlgo(config) {
|
|
378
|
+
return this._request(MSG_TYPE.START_ALGO, config);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Stop algo trading
|
|
383
|
+
* @param {string} algoId
|
|
384
|
+
* @returns {Promise<Object>}
|
|
385
|
+
*/
|
|
386
|
+
async stopAlgo(algoId) {
|
|
387
|
+
return this._request(MSG_TYPE.STOP_ALGO, { algoId });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Shutdown daemon
|
|
392
|
+
* @returns {Promise<Object>}
|
|
393
|
+
*/
|
|
394
|
+
async shutdown() {
|
|
395
|
+
return this._request(MSG_TYPE.SHUTDOWN);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Singleton instance
|
|
400
|
+
let instance = null;
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get daemon client instance
|
|
404
|
+
* @returns {DaemonClient}
|
|
405
|
+
*/
|
|
406
|
+
function getDaemonClient() {
|
|
407
|
+
if (!instance) {
|
|
408
|
+
instance = new DaemonClient();
|
|
409
|
+
}
|
|
410
|
+
return instance;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
module.exports = { DaemonClient, getDaemonClient };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Daemon Constants
|
|
3
|
+
* @module services/daemon/constants
|
|
4
|
+
*
|
|
5
|
+
* Configuration for HQX Daemon - persistent Rithmic connection service
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/** Daemon socket path */
|
|
14
|
+
const SOCKET_DIR = path.join(os.homedir(), '.hedgequantx');
|
|
15
|
+
const SOCKET_PATH = path.join(SOCKET_DIR, 'hqx.sock');
|
|
16
|
+
|
|
17
|
+
/** Daemon PID file */
|
|
18
|
+
const PID_FILE = path.join(SOCKET_DIR, 'daemon.pid');
|
|
19
|
+
|
|
20
|
+
/** IPC Protocol version */
|
|
21
|
+
const PROTOCOL_VERSION = 1;
|
|
22
|
+
|
|
23
|
+
/** Message types for IPC communication */
|
|
24
|
+
const MSG_TYPE = {
|
|
25
|
+
// Connection
|
|
26
|
+
PING: 'ping',
|
|
27
|
+
PONG: 'pong',
|
|
28
|
+
HANDSHAKE: 'handshake',
|
|
29
|
+
HANDSHAKE_ACK: 'handshake_ack',
|
|
30
|
+
|
|
31
|
+
// Auth
|
|
32
|
+
LOGIN: 'login',
|
|
33
|
+
LOGIN_RESULT: 'login_result',
|
|
34
|
+
LOGOUT: 'logout',
|
|
35
|
+
RESTORE_SESSION: 'restore_session',
|
|
36
|
+
|
|
37
|
+
// Data requests
|
|
38
|
+
GET_ACCOUNTS: 'get_accounts',
|
|
39
|
+
GET_POSITIONS: 'get_positions',
|
|
40
|
+
GET_ORDERS: 'get_orders',
|
|
41
|
+
GET_PNL: 'get_pnl',
|
|
42
|
+
GET_STATUS: 'get_status',
|
|
43
|
+
GET_CONTRACTS: 'get_contracts',
|
|
44
|
+
SEARCH_CONTRACTS: 'search_contracts',
|
|
45
|
+
|
|
46
|
+
// Data responses
|
|
47
|
+
ACCOUNTS: 'accounts',
|
|
48
|
+
POSITIONS: 'positions',
|
|
49
|
+
ORDERS: 'orders',
|
|
50
|
+
PNL: 'pnl',
|
|
51
|
+
STATUS: 'status',
|
|
52
|
+
CONTRACTS: 'contracts',
|
|
53
|
+
|
|
54
|
+
// Trading
|
|
55
|
+
PLACE_ORDER: 'place_order',
|
|
56
|
+
CANCEL_ORDER: 'cancel_order',
|
|
57
|
+
CANCEL_ALL: 'cancel_all',
|
|
58
|
+
CLOSE_POSITION: 'close_position',
|
|
59
|
+
ORDER_RESULT: 'order_result',
|
|
60
|
+
|
|
61
|
+
// Market data
|
|
62
|
+
SUBSCRIBE_MARKET: 'subscribe_market',
|
|
63
|
+
UNSUBSCRIBE_MARKET: 'unsubscribe_market',
|
|
64
|
+
MARKET_DATA: 'market_data',
|
|
65
|
+
TICK: 'tick',
|
|
66
|
+
|
|
67
|
+
// Algo trading
|
|
68
|
+
START_ALGO: 'start_algo',
|
|
69
|
+
STOP_ALGO: 'stop_algo',
|
|
70
|
+
ALGO_STATUS: 'algo_status',
|
|
71
|
+
ALGO_LOG: 'algo_log',
|
|
72
|
+
|
|
73
|
+
// Events (daemon → TUI push)
|
|
74
|
+
EVENT_ORDER_UPDATE: 'event_order_update',
|
|
75
|
+
EVENT_POSITION_UPDATE: 'event_position_update',
|
|
76
|
+
EVENT_PNL_UPDATE: 'event_pnl_update',
|
|
77
|
+
EVENT_FILL: 'event_fill',
|
|
78
|
+
EVENT_DISCONNECTED: 'event_disconnected',
|
|
79
|
+
EVENT_RECONNECTED: 'event_reconnected',
|
|
80
|
+
|
|
81
|
+
// Errors
|
|
82
|
+
ERROR: 'error',
|
|
83
|
+
|
|
84
|
+
// Daemon control
|
|
85
|
+
SHUTDOWN: 'shutdown',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Timeouts */
|
|
89
|
+
const TIMEOUTS = {
|
|
90
|
+
HANDSHAKE: 5000,
|
|
91
|
+
REQUEST: 30000,
|
|
92
|
+
LOGIN: 60000,
|
|
93
|
+
PING_INTERVAL: 10000,
|
|
94
|
+
PING_TIMEOUT: 5000,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
SOCKET_DIR,
|
|
99
|
+
SOCKET_PATH,
|
|
100
|
+
PID_FILE,
|
|
101
|
+
PROTOCOL_VERSION,
|
|
102
|
+
MSG_TYPE,
|
|
103
|
+
TIMEOUTS,
|
|
104
|
+
};
|