hedgequantx 2.6.49 → 2.6.51
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 +30 -0
- package/src/pages/algo/one-account.js +39 -13
- package/src/pages/algo/smart-logs.js +394 -0
- package/src/pages/algo/ui.js +10 -76
package/package.json
CHANGED
|
@@ -64,6 +64,8 @@ class CopyEngine {
|
|
|
64
64
|
this.pollCount = 0;
|
|
65
65
|
this.orderCount = 0;
|
|
66
66
|
this.failedOrders = 0;
|
|
67
|
+
this.lastLogTime = 0;
|
|
68
|
+
this.positionEntryTime = null;
|
|
67
69
|
|
|
68
70
|
// Retry configuration
|
|
69
71
|
this.maxRetries = 3;
|
|
@@ -212,6 +214,9 @@ class CopyEngine {
|
|
|
212
214
|
const size = Math.abs(position.quantity);
|
|
213
215
|
const entry = position.averagePrice || 0;
|
|
214
216
|
|
|
217
|
+
// Track entry time for smart logs
|
|
218
|
+
this.positionEntryTime = Date.now();
|
|
219
|
+
|
|
215
220
|
algoLogger.positionOpened(this.ui, displaySymbol, side, size, entry);
|
|
216
221
|
|
|
217
222
|
// Feed to AI supervisor
|
|
@@ -282,6 +287,9 @@ class CopyEngine {
|
|
|
282
287
|
const displaySymbol = position.symbol || this.symbol.name;
|
|
283
288
|
const size = Math.abs(position.quantity);
|
|
284
289
|
|
|
290
|
+
// Reset entry time
|
|
291
|
+
this.positionEntryTime = null;
|
|
292
|
+
|
|
285
293
|
algoLogger.positionClosed(this.ui, displaySymbol, side, size, exitPrice, pnl);
|
|
286
294
|
|
|
287
295
|
// Feed to AI supervisor
|
|
@@ -432,6 +440,28 @@ class CopyEngine {
|
|
|
432
440
|
|
|
433
441
|
this.pollCount++;
|
|
434
442
|
|
|
443
|
+
// Smart logs every second - non-repetitive status updates
|
|
444
|
+
const now = Date.now();
|
|
445
|
+
if (now - this.lastLogTime > 1000) {
|
|
446
|
+
const smartLogs = require('./smart-logs');
|
|
447
|
+
|
|
448
|
+
if (this.leadPositions.size > 0) {
|
|
449
|
+
// In position - show position status
|
|
450
|
+
for (const [key, pos] of this.leadPositions) {
|
|
451
|
+
const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
|
|
452
|
+
const unrealizedPnl = pos.profitAndLoss || 0;
|
|
453
|
+
const holdTime = Math.floor((now - (this.positionEntryTime || now)) / 1000);
|
|
454
|
+
const posLog = smartLogs.getPositionUpdateLog(side, Math.abs(pos.quantity), unrealizedPnl, pos.averagePrice || 0, pos.averagePrice || 0, holdTime);
|
|
455
|
+
algoLogger.trade(this.ui, posLog.message, posLog.details);
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
// No position - show scanning status
|
|
459
|
+
const scanLog = smartLogs.getScanningLog(true);
|
|
460
|
+
algoLogger.info(this.ui, scanLog.message, `poll #${this.pollCount} | ${pollTime}ms`);
|
|
461
|
+
}
|
|
462
|
+
this.lastLogTime = now;
|
|
463
|
+
}
|
|
464
|
+
|
|
435
465
|
} catch (err) {
|
|
436
466
|
log.warn('Poll error', { error: err.message });
|
|
437
467
|
}
|
|
@@ -430,10 +430,18 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
430
430
|
if (pos.symbol !== symbolName && !pos.symbol?.includes(symbolName.replace(/[A-Z]\d+$/, ''))) return;
|
|
431
431
|
|
|
432
432
|
// Update position info (like R Trader Positions panel)
|
|
433
|
+
const wasFlat = stats.position === 0;
|
|
433
434
|
stats.position = pos.quantity || 0;
|
|
434
435
|
stats.entryPrice = pos.averagePrice || 0;
|
|
435
436
|
currentPosition = pos.quantity || 0;
|
|
436
437
|
|
|
438
|
+
// Track entry time when opening a new position
|
|
439
|
+
if (wasFlat && stats.position !== 0) {
|
|
440
|
+
stats.entryTime = Date.now();
|
|
441
|
+
} else if (stats.position === 0) {
|
|
442
|
+
stats.entryTime = null;
|
|
443
|
+
}
|
|
444
|
+
|
|
437
445
|
// CRITICAL: Update Open P&L from instrument-level data (real-time, same as R Trader)
|
|
438
446
|
// pos.openPnl comes from INSTRUMENT_PNL_UPDATE (450) - this is the unrealized P&L
|
|
439
447
|
if (pos.openPnl !== undefined && pos.openPnl !== null) {
|
|
@@ -716,25 +724,43 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
716
724
|
|
|
717
725
|
stats.latency = Date.now() - latencyStart;
|
|
718
726
|
|
|
719
|
-
//
|
|
720
|
-
|
|
721
|
-
|
|
727
|
+
// Smart logs every 1 second - non-repetitive, varied messages
|
|
728
|
+
const now = Date.now();
|
|
729
|
+
if (now - lastHeartbeat > 1000) {
|
|
722
730
|
const modelValues = strategy.getModelValues?.() || strategy.getModelValues?.(contractId) || null;
|
|
723
731
|
|
|
724
732
|
if (modelValues && modelValues.ofi !== undefined) {
|
|
725
|
-
|
|
726
|
-
const ofi = (modelValues.ofi || 0).toFixed(2);
|
|
733
|
+
const ofi = modelValues.ofi || 0;
|
|
727
734
|
const delta = modelValues.delta || 0;
|
|
728
|
-
const zscore =
|
|
729
|
-
const mom =
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
735
|
+
const zscore = modelValues.zscore || 0;
|
|
736
|
+
const mom = modelValues.momentum || 0;
|
|
737
|
+
|
|
738
|
+
// Get smart varied log based on market state
|
|
739
|
+
const smartLogs = require('./smart-logs');
|
|
740
|
+
const stateLog = smartLogs.getMarketStateLog(ofi, zscore, mom, delta);
|
|
741
|
+
|
|
742
|
+
if (currentPosition !== 0) {
|
|
743
|
+
// In position - show position status with varied messages
|
|
744
|
+
const side = currentPosition > 0 ? 'LONG' : 'SHORT';
|
|
745
|
+
const unrealizedPnl = stats.openPnl || 0;
|
|
746
|
+
const holdTime = Math.floor((now - (stats.entryTime || now)) / 1000);
|
|
747
|
+
const posLog = smartLogs.getPositionUpdateLog(side, Math.abs(currentPosition), unrealizedPnl, stats.entryPrice, tickData.price, holdTime);
|
|
748
|
+
ui.addLog('trade', `${posLog.message} - ${posLog.details}`);
|
|
749
|
+
} else {
|
|
750
|
+
// Not in position - show market state with varied messages
|
|
751
|
+
if (stateLog.details) {
|
|
752
|
+
ui.addLog('analysis', `${stateLog.message} - ${stateLog.details}`);
|
|
753
|
+
} else {
|
|
754
|
+
ui.addLog('info', stateLog.message);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
733
757
|
} else {
|
|
734
|
-
//
|
|
735
|
-
|
|
758
|
+
// Waiting for data
|
|
759
|
+
const smartLogs = require('./smart-logs');
|
|
760
|
+
const scanLog = smartLogs.getScanningLog(true);
|
|
761
|
+
ui.addLog('info', `${scanLog.message} ${tps} ticks/s`);
|
|
736
762
|
}
|
|
737
|
-
lastHeartbeat =
|
|
763
|
+
lastHeartbeat = now;
|
|
738
764
|
tps = 0;
|
|
739
765
|
}
|
|
740
766
|
});
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* Smart Logging System - Non-repetitive, contextual, varied messages
|
|
4
|
+
* =============================================================================
|
|
5
|
+
* Copied from HQX-TG smart-logs.ts
|
|
6
|
+
* - Uses message pools to avoid repetition
|
|
7
|
+
* - Tracks recent messages to ensure variety
|
|
8
|
+
* - Provides market-context-aware messages
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
// Track recently used messages to avoid repetition
|
|
14
|
+
const recentMessages = new Map();
|
|
15
|
+
const MAX_RECENT = 5;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get a message from a pool, avoiding recent ones
|
|
19
|
+
*/
|
|
20
|
+
function getVariedMessage(category, pool, defaultMsg) {
|
|
21
|
+
const recent = recentMessages.get(category) || [];
|
|
22
|
+
|
|
23
|
+
// Filter out recently used messages
|
|
24
|
+
const available = pool.filter(msg => !recent.includes(msg));
|
|
25
|
+
|
|
26
|
+
// If all messages were recently used, reset
|
|
27
|
+
if (available.length === 0) {
|
|
28
|
+
recentMessages.set(category, []);
|
|
29
|
+
return pool[Math.floor(Math.random() * pool.length)] || defaultMsg;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Pick a random available message
|
|
33
|
+
const chosen = available[Math.floor(Math.random() * available.length)] || defaultMsg;
|
|
34
|
+
|
|
35
|
+
// Track it
|
|
36
|
+
recent.push(chosen);
|
|
37
|
+
if (recent.length > MAX_RECENT) recent.shift();
|
|
38
|
+
recentMessages.set(category, recent);
|
|
39
|
+
|
|
40
|
+
return chosen;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// MESSAGE POOLS - Market Flow
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
const LONG_BIAS_MESSAGES = [
|
|
48
|
+
'Bullish bias detected',
|
|
49
|
+
'Buyers in control',
|
|
50
|
+
'Long-side pressure',
|
|
51
|
+
'Bid accumulation',
|
|
52
|
+
'Buy programs active',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const SHORT_BIAS_MESSAGES = [
|
|
56
|
+
'Bearish bias detected',
|
|
57
|
+
'Sellers in control',
|
|
58
|
+
'Short-side pressure',
|
|
59
|
+
'Offer distribution',
|
|
60
|
+
'Sell programs active',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const FLAT_BIAS_MESSAGES = [
|
|
64
|
+
'Market balanced',
|
|
65
|
+
'Two-way flow',
|
|
66
|
+
'Consolidation mode',
|
|
67
|
+
'No clear direction',
|
|
68
|
+
'Mixed signals',
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// MESSAGE POOLS - Trading Events
|
|
73
|
+
// =============================================================================
|
|
74
|
+
|
|
75
|
+
const SIGNAL_LONG_MESSAGES = [
|
|
76
|
+
'Buy signal generated',
|
|
77
|
+
'Long opportunity detected',
|
|
78
|
+
'Bullish setup confirmed',
|
|
79
|
+
'Entry signal: LONG',
|
|
80
|
+
'Buy zone activated',
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const SIGNAL_SHORT_MESSAGES = [
|
|
84
|
+
'Sell signal generated',
|
|
85
|
+
'Short opportunity detected',
|
|
86
|
+
'Bearish setup confirmed',
|
|
87
|
+
'Entry signal: SHORT',
|
|
88
|
+
'Sell zone activated',
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const ENTRY_LONG_MESSAGES = [
|
|
92
|
+
'Long position opened',
|
|
93
|
+
'Buy order filled',
|
|
94
|
+
'Entered long',
|
|
95
|
+
'Long initiated',
|
|
96
|
+
'Position: LONG',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const ENTRY_SHORT_MESSAGES = [
|
|
100
|
+
'Short position opened',
|
|
101
|
+
'Sell order filled',
|
|
102
|
+
'Entered short',
|
|
103
|
+
'Short initiated',
|
|
104
|
+
'Position: SHORT',
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const EXIT_PROFIT_MESSAGES = [
|
|
108
|
+
'Target reached',
|
|
109
|
+
'Profit taken',
|
|
110
|
+
'Winner closed',
|
|
111
|
+
'TP hit',
|
|
112
|
+
'Profit locked',
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const EXIT_LOSS_MESSAGES = [
|
|
116
|
+
'Stop triggered',
|
|
117
|
+
'Loss taken',
|
|
118
|
+
'Loser closed',
|
|
119
|
+
'SL hit',
|
|
120
|
+
'Risk contained',
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
// =============================================================================
|
|
124
|
+
// MESSAGE POOLS - Analysis
|
|
125
|
+
// =============================================================================
|
|
126
|
+
|
|
127
|
+
const MOMENTUM_UP_MESSAGES = [
|
|
128
|
+
'Momentum building up',
|
|
129
|
+
'Price accelerating',
|
|
130
|
+
'Bullish momentum',
|
|
131
|
+
'Upward thrust',
|
|
132
|
+
'Buying momentum',
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const MOMENTUM_DOWN_MESSAGES = [
|
|
136
|
+
'Momentum building down',
|
|
137
|
+
'Price declining',
|
|
138
|
+
'Bearish momentum',
|
|
139
|
+
'Downward thrust',
|
|
140
|
+
'Selling momentum',
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const OVERBOUGHT_MESSAGES = [
|
|
144
|
+
'Overbought condition',
|
|
145
|
+
'Extended to upside',
|
|
146
|
+
'Mean reversion setup (short)',
|
|
147
|
+
'Exhaustion detected',
|
|
148
|
+
'Stretched high',
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const OVERSOLD_MESSAGES = [
|
|
152
|
+
'Oversold condition',
|
|
153
|
+
'Extended to downside',
|
|
154
|
+
'Mean reversion setup (long)',
|
|
155
|
+
'Capitulation detected',
|
|
156
|
+
'Stretched low',
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const HIGH_VOLATILITY_MESSAGES = [
|
|
160
|
+
'Volatility elevated',
|
|
161
|
+
'High ATR detected',
|
|
162
|
+
'Wide swings',
|
|
163
|
+
'Market volatile',
|
|
164
|
+
'Increased range',
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const LOW_VOLATILITY_MESSAGES = [
|
|
168
|
+
'Low volatility',
|
|
169
|
+
'Tight range',
|
|
170
|
+
'Compressed action',
|
|
171
|
+
'Market quiet',
|
|
172
|
+
'Narrow swings',
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
// =============================================================================
|
|
176
|
+
// MESSAGE POOLS - Status
|
|
177
|
+
// =============================================================================
|
|
178
|
+
|
|
179
|
+
const SCANNING_MESSAGES = [
|
|
180
|
+
'Scanning market...',
|
|
181
|
+
'Analyzing flow...',
|
|
182
|
+
'Monitoring price action...',
|
|
183
|
+
'Watching for setups...',
|
|
184
|
+
'Evaluating conditions...',
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
const WAITING_MESSAGES = [
|
|
188
|
+
'Waiting for confirmation...',
|
|
189
|
+
'Pending trigger...',
|
|
190
|
+
'Standby mode...',
|
|
191
|
+
'Awaiting signal...',
|
|
192
|
+
'Ready to act...',
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const IN_POSITION_PROFIT_MESSAGES = [
|
|
196
|
+
'Running profit',
|
|
197
|
+
'In the money',
|
|
198
|
+
'Trade working',
|
|
199
|
+
'Position profitable',
|
|
200
|
+
'Gains accumulating',
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
const IN_POSITION_LOSS_MESSAGES = [
|
|
204
|
+
'Managing drawdown',
|
|
205
|
+
'Underwater',
|
|
206
|
+
'Holding through',
|
|
207
|
+
'Defending position',
|
|
208
|
+
'Monitoring risk',
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
// =============================================================================
|
|
212
|
+
// SMART LOG GENERATORS
|
|
213
|
+
// =============================================================================
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get a market bias log
|
|
217
|
+
*/
|
|
218
|
+
function getMarketBiasLog(direction, delta, ofi) {
|
|
219
|
+
let pool;
|
|
220
|
+
switch (direction) {
|
|
221
|
+
case 'LONG':
|
|
222
|
+
pool = LONG_BIAS_MESSAGES;
|
|
223
|
+
break;
|
|
224
|
+
case 'SHORT':
|
|
225
|
+
pool = SHORT_BIAS_MESSAGES;
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
pool = FLAT_BIAS_MESSAGES;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const message = getVariedMessage(`bias_${direction}`, pool, `${direction} bias`);
|
|
232
|
+
const arrow = direction === 'LONG' ? '▲' : direction === 'SHORT' ? '▼' : '=';
|
|
233
|
+
|
|
234
|
+
let details;
|
|
235
|
+
if (delta !== undefined && ofi !== undefined) {
|
|
236
|
+
const deltaStr = delta >= 0 ? `+${delta}` : `${delta}`;
|
|
237
|
+
details = `${arrow} Δ=${deltaStr} | OFI=${ofi.toFixed(2)}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { message, details };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get a momentum log
|
|
245
|
+
*/
|
|
246
|
+
function getMomentumLog(momentum, zscore) {
|
|
247
|
+
const isUp = momentum > 0;
|
|
248
|
+
const pool = isUp ? MOMENTUM_UP_MESSAGES : MOMENTUM_DOWN_MESSAGES;
|
|
249
|
+
const category = isUp ? 'mom_up' : 'mom_down';
|
|
250
|
+
const message = getVariedMessage(category, pool, isUp ? 'Momentum up' : 'Momentum down');
|
|
251
|
+
const arrow = isUp ? '▲' : '▼';
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
message: `${arrow} ${message}`,
|
|
255
|
+
details: `Mom=${momentum.toFixed(1)} | Z=${zscore.toFixed(2)}`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get a mean reversion log
|
|
261
|
+
*/
|
|
262
|
+
function getMeanReversionLog(zscore) {
|
|
263
|
+
const isOverbought = zscore > 0;
|
|
264
|
+
const pool = isOverbought ? OVERBOUGHT_MESSAGES : OVERSOLD_MESSAGES;
|
|
265
|
+
const category = isOverbought ? 'mr_overbought' : 'mr_oversold';
|
|
266
|
+
const message = getVariedMessage(category, pool, isOverbought ? 'Overbought' : 'Oversold');
|
|
267
|
+
const arrow = isOverbought ? '▼' : '▲'; // Opposite - looking for reversal
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
message: `${arrow} ${message}`,
|
|
271
|
+
details: `Z-Score: ${zscore.toFixed(2)} | Looking for ${isOverbought ? 'SHORT' : 'LONG'}`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get a signal log
|
|
277
|
+
*/
|
|
278
|
+
function getSignalLog(direction, symbol, confidence, strategy) {
|
|
279
|
+
const pool = direction === 'LONG' ? SIGNAL_LONG_MESSAGES : SIGNAL_SHORT_MESSAGES;
|
|
280
|
+
const message = getVariedMessage(`signal_${direction}`, pool, `${direction} signal`);
|
|
281
|
+
const arrow = direction === 'LONG' ? '▲' : '▼';
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
message: `${arrow} ${message}`,
|
|
285
|
+
details: `${symbol} | ${confidence.toFixed(0)}% | ${strategy}`,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get an entry log
|
|
291
|
+
*/
|
|
292
|
+
function getEntryLog(direction, symbol, size, price) {
|
|
293
|
+
const pool = direction === 'LONG' ? ENTRY_LONG_MESSAGES : ENTRY_SHORT_MESSAGES;
|
|
294
|
+
const message = getVariedMessage(`entry_${direction}`, pool, `${direction} entry`);
|
|
295
|
+
const arrow = direction === 'LONG' ? '▲' : '▼';
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
message: `${arrow} ${message}`,
|
|
299
|
+
details: `${size}x ${symbol} @ ${price.toFixed(2)}`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get an exit log
|
|
305
|
+
*/
|
|
306
|
+
function getExitLog(isProfit, symbol, size, price, pnl) {
|
|
307
|
+
const pool = isProfit ? EXIT_PROFIT_MESSAGES : EXIT_LOSS_MESSAGES;
|
|
308
|
+
const category = isProfit ? 'exit_profit' : 'exit_loss';
|
|
309
|
+
const message = getVariedMessage(category, pool, isProfit ? 'Profit taken' : 'Loss taken');
|
|
310
|
+
const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
message,
|
|
314
|
+
details: `${size}x ${symbol} @ ${price.toFixed(2)} | ${pnlStr}`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get a volatility log
|
|
320
|
+
*/
|
|
321
|
+
function getVolatilityLog(isHigh, atr) {
|
|
322
|
+
const pool = isHigh ? HIGH_VOLATILITY_MESSAGES : LOW_VOLATILITY_MESSAGES;
|
|
323
|
+
const category = isHigh ? 'vol_high' : 'vol_low';
|
|
324
|
+
const message = getVariedMessage(category, pool, isHigh ? 'High volatility' : 'Low volatility');
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
message,
|
|
328
|
+
details: atr ? `ATR: ${atr.toFixed(2)}` : undefined,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get a scanning/waiting log
|
|
334
|
+
*/
|
|
335
|
+
function getScanningLog(isScanning) {
|
|
336
|
+
const pool = isScanning ? SCANNING_MESSAGES : WAITING_MESSAGES;
|
|
337
|
+
const category = isScanning ? 'scanning' : 'waiting';
|
|
338
|
+
const message = getVariedMessage(category, pool, isScanning ? 'Scanning...' : 'Waiting...');
|
|
339
|
+
|
|
340
|
+
return { message, details: undefined };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Get position update log with varied messaging
|
|
345
|
+
*/
|
|
346
|
+
function getPositionUpdateLog(side, size, unrealizedPnL, entryPrice, currentPrice, holdTime) {
|
|
347
|
+
const arrow = side === 'LONG' ? '▲' : '▼';
|
|
348
|
+
const pnlStr = unrealizedPnL >= 0 ? `+$${unrealizedPnL.toFixed(2)}` : `-$${Math.abs(unrealizedPnL).toFixed(2)}`;
|
|
349
|
+
const isProfit = unrealizedPnL >= 0;
|
|
350
|
+
|
|
351
|
+
const pool = isProfit ? IN_POSITION_PROFIT_MESSAGES : IN_POSITION_LOSS_MESSAGES;
|
|
352
|
+
const category = isProfit ? 'pos_profit' : 'pos_loss';
|
|
353
|
+
const prefix = getVariedMessage(category, pool, isProfit ? 'In profit' : 'Managing');
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
message: `${arrow} ${side} ${size}x`,
|
|
357
|
+
details: `${prefix} ${pnlStr} @ ${currentPrice.toFixed(2)} | ${holdTime}s`,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Get market state log based on model values
|
|
363
|
+
*/
|
|
364
|
+
function getMarketStateLog(ofi, zscore, momentum, delta) {
|
|
365
|
+
// Determine primary market state
|
|
366
|
+
const absZ = Math.abs(zscore);
|
|
367
|
+
const absOfi = Math.abs(ofi);
|
|
368
|
+
const absMom = Math.abs(momentum);
|
|
369
|
+
|
|
370
|
+
// Priority: Mean reversion > OFI pressure > Momentum
|
|
371
|
+
if (absZ > 1.5) {
|
|
372
|
+
return getMeanReversionLog(zscore);
|
|
373
|
+
} else if (absOfi > 0.3) {
|
|
374
|
+
const direction = ofi > 0 ? 'LONG' : 'SHORT';
|
|
375
|
+
return getMarketBiasLog(direction, delta, ofi);
|
|
376
|
+
} else if (absMom > 2) {
|
|
377
|
+
return getMomentumLog(momentum, zscore);
|
|
378
|
+
} else {
|
|
379
|
+
return getScanningLog(true);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
module.exports = {
|
|
384
|
+
getMarketBiasLog,
|
|
385
|
+
getMomentumLog,
|
|
386
|
+
getMeanReversionLog,
|
|
387
|
+
getSignalLog,
|
|
388
|
+
getEntryLog,
|
|
389
|
+
getExitLog,
|
|
390
|
+
getVolatilityLog,
|
|
391
|
+
getScanningLog,
|
|
392
|
+
getPositionUpdateLog,
|
|
393
|
+
getMarketStateLog,
|
|
394
|
+
};
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -102,11 +102,7 @@ class AlgoUI {
|
|
|
102
102
|
this.config = config;
|
|
103
103
|
this.W = 96; // Fixed width
|
|
104
104
|
this.logs = [];
|
|
105
|
-
//
|
|
106
|
-
// Header (logo + titles): ~12 lines, Stats: ~12 lines, Log header: 2 lines, Bottom: 1 line = 27 fixed
|
|
107
|
-
const terminalHeight = process.stdout.rows || 50;
|
|
108
|
-
const fixedLines = 27;
|
|
109
|
-
this.maxLogs = Math.max(10, terminalHeight - fixedLines); // Min 10 logs
|
|
105
|
+
this.maxLogs = 40; // Fixed at 40 logs for one-account and copy-trading
|
|
110
106
|
this.spinnerFrame = 0;
|
|
111
107
|
this.firstDraw = true;
|
|
112
108
|
this.isDrawing = false;
|
|
@@ -186,83 +182,21 @@ class AlgoUI {
|
|
|
186
182
|
|
|
187
183
|
this._line(chalk.cyan(GM));
|
|
188
184
|
|
|
189
|
-
// Row 2:
|
|
190
|
-
const posQty = stats.position || 0;
|
|
191
|
-
const posStr = posQty === 0 ? 'FLAT' : (posQty > 0 ? `+${posQty} LONG` : `${posQty} SHORT`);
|
|
192
|
-
const posColor = posQty === 0 ? chalk.gray : (posQty > 0 ? chalk.green : chalk.red);
|
|
193
|
-
const r2c1 = buildCell('POSITION', posStr, posColor, colL);
|
|
194
|
-
const r2c2 = buildCell('P&L', pnlStr, pnlColor, colR);
|
|
195
|
-
row(r2c1.padded, r2c2.padded);
|
|
196
|
-
|
|
197
|
-
this._line(chalk.cyan(GM));
|
|
198
|
-
|
|
199
|
-
// Row 3: Open P&L | Closed P&L (R Trader style breakdown)
|
|
200
|
-
// Data from Rithmic PNL_PLANT WebSocket - show '--' if null (no data from API)
|
|
201
|
-
const openPnl = stats.openPnl;
|
|
202
|
-
const closedPnl = stats.closedPnl;
|
|
203
|
-
const openPnlStr = openPnl === null || openPnl === undefined
|
|
204
|
-
? '--'
|
|
205
|
-
: (openPnl >= 0 ? `+$${openPnl.toFixed(2)}` : `-$${Math.abs(openPnl).toFixed(2)}`);
|
|
206
|
-
const closedPnlStr = closedPnl === null || closedPnl === undefined
|
|
207
|
-
? '--'
|
|
208
|
-
: (closedPnl >= 0 ? `+$${closedPnl.toFixed(2)}` : `-$${Math.abs(closedPnl).toFixed(2)}`);
|
|
209
|
-
const openPnlColor = (openPnl || 0) >= 0 ? chalk.green : chalk.red;
|
|
210
|
-
const closedPnlColor = (closedPnl || 0) >= 0 ? chalk.green : chalk.red;
|
|
211
|
-
const r3c1 = buildCell('OPEN P&L', openPnlStr, openPnlColor, colL);
|
|
212
|
-
const r3c2 = buildCell('CLOSED P&L', closedPnlStr, closedPnlColor, colR);
|
|
213
|
-
row(r3c1.padded, r3c2.padded);
|
|
214
|
-
|
|
215
|
-
this._line(chalk.cyan(GM));
|
|
216
|
-
|
|
217
|
-
// Row 4: Balance | Net Liquidation (R Trader style)
|
|
218
|
-
const balanceStr = stats.balance !== null && stats.balance !== undefined
|
|
219
|
-
? '$' + stats.balance.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
220
|
-
: '--';
|
|
221
|
-
const netLiqStr = stats.netLiquidation !== null && stats.netLiquidation !== undefined
|
|
222
|
-
? '$' + stats.netLiquidation.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
223
|
-
: '--';
|
|
224
|
-
const r4c1 = buildCell('BALANCE', balanceStr, chalk.white, colL);
|
|
225
|
-
const r4c2 = buildCell('NET LIQ', netLiqStr, chalk.cyan, colR);
|
|
226
|
-
row(r4c1.padded, r4c2.padded);
|
|
227
|
-
|
|
228
|
-
this._line(chalk.cyan(GM));
|
|
229
|
-
|
|
230
|
-
// Row 5: Buying Power | Margin (R Trader style)
|
|
231
|
-
const bpStr = stats.buyingPower !== null && stats.buyingPower !== undefined
|
|
232
|
-
? '$' + stats.buyingPower.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
233
|
-
: '--';
|
|
234
|
-
const marginStr = stats.margin !== null && stats.margin !== undefined
|
|
235
|
-
? '$' + stats.margin.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
236
|
-
: '--';
|
|
237
|
-
const r5c1 = buildCell('BUYING PWR', bpStr, chalk.green, colL);
|
|
238
|
-
const r5c2 = buildCell('MARGIN', marginStr, chalk.yellow, colR);
|
|
239
|
-
row(r5c1.padded, r5c2.padded);
|
|
240
|
-
|
|
241
|
-
this._line(chalk.cyan(GM));
|
|
242
|
-
|
|
243
|
-
// Row 6: Target | Risk
|
|
185
|
+
// Row 2: Target | Risk
|
|
244
186
|
const targetStr = stats.target !== null && stats.target !== undefined ? '$' + stats.target.toFixed(2) : '--';
|
|
245
187
|
const riskStr = stats.risk !== null && stats.risk !== undefined ? '$' + stats.risk.toFixed(2) : '--';
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
row(
|
|
249
|
-
|
|
250
|
-
this._line(chalk.cyan(GM));
|
|
251
|
-
|
|
252
|
-
// Row 7: Trades W/L | Entry Price (if in position)
|
|
253
|
-
const r7c1t = ` ${chalk.bold('TRADES')}: ${chalk.cyan.bold(stats.trades || 0)} ${chalk.bold('W/L')}: ${chalk.green.bold(stats.wins || 0)}/${chalk.red.bold(stats.losses || 0)}`;
|
|
254
|
-
const r7c1p = ` TRADES: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
|
|
255
|
-
const entryStr = stats.entryPrice > 0 ? stats.entryPrice.toFixed(2) : '--';
|
|
256
|
-
const r7c2 = buildCell('ENTRY', entryStr, chalk.yellow, colR);
|
|
257
|
-
row(r7c1t + pad(colL - r7c1p.length), r7c2.padded);
|
|
188
|
+
const r2c1 = buildCell('TARGET', targetStr, chalk.green, colL);
|
|
189
|
+
const r2c2 = buildCell('RISK', riskStr, chalk.red, colR);
|
|
190
|
+
row(r2c1.padded, r2c2.padded);
|
|
258
191
|
|
|
259
192
|
this._line(chalk.cyan(GM));
|
|
260
193
|
|
|
261
|
-
// Row
|
|
194
|
+
// Row 3: Trades W/L | Connection
|
|
195
|
+
const r3c1t = ` ${chalk.bold('TRADES')}: ${chalk.cyan.bold(stats.trades || 0)} ${chalk.bold('W/L')}: ${chalk.green.bold(stats.wins || 0)}/${chalk.red.bold(stats.losses || 0)}`;
|
|
196
|
+
const r3c1p = ` TRADES: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
|
|
262
197
|
const connection = stats.platform || 'ProjectX';
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
row(r8c1.padded, r8c2.padded);
|
|
198
|
+
const r3c2 = buildCell('CONNECTION', connection, chalk.cyan, colR);
|
|
199
|
+
row(r3c1t + pad(colL - r3c1p.length), r3c2.padded);
|
|
266
200
|
|
|
267
201
|
this._line(chalk.cyan(GB));
|
|
268
202
|
}
|