hedgequantx 2.3.23 → 2.4.0

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/dist/lib/api.jsc CHANGED
Binary file
package/dist/lib/api2.jsc CHANGED
Binary file
package/dist/lib/core.jsc CHANGED
Binary file
Binary file
package/dist/lib/data.jsc CHANGED
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/lib/n/r1.jsc CHANGED
Binary file
package/dist/lib/n/r2.jsc CHANGED
Binary file
package/dist/lib/n/r3.jsc CHANGED
Binary file
package/dist/lib/n/r4.jsc CHANGED
Binary file
package/dist/lib/n/r5.jsc CHANGED
Binary file
package/dist/lib/n/r6.jsc CHANGED
Binary file
package/dist/lib/n/r7.jsc CHANGED
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.3.23",
3
+ "version": "2.4.0",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * One Account Mode
2
+ * One Account Mode - HQX Ultra Scalping
3
3
  */
4
4
 
5
5
  const chalk = require('chalk');
@@ -11,6 +11,10 @@ const { AlgoUI, renderSessionSummary } = require('./ui');
11
11
  const { prompts } = require('../../utils');
12
12
  const { checkMarketHours } = require('../../services/projectx/market');
13
13
 
14
+ // Strategy & Market Data (compiled bytecode)
15
+ const { M1 } = require('../../lib/m/s1');
16
+ const { MarketDataFeed } = require('../../lib/data');
17
+
14
18
 
15
19
 
16
20
  /**
@@ -155,8 +159,8 @@ const configureAlgo = async (account, contract) => {
155
159
  };
156
160
 
157
161
  /**
158
- * Launch algo trading
159
- * 100% API data - no simulation, no mock data, no local calculations
162
+ * Launch algo trading - HQX Ultra Scalping Strategy
163
+ * Real-time market data + Strategy signals + Auto order execution
160
164
  */
