hedgequantx 2.9.215 → 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 CHANGED
@@ -3,7 +3,13 @@
3
3
  /**
4
4
  * HedgeQuantX CLI - Entry Point
5
5
  * Prop Futures Algo Trading with Protected Strategy
6
- * @version 2.1.0
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
- // Handle -u flag before parsing commands
55
- if (process.argv.includes('-u') || process.argv.includes('--update')) {
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 install -g @hedgequantx/cli@latest', { stdio: 'inherit' });
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
- // Parse and run
68
- program.parse(process.argv);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.215",
3
+ "version": "2.9.217",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -8,6 +8,16 @@
8
8
  * - Uses smartLogs.getLiveAnalysisLog() for varied, non-repetitive messages
9
9
  *
10
10
  * Only logs when something SIGNIFICANT happens - no spam, no repetitive messages
11
+ *
12
+ * COLOR SCHEME:
13
+ * - Symbols: cyan (NQ, ES, CL, GC)
14
+ * - Prices: white bold
15
+ * - Bullish/Long: green
16
+ * - Bearish/Short: red
17
+ * - Neutral/System: gray/dim
18
+ * - Signals: yellow/magenta
19
+ * - Risk/Warnings: red bold
20
+ * - Values: blue (Z-Score, VPIN, OFI numbers)
11
21
  */
12
22
 
13
23
  'use strict';
@@ -16,6 +26,39 @@ const chalk = require('chalk');
16
26
  const smartLogs = require('./smart-logs');
17
27
  const { getContextualMessage } = require('./smart-logs-context');
18
28
 
