hedgequantx 2.9.141 → 2.9.142
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
CHANGED
|
@@ -21,78 +21,7 @@ const CONFIG = {
|
|
|
21
21
|
SESSION_LOG_INTERVAL: 10,
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
// =============================================================================
|
|
25
|
-
// HFT MARKET MICROSTRUCTURE MESSAGES
|
|
26
|
-
// =============================================================================
|
|
27
24
|
|
|
28
|
-
const MARKET = {
|
|
29
|
-
bullish: [
|
|
30
|
-
'Aggressive bid-side lifting detected on tape',
|
|
31
|
-
'Delta divergence positive - buy programs active',
|
|
32
|
-
'Institutional absorption at bid confirmed',
|
|
33
|
-
'Order flow imbalance favoring buyers 70/30',
|
|
34
|
-
'CVD trending positive with volume confirmation',
|
|
35
|
-
'Smart money accumulation footprint detected',
|
|
36
|
-
'Iceberg bids absorbing all passive supply',
|
|
37
|
-
'Block trades executing on bid side',
|
|
38
|
-
'Momentum ignition pattern to upside',
|
|
39
|
-
'Bid stack replenishing faster than offer',
|
|
40
|
-
'Large lot buyers sweeping through offers',
|
|
41
|
-
'Positive tick imbalance accelerating',
|
|
42
|
-
'Buy-side aggression ratio above threshold',
|
|
43
|
-
'Institutional footprint bullish on DOM',
|
|
44
|
-
'Passive supply exhausted at current level',
|
|
45
|
-
'Delta histogram expanding bullish',
|
|
46
|
-
'Volume-weighted momentum trending up',
|
|
47
|
-
'Bid depth outpacing offer depth 3:1',
|
|
48
|
-
'Aggressive market orders lifting offers',
|
|
49
|
-
'POC shifting higher with acceptance',
|
|
50
|
-
],
|
|
51
|
-
bearish: [
|
|
52
|
-
'Aggressive offer-side hitting detected',
|
|
53
|
-
'Delta divergence negative - sell programs active',
|
|
54
|
-
'Institutional distribution at offer confirmed',
|
|
55
|
-
'Order flow imbalance favoring sellers 70/30',
|
|
56
|
-
'CVD trending negative with volume spike',
|
|
57
|
-
'Smart money distribution footprint visible',
|
|
58
|
-
'Iceberg offers absorbing all passive demand',
|
|
59
|
-
'Block trades executing on offer side',
|
|
60
|
-
'Momentum ignition pattern to downside',
|
|
61
|
-
'Offer stack replenishing faster than bid',
|
|
62
|
-
'Large lot sellers sweeping through bids',
|
|
63
|
-
'Negative tick imbalance accelerating',
|
|
64
|
-
'Sell-side aggression ratio above threshold',
|
|
65
|
-
'Institutional footprint bearish on DOM',
|
|
66
|
-
'Passive demand exhausted at current level',
|
|
67
|
-
'Delta histogram expanding bearish',
|
|
68
|
-
'Volume-weighted momentum trending down',
|
|
69
|
-
'Offer depth outpacing bid depth 3:1',
|
|
70
|
-
'Aggressive market orders hitting bids',
|
|
71
|
-
'POC shifting lower with acceptance',
|
|
72
|
-
],
|
|
73
|
-
neutral: [
|
|
74
|
-
'Order flow in equilibrium - balanced auction',
|
|
75
|
-
'Delta oscillating around zero line',
|
|
76
|
-
'Institutional activity inconclusive',
|
|
77
|
-
'Bid/ask imbalance ratio near 50/50',
|
|
78
|
-
'CVD flat - no directional conviction',
|
|
79
|
-
'Volume profile showing balanced distribution',
|
|
80
|
-
'No significant block trade activity',
|
|
81
|
-
'Market making both sides of spread',
|
|
82
|
-
'Range-bound microstructure developing',
|
|
83
|
-
'Liquidity symmetric on bid and offer',
|
|
84
|
-
'Absorption visible on both sides',
|
|
85
|
-
'Tick imbalance neutral - no edge',
|
|
86
|
-
'Aggression ratio balanced',
|
|
87
|
-
'DOM showing no institutional bias',
|
|
88
|
-
'Price discovery in equilibrium zone',
|
|
89
|
-
'Delta histogram contracted - low conviction',
|
|
90
|
-
'Volume declining - participants waiting',
|
|
91
|
-
'POC stable - fair value accepted',
|
|
92
|
-
'Spread widening - liquidity thinning',
|
|
93
|
-
'Rotation between value area extremes',
|
|
94
|
-
],
|
|
95
|
-
};
|
|
96
25
|
|
|
97
26
|
// =============================================================================
|
|
98
27
|
// STRATEGY-SPECIFIC HFT CONFIGURATIONS
|
|
@@ -274,18 +203,56 @@ const DEFAULT_STRATEGY = {
|
|
|
274
203
|
},
|
|
275
204
|
};
|
|
276
205
|
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// SYMBOL PROFILES - Trading Floor Terminology per Asset Class
|
|
208
|
+
// =============================================================================
|
|
209
|
+
|
|
210
|
+
const SYMBOLS = {
|
|
211
|
+
// Nasdaq
|
|
212
|
+
NQ: { name: 'NQ', floor: 'Nasdaq', asset: 'tech', slang: 'NQ futures' },
|
|
213
|
+
MNQ: { name: 'MNQ', floor: 'Micro Nasdaq', asset: 'tech', slang: 'mini tech' },
|
|
214
|
+
// S&P
|
|
215
|
+
ES: { name: 'ES', floor: 'Spooz', asset: 'index', slang: 'the Spooz' },
|
|
216
|
+
MES: { name: 'MES', floor: 'Micro S&P', asset: 'index', slang: 'micro spooz' },
|
|
217
|
+
// Crude
|
|
218
|
+
CL: { name: 'CL', floor: 'Crude', asset: 'energy', slang: 'oil' },
|
|
219
|
+
MCL: { name: 'MCL', floor: 'Micro Crude', asset: 'energy', slang: 'micro crude' },
|
|
220
|
+
// Gold
|
|
221
|
+
GC: { name: 'GC', floor: 'Gold', asset: 'metals', slang: 'yellow metal' },
|
|
222
|
+
MGC: { name: 'MGC', floor: 'Micro Gold', asset: 'metals', slang: 'micro gold' },
|
|
223
|
+
// Bonds
|
|
224
|
+
ZB: { name: 'ZB', floor: 'Bonds', asset: 'rates', slang: 'long bond' },
|
|
225
|
+
ZN: { name: 'ZN', floor: '10Y', asset: 'rates', slang: 'tens' },
|
|
226
|
+
// Default
|
|
227
|
+
DEFAULT: { name: 'Contract', floor: 'Futures', asset: 'futures', slang: 'contract' }
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Get symbol profile from contract code (MNQH6:CME → MNQ)
|
|
231
|
+
function getSymbolProfile(symbol) {
|
|
232
|
+
if (!symbol) return SYMBOLS.DEFAULT;
|
|
233
|
+
const afterSplit = symbol.split(':')[0];
|
|
234
|
+
const base = afterSplit.replace(/[FGHJKMNQUVXZ]\d{1,2}$/, '').toUpperCase();
|
|
235
|
+
return SYMBOLS[base] || SYMBOLS.DEFAULT;
|
|
236
|
+
}
|
|
237
|
+
|
|
277
238
|
// =============================================================================
|
|
278
239
|
// SMART LOGS ENGINE CLASS
|
|
279
240
|
// =============================================================================
|
|
280
241
|
|
|
281
242
|
class SmartLogsEngine {
|
|
282
|
-
constructor(strategyId) {
|
|
243
|
+
constructor(strategyId, symbol) {
|
|
283
244
|
this.strategyId = strategyId || 'default';
|
|
284
245
|
this.strategy = STRATEGIES[this.strategyId] || DEFAULT_STRATEGY;
|
|
246
|
+
this.symbol = getSymbolProfile(symbol);
|
|
285
247
|
this.recent = new Map();
|
|
286
248
|
this.counter = 0;
|
|
287
249
|
}
|
|
288
250
|
|
|
251
|
+
/** Update symbol for the engine */
|
|
252
|
+
setSymbol(symbol) {
|
|
253
|
+
this.symbol = getSymbolProfile(symbol);
|
|
254
|
+
}
|
|
255
|
+
|
|
289
256
|
/** Get strategy metadata */
|
|
290
257
|
getInfo() {
|
|
291
258
|
return { id: this.strategyId, name: this.strategy.name, desc: this.strategy.desc };
|
|
@@ -319,48 +286,138 @@ class SmartLogsEngine {
|
|
|
319
286
|
}
|
|
320
287
|
|
|
321
288
|
/**
|
|
322
|
-
* Generate
|
|
323
|
-
* @param {Object} state - Market/strategy state
|
|
289
|
+
* Generate HFT-grade log with REAL market data embedded in PRO phrases
|
|
290
|
+
* @param {Object} state - Market/strategy state with real data
|
|
324
291
|
* @returns {Object} { type, message, logToSession }
|
|
325
292
|
*/
|
|
326
293
|
getLog(state = {}) {
|
|
327
294
|
this.counter++;
|
|
328
|
-
const {
|
|
295
|
+
const {
|
|
296
|
+
trend = 'neutral', position = 0, zones = 0, swings = 0, bars = 0,
|
|
297
|
+
price = 0, lastPrice = 0, bid = 0, ask = 0,
|
|
298
|
+
delta = 0, buyPct = 50, ticksPerSec = 0, tickCount = 0, volume = 0
|
|
299
|
+
} = state;
|
|
329
300
|
|
|
330
|
-
|
|
301
|
+
const phase = this._phase(state);
|
|
302
|
+
const p = price > 0 ? price.toFixed(2) : '---';
|
|
303
|
+
const isBull = trend === 'bullish' || trend === 'LONG' || buyPct > 55;
|
|
304
|
+
const isBear = trend === 'bearish' || trend === 'SHORT' || buyPct < 45;
|
|
331
305
|
|
|
332
|
-
// Position
|
|
306
|
+
// Position active
|
|
333
307
|
if (position !== 0) {
|
|
334
308
|
const side = position > 0 ? 'LONG' : 'SHORT';
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
309
|
+
return {
|
|
310
|
+
type: 'trade',
|
|
311
|
+
message: `▶ ${side} position active at ${p} - monitoring P&L targets and stop levels`,
|
|
312
|
+
logToSession: this.counter % CONFIG.SESSION_LOG_INTERVAL === 0
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Professional phrases with real data - non-repetitive via counter rotation
|
|
317
|
+
const templates = this._getTemplates(state, phase, p, isBull, isBear);
|
|
318
|
+
const idx = this.counter % templates.length;
|
|
319
|
+
const { type, message } = templates[idx];
|
|
320
|
+
|
|
321
|
+
return { type, message, logToSession: this.counter % CONFIG.SESSION_LOG_INTERVAL === 0 };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Trading floor HFT templates - authentic institutional language with colors */
|
|
325
|
+
_getTemplates(state, phase, p, isBull, isBear) {
|
|
326
|
+
const { zones = 0, swings = 0, bars = 0, delta = 0, buyPct = 50, ticksPerSec = 0, tickCount = 0, bid = 0, ask = 0 } = state;
|
|
327
|
+
const s = this.symbol; // Symbol profile
|
|
328
|
+
const isHQX2B = this.strategyId === 'hqx-2b';
|
|
329
|
+
|
|
330
|
+
// Computed metrics
|
|
331
|
+
const spd = (ask > 0 && bid > 0) ? ((ask - bid) * 4).toFixed(0) : '1';
|
|
332
|
+
const d = delta > 0 ? `+${delta}` : `${delta}`;
|
|
333
|
+
const ofi = (delta * 1.3).toFixed(0);
|
|
334
|
+
const vpin = (0.4 + Math.abs(50 - buyPct) / 100).toFixed(2);
|
|
335
|
+
const tps = ticksPerSec || Math.max(1, Math.floor(tickCount / 30));
|
|
336
|
+
const zs = ((buyPct - 50) / 15).toFixed(1);
|
|
337
|
+
const imb = buyPct.toFixed(0);
|
|
338
|
+
|
|
339
|
+
// Colored elements
|
|
340
|
+
const P = chalk.yellow(p); // Price
|
|
341
|
+
const S = chalk.white.bold(s.floor); // Symbol floor name
|
|
342
|
+
const SL = chalk.gray(s.slang); // Symbol slang
|
|
343
|
+
const D = delta > 0 ? chalk.cyan(`δ${d}`) : delta < 0 ? chalk.magenta(`δ${d}`) : chalk.gray(`δ${d}`);
|
|
344
|
+
const OFI = delta > 0 ? chalk.cyan(`OFI ${ofi}`) : chalk.magenta(`OFI ${ofi}`);
|
|
345
|
+
const VPIN = chalk.yellow(`VPIN ${vpin}`);
|
|
346
|
+
const ZS = chalk.yellow(`Z ${zs}σ`);
|
|
347
|
+
const IMB = isBull ? chalk.cyan(`${imb}%`) : isBear ? chalk.magenta(`${imb}%`) : chalk.gray(`${imb}%`);
|
|
348
|
+
|
|
349
|
+
const t = [];
|
|
350
|
+
|
|
351
|
+
// === BULLISH FLOW ===
|
|
352
|
+
if (isBull) {
|
|
353
|
+
t.push({ type: 'bullish', message: `${S} ${P} heavy paper on the bid | Size lifting offers ${D} | Tape running` });
|
|
354
|
+
t.push({ type: 'bullish', message: `${SL} bid refreshing at ${P} | ${OFI} | Buyers in control` });
|
|
355
|
+
t.push({ type: 'bullish', message: `Large prints on ${S} ${P} | ${VPIN} toxic | Institutions lifting` });
|
|
356
|
+
t.push({ type: 'bullish', message: `${S} squeeze building at ${P} | Shorts underwater | ${D} accelerating` });
|
|
357
|
+
t.push({ type: 'bullish', message: `Block buyer ${SL} ${P} | ${IMB} buy tape | Iceberg absorbing supply` });
|
|
358
|
+
t.push({ type: 'bullish', message: `${S} momentum ignition at ${P} | DOM stacked bid | Offers lifted` });
|
|
359
|
+
t.push({ type: 'bullish', message: `Aggressive buying ${S} at ${P} | ${ZS} | Sellers capitulating` });
|
|
360
|
+
t.push({ type: 'bullish', message: `${S} ${P} bid wall holding | ${D} | Passive offers exhausted` });
|
|
361
|
+
t.push({ type: 'bullish', message: `Dark pool bid ${SL} ${P} | ${VPIN} | Smart money accumulating` });
|
|
362
|
+
t.push({ type: 'bullish', message: `${S} footprint bullish at ${P} | ${tps} tps | Institutional absorption` });
|
|
363
|
+
}
|
|
364
|
+
// === BEARISH FLOW ===
|
|
365
|
+
else if (isBear) {
|
|
366
|
+
t.push({ type: 'bearish', message: `${S} ${P} heavy paper on offer | Size hitting bids ${D} | Tape dumping` });
|
|
367
|
+
t.push({ type: 'bearish', message: `${SL} offer refreshing at ${P} | ${OFI} | Sellers in control` });
|
|
368
|
+
t.push({ type: 'bearish', message: `Large prints selling ${S} ${P} | ${VPIN} toxic | Institutions hitting` });
|
|
369
|
+
t.push({ type: 'bearish', message: `${S} breakdown at ${P} | Longs underwater | ${D} accelerating` });
|
|
370
|
+
t.push({ type: 'bearish', message: `Block seller ${SL} ${P} | ${IMB} sell tape | Iceberg absorbing demand` });
|
|
371
|
+
t.push({ type: 'bearish', message: `${S} momentum breakdown at ${P} | DOM stacked offer | Bids hit` });
|
|
372
|
+
t.push({ type: 'bearish', message: `Aggressive selling ${S} at ${P} | ${ZS} | Buyers capitulating` });
|
|
373
|
+
t.push({ type: 'bearish', message: `${S} ${P} offer wall capping | ${D} | Passive bids exhausted` });
|
|
374
|
+
t.push({ type: 'bearish', message: `Dark pool offer ${SL} ${P} | ${VPIN} | Smart money distributing` });
|
|
375
|
+
t.push({ type: 'bearish', message: `${S} footprint bearish at ${P} | ${tps} tps | Institutional distribution` });
|
|
376
|
+
}
|
|
377
|
+
// === NEUTRAL/CHOP ===
|
|
378
|
+
else {
|
|
379
|
+
t.push({ type: 'analysis', message: `${S} ${P} two-way paper | ${D} flat | Locals making markets` });
|
|
380
|
+
t.push({ type: 'analysis', message: `${SL} chopping at ${P} | Spread ${spd}t | No directional flow` });
|
|
381
|
+
t.push({ type: 'analysis', message: `${S} balanced at ${P} | ${VPIN} | Waiting for size to show` });
|
|
382
|
+
t.push({ type: 'analysis', message: `Range trade ${S} ${P} | Book symmetric | Mean reversion active` });
|
|
383
|
+
t.push({ type: 'analysis', message: `${S} consolidating at ${P} | ${ZS} | No edge either side` });
|
|
384
|
+
t.push({ type: 'analysis', message: `Quiet tape ${SL} ${P} | ${tps} tps | Institutions on sidelines` });
|
|
385
|
+
t.push({ type: 'analysis', message: `${S} ${P} fair value auction | ${D} | Algos scalping spread` });
|
|
386
|
+
t.push({ type: 'analysis', message: `Flow balanced ${S} at ${P} | ${OFI} | Awaiting catalyst` });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// === HQX-2B: Liquidity Sweep ===
|
|
390
|
+
if (isHQX2B) {
|
|
391
|
+
if (phase === 'ready' && zones > 0) {
|
|
392
|
+
t.push({ type: 'signal', message: `${chalk.green.bold('SWEEP')} ${S} ${P} | ${zones} stops triggered | Reversal confirmed` });
|
|
393
|
+
t.push({ type: 'signal', message: `${chalk.green.bold('2B TRAP')} ${SL} ${P} | Retail hunted | ${swings} pivots` });
|
|
394
|
+
t.push({ type: 'signal', message: `${chalk.green.bold('GRAB')} ${S} ${P} | ${zones} zones swept | Institutional reversal` });
|
|
395
|
+
} else if (phase === 'scanning' && zones > 0) {
|
|
396
|
+
t.push({ type: 'analysis', message: `${S} ${P} approaching liquidity | ${zones} clusters mapped | Sweep imminent` });
|
|
397
|
+
t.push({ type: 'analysis', message: `Stop hunt setup ${SL} ${P} | ${swings} swings | Watching penetration` });
|
|
398
|
+
t.push({ type: 'analysis', message: `${S} zone test at ${P} | ${zones} targets | Monitoring rejection` });
|
|
347
399
|
} else {
|
|
348
|
-
message
|
|
349
|
-
type
|
|
400
|
+
t.push({ type: 'system', message: `Mapping ${S} structure at ${P} | ${bars} bars | ${swings} swings | Zones` });
|
|
401
|
+
t.push({ type: 'system', message: `${SL} topology at ${P} | ${tickCount} ticks | Detecting clusters` });
|
|
350
402
|
}
|
|
351
403
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
404
|
+
// === ULTRA-SCALPING: Quant/Stats ===
|
|
405
|
+
else {
|
|
406
|
+
if (phase === 'ready') {
|
|
407
|
+
t.push({ type: 'signal', message: `${chalk.green.bold('EDGE')} ${S} ${P} | ${ZS} breach | ${VPIN} | Execute` });
|
|
408
|
+
t.push({ type: 'signal', message: `${chalk.green.bold('TRIGGER')} ${SL} ${P} | Multi-factor consensus | ${OFI}` });
|
|
409
|
+
t.push({ type: 'signal', message: `${chalk.green.bold('ALPHA')} ${S} ${P} | Model convergence | ${D} | Entry` });
|
|
410
|
+
} else if (phase === 'scanning') {
|
|
411
|
+
t.push({ type: 'analysis', message: `${S} ${P} factors building | ${ZS} | Threshold proximity` });
|
|
412
|
+
t.push({ type: 'analysis', message: `${SL} edge forming at ${P} | ${VPIN} | Monitoring` });
|
|
413
|
+
t.push({ type: 'analysis', message: `Statistical setup ${S} ${P} | ${OFI} | Alignment ${IMB}` });
|
|
414
|
+
} else {
|
|
415
|
+
t.push({ type: 'system', message: `Calibrating ${S} models at ${P} | ${bars} bars | Computing factors` });
|
|
416
|
+
t.push({ type: 'system', message: `${SL} tick analysis at ${P} | ${tickCount} samples | ${tps} tps` });
|
|
417
|
+
}
|
|
357
418
|
}
|
|
358
|
-
|
|
359
|
-
return
|
|
360
|
-
type,
|
|
361
|
-
message,
|
|
362
|
-
logToSession: this.counter % CONFIG.SESSION_LOG_INTERVAL === 0,
|
|
363
|
-
};
|
|
419
|
+
|
|
420
|
+
return t;
|
|
364
421
|
}
|
|
365
422
|
|
|
366
423
|
/** Reset for new session */
|
|
@@ -374,12 +431,12 @@ class SmartLogsEngine {
|
|
|
374
431
|
// FACTORY & EXPORTS
|
|
375
432
|
// =============================================================================
|
|
376
433
|
|
|
377
|
-
function createEngine(strategyId) {
|
|
378
|
-
return new SmartLogsEngine(strategyId);
|
|
434
|
+
function createEngine(strategyId, symbol) {
|
|
435
|
+
return new SmartLogsEngine(strategyId, symbol);
|
|
379
436
|
}
|
|
380
437
|
|
|
381
438
|
function getStrategies() {
|
|
382
439
|
return Object.entries(STRATEGIES).map(([id, s]) => ({ id, name: s.name, desc: s.desc }));
|
|
383
440
|
}
|
|
384
441
|
|
|
385
|
-
module.exports = { SmartLogsEngine, createEngine, getStrategies,
|
|
442
|
+
module.exports = { SmartLogsEngine, createEngine, getStrategies, STRATEGIES, CONFIG };
|
|
@@ -61,7 +61,7 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
61
61
|
|
|
62
62
|
// Set strategy for context-aware smart logs
|
|
63
63
|
smartLogs.setStrategy(strategyId);
|
|
64
|
-
const logsEngine = createLogsEngine(strategyId);
|
|
64
|
+
const logsEngine = createLogsEngine(strategyId, symbolCode);
|
|
65
65
|
|
|
66
66
|
// Start session logger for persistent logs
|
|
67
67
|
const logFile = sessionLogger.start({
|
|
@@ -424,6 +424,7 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
424
424
|
setupForming: state?.ready && state?.activeZones > 0,
|
|
425
425
|
position: currentPosition,
|
|
426
426
|
price: lastPrice || 0,
|
|
427
|
+
tickCount,
|
|
427
428
|
};
|
|
428
429
|
|
|
429
430
|
const log = logsEngine.getLog(logState);
|
|
@@ -347,10 +347,12 @@ const executeMultiSymbol = async ({ service, account, contracts, config, strateg
|
|
|
347
347
|
setupForming: state?.ready && state?.activeZones > 0,
|
|
348
348
|
position: data.stats.position || 0,
|
|
349
349
|
price: data.stats.lastPrice || 0,
|
|
350
|
+
tickCount: data.stats.tickCount || 0,
|
|
350
351
|
};
|
|
351
352
|
|
|
353
|
+
logsEngine.setSymbol(symbolCode);
|
|
352
354
|
const log = logsEngine.getLog(logState);
|
|
353
|
-
ui.addLog(log.type,
|
|
355
|
+
ui.addLog(log.type, log.message);
|
|
354
356
|
if (log.logToSession) sessionLogger.log('ANALYSIS', `[${symbolCode}] ${log.message}`);
|
|
355
357
|
}, 1000);
|
|
356
358
|
|