hedgequantx 2.4.30 → 2.4.32
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/pages/algo/copy-trading.js +29 -8
- package/src/pages/algo/logger.js +245 -0
- package/src/pages/algo/one-account.js +59 -33
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ const { connections } = require('../../services');
|
|
|
11
11
|
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
12
12
|
const { logger, prompts } = require('../../utils');
|
|
13
13
|
const { checkMarketHours } = require('../../services/projectx/market');
|
|
14
|
+
const { algoLogger } = require('./logger');
|
|
14
15
|
|
|
15
16
|
const log = logger.scope('CopyTrading');
|
|
16
17
|
|
|
@@ -299,10 +300,17 @@ const launchCopyTrading = async (config) => {
|
|
|
299
300
|
}
|
|
300
301
|
};
|
|
301
302
|
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
303
|
+
// Smart startup logs (same as HQX-TG)
|
|
304
|
+
const market = checkMarketHours();
|
|
305
|
+
const sessionName = market.session || 'AMERICAN';
|
|
306
|
+
const etTime = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'America/New_York' });
|
|
307
|
+
|
|
308
|
+
algoLogger.connectingToEngine(ui, lead.account.accountId);
|
|
309
|
+
algoLogger.engineStarting(ui, stats.platform, dailyTarget, maxRisk);
|
|
310
|
+
algoLogger.marketOpen(ui, sessionName.toUpperCase(), etTime);
|
|
311
|
+
algoLogger.info(ui, 'COPY MODE', `Lead: ${lead.propfirm} -> Follower: ${follower.propfirm}`);
|
|
312
|
+
algoLogger.dataConnected(ui, 'API');
|
|
313
|
+
algoLogger.algoOperational(ui, stats.platform);
|
|
306
314
|
stats.connected = true;
|
|
307
315
|
|
|
308
316
|
// Track lead positions and copy to follower
|
|
@@ -321,7 +329,12 @@ const launchCopyTrading = async (config) => {
|
|
|
321
329
|
const existing = lastLeadPositions.find(p => p.contractId === pos.contractId);
|
|
322
330
|
if (!existing && pos.quantity !== 0) {
|
|
323
331
|
// New position opened - copy to follower
|
|
324
|
-
|
|
332
|
+
const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
|
|
333
|
+
const symbol = pos.symbol || pos.contractId;
|
|
334
|
+
const size = Math.abs(pos.quantity);
|
|
335
|
+
const entry = pos.averagePrice || 0;
|
|
336
|
+
algoLogger.positionOpened(ui, symbol, side, size, entry);
|
|
337
|
+
algoLogger.info(ui, 'COPYING TO FOLLOWER', `${side} ${size}x ${symbol}`);
|
|
325
338
|
// TODO: Place order on follower account
|
|
326
339
|
}
|
|
327
340
|
}
|
|
@@ -330,7 +343,13 @@ const launchCopyTrading = async (config) => {
|
|
|
330
343
|
for (const oldPos of lastLeadPositions) {
|
|
331
344
|
const stillOpen = currentPositions.find(p => p.contractId === oldPos.contractId);
|
|
332
345
|
if (!stillOpen || stillOpen.quantity === 0) {
|
|
333
|
-
|
|
346
|
+
const side = oldPos.quantity > 0 ? 'LONG' : 'SHORT';
|
|
347
|
+
const symbol = oldPos.symbol || oldPos.contractId;
|
|
348
|
+
const size = Math.abs(oldPos.quantity);
|
|
349
|
+
const exit = stillOpen?.averagePrice || oldPos.averagePrice || 0;
|
|
350
|
+
const pnl = oldPos.profitAndLoss || 0;
|
|
351
|
+
algoLogger.positionClosed(ui, symbol, side, size, exit, pnl);
|
|
352
|
+
algoLogger.info(ui, 'CLOSING ON FOLLOWER', symbol);
|
|
334
353
|
// TODO: Close position on follower account
|
|
335
354
|
}
|
|
336
355
|
}
|
|
@@ -353,11 +372,13 @@ const launchCopyTrading = async (config) => {
|
|
|
353
372
|
if (stats.pnl >= dailyTarget) {
|
|
354
373
|
stopReason = 'target';
|
|
355
374
|
running = false;
|
|
356
|
-
|
|
375
|
+
algoLogger.targetHit(ui, lead.symbol.name, 0, stats.pnl);
|
|
376
|
+
algoLogger.info(ui, 'DAILY TARGET REACHED', `+$${stats.pnl.toFixed(2)} - Stopping algo`);
|
|
357
377
|
} else if (stats.pnl <= -maxRisk) {
|
|
358
378
|
stopReason = 'risk';
|
|
359
379
|
running = false;
|
|
360
|
-
|
|
380
|
+
algoLogger.dailyLimitWarning(ui, stats.pnl, -maxRisk);
|
|
381
|
+
algoLogger.error(ui, 'MAX RISK HIT', `-$${Math.abs(stats.pnl).toFixed(2)} - Stopping algo`);
|
|
361
382
|
}
|
|
362
383
|
} catch (e) {
|
|
363
384
|
// Silent fail - will retry
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* HQX Algo Trading System - Smart Algo Logger
|
|
4
|
+
* =============================================================================
|
|
5
|
+
* Provides rich, detailed logs for the algo UI
|
|
6
|
+
* Copied from HQX-TG algo-logger.ts - All 47 log types
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Smart Algo Logger - All 47 log types from HQX-TG
|
|
13
|
+
*/
|
|
14
|
+
const algoLogger = {
|
|
15
|
+
// === MARKET DATA LOGS ===
|
|
16
|
+
quote(ui, symbol, bid, ask, spread) {
|
|
17
|
+
ui.addLog('info', `QUOTE ${symbol} - bid: ${bid.toFixed(2)} | ask: ${ask.toFixed(2)} | spread: ${spread.toFixed(2)}`);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
tape(ui, symbol, buyVol, sellVol, delta) {
|
|
21
|
+
const sign = delta > 0 ? '+' : '';
|
|
22
|
+
ui.addLog('info', `TAPE ${symbol} - buy: ${buyVol} | sell: ${sellVol} | delta: ${sign}${delta}`);
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
dom(ui, symbol, bidDepth, askDepth, imbalance) {
|
|
26
|
+
const direction = imbalance > 0 ? '▲' : imbalance < 0 ? '▼' : '=';
|
|
27
|
+
ui.addLog('info', `DOM ${symbol} - bidDepth: ${bidDepth} | askDepth: ${askDepth} | imbalance: ${direction}${Math.abs(imbalance).toFixed(1)}%`);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
volumeSpike(ui, symbol, volume, avgVolume, ratio) {
|
|
31
|
+
ui.addLog('analysis', `VOLUME SPIKE ${symbol} - vol: ${volume} | avg: ${avgVolume.toFixed(0)} | ratio: ${ratio.toFixed(1)}x`);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// === ANALYSIS LOGS ===
|
|
35
|
+
orderFlow(ui, symbol, score, direction) {
|
|
36
|
+
const arrow = direction === 'LONG' ? '▲' : direction === 'SHORT' ? '▼' : '=';
|
|
37
|
+
ui.addLog('analysis', `ORDER FLOW ${symbol} - score: ${score.toFixed(0)} | direction: ${arrow} ${direction}`);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
absorption(ui, symbol, level, side, strength) {
|
|
41
|
+
ui.addLog('analysis', `ABSORPTION ${side} - price: ${level.toFixed(2)} | strength: ${strength.toFixed(0)}%`);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
sweep(ui, symbol, side, levels, volume) {
|
|
45
|
+
ui.addLog('analysis', `SWEEP ${side} - levels: ${levels} | volume: ${volume}`);
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
iceberg(ui, symbol, price, hiddenSize) {
|
|
49
|
+
ui.addLog('analysis', `ICEBERG DETECTED - price: ${price.toFixed(2)} | hidden: ${hiddenSize}`);
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
deltaDivergence(ui, symbol, priceDir, deltaDir) {
|
|
53
|
+
ui.addLog('analysis', `DELTA DIVERGENCE - price: ${priceDir} | delta: ${deltaDir}`);
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
vpoc(ui, symbol, poc, valueHigh, valueLow) {
|
|
57
|
+
ui.addLog('analysis', `VPOC ${symbol} - poc: ${poc.toFixed(2)} | VAH: ${valueHigh.toFixed(2)} | VAL: ${valueLow.toFixed(2)}`);
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
regime(ui, symbol, regime, confidence) {
|
|
61
|
+
ui.addLog('analysis', `REGIME ${symbol} - ${regime} | confidence: ${confidence.toFixed(0)}%`);
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
volatility(ui, symbol, atr, regime) {
|
|
65
|
+
ui.addLog('analysis', `VOLATILITY ${symbol} - ATR: ${atr.toFixed(2)} | regime: ${regime}`);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// === SIGNAL LOGS ===
|
|
69
|
+
signalGenerated(ui, symbol, direction, confidence, strategy) {
|
|
70
|
+
const arrow = direction === 'LONG' ? '▲' : '▼';
|
|
71
|
+
ui.addLog('signal', `SIGNAL ${arrow} ${direction} - ${symbol} | conf: ${confidence.toFixed(0)}% | strategy: ${strategy}`);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
signalRejected(ui, symbol, reason) {
|
|
75
|
+
ui.addLog('warning', `SIGNAL REJECTED - ${symbol} | reason: ${reason}`);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
signalExpired(ui, symbol) {
|
|
79
|
+
ui.addLog('info', `SIGNAL EXPIRED - ${symbol}`);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// === TRADE LOGS ===
|
|
83
|
+
orderSubmitted(ui, symbol, side, size, price) {
|
|
84
|
+
ui.addLog('trade', `ORDER SUBMITTED - ${side} ${size} ${symbol} @ ${price.toFixed(2)}`);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
orderFilled(ui, symbol, side, size, price) {
|
|
88
|
+
ui.addLog('trade', `ORDER FILLED - ${side} ${size} ${symbol} @ ${price.toFixed(2)}`);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
orderRejected(ui, symbol, reason) {
|
|
92
|
+
ui.addLog('error', `ORDER REJECTED - ${symbol} | ${reason}`);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
orderCancelled(ui, symbol, orderId) {
|
|
96
|
+
ui.addLog('warning', `ORDER CANCELLED - ${symbol} | id: ${orderId}`);
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
positionOpened(ui, symbol, side, size, entry) {
|
|
100
|
+
const arrow = side === 'LONG' ? '▲' : '▼';
|
|
101
|
+
ui.addLog('trade', `POSITION OPENED ${arrow} - ${side} ${size} ${symbol} @ ${entry.toFixed(2)}`);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
positionClosed(ui, symbol, side, size, exit, pnl) {
|
|
105
|
+
const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
|
|
106
|
+
const type = pnl >= 0 ? 'trade' : 'warning';
|
|
107
|
+
ui.addLog(type, `POSITION CLOSED - ${side} ${size} ${symbol} @ ${exit.toFixed(2)} | PnL: ${pnlStr}`);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
stopHit(ui, symbol, price, loss) {
|
|
111
|
+
ui.addLog('warning', `STOP LOSS HIT - ${symbol} @ ${price.toFixed(2)} | loss: -$${Math.abs(loss).toFixed(2)}`);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
targetHit(ui, symbol, price, profit) {
|
|
115
|
+
ui.addLog('trade', `TARGET HIT - ${symbol} @ ${price.toFixed(2)} | profit: +$${profit.toFixed(2)}`);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
trailingStopMoved(ui, symbol, oldStop, newStop) {
|
|
119
|
+
ui.addLog('info', `TRAILING STOP MOVED - ${symbol} | ${oldStop.toFixed(2)} → ${newStop.toFixed(2)}`);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
fillSync(ui, side, size, price, orderId) {
|
|
123
|
+
ui.addLog('trade', `FILL (SYNC) - ${side} ${size} @ ${price.toFixed(2)} (order #${orderId})`);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
entryConfirmed(ui, side, size, symbol, price) {
|
|
127
|
+
ui.addLog('info', `ENTRY CONFIRMED - ${side} ${size}x ${symbol} @ ${price.toFixed(2)}`);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
stopsSet(ui, sl, tp) {
|
|
131
|
+
ui.addLog('info', `STOPS SET - SL: ${sl.toFixed(2)} | TP: ${tp.toFixed(2)}`);
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// === RISK LOGS ===
|
|
135
|
+
riskCheck(ui, passed, reason) {
|
|
136
|
+
const type = passed ? 'info' : 'warning';
|
|
137
|
+
ui.addLog(type, `RISK CHECK ${passed ? 'PASSED' : 'BLOCKED'} - ${reason}`);
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
dailyLimitWarning(ui, current, limit) {
|
|
141
|
+
ui.addLog('warning', `DAILY LIMIT WARNING - PnL: $${current.toFixed(2)} / limit: $${limit.toFixed(2)}`);
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
maxDrawdownWarning(ui, current, max) {
|
|
145
|
+
ui.addLog('warning', `DRAWDOWN WARNING - current: ${current.toFixed(1)}% / max: ${max.toFixed(1)}%`);
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
positionSized(ui, contracts, kelly, riskAmount, riskPct) {
|
|
149
|
+
ui.addLog('info', `POSITION SIZE - ${contracts} contracts | kelly: ${kelly.toFixed(2)} | risk: $${riskAmount} (${riskPct}% of max)`);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
bracketSet(ui, stopTicks, targetTicks, rr) {
|
|
153
|
+
ui.addLog('info', `BRACKET SET - SL: ${stopTicks}t | TP: ${targetTicks}t | R:R: ${rr.toFixed(1)}`);
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// === STRATEGY LOGS ===
|
|
157
|
+
strategySelected(ui, strategy, session, regime) {
|
|
158
|
+
ui.addLog('info', `STRATEGY SELECTED - ${strategy} | session: ${session} | regime: ${regime}`);
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
strategySwitch(ui, from, to, reason) {
|
|
162
|
+
ui.addLog('info', `STRATEGY SWITCH - ${from} → ${to} | ${reason}`);
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// === SESSION LOGS ===
|
|
166
|
+
sessionStart(ui, session) {
|
|
167
|
+
ui.addLog('info', `SESSION START - ${session}`);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
sessionEnd(ui, session) {
|
|
171
|
+
ui.addLog('info', `SESSION END - ${session}`);
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
marketOpen(ui, session, etTime) {
|
|
175
|
+
ui.addLog('info', `MARKET OPEN - ${session} SESSION | ET: ${etTime} ET`);
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
marketClosed(ui) {
|
|
179
|
+
ui.addLog('warning', `MARKET CLOSED - Trading paused`);
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// === SYSTEM LOGS ===
|
|
183
|
+
connectingToEngine(ui, accountId) {
|
|
184
|
+
ui.addLog('info', `CONNECTING TO ALGO ENGINE... - Account: ${accountId}`);
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
engineStarting(ui, platform, dailyTarget, dailyRisk) {
|
|
188
|
+
ui.addLog('info', `ALGO ENGINE STARTING... - Platform: ${platform} | Daily Target: $${dailyTarget} | Daily Risk: $${dailyRisk}`);
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
engineStarted(ui, platform, status) {
|
|
192
|
+
ui.addLog('info', `ALGO ENGINE STARTED - Status: ${status}`);
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
engineStopped(ui, reason) {
|
|
196
|
+
ui.addLog('info', `ENGINE STOPPED - ${reason || 'All positions flat'}`);
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
algoOperational(ui, platform) {
|
|
200
|
+
ui.addLog('info', `ALGO FULLY OPERATIONAL - Connected to ${platform} - Scanning for alpha...`);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
dataConnected(ui, source) {
|
|
204
|
+
ui.addLog('info', `WEBSOCKET CONNECTED - Real-time updates enabled`);
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
dataDisconnected(ui, source, reason) {
|
|
208
|
+
ui.addLog('warning', `WEBSOCKET DISCONNECTED - Attempting to reconnect...`);
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
heartbeat(ui, tps, latency) {
|
|
212
|
+
ui.addLog('info', `HEARTBEAT - tps: ${tps} | latency: ${latency}ms`);
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
latencyReport(ui, latency) {
|
|
216
|
+
ui.addLog('analysis', `LATENCY - ${latency}ms order-to-fill`);
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// === GENERIC ===
|
|
220
|
+
info(ui, message, details) {
|
|
221
|
+
ui.addLog('info', details ? `${message} - ${details}` : message);
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
warning(ui, message, details) {
|
|
225
|
+
ui.addLog('warning', details ? `${message} - ${details}` : message);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
error(ui, message, details) {
|
|
229
|
+
ui.addLog('error', details ? `${message} - ${details}` : message);
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
signal(ui, message, details) {
|
|
233
|
+
ui.addLog('signal', details ? `${message} - ${details}` : message);
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
trade(ui, message, details) {
|
|
237
|
+
ui.addLog('trade', details ? `${message} - ${details}` : message);
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
analysis(ui, message, details) {
|
|
241
|
+
ui.addLog('analysis', details ? `${message} - ${details}` : message);
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
module.exports = { algoLogger };
|
|
@@ -14,6 +14,7 @@ const { checkMarketHours } = require('../../services/projectx/market');
|
|
|
14
14
|
// Strategy & Market Data (obfuscated)
|
|
15
15
|
const { M1 } = require('../../../dist/lib/m/s1');
|
|
16
16
|
const { MarketDataFeed } = require('../../../dist/lib/data');
|
|
17
|
+
const { algoLogger } = require('./logger');
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
|
|
@@ -213,12 +214,14 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
213
214
|
// Initialize Market Data Feed
|
|
214
215
|
const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
|
|
215
216
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
217
|
+
// Smart startup logs (same as HQX-TG)
|
|
218
|
+
const market = checkMarketHours();
|
|
219
|
+
const sessionName = market.session || 'AMERICAN';
|
|
220
|
+
const etTime = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'America/New_York' });
|
|
221
|
+
|
|
222
|
+
algoLogger.connectingToEngine(ui, account.accountId);
|
|
223
|
+
algoLogger.engineStarting(ui, connectionType, dailyTarget, maxRisk);
|
|
224
|
+
algoLogger.marketOpen(ui, sessionName.toUpperCase(), etTime);
|
|
222
225
|
|
|
223
226
|
// Handle strategy signals
|
|
224
227
|
strategy.on('signal', async (signal) => {
|
|
@@ -226,7 +229,12 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
226
229
|
|
|
227
230
|
const { side, direction, entry, stopLoss, takeProfit, confidence } = signal;
|
|
228
231
|
|
|
229
|
-
|
|
232
|
+
// Calculate position size with kelly
|
|
233
|
+
const kelly = Math.min(0.25, confidence);
|
|
234
|
+
const riskAmount = Math.round(maxRisk * kelly);
|
|
235
|
+
const riskPct = Math.round((riskAmount / maxRisk) * 100);
|
|
236
|
+
|
|
237
|
+
algoLogger.positionSized(ui, contracts, kelly, riskAmount, riskPct);
|
|
230
238
|
|
|
231
239
|
// Place order via API
|
|
232
240
|
pendingOrder = true;
|
|
@@ -243,7 +251,13 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
243
251
|
if (orderResult.success) {
|
|
244
252
|
currentPosition = direction === 'long' ? contracts : -contracts;
|
|
245
253
|
stats.trades++;
|
|
246
|
-
|
|
254
|
+
const sideStr = direction === 'long' ? 'BUY' : 'SELL';
|
|
255
|
+
const positionSide = direction === 'long' ? 'LONG' : 'SHORT';
|
|
256
|
+
|
|
257
|
+
algoLogger.orderSubmitted(ui, symbolName, sideStr, contracts, entry);
|
|
258
|
+
algoLogger.orderFilled(ui, symbolName, sideStr, contracts, entry);
|
|
259
|
+
algoLogger.positionOpened(ui, symbolName, positionSide, contracts, entry);
|
|
260
|
+
algoLogger.entryConfirmed(ui, sideStr, contracts, symbolName, entry);
|
|
247
261
|
|
|
248
262
|
// Place bracket orders (SL/TP)
|
|
249
263
|
if (stopLoss && takeProfit) {
|
|
@@ -267,55 +281,67 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
267
281
|
limitPrice: takeProfit
|
|
268
282
|
});
|
|
269
283
|
|
|
270
|
-
|
|
284
|
+
algoLogger.stopsSet(ui, stopLoss, takeProfit);
|
|
271
285
|
}
|
|
272
286
|
} else {
|
|
273
|
-
|
|
287
|
+
algoLogger.orderRejected(ui, symbolName, orderResult.error || 'Unknown error');
|
|
274
288
|
}
|
|
275
289
|
} catch (e) {
|
|
276
|
-
|
|
290
|
+
algoLogger.error(ui, 'ORDER ERROR', e.message);
|
|
277
291
|
}
|
|
278
292
|
pendingOrder = false;
|
|
279
293
|
});
|
|
280
294
|
|
|
281
295
|
// Handle market data ticks
|
|
296
|
+
let lastHeartbeat = Date.now();
|
|
297
|
+
let tps = 0;
|
|
298
|
+
|
|
282
299
|
marketFeed.on('tick', (tick) => {
|
|
283
300
|
tickCount++;
|
|
301
|
+
tps++;
|
|
284
302
|
const latencyStart = Date.now();
|
|
285
303
|
|
|
304
|
+
// Debug: log first tick to see structure
|
|
305
|
+
if (tickCount === 1) {
|
|
306
|
+
algoLogger.info(ui, 'FIRST TICK', `price=${tick.price} bid=${tick.bid} ask=${tick.ask} vol=${tick.volume}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
286
309
|
// Feed tick to strategy
|
|
287
|
-
|
|
310
|
+
const tickData = {
|
|
288
311
|
contractId: tick.contractId || contractId,
|
|
289
|
-
price: tick.price,
|
|
312
|
+
price: tick.price || tick.lastPrice || tick.bid,
|
|
290
313
|
bid: tick.bid,
|
|
291
314
|
ask: tick.ask,
|
|
292
|
-
volume: tick.volume || 1,
|
|
293
|
-
side: tick.lastTradeSide || 'unknown',
|
|
315
|
+
volume: tick.volume || tick.size || 1,
|
|
316
|
+
side: tick.lastTradeSide || tick.side || 'unknown',
|
|
294
317
|
timestamp: tick.timestamp || Date.now()
|
|
295
|
-
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
strategy.processTick(tickData);
|
|
296
321
|
|
|
297
322
|
stats.latency = Date.now() - latencyStart;
|
|
298
323
|
|
|
299
|
-
//
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
324
|
+
// Heartbeat every 30 seconds (smart log instead of tick count)
|
|
325
|
+
if (Date.now() - lastHeartbeat > 30000) {
|
|
326
|
+
algoLogger.heartbeat(ui, tps, stats.latency);
|
|
327
|
+
lastHeartbeat = Date.now();
|
|
328
|
+
tps = 0;
|
|
303
329
|
}
|
|
304
330
|
});
|
|
305
331
|
|
|
306
332
|
marketFeed.on('connected', () => {
|
|
307
333
|
stats.connected = true;
|
|
308
|
-
|
|
334
|
+
algoLogger.dataConnected(ui, 'RTC');
|
|
335
|
+
algoLogger.algoOperational(ui, connectionType);
|
|
309
336
|
});
|
|
310
337
|
|
|
311
338
|
marketFeed.on('error', (err) => {
|
|
312
|
-
|
|
339
|
+
algoLogger.error(ui, 'MARKET ERROR', err.message);
|
|
313
340
|
});
|
|
314
341
|
|
|
315
342
|
marketFeed.on('disconnected', (err) => {
|
|
316
343
|
stats.connected = false;
|
|
317
|
-
|
|
318
|
-
ui.addLog('error', `DISC: ${reason.substring(0, 70).toUpperCase()}`);
|
|
344
|
+
algoLogger.dataDisconnected(ui, 'WEBSOCKET', err?.message);
|
|
319
345
|
});
|
|
320
346
|
|
|
321
347
|
// Connect to market data
|
|
@@ -324,14 +350,14 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
324
350
|
|
|
325
351
|
// CRITICAL: Get a fresh token for WebSocket connection
|
|
326
352
|
// TopStep invalidates WebSocket sessions for old tokens
|
|
327
|
-
|
|
353
|
+
algoLogger.info(ui, 'REFRESHING AUTH TOKEN...');
|
|
328
354
|
const token = await service.getFreshToken?.() || service.token || service.getToken?.();
|
|
329
355
|
|
|
330
356
|
if (!token) {
|
|
331
|
-
|
|
357
|
+
algoLogger.error(ui, 'NO AUTH TOKEN', 'Please reconnect');
|
|
332
358
|
} else {
|
|
333
|
-
|
|
334
|
-
|
|
359
|
+
algoLogger.info(ui, 'TOKEN OK', `${token.length} chars`);
|
|
360
|
+
algoLogger.info(ui, 'CONNECTING', `${propfirmKey.toUpperCase()} | ${contractId}`);
|
|
335
361
|
|
|
336
362
|
await marketFeed.connect(token, propfirmKey);
|
|
337
363
|
|
|
@@ -340,13 +366,13 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
340
366
|
|
|
341
367
|
if (marketFeed.isConnected()) {
|
|
342
368
|
await marketFeed.subscribe(symbolName, contractId);
|
|
343
|
-
|
|
369
|
+
algoLogger.info(ui, 'SUBSCRIBED', `${symbolName} real-time feed active`);
|
|
344
370
|
} else {
|
|
345
|
-
|
|
371
|
+
algoLogger.error(ui, 'CONNECTION LOST', 'Before subscribe');
|
|
346
372
|
}
|
|
347
373
|
}
|
|
348
374
|
} catch (e) {
|
|
349
|
-
|
|
375
|
+
algoLogger.error(ui, 'CONNECTION ERROR', e.message.substring(0, 50));
|
|
350
376
|
}
|
|
351
377
|
|
|
352
378
|
// Poll account P&L from API
|
|
@@ -391,11 +417,11 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
391
417
|
if (stats.pnl >= dailyTarget) {
|
|
392
418
|
stopReason = 'target';
|
|
393
419
|
running = false;
|
|
394
|
-
|
|
420
|
+
algoLogger.targetHit(ui, symbolName, 0, stats.pnl);
|
|
395
421
|
} else if (stats.pnl <= -maxRisk) {
|
|
396
422
|
stopReason = 'risk';
|
|
397
423
|
running = false;
|
|
398
|
-
|
|
424
|
+
algoLogger.dailyLimitWarning(ui, stats.pnl, -maxRisk);
|
|
399
425
|
}
|
|
400
426
|
} catch (e) {
|
|
401
427
|
// Silently handle polling errors
|