161
165
  const launchAlgo = async (service, account, contract, config) => {
162
166
  const { contracts, dailyTarget, maxRisk, showName } = config;
@@ -166,9 +170,11 @@ const launchAlgo = async (service, account, contract, config) => {
166
170
  ? (account.accountName || account.rithmicAccountId || account.accountId)
167
171
  : 'HQX *****';
168
172
  const symbolName = contract.name;
173
+ const contractId = contract.id;
169
174
  const connectionType = account.platform || 'ProjectX';
175
+ const tickSize = contract.tickSize || 0.25;
170
176
 
171
- const ui = new AlgoUI({ subtitle: 'HQX Algo Trading', mode: 'one-account' });
177
+ const ui = new AlgoUI({ subtitle: 'HQX Ultra Scalping', mode: 'one-account' });
172
178
 
173
179
  const stats = {
174
180
  accountName,
@@ -183,118 +189,195 @@ const launchAlgo = async (service, account, contract, config) => {
183
189
  wins: 0,
184
190
  losses: 0,
185
191
  latency: 0,
186
- connected: true,
192
+ connected: false,
187
193
  startTime: Date.now()
188
194
  };
189
195
 
190
196
  let running = true;
191
197
  let stopReason = null;
192
- let startingPnL = null; // P&L at algo start (from API)
193
- let lastPositionPnL = 0;
194
- let pollCount = 0;
198
+ let startingPnL = null;
199
+ let currentPosition = 0; // Current position qty (+ long, - short)
200
+ let pendingOrder = false; // Prevent duplicate orders
201
+ let tickCount = 0;
202
+
203
+ // Initialize Strategy
204
+ const strategy = new M1({ tickSize });
205
+ strategy.initialize(contractId, tickSize);
206
+
207
+ // Initialize Market Data Feed
208
+ const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
195
209
 
196
- // Log startup info from API
210
+ // Log startup
197
211
  ui.addLog('info', `Connection: ${connectionType}`);
198
212
  ui.addLog('info', `Account: ${accountName}`);
199
213
  ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
200
214
  ui.addLog('info', `Target: $${dailyTarget} | Max Risk: $${maxRisk}`);
201
- ui.addLog('info', 'Monitoring positions from API...');
215
+ ui.addLog('info', 'Connecting to market data...');
202
216
 
203
- // Poll data from API - 100% real data, measure latency during poll
204
- const pollAPI = async () => {
205
- const pollStart = Date.now();
217
+ // Handle strategy signals
218
+ strategy.on('signal', async (signal) => {
219
+ if (!running || pendingOrder || currentPosition !== 0) return;
220
+
221
+ const { side, direction, entry, stopLoss, takeProfit, confidence } = signal;
222
+
223
+ ui.addLog('signal', `${direction.toUpperCase()} signal @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
224
+
225
+ // Place order via API
226
+ pendingOrder = true;
206
227
  try {
207
- pollCount++;
228
+ const orderSide = direction === 'long' ? 0 : 1; // 0=Buy, 1=Sell
229
+ const orderResult = await service.placeOrder({
230
+ accountId: account.accountId,
231
+ contractId: contractId,
232
+ type: 2, // Market order
233
+ side: orderSide,
234
+ size: contracts
235
+ });
208
236
 
209
- // Get account P&L from API
237
+ if (orderResult.success) {
238
+ currentPosition = direction === 'long' ? contracts : -contracts;
239
+ stats.trades++;
240
+ ui.addLog('trade', `OPENED ${direction.toUpperCase()} ${contracts}x @ market`);
241
+
242
+ // Place bracket orders (SL/TP)
243
+ if (stopLoss && takeProfit) {
244
+ // Stop Loss
245
+ await service.placeOrder({
246
+ accountId: account.accountId,
247
+ contractId: contractId,
248
+ type: 4, // Stop order
249
+ side: direction === 'long' ? 1 : 0, // Opposite side
250
+ size: contracts,
251
+ stopPrice: stopLoss
252
+ });
253
+
254
+ // Take Profit
255
+ await service.placeOrder({
256
+ accountId: account.accountId,
257
+ contractId: contractId,
258
+ type: 1, // Limit order
259
+ side: direction === 'long' ? 1 : 0,
260
+ size: contracts,
261
+ limitPrice: takeProfit
262
+ });
263
+
264
+ ui.addLog('info', `SL: ${stopLoss.toFixed(2)} | TP: ${takeProfit.toFixed(2)}`);
265
+ }
266
+ } else {
267
+ ui.addLog('error', `Order failed: ${orderResult.error}`);
268
+ }
269
+ } catch (e) {
270
+ ui.addLog('error', `Order error: ${e.message}`);
271
+ }
272
+ pendingOrder = false;
273
+ });
274
+
275
+ // Handle market data ticks
276
+ marketFeed.on('tick', (tick) => {
277
+ tickCount++;
278
+ const latencyStart = Date.now();
279
+
280
+ // Feed tick to strategy
281
+ strategy.processTick({
282
+ contractId: tick.contractId || contractId,
283
+ price: tick.price,
284
+ bid: tick.bid,
285
+ ask: tick.ask,
286
+ volume: tick.volume || 1,
287
+ side: tick.lastTradeSide || 'unknown',
288
+ timestamp: tick.timestamp || Date.now()
289
+ });
290
+
291
+ stats.latency = Date.now() - latencyStart;
292
+
293
+ // Log every 100th tick to show activity
294
+ if (tickCount % 100 === 0) {
295
+ ui.addLog('info', `Tick #${tickCount} @ ${tick.price?.toFixed(2) || 'N/A'}`);
296
+ }
297
+ });
298
+
299
+ marketFeed.on('connected', () => {
300
+ stats.connected = true;
301
+ ui.addLog('success', 'Market data connected!');
302
+ });
303
+
304
+ marketFeed.on('error', (err) => {
305
+ ui.addLog('error', `Market: ${err.message}`);
306
+ });
307
+
308
+ marketFeed.on('disconnected', () => {
309
+ stats.connected = false;
310
+ ui.addLog('error', 'Market data disconnected');
311
+ });
312
+
313
+ // Connect to market data
314
+ try {
315
+ const token = service.token || service.getToken?.();
316
+ await marketFeed.connect(token, account.propfirm, contractId);
317
+ await marketFeed.subscribe(symbolName, contractId);
318
+ } catch (e) {
319
+ ui.addLog('error', `Failed to connect: ${e.message}`);
320
+ }
321
+
322
+ // Poll account P&L from API
323
+ const pollPnL = async () => {
324
+ try {
210
325
  const accountResult = await service.getTradingAccounts();
211
326
  if (accountResult.success && accountResult.accounts) {
212
327
  const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
213
328
  if (acc && acc.profitAndLoss !== undefined) {
214
- const accountPnL = acc.profitAndLoss;
329
+ if (startingPnL === null) startingPnL = acc.profitAndLoss;
330
+ stats.pnl = acc.profitAndLoss - startingPnL;
215
331
 
216
- // Set starting P&L on first poll (silent - don't expose account P&L)
217
- if (startingPnL === null) {
218
- startingPnL = accountPnL;
332
+ // Record trade result in strategy
333
+ if (stats.pnl !== 0) {
334
+ strategy.recordTradeResult(stats.pnl);
219
335
  }
220
-
221
- // Session P&L = current - starting (both from API)
222
- const sessionPnL = accountPnL - startingPnL;
223
- stats.pnl = sessionPnL;
224
336
  }
225
337
  }
226
338
 
227
- // Get positions from API
339
+ // Check positions
228
340
  const posResult = await service.getPositions(account.accountId);
229
-
230
341
  if (posResult.success && posResult.positions) {
231
- // Find position for selected contract
232
- const position = posResult.positions.find(p => {
233
- const posSymbol = p.contractId || p.symbol || '';
234
- return posSymbol.includes(contract.name) || posSymbol.includes(contract.id);
342
+ const pos = posResult.positions.find(p => {
343
+ const sym = p.contractId || p.symbol || '';
344
+ return sym.includes(contract.name) || sym.includes(contractId);
235
345
  });
236
346
 
237
- if (position) {
238
- const positionPnL = position.profitAndLoss || 0;
347
+ if (pos && pos.quantity !== 0) {
348
+ currentPosition = pos.quantity;
349
+ const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
350
+ const pnl = pos.profitAndLoss || 0;
239
351
 
240
- // Detect trade completion (position P&L changed significantly)
241
- if (lastPositionPnL !== 0 && Math.abs(positionPnL - lastPositionPnL) > 0.01) {
242
- const tradePnL = positionPnL - lastPositionPnL;
243
- stats.trades++;
244
-
245
- if (tradePnL > 0) {
246
- stats.wins++;
247
- ui.addLog('trade', `+$${tradePnL.toFixed(2)}`);
248
- } else {
249
- stats.losses++;
250
- ui.addLog('loss', `-$${Math.abs(tradePnL).toFixed(2)}`);
251
- }
252
- }
253
-
254
- lastPositionPnL = positionPnL;
255
-
256
- // Log position info from API
257
- if (position.quantity && position.quantity !== 0) {
258
- const side = position.quantity > 0 ? 'LONG' : 'SHORT';
259
- const qty = Math.abs(position.quantity);
260
- ui.addLog('info', `${side} ${qty}x @ P&L: $${positionPnL.toFixed(2)}`);
261
- }
352
+ // Check if position closed (win/loss)
353
+ if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
354
+ else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
262
355
  } else {
263
- // No position for this symbol - log status every 15 polls (~30 sec)
264
- if (pollCount % 15 === 0) {
265
- ui.addLog('info', `Waiting for ${symbolName} position... (Session P&L: $${stats.pnl.toFixed(2)})`);
266
- }
267
- lastPositionPnL = 0;
356
+ currentPosition = 0;
268
357
  }
269
358
  }
270
359
 
271
- // Check target/risk limits (using SESSION P&L, not account total)
360
+ // Check target/risk
272
361
  if (stats.pnl >= dailyTarget) {
273
362
  stopReason = 'target';
274
363
  running = false;
275
- ui.addLog('success', `SESSION TARGET! +$${stats.pnl.toFixed(2)}`);
364
+ ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
276
365
  } else if (stats.pnl <= -maxRisk) {
277
366
  stopReason = 'risk';
278
367
  running = false;
279
- ui.addLog('error', `SESSION MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
368
+ ui.addLog('error', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
280
369
  }
281
-
282
- // Update latency (measured during this poll)
283
- stats.latency = Date.now() - pollStart;
284
-
285
370
  } catch (e) {
286
- stats.latency = Date.now() - pollStart;
287
- ui.addLog('error', `API: ${e.message}`);
371
+ // Silently handle polling errors
288
372
  }
289
373
  };
290
374
 
375
+ // Start polling and UI refresh
291
376
  const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
377
+ const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
378
+ pollPnL(); // Initial poll
292
379
 
293
- // Poll data from API every 2 seconds
294
- pollAPI(); // Initial poll
295
- const apiInterval = setInterval(() => { if (running) pollAPI(); }, 2000);
296
-
297
- // Keyboard
380
+ // Keyboard handler
298
381
  const setupKeyHandler = () => {
299
382
  if (!process.stdin.isTTY) return;
300
383
  readline.emitKeypressEvents(process.stdin);
@@ -303,7 +386,8 @@ const launchAlgo = async (service, account, contract, config) => {
303
386
 
304
387
  const onKey = (str, key) => {
305
388
  if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
306
- running = false; stopReason = 'manual';
389
+ running = false;
390
+ stopReason = 'manual';
307
391
  }
308
392
  };
309
393
  process.stdin.on('keypress', onKey);
@@ -315,22 +399,29 @@ const launchAlgo = async (service, account, contract, config) => {
315
399
 
316
400
  const cleanupKeys = setupKeyHandler();
317
401
 
402
+ // Wait for stop
318
403
  await new Promise(resolve => {
319
- const check = setInterval(() => { if (!running) { clearInterval(check); resolve(); } }, 100);
404
+ const check = setInterval(() => {
405
+ if (!running) {
406
+ clearInterval(check);
407
+ resolve();
408
+ }
409
+ }, 100);
320
410
  });
321
411
 
412
+ // Cleanup
322
413
  clearInterval(refreshInterval);
323
- clearInterval(apiInterval);
414
+ clearInterval(pnlInterval);
415
+ await marketFeed.disconnect();
324
416
  if (cleanupKeys) cleanupKeys();
325
417
  ui.cleanup();
326
418
 
327
- // Ensure stdin is ready for prompts after algo cleanup
328
419
  if (process.stdin.isTTY) {
329
420
  process.stdin.setRawMode(false);
330
421
  }
331
422
  process.stdin.resume();
332
423
 
333
- // Calculate duration
424
+ // Duration
334
425
  const durationMs = Date.now() - stats.startTime;
335
426
  const hours = Math.floor(durationMs / 3600000);
336
427
  const minutes = Math.floor((durationMs % 3600000) / 60000);
@@ -344,7 +435,6 @@ const launchAlgo = async (service, account, contract, config) => {
344
435
  // Summary
345
436
  renderSessionSummary(stats, stopReason);
346
437
 
347
- // Wait 3 seconds then return to menu (avoid stdin issues after raw mode)
348
438
  console.log('\n Returning to menu in 3 seconds...');
349
439
  await new Promise(resolve => setTimeout(resolve, 3000));
350
440
  };