29
+ // Color helpers for consistent styling
30
+ const C = {
31
+ // Symbols & identifiers
32
+ sym: (s) => chalk.cyan.bold(s),
33
+
34
+ // Prices
35
+ price: (p) => chalk.white.bold(p),
36
+
37
+ // Direction
38
+ long: (s) => chalk.green.bold(s),
39
+ short: (s) => chalk.red.bold(s),
40
+ bull: (s) => chalk.green(s),
41
+ bear: (s) => chalk.red(s),
42
+
43
+ // Values & metrics
44
+ val: (v) => chalk.blue(v),
45
+ valHigh: (v) => chalk.magenta.bold(v),
46
+
47
+ // Status
48
+ ok: (s) => chalk.green(s),
49
+ warn: (s) => chalk.yellow(s),
50
+ danger: (s) => chalk.red.bold(s),
51
+
52
+ // System/neutral
53
+ dim: (s) => chalk.dim(s),
54
+ info: (s) => chalk.gray(s),
55
+
56
+ // Special
57
+ signal: (s) => chalk.yellow.bold(s),
58
+ zone: (s) => chalk.magenta(s),
59
+ regime: (s) => chalk.cyan(s),
60
+ };
61
+
19
62
  const CONFIG = {
20
63
  SESSION_LOG_INTERVAL: 10,
21
64
  // HQX-2B thresholds
@@ -84,11 +127,14 @@ class SmartLogsEngine {
84
127
 
85
128
  // Active position - same for all strategies
86
129
  if (position !== 0) {
87
- const side = position > 0 ? 'LONG' : 'SHORT';
88
- const flow = (position > 0 && delta > 0) || (position < 0 && delta < 0) ? 'FAVOR' : 'ADVERSE';
130
+ const isLong = position > 0;
131
+ const side = isLong ? C.long('LONG') : C.short('SHORT');
132
+ const flowFavor = (isLong && delta > 0) || (!isLong && delta < 0);
133
+ const flowLabel = flowFavor ? C.ok('FAVOR') : C.danger('ADVERSE');
134
+ const deltaStr = delta > 0 ? C.bull(`+${delta}`) : C.bear(`${delta}`);
89
135
  return {
90
136
  type: 'trade',
91
- message: `[${sym}] ${side} ACTIVE @ ${price} | Delta: ${delta > 0 ? '+' : ''}${delta} | Flow: ${flow}`,
137
+ message: `[${C.sym(sym)}] ${side} ACTIVE @ ${C.price(price)} | Delta: ${deltaStr} | Flow: ${flowLabel}`,
92
138
  logToSession: true
93
139
  };
94
140
  }
@@ -116,35 +162,37 @@ class SmartLogsEngine {
116
162
  this.warmupLogged = true;
117
163
  event = 'warmup';
118
164
  const warmupMsg = getContextualMessage(this.symbolCode, this.strategyId, 'warmup');
119
- message = `[${sym}] 2B ready | ${bars} bars | ${warmupMsg}`;
165
+ message = `[${C.sym(sym)}] ${C.ok('2B ready')} | ${C.val(bars)} bars | ${C.dim(warmupMsg)}`;
120
166
  logType = 'system';
121
167
  }
122
168
  // EVENT 2: New zone created
123
169
  else if (zones > this.lastZones && zones > 0) {
124
170
  event = 'new_zone';
125
171
  const signalMsg = getContextualMessage(this.symbolCode, this.strategyId, 'signal');
126
- message = `[${sym}] ${price} | Zone #${zones} | ${signalMsg}`;
172
+ message = `[${C.sym(sym)}] ${C.price(price)} | ${C.zone('Zone #' + zones)} | ${C.signal(signalMsg)}`;
127
173
  logType = 'signal';
128
174
  }
129
175
  // EVENT 3: New swing detected
130
176
  else if (swings > this.lastSwings && swings > 0) {
131
177
  event = 'new_swing';
132
178
  const scanMsg = getContextualMessage(this.symbolCode, this.strategyId, 'scanning');
133
- message = `[${sym}] ${price} | Swing #${swings} | ${scanMsg}`;
179
+ message = `[${C.sym(sym)}] ${C.price(price)} | ${C.info('Swing #' + swings)} | ${C.dim(scanMsg)}`;
134
180
  }
135
181
  // EVENT 4: Zone approach (price near zone)
136
182
  else if (nearZone && !this.lastNearZone && zones > 0) {
137
183
  event = 'zone_approach';
138
184
  const signalMsg = getContextualMessage(this.symbolCode, this.strategyId, 'signal');
139
- message = `[${sym}] ${price} | Zone approach | ${signalMsg}`;
185
+ message = `[${C.sym(sym)}] ${C.price(price)} | ${C.warn('Zone approach')} | ${C.signal(signalMsg)}`;
140
186
  logType = 'signal';
141
187
  }
142
188
  // EVENT 5: Bias flip
143
189
  else if (this.lastBias && trend !== this.lastBias && trend !== 'neutral' && this.lastBias !== 'neutral') {
144
190
  event = 'bias_flip';
145
- const arrow = trend === 'bullish' ? chalk.green('▲') : chalk.red('▼');
191
+ const arrow = trend === 'bullish' ? C.bull('▲') : C.bear('▼');
192
+ const oldBias = this.lastBias === 'bullish' ? C.bull(this.lastBias) : C.bear(this.lastBias);
193
+ const newBias = trend === 'bullish' ? C.bull(trend) : C.bear(trend);
146
194
  const flipMsg = getContextualMessage(this.symbolCode, this.strategyId, trend);
147
- message = `[${sym}] ${arrow} ${this.lastBias} → ${trend} | ${flipMsg}`;
195
+ message = `[${C.sym(sym)}] ${arrow} ${oldBias} → ${newBias} | ${C.dim(flipMsg)}`;
148
196
  }
149
197
 
150
198
  // Update state tracking
@@ -176,12 +224,22 @@ class SmartLogsEngine {
176
224
  let logType = 'analysis';
177
225
  let message = null;
178
226
 
227
+ // Helper for Z-Score color
228
+ const zColor = (z) => {
229
+ const absVal = Math.abs(z);
230
+ const formatted = `${z.toFixed(1)}σ`;
231
+ if (absVal >= CONFIG.Z_EXTREME) return C.valHigh(formatted);
232
+ if (absVal >= CONFIG.Z_HIGH) return C.warn(formatted);
233
+ if (absVal >= CONFIG.Z_BUILDING) return C.val(formatted);
234
+ return C.dim(formatted);
235
+ };
236
+
179
237
  // EVENT 1: Warmup complete (250 ticks for QUANT models)
180
238
  if (ticks >= CONFIG.QUANT_WARMUP_TICKS && !this.warmupLogged) {
181
239
  this.warmupLogged = true;
182
240
  event = 'warmup';
183
241
  const warmupMsg = getContextualMessage(this.symbolCode, this.strategyId, 'warmup');
184
- message = `[${sym}] QUANT ready | ${ticks} ticks | ${warmupMsg}`;
242
+ message = `[${C.sym(sym)}] ${C.ok('QUANT ready')} | ${C.val(ticks)} ticks | ${C.dim(warmupMsg)}`;
185
243
  logType = 'system';
186
244
  }
187
245
  // EVENT 2: Z-Score regime change
@@ -193,34 +251,37 @@ class SmartLogsEngine {
193
251
 
194
252
  if (zRegime === 'extreme') {
195
253
  logType = 'signal';
196
- const dir = zScore < 0 ? 'LONG' : 'SHORT';
254
+ const dir = zScore < 0 ? C.long('LONG') : C.short('SHORT');
197
255
  const signalMsg = getContextualMessage(this.symbolCode, this.strategyId, 'signal');
198
- message = `[${sym}] ${price} | Z: ${zScore.toFixed(1)}σ | ${dir} | ${signalMsg}`;
256
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z: ${zColor(zScore)} ${C.signal('EXTREME')} | ${dir} | ${C.signal(signalMsg)}`;
199
257
  } else if (zRegime === 'high') {
200
258
  logType = 'signal';
201
- message = `[${sym}] ${price} | Z: ${zScore.toFixed(1)}σ | ${instrumentMsg}`;
259
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z: ${zColor(zScore)} ${C.warn('HIGH')} | ${C.dim(instrumentMsg)}`;
202
260
  } else if (zRegime === 'building') {
203
- message = `[${sym}] ${price} | Z building (${zScore.toFixed(1)}σ) | ${instrumentMsg}`;
261
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z: ${zColor(zScore)} ${C.info('building')} | ${C.dim(instrumentMsg)}`;
204
262
  } else {
205
263
  const scanMsg = getContextualMessage(this.symbolCode, this.strategyId, 'scanning');
206
- message = `[${sym}] ${price} | Z normalized | ${scanMsg}`;
264
+ message = `[${C.sym(sym)}] ${C.price(price)} | Z: ${C.ok('normalized')} | ${C.dim(scanMsg)}`;
207
265
  }
208
266
  }
209
267
  // EVENT 3: Bias flip (OFI direction change)
210
268
  else if (this.lastBias !== null && bias !== this.lastBias && bias !== 'neutral' && this.lastBias !== 'neutral') {
211
269
  event = 'bias_flip';
212
- const arrow = bias === 'bullish' ? chalk.green('▲') : chalk.red('▼');
270
+ const arrow = bias === 'bullish' ? C.bull('▲') : C.bear('▼');
271
+ const oldBias = this.lastBias === 'bullish' ? C.bull(this.lastBias) : C.bear(this.lastBias);
272
+ const newBias = bias === 'bullish' ? C.bull(bias) : C.bear(bias);
213
273
  const flipMsg = getContextualMessage(this.symbolCode, this.strategyId, bias);
214
- message = `[${sym}] ${arrow} OFI: ${this.lastBias} → ${bias} | ${flipMsg}`;
274
+ message = `[${C.sym(sym)}] ${arrow} OFI: ${oldBias} → ${newBias} | ${C.dim(flipMsg)}`;
215
275
  }
216
276
  // EVENT 4: VPIN toxicity change
217
277
  else if (this.lastVpinToxic !== null && vpinToxic !== this.lastVpinToxic) {
218
278
  event = 'vpin';
279
+ const vpinPct = (vpin * 100).toFixed(0);
219
280
  if (vpinToxic) {
220
- message = `[${sym}] ${price} | VPIN toxic (${(vpin * 100).toFixed(0)}%) - informed flow detected`;
281
+ message = `[${C.sym(sym)}] ${C.price(price)} | VPIN: ${C.danger(vpinPct + '%')} ${C.danger('TOXIC')} - informed flow`;
221
282
  logType = 'risk';
222
283
  } else {
223
- message = `[${sym}] ${price} | VPIN clean (${(vpin * 100).toFixed(0)}%) - normal flow`;
284
+ message = `[${C.sym(sym)}] ${C.price(price)} | VPIN: ${C.ok(vpinPct + '%')} ${C.ok('clean')} - normal flow`;
224
285
  }
225
286
  }
226
287
 
@@ -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 };