prab-cli 1.2.4 → 1.2.6

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.
@@ -0,0 +1,955 @@
1
+ "use strict";
2
+ /**
3
+ * ICT (Inner Circle Trader) Strategy Module
4
+ * Complete implementation of ICT trading concepts
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.generateICTSignal = generateICTSignal;
11
+ exports.displayICTSignal = displayICTSignal;
12
+ exports.runICTStrategy = runICTStrategy;
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const ora_1 = __importDefault(require("ora"));
15
+ const data_fetcher_1 = require("./data-fetcher");
16
+ const smc_indicators_1 = require("./smc-indicators");
17
+ const indicators_1 = require("./indicators");
18
+ // ============================================
19
+ // KILLZONE DETECTION
20
+ // ============================================
21
+ const KILLZONES = [
22
+ {
23
+ name: "Asian Session",
24
+ type: "asian",
25
+ startHourUTC: 0, // 00:00 UTC (8 PM EST)
26
+ endHourUTC: 6, // 06:00 UTC
27
+ description: "Range forming session - identify highs/lows",
28
+ },
29
+ {
30
+ name: "London Open",
31
+ type: "london",
32
+ startHourUTC: 7, // 07:00 UTC (2 AM EST)
33
+ endHourUTC: 10, // 10:00 UTC (5 AM EST)
34
+ description: "High volatility - trend continuation or reversal",
35
+ },
36
+ {
37
+ name: "New York AM",
38
+ type: "new_york_am",
39
+ startHourUTC: 13, // 13:00 UTC (8 AM EST)
40
+ endHourUTC: 16, // 16:00 UTC (11 AM EST)
41
+ description: "Highest volatility - major moves",
42
+ },
43
+ {
44
+ name: "New York PM",
45
+ type: "new_york_pm",
46
+ startHourUTC: 18, // 18:00 UTC (1 PM EST)
47
+ endHourUTC: 21, // 21:00 UTC (4 PM EST)
48
+ description: "Profit taking and reversals",
49
+ },
50
+ {
51
+ name: "Silver Bullet AM",
52
+ type: "silver_bullet_am",
53
+ startHourUTC: 14, // 14:00 UTC (10 AM EST)
54
+ endHourUTC: 15, // 15:00 UTC (11 AM EST)
55
+ description: "ICT Silver Bullet - high probability entry window",
56
+ },
57
+ {
58
+ name: "Silver Bullet PM",
59
+ type: "silver_bullet_pm",
60
+ startHourUTC: 19, // 19:00 UTC (2 PM EST)
61
+ endHourUTC: 20, // 20:00 UTC (3 PM EST)
62
+ description: "ICT Silver Bullet - afternoon entry window",
63
+ },
64
+ ];
65
+ function getCurrentKillzone() {
66
+ const now = new Date();
67
+ const hourUTC = now.getUTCHours();
68
+ for (const kz of KILLZONES) {
69
+ if (hourUTC >= kz.startHourUTC && hourUTC < kz.endHourUTC) {
70
+ return {
71
+ ...kz,
72
+ active: true,
73
+ bias: "neutral",
74
+ };
75
+ }
76
+ }
77
+ return {
78
+ name: "No Active Killzone",
79
+ type: "none",
80
+ active: false,
81
+ startHourUTC: 0,
82
+ endHourUTC: 0,
83
+ bias: "neutral",
84
+ description: "Outside of main trading sessions",
85
+ };
86
+ }
87
+ // ============================================
88
+ // OTE (OPTIMAL TRADE ENTRY)
89
+ // ============================================
90
+ function calculateOTE(candles, swingPoints, currentPrice) {
91
+ const recentHighs = swingPoints.filter((p) => p.type === "high").slice(-5);
92
+ const recentLows = swingPoints.filter((p) => p.type === "low").slice(-5);
93
+ if (recentHighs.length < 2 || recentLows.length < 2) {
94
+ return {
95
+ isValid: false,
96
+ fibLevel: 0,
97
+ zone: { low: currentPrice, high: currentPrice },
98
+ swingHigh: currentPrice,
99
+ swingLow: currentPrice,
100
+ direction: "bullish",
101
+ quality: "suboptimal",
102
+ };
103
+ }
104
+ // Get most recent swing high and low
105
+ const swingHigh = Math.max(...recentHighs.map((h) => h.price));
106
+ const swingLow = Math.min(...recentLows.map((l) => l.price));
107
+ const range = swingHigh - swingLow;
108
+ // Calculate current position as Fib level
109
+ const fibLevel = (currentPrice - swingLow) / range;
110
+ // Determine direction based on recent price action
111
+ const lastCandles = candles.slice(-10);
112
+ const avgClose = lastCandles.reduce((sum, c) => sum + c.close, 0) / lastCandles.length;
113
+ const direction = currentPrice > avgClose ? "bullish" : "bearish";
114
+ // OTE Zone is 62% - 79% retracement
115
+ let oteZone;
116
+ if (direction === "bullish") {
117
+ // For bullish OTE, we want price to retrace into discount
118
+ oteZone = {
119
+ low: swingLow + range * 0.62,
120
+ high: swingLow + range * 0.79,
121
+ };
122
+ }
123
+ else {
124
+ // For bearish OTE, we want price to retrace into premium
125
+ oteZone = {
126
+ low: swingHigh - range * 0.79,
127
+ high: swingHigh - range * 0.62,
128
+ };
129
+ }
130
+ // Determine quality based on fib level
131
+ let quality;
132
+ if (direction === "bullish") {
133
+ if (fibLevel >= 0.62 && fibLevel <= 0.79)
134
+ quality = "premium";
135
+ else if (fibLevel >= 0.5 && fibLevel <= 0.85)
136
+ quality = "standard";
137
+ else
138
+ quality = "suboptimal";
139
+ }
140
+ else {
141
+ if (fibLevel >= 0.21 && fibLevel <= 0.38)
142
+ quality = "premium";
143
+ else if (fibLevel >= 0.15 && fibLevel <= 0.5)
144
+ quality = "standard";
145
+ else
146
+ quality = "suboptimal";
147
+ }
148
+ const isValid = quality !== "suboptimal";
149
+ return {
150
+ isValid,
151
+ fibLevel,
152
+ zone: oteZone,
153
+ swingHigh,
154
+ swingLow,
155
+ direction,
156
+ quality,
157
+ };
158
+ }
159
+ // ============================================
160
+ // BREAKER BLOCKS
161
+ // ============================================
162
+ function findBreakerBlocks(candles, orderBlocks) {
163
+ const breakerBlocks = [];
164
+ const currentPrice = candles[candles.length - 1].close;
165
+ for (const ob of orderBlocks) {
166
+ // Check if this order block was broken (mitigated) and then price returned
167
+ if (ob.mitigated) {
168
+ // Find where it was broken
169
+ let breakIndex = -1;
170
+ let breakPrice = 0;
171
+ for (let i = ob.index + 1; i < candles.length; i++) {
172
+ if (ob.type === "bullish" && candles[i].close < ob.bottom) {
173
+ breakIndex = i;
174
+ breakPrice = candles[i].close;
175
+ break;
176
+ }
177
+ if (ob.type === "bearish" && candles[i].close > ob.top) {
178
+ breakIndex = i;
179
+ breakPrice = candles[i].close;
180
+ break;
181
+ }
182
+ }
183
+ if (breakIndex > 0) {
184
+ // A broken bullish OB becomes a bearish breaker
185
+ // A broken bearish OB becomes a bullish breaker
186
+ const breakerType = ob.type === "bullish" ? "bearish" : "bullish";
187
+ // Check if price has returned to this level
188
+ const priceNearBreaker = (breakerType === "bullish" &&
189
+ currentPrice >= ob.bottom * 0.99 &&
190
+ currentPrice <= ob.top * 1.01) ||
191
+ (breakerType === "bearish" &&
192
+ currentPrice >= ob.bottom * 0.99 &&
193
+ currentPrice <= ob.top * 1.01);
194
+ if (priceNearBreaker || breakerBlocks.length < 5) {
195
+ breakerBlocks.push({
196
+ type: breakerType,
197
+ top: ob.top,
198
+ bottom: ob.bottom,
199
+ originalOB: ob,
200
+ breakPrice,
201
+ strength: ob.strength,
202
+ index: ob.index,
203
+ });
204
+ }
205
+ }
206
+ }
207
+ }
208
+ return breakerBlocks.slice(0, 5);
209
+ }
210
+ // ============================================
211
+ // INDUCEMENT DETECTION
212
+ // ============================================
213
+ function findInducements(candles, swingPoints) {
214
+ const inducements = [];
215
+ const currentPrice = candles[candles.length - 1].close;
216
+ // Find equal highs (buy-side liquidity)
217
+ const highs = swingPoints.filter((p) => p.type === "high");
218
+ for (let i = 1; i < highs.length; i++) {
219
+ const diff = Math.abs(highs[i].price - highs[i - 1].price) / highs[i].price;
220
+ if (diff < 0.003) {
221
+ // Within 0.3% = equal highs
222
+ const level = (highs[i].price + highs[i - 1].price) / 2;
223
+ // Check if swept
224
+ let swept = false;
225
+ let sweepCandle = null;
226
+ for (let j = highs[i].index + 1; j < candles.length; j++) {
227
+ if (candles[j].high > level * 1.001) {
228
+ swept = true;
229
+ sweepCandle = j;
230
+ break;
231
+ }
232
+ }
233
+ inducements.push({
234
+ type: "buy_side",
235
+ level,
236
+ swept,
237
+ sweepCandle,
238
+ trapType: "equal_highs",
239
+ });
240
+ }
241
+ }
242
+ // Find equal lows (sell-side liquidity)
243
+ const lows = swingPoints.filter((p) => p.type === "low");
244
+ for (let i = 1; i < lows.length; i++) {
245
+ const diff = Math.abs(lows[i].price - lows[i - 1].price) / lows[i].price;
246
+ if (diff < 0.003) {
247
+ const level = (lows[i].price + lows[i - 1].price) / 2;
248
+ let swept = false;
249
+ let sweepCandle = null;
250
+ for (let j = lows[i].index + 1; j < candles.length; j++) {
251
+ if (candles[j].low < level * 0.999) {
252
+ swept = true;
253
+ sweepCandle = j;
254
+ break;
255
+ }
256
+ }
257
+ inducements.push({
258
+ type: "sell_side",
259
+ level,
260
+ swept,
261
+ sweepCandle,
262
+ trapType: "equal_lows",
263
+ });
264
+ }
265
+ }
266
+ return inducements.slice(0, 10);
267
+ }
268
+ // ============================================
269
+ // DISPLACEMENT DETECTION
270
+ // ============================================
271
+ function detectDisplacement(candles, atrValue) {
272
+ const recentCandles = candles.slice(-10);
273
+ for (let i = 0; i < recentCandles.length - 1; i++) {
274
+ const candle = recentCandles[i];
275
+ const body = Math.abs(candle.close - candle.open);
276
+ // Displacement = candle body > 2x ATR
277
+ if (body > atrValue * 2) {
278
+ const direction = candle.close > candle.open ? "bullish" : "bearish";
279
+ // Check if FVG was created
280
+ let fvgCreated = false;
281
+ if (i > 0 && i < recentCandles.length - 1) {
282
+ const prev = recentCandles[i - 1];
283
+ const next = recentCandles[i + 1];
284
+ if (direction === "bullish" && next.low > prev.high)
285
+ fvgCreated = true;
286
+ if (direction === "bearish" && next.high < prev.low)
287
+ fvgCreated = true;
288
+ }
289
+ return {
290
+ detected: true,
291
+ direction,
292
+ strength: body / atrValue,
293
+ startIndex: candles.length - 10 + i,
294
+ fvgCreated,
295
+ };
296
+ }
297
+ }
298
+ return {
299
+ detected: false,
300
+ direction: "bullish",
301
+ strength: 0,
302
+ startIndex: 0,
303
+ fvgCreated: false,
304
+ };
305
+ }
306
+ // ============================================
307
+ // POWER OF 3 (AMD MODEL)
308
+ // ============================================
309
+ function analyzePowerOf3(candles) {
310
+ // Need at least 24 hourly candles for daily analysis
311
+ if (candles.length < 24) {
312
+ return {
313
+ phase: "unknown",
314
+ asianRangeHigh: 0,
315
+ asianRangeLow: 0,
316
+ manipulation: "none",
317
+ expectedMove: "unclear",
318
+ };
319
+ }
320
+ const now = new Date();
321
+ const hourUTC = now.getUTCHours();
322
+ // Get approximate Asian session candles (last 6-12 candles depending on timeframe)
323
+ const asianCandles = candles.slice(-12, -6);
324
+ if (asianCandles.length === 0) {
325
+ return {
326
+ phase: "unknown",
327
+ asianRangeHigh: 0,
328
+ asianRangeLow: 0,
329
+ manipulation: "none",
330
+ expectedMove: "unclear",
331
+ };
332
+ }
333
+ const asianRangeHigh = Math.max(...asianCandles.map((c) => c.high));
334
+ const asianRangeLow = Math.min(...asianCandles.map((c) => c.low));
335
+ // Get recent candles (London/NY session)
336
+ const recentCandles = candles.slice(-6);
337
+ const currentPrice = candles[candles.length - 1].close;
338
+ const recentHigh = Math.max(...recentCandles.map((c) => c.high));
339
+ const recentLow = Math.min(...recentCandles.map((c) => c.low));
340
+ // Determine manipulation
341
+ let manipulation = "none";
342
+ if (recentHigh > asianRangeHigh * 1.001)
343
+ manipulation = "above";
344
+ else if (recentLow < asianRangeLow * 0.999)
345
+ manipulation = "below";
346
+ // Determine phase
347
+ let phase;
348
+ if (hourUTC >= 0 && hourUTC < 7) {
349
+ phase = "accumulation";
350
+ }
351
+ else if (hourUTC >= 7 && hourUTC < 14) {
352
+ phase = "manipulation";
353
+ }
354
+ else {
355
+ phase = "distribution";
356
+ }
357
+ // Expected move based on manipulation
358
+ let expectedMove = "unclear";
359
+ if (manipulation === "above") {
360
+ // Swept buy-side liquidity, expect bearish move
361
+ expectedMove = "bearish";
362
+ }
363
+ else if (manipulation === "below") {
364
+ // Swept sell-side liquidity, expect bullish move
365
+ expectedMove = "bullish";
366
+ }
367
+ return {
368
+ phase,
369
+ asianRangeHigh,
370
+ asianRangeLow,
371
+ manipulation,
372
+ expectedMove,
373
+ };
374
+ }
375
+ // ============================================
376
+ // MAIN ICT ANALYSIS
377
+ // ============================================
378
+ async function generateICTSignal(symbol, interval = "1h") {
379
+ const spinner = (0, ora_1.default)(`Analyzing ${symbol} with ICT methodology...`).start();
380
+ try {
381
+ // Fetch data
382
+ spinner.text = "Fetching market data...";
383
+ const data = await (0, data_fetcher_1.fetchCryptoData)(symbol, interval, 200);
384
+ const candles = data.candles;
385
+ const currentPrice = data.currentPrice;
386
+ // Get current killzone
387
+ spinner.text = "Checking killzones...";
388
+ const killzone = getCurrentKillzone();
389
+ // Find swing points
390
+ spinner.text = "Analyzing market structure...";
391
+ const swingPoints = (0, smc_indicators_1.findSwingPoints)(candles, 3);
392
+ // Detect structure breaks
393
+ const structureBreaks = (0, smc_indicators_1.detectStructureBreaks)(candles, swingPoints);
394
+ const lastBOS = structureBreaks.filter((b) => b.type === "BOS").pop() || null;
395
+ const lastCHoCH = structureBreaks.filter((b) => b.type === "CHoCH").pop() || null;
396
+ // Determine trend
397
+ let trend = "ranging";
398
+ if (lastBOS)
399
+ trend = lastBOS.direction;
400
+ if (lastCHoCH && (!lastBOS || lastCHoCH.index > lastBOS.index)) {
401
+ trend = lastCHoCH.direction;
402
+ }
403
+ // Check for MSS (Market Structure Shift)
404
+ const mss = lastCHoCH !== null && lastCHoCH.index > candles.length - 20;
405
+ // Calculate indicators
406
+ spinner.text = "Calculating ICT concepts...";
407
+ const closes = candles.map((c) => c.close);
408
+ const rsi = (0, indicators_1.calculateRSI)(closes, 14);
409
+ const atr = (0, indicators_1.calculateATR)(candles, 14);
410
+ // Find order blocks and FVGs
411
+ const orderBlocks = (0, smc_indicators_1.findOrderBlocks)(candles, 100);
412
+ const fvgs = (0, smc_indicators_1.findFairValueGaps)(candles, 50);
413
+ // Calculate OTE
414
+ const ote = calculateOTE(candles, swingPoints, currentPrice);
415
+ // Find breaker blocks
416
+ const breakerBlocks = findBreakerBlocks(candles, orderBlocks);
417
+ // Find inducements
418
+ const inducements = findInducements(candles, swingPoints);
419
+ // Detect displacement
420
+ const displacement = detectDisplacement(candles, atr.current);
421
+ // Analyze Power of 3
422
+ const powerOf3 = analyzePowerOf3(candles);
423
+ // Premium/Discount
424
+ const premiumDiscount = (0, smc_indicators_1.calculatePremiumDiscount)(candles, 50);
425
+ // ============================================
426
+ // SIGNAL GENERATION
427
+ // ============================================
428
+ spinner.text = "Generating ICT signal...";
429
+ let signal = "WAIT";
430
+ let confidence = 30;
431
+ let modelType = "standard";
432
+ let entry = {
433
+ price: currentPrice,
434
+ zone: { low: currentPrice * 0.99, high: currentPrice * 1.01 },
435
+ type: "Wait for Setup",
436
+ reason: "No valid ICT setup detected",
437
+ };
438
+ let stopLoss = currentPrice;
439
+ let tp1 = currentPrice, tp2 = currentPrice, tp3 = currentPrice;
440
+ const reasoning = [];
441
+ const confluenceFactors = [];
442
+ const warnings = [];
443
+ // Check for active breaker block
444
+ const activeBreaker = breakerBlocks.find((bb) => {
445
+ return currentPrice >= bb.bottom * 0.995 && currentPrice <= bb.top * 1.005;
446
+ }) || null;
447
+ // Recent sweep detection
448
+ const recentSweep = inducements.find((ind) => {
449
+ if (!ind.swept || ind.sweepCandle === null)
450
+ return false;
451
+ return ind.sweepCandle >= candles.length - 5;
452
+ }) || null;
453
+ // ============================================
454
+ // SILVER BULLET MODEL
455
+ // ============================================
456
+ if (killzone.type === "silver_bullet_am" || killzone.type === "silver_bullet_pm") {
457
+ modelType = "silver_bullet";
458
+ confluenceFactors.push("In Silver Bullet window");
459
+ // Look for FVG in direction of trend
460
+ const unfilledFVGs = fvgs.filter((f) => !f.filled);
461
+ const trendFVG = unfilledFVGs.find((f) => f.type === (trend === "bullish" ? "bullish" : "bearish"));
462
+ if (trendFVG && displacement.detected && displacement.direction === trend) {
463
+ if (trend === "bullish") {
464
+ signal = "BUY";
465
+ confidence = 75;
466
+ entry = {
467
+ price: (trendFVG.top + trendFVG.bottom) / 2,
468
+ zone: { low: trendFVG.bottom, high: trendFVG.top },
469
+ type: "Silver Bullet FVG Entry",
470
+ reason: "FVG fill in bullish trend during Silver Bullet",
471
+ };
472
+ stopLoss = trendFVG.bottom * 0.995;
473
+ const risk = entry.price - stopLoss;
474
+ tp1 = entry.price + risk * 2;
475
+ tp2 = entry.price + risk * 3;
476
+ tp3 = entry.price + risk * 5;
477
+ }
478
+ else {
479
+ signal = "SELL";
480
+ confidence = 75;
481
+ entry = {
482
+ price: (trendFVG.top + trendFVG.bottom) / 2,
483
+ zone: { low: trendFVG.bottom, high: trendFVG.top },
484
+ type: "Silver Bullet FVG Entry",
485
+ reason: "FVG fill in bearish trend during Silver Bullet",
486
+ };
487
+ stopLoss = trendFVG.top * 1.005;
488
+ const risk = stopLoss - entry.price;
489
+ tp1 = entry.price - risk * 2;
490
+ tp2 = entry.price - risk * 3;
491
+ tp3 = entry.price - risk * 5;
492
+ }
493
+ reasoning.push("Silver Bullet setup detected with FVG and displacement");
494
+ }
495
+ }
496
+ // ============================================
497
+ // OTE MODEL
498
+ // ============================================
499
+ if (signal === "WAIT" && ote.isValid && ote.quality === "premium") {
500
+ modelType = "ote";
501
+ // Check if price is in OTE zone
502
+ const inOTEZone = currentPrice >= ote.zone.low && currentPrice <= ote.zone.high;
503
+ if (inOTEZone) {
504
+ confluenceFactors.push("Price in OTE zone (62-79% retracement)");
505
+ if (ote.direction === "bullish" && premiumDiscount.zone === "discount") {
506
+ signal = "BUY";
507
+ confidence = 70;
508
+ entry = {
509
+ price: currentPrice,
510
+ zone: ote.zone,
511
+ type: "OTE Long Entry",
512
+ reason: "Bullish OTE in discount zone",
513
+ };
514
+ stopLoss = ote.swingLow * 0.995;
515
+ const risk = entry.price - stopLoss;
516
+ tp1 = entry.price + risk * 1.5;
517
+ tp2 = ote.swingHigh;
518
+ tp3 = ote.swingHigh + risk;
519
+ reasoning.push(`OTE at ${(ote.fibLevel * 100).toFixed(0)}% retracement`);
520
+ }
521
+ else if (ote.direction === "bearish" && premiumDiscount.zone === "premium") {
522
+ signal = "SELL";
523
+ confidence = 70;
524
+ entry = {
525
+ price: currentPrice,
526
+ zone: ote.zone,
527
+ type: "OTE Short Entry",
528
+ reason: "Bearish OTE in premium zone",
529
+ };
530
+ stopLoss = ote.swingHigh * 1.005;
531
+ const risk = stopLoss - entry.price;
532
+ tp1 = entry.price - risk * 1.5;
533
+ tp2 = ote.swingLow;
534
+ tp3 = ote.swingLow - risk;
535
+ reasoning.push(`OTE at ${(ote.fibLevel * 100).toFixed(0)}% retracement`);
536
+ }
537
+ }
538
+ }
539
+ // ============================================
540
+ // BREAKER BLOCK MODEL
541
+ // ============================================
542
+ if (signal === "WAIT" && activeBreaker) {
543
+ modelType = "breaker";
544
+ confluenceFactors.push("Price at Breaker Block");
545
+ if (activeBreaker.type === "bullish") {
546
+ signal = "BUY";
547
+ confidence = 65;
548
+ entry = {
549
+ price: (activeBreaker.top + activeBreaker.bottom) / 2,
550
+ zone: { low: activeBreaker.bottom, high: activeBreaker.top },
551
+ type: "Bullish Breaker Entry",
552
+ reason: "Price retesting bullish breaker block",
553
+ };
554
+ stopLoss = activeBreaker.bottom * 0.99;
555
+ const risk = entry.price - stopLoss;
556
+ tp1 = entry.price + risk * 2;
557
+ tp2 = entry.price + risk * 3;
558
+ tp3 = entry.price + risk * 5;
559
+ reasoning.push("Bullish breaker block retest");
560
+ }
561
+ else {
562
+ signal = "SELL";
563
+ confidence = 65;
564
+ entry = {
565
+ price: (activeBreaker.top + activeBreaker.bottom) / 2,
566
+ zone: { low: activeBreaker.bottom, high: activeBreaker.top },
567
+ type: "Bearish Breaker Entry",
568
+ reason: "Price retesting bearish breaker block",
569
+ };
570
+ stopLoss = activeBreaker.top * 1.01;
571
+ const risk = stopLoss - entry.price;
572
+ tp1 = entry.price - risk * 2;
573
+ tp2 = entry.price - risk * 3;
574
+ tp3 = entry.price - risk * 5;
575
+ reasoning.push("Bearish breaker block retest");
576
+ }
577
+ }
578
+ // ============================================
579
+ // DISPLACEMENT MODEL
580
+ // ============================================
581
+ if (signal === "WAIT" && displacement.detected && displacement.fvgCreated) {
582
+ modelType = "displacement";
583
+ confluenceFactors.push(`${displacement.direction} displacement detected`);
584
+ // Find the FVG created by displacement
585
+ const displacementFVG = fvgs.find((f) => f.type === displacement.direction && !f.filled && f.index >= displacement.startIndex - 2);
586
+ if (displacementFVG) {
587
+ const distToFVG = displacement.direction === "bullish"
588
+ ? ((displacementFVG.bottom - currentPrice) / currentPrice) * 100
589
+ : ((currentPrice - displacementFVG.top) / currentPrice) * 100;
590
+ if (distToFVG < 2) {
591
+ if (displacement.direction === "bullish") {
592
+ signal = "BUY";
593
+ confidence = 60;
594
+ entry = {
595
+ price: displacementFVG.top,
596
+ zone: { low: displacementFVG.bottom, high: displacementFVG.top },
597
+ type: "Displacement FVG Entry",
598
+ reason: "Bullish displacement with FVG fill opportunity",
599
+ };
600
+ stopLoss = displacementFVG.bottom * 0.99;
601
+ }
602
+ else {
603
+ signal = "SELL";
604
+ confidence = 60;
605
+ entry = {
606
+ price: displacementFVG.bottom,
607
+ zone: { low: displacementFVG.bottom, high: displacementFVG.top },
608
+ type: "Displacement FVG Entry",
609
+ reason: "Bearish displacement with FVG fill opportunity",
610
+ };
611
+ stopLoss = displacementFVG.top * 1.01;
612
+ }
613
+ const risk = Math.abs(entry.price - stopLoss);
614
+ tp1 =
615
+ displacement.direction === "bullish" ? entry.price + risk * 2 : entry.price - risk * 2;
616
+ tp2 =
617
+ displacement.direction === "bullish" ? entry.price + risk * 3 : entry.price - risk * 3;
618
+ tp3 =
619
+ displacement.direction === "bullish" ? entry.price + risk * 5 : entry.price - risk * 5;
620
+ reasoning.push(`Displacement strength: ${displacement.strength.toFixed(1)}x ATR`);
621
+ }
622
+ }
623
+ }
624
+ // ============================================
625
+ // ADD CONFLUENCE FACTORS
626
+ // ============================================
627
+ if (killzone.active)
628
+ confluenceFactors.push(`Active Killzone: ${killzone.name}`);
629
+ if (mss)
630
+ confluenceFactors.push("Recent Market Structure Shift");
631
+ if (recentSweep)
632
+ confluenceFactors.push(`${recentSweep.type} liquidity swept`);
633
+ if (powerOf3.manipulation !== "none") {
634
+ confluenceFactors.push(`AMD: ${powerOf3.manipulation} manipulation detected`);
635
+ }
636
+ // Adjust confidence based on confluence
637
+ confidence += Math.min(confluenceFactors.length * 5, 20);
638
+ confidence = Math.min(confidence, 95);
639
+ // ============================================
640
+ // WARNINGS
641
+ // ============================================
642
+ if (!killzone.active) {
643
+ warnings.push("Outside of major killzones - lower probability");
644
+ }
645
+ if (signal !== "WAIT" && trend !== (signal === "BUY" ? "bullish" : "bearish")) {
646
+ warnings.push("Counter-trend trade - manage risk carefully");
647
+ }
648
+ if (rsi.current > 75 && signal === "BUY") {
649
+ warnings.push("RSI overbought - consider waiting for pullback");
650
+ }
651
+ if (rsi.current < 25 && signal === "SELL") {
652
+ warnings.push("RSI oversold - consider waiting for bounce");
653
+ }
654
+ // Add general context
655
+ reasoning.push(`Trend: ${trend} | RSI: ${rsi.current.toFixed(1)}`);
656
+ reasoning.push(`Premium/Discount: ${premiumDiscount.zone} (${premiumDiscount.fibLevel.toFixed(0)}%)`);
657
+ if (powerOf3.phase !== "unknown") {
658
+ reasoning.push(`Power of 3: ${powerOf3.phase} phase`);
659
+ }
660
+ // Calculate R:R
661
+ const riskAmount = Math.abs(entry.price - stopLoss);
662
+ const rewardAmount = Math.abs(tp2 - entry.price);
663
+ const riskRewardRatio = riskAmount > 0 ? rewardAmount / riskAmount : 0;
664
+ spinner.succeed(`ICT analysis complete for ${data.symbol}`);
665
+ return {
666
+ symbol: data.symbol,
667
+ currentPrice,
668
+ timestamp: Date.now(),
669
+ signal,
670
+ confidence,
671
+ modelType,
672
+ killzone,
673
+ inKillzone: killzone.active,
674
+ ote,
675
+ breakerBlocks,
676
+ activeBreaker,
677
+ inducements,
678
+ recentSweep,
679
+ displacement,
680
+ powerOf3,
681
+ marketStructure: {
682
+ trend,
683
+ lastBOS,
684
+ lastCHoCH,
685
+ mss,
686
+ },
687
+ premiumDiscount: {
688
+ zone: premiumDiscount.zone,
689
+ fibLevel: premiumDiscount.fibLevel,
690
+ },
691
+ entry,
692
+ stopLoss,
693
+ takeProfit1: tp1,
694
+ takeProfit2: tp2,
695
+ takeProfit3: tp3,
696
+ riskRewardRatio,
697
+ reasoning,
698
+ confluenceFactors,
699
+ warnings,
700
+ };
701
+ }
702
+ catch (error) {
703
+ spinner.fail(`Failed to analyze ${symbol}`);
704
+ throw error;
705
+ }
706
+ }
707
+ // ============================================
708
+ // DISPLAY FUNCTION
709
+ // ============================================
710
+ function formatPrice(price) {
711
+ if (price < 0.001)
712
+ return `$${price.toFixed(8)}`;
713
+ if (price < 1)
714
+ return `$${price.toFixed(6)}`;
715
+ if (price < 100)
716
+ return `$${price.toFixed(4)}`;
717
+ return `$${price.toLocaleString(undefined, { maximumFractionDigits: 2 })}`;
718
+ }
719
+ function getConfidenceBar(confidence, width = 20) {
720
+ const filled = Math.round((confidence / 100) * width);
721
+ const empty = width - filled;
722
+ let color = chalk_1.default.green;
723
+ if (confidence < 40)
724
+ color = chalk_1.default.red;
725
+ else if (confidence < 60)
726
+ color = chalk_1.default.yellow;
727
+ return "[" + color("█".repeat(filled)) + chalk_1.default.gray("░".repeat(empty)) + "]";
728
+ }
729
+ function displayICTSignal(result) {
730
+ console.log("");
731
+ // Header
732
+ const headerColor = result.signal === "BUY" ? chalk_1.default.green : result.signal === "SELL" ? chalk_1.default.red : chalk_1.default.yellow;
733
+ const signalIcon = result.signal === "BUY" ? "🟢" : result.signal === "SELL" ? "🔴" : "⏳";
734
+ console.log(headerColor(" ╔═══════════════════════════════════════════════════════════════════════╗"));
735
+ console.log(headerColor(` ║ ${signalIcon} ICT TRADING STRATEGY - ${result.symbol.padEnd(20)} ║`));
736
+ console.log(headerColor(" ╚═══════════════════════════════════════════════════════════════════════╝"));
737
+ console.log("");
738
+ // Current Price & Signal
739
+ console.log(` ${chalk_1.default.cyan("Current Price:")} ${chalk_1.default.white.bold(formatPrice(result.currentPrice))}`);
740
+ console.log("");
741
+ const signalBadge = result.signal === "BUY"
742
+ ? chalk_1.default.bgGreen.white.bold(" BUY ")
743
+ : result.signal === "SELL"
744
+ ? chalk_1.default.bgRed.white.bold(" SELL ")
745
+ : chalk_1.default.bgYellow.black.bold(" WAIT ");
746
+ const modelBadge = chalk_1.default.bgBlue.white(` ${result.modelType.toUpperCase().replace("_", " ")} `);
747
+ console.log(` ${signalBadge} ${modelBadge} Confidence: ${getConfidenceBar(result.confidence)} ${result.confidence}%`);
748
+ console.log("");
749
+ // Killzone Status
750
+ console.log(chalk_1.default.magenta(" ┌─────────────────────────────────────────────────────────────────────┐"));
751
+ console.log(chalk_1.default.magenta(" │ ⏰ KILLZONE STATUS │"));
752
+ console.log(chalk_1.default.magenta(" ├─────────────────────────────────────────────────────────────────────┤"));
753
+ const kzColor = result.inKillzone ? chalk_1.default.green : chalk_1.default.yellow;
754
+ console.log(` │ ${kzColor(result.killzone.name)} ${result.inKillzone ? chalk_1.default.green("● ACTIVE") : chalk_1.default.yellow("○ INACTIVE")}`);
755
+ console.log(` │ ${chalk_1.default.dim(result.killzone.description)}`);
756
+ // Show all killzones with times
757
+ console.log(` │`);
758
+ console.log(` │ ${chalk_1.default.dim("Session Times (UTC):")}`);
759
+ console.log(` │ ${chalk_1.default.cyan("Asian:")} 00:00-06:00 ${chalk_1.default.yellow("London:")} 07:00-10:00`);
760
+ console.log(` │ ${chalk_1.default.green("NY AM:")} 13:00-16:00 ${chalk_1.default.red("NY PM:")} 18:00-21:00`);
761
+ console.log(` │ ${chalk_1.default.magenta("Silver Bullet:")} 14:00-15:00 & 19:00-20:00`);
762
+ console.log(chalk_1.default.magenta(" └─────────────────────────────────────────────────────────────────────┘"));
763
+ console.log("");
764
+ // OTE Analysis
765
+ console.log(chalk_1.default.cyan(" ┌─────────────────────────────────────────────────────────────────────┐"));
766
+ console.log(chalk_1.default.cyan(" │ 📐 OTE (OPTIMAL TRADE ENTRY) │"));
767
+ console.log(chalk_1.default.cyan(" ├─────────────────────────────────────────────────────────────────────┤"));
768
+ const oteQualityColor = result.ote.quality === "premium"
769
+ ? chalk_1.default.green
770
+ : result.ote.quality === "standard"
771
+ ? chalk_1.default.yellow
772
+ : chalk_1.default.red;
773
+ console.log(` │ ${chalk_1.default.dim("Quality:")} ${oteQualityColor(result.ote.quality.toUpperCase())}`);
774
+ console.log(` │ ${chalk_1.default.dim("Fib Level:")} ${(result.ote.fibLevel * 100).toFixed(1)}% (Optimal: 62-79%)`);
775
+ console.log(` │ ${chalk_1.default.dim("OTE Zone:")} ${formatPrice(result.ote.zone.low)} - ${formatPrice(result.ote.zone.high)}`);
776
+ console.log(` │ ${chalk_1.default.dim("Swing Range:")} ${formatPrice(result.ote.swingLow)} - ${formatPrice(result.ote.swingHigh)}`);
777
+ console.log(chalk_1.default.cyan(" └─────────────────────────────────────────────────────────────────────┘"));
778
+ console.log("");
779
+ // Power of 3
780
+ console.log(chalk_1.default.yellow(" ┌─────────────────────────────────────────────────────────────────────┐"));
781
+ console.log(chalk_1.default.yellow(" │ ⚡ POWER OF 3 (AMD MODEL) │"));
782
+ console.log(chalk_1.default.yellow(" ├─────────────────────────────────────────────────────────────────────┤"));
783
+ const phaseColor = result.powerOf3.phase === "accumulation"
784
+ ? chalk_1.default.blue
785
+ : result.powerOf3.phase === "manipulation"
786
+ ? chalk_1.default.yellow
787
+ : result.powerOf3.phase === "distribution"
788
+ ? chalk_1.default.red
789
+ : chalk_1.default.gray;
790
+ console.log(` │ ${chalk_1.default.dim("Current Phase:")} ${phaseColor(result.powerOf3.phase.toUpperCase())}`);
791
+ if (result.powerOf3.asianRangeHigh > 0) {
792
+ console.log(` │ ${chalk_1.default.dim("Asian Range:")} ${formatPrice(result.powerOf3.asianRangeLow)} - ${formatPrice(result.powerOf3.asianRangeHigh)}`);
793
+ }
794
+ const manipColor = result.powerOf3.manipulation === "above"
795
+ ? chalk_1.default.red
796
+ : result.powerOf3.manipulation === "below"
797
+ ? chalk_1.default.green
798
+ : chalk_1.default.gray;
799
+ console.log(` │ ${chalk_1.default.dim("Manipulation:")} ${manipColor(result.powerOf3.manipulation)}`);
800
+ const expectedColor = result.powerOf3.expectedMove === "bullish"
801
+ ? chalk_1.default.green
802
+ : result.powerOf3.expectedMove === "bearish"
803
+ ? chalk_1.default.red
804
+ : chalk_1.default.yellow;
805
+ console.log(` │ ${chalk_1.default.dim("Expected Move:")} ${expectedColor(result.powerOf3.expectedMove)}`);
806
+ console.log(chalk_1.default.yellow(" └─────────────────────────────────────────────────────────────────────┘"));
807
+ console.log("");
808
+ // Market Structure
809
+ console.log(chalk_1.default.blue(" ┌─────────────────────────────────────────────────────────────────────┐"));
810
+ console.log(chalk_1.default.blue(" │ 📊 MARKET STRUCTURE │"));
811
+ console.log(chalk_1.default.blue(" ├─────────────────────────────────────────────────────────────────────┤"));
812
+ const trendColor = result.marketStructure.trend === "bullish"
813
+ ? chalk_1.default.green
814
+ : result.marketStructure.trend === "bearish"
815
+ ? chalk_1.default.red
816
+ : chalk_1.default.yellow;
817
+ console.log(` │ ${chalk_1.default.dim("Trend:")} ${trendColor(result.marketStructure.trend.toUpperCase())}`);
818
+ if (result.marketStructure.lastBOS) {
819
+ const bosColor = result.marketStructure.lastBOS.direction === "bullish" ? chalk_1.default.green : chalk_1.default.red;
820
+ console.log(` │ ${chalk_1.default.dim("Last BOS:")} ${bosColor(result.marketStructure.lastBOS.direction)} @ ${formatPrice(result.marketStructure.lastBOS.level)}`);
821
+ }
822
+ if (result.marketStructure.lastCHoCH) {
823
+ const chochColor = result.marketStructure.lastCHoCH.direction === "bullish" ? chalk_1.default.green : chalk_1.default.red;
824
+ console.log(` │ ${chalk_1.default.dim("Last CHoCH:")} ${chochColor(result.marketStructure.lastCHoCH.direction)} @ ${formatPrice(result.marketStructure.lastCHoCH.level)}`);
825
+ }
826
+ console.log(` │ ${chalk_1.default.dim("MSS (Shift):")} ${result.marketStructure.mss ? chalk_1.default.green("YES") : chalk_1.default.gray("NO")}`);
827
+ const pdColor = result.premiumDiscount.zone === "discount"
828
+ ? chalk_1.default.green
829
+ : result.premiumDiscount.zone === "premium"
830
+ ? chalk_1.default.red
831
+ : chalk_1.default.yellow;
832
+ console.log(` │ ${chalk_1.default.dim("Zone:")} ${pdColor(result.premiumDiscount.zone)} (${result.premiumDiscount.fibLevel.toFixed(0)}%)`);
833
+ console.log(chalk_1.default.blue(" └─────────────────────────────────────────────────────────────────────┘"));
834
+ console.log("");
835
+ // Displacement & Inducement
836
+ console.log(chalk_1.default.gray(" ┌─────────────────────────────────────────────────────────────────────┐"));
837
+ console.log(chalk_1.default.gray(" │ 💥 DISPLACEMENT & INDUCEMENT │"));
838
+ console.log(chalk_1.default.gray(" ├─────────────────────────────────────────────────────────────────────┤"));
839
+ if (result.displacement.detected) {
840
+ const dispColor = result.displacement.direction === "bullish" ? chalk_1.default.green : chalk_1.default.red;
841
+ console.log(` │ ${chalk_1.default.dim("Displacement:")} ${dispColor(result.displacement.direction)} (${result.displacement.strength.toFixed(1)}x ATR)`);
842
+ console.log(` │ ${chalk_1.default.dim("FVG Created:")} ${result.displacement.fvgCreated ? chalk_1.default.green("YES") : chalk_1.default.gray("NO")}`);
843
+ }
844
+ else {
845
+ console.log(` │ ${chalk_1.default.dim("Displacement:")} ${chalk_1.default.gray("None detected")}`);
846
+ }
847
+ console.log(` │`);
848
+ console.log(` │ ${chalk_1.default.dim("Inducements:")} ${result.inducements.length} found`);
849
+ if (result.recentSweep) {
850
+ const sweepColor = result.recentSweep.type === "buy_side" ? chalk_1.default.red : chalk_1.default.green;
851
+ console.log(` │ ${chalk_1.default.yellow("⚠ Recent Sweep:")} ${sweepColor(result.recentSweep.type)} @ ${formatPrice(result.recentSweep.level)}`);
852
+ }
853
+ console.log(chalk_1.default.gray(" └─────────────────────────────────────────────────────────────────────┘"));
854
+ console.log("");
855
+ // Breaker Blocks
856
+ if (result.breakerBlocks.length > 0) {
857
+ console.log(chalk_1.default.red(" ┌─────────────────────────────────────────────────────────────────────┐"));
858
+ console.log(chalk_1.default.red(" │ 🔄 BREAKER BLOCKS │"));
859
+ console.log(chalk_1.default.red(" ├─────────────────────────────────────────────────────────────────────┤"));
860
+ result.breakerBlocks.slice(0, 4).forEach((bb) => {
861
+ const bbColor = bb.type === "bullish" ? chalk_1.default.green : chalk_1.default.red;
862
+ const isActive = result.activeBreaker === bb;
863
+ const marker = isActive ? chalk_1.default.yellow(" ← ACTIVE") : "";
864
+ console.log(` │ ${bbColor(bb.type.padEnd(8))} ${formatPrice(bb.bottom)} - ${formatPrice(bb.top)} [${bb.strength}]${marker}`);
865
+ });
866
+ console.log(chalk_1.default.red(" └─────────────────────────────────────────────────────────────────────┘"));
867
+ console.log("");
868
+ }
869
+ // Trade Setup
870
+ if (result.signal !== "WAIT") {
871
+ console.log(chalk_1.default.green(" ┌─────────────────────────────────────────────────────────────────────┐"));
872
+ console.log(chalk_1.default.green(" │ 📍 TRADE SETUP │"));
873
+ console.log(chalk_1.default.green(" ├─────────────────────────────────────────────────────────────────────┤"));
874
+ console.log(` │ ${chalk_1.default.cyan("Entry Type:")} ${result.entry.type}`);
875
+ console.log(` │ ${chalk_1.default.yellow("Entry Zone:")} ${formatPrice(result.entry.zone.low)} - ${formatPrice(result.entry.zone.high)}`);
876
+ console.log(` │ ${chalk_1.default.yellow("Entry Price:")} ${chalk_1.default.white.bold(formatPrice(result.entry.price))}`);
877
+ console.log(` │ ${chalk_1.default.dim(result.entry.reason)}`);
878
+ console.log(` │`);
879
+ console.log(` │ ${chalk_1.default.red("Stop Loss:")} ${chalk_1.default.red.bold(formatPrice(result.stopLoss))} ${chalk_1.default.dim(`(${((Math.abs(result.entry.price - result.stopLoss) / result.entry.price) * 100).toFixed(2)}%)`)}`);
880
+ console.log(` │`);
881
+ console.log(` │ ${chalk_1.default.green("TP1:")} ${formatPrice(result.takeProfit1)} ${chalk_1.default.dim("(2R)")}`);
882
+ console.log(` │ ${chalk_1.default.green("TP2:")} ${formatPrice(result.takeProfit2)} ${chalk_1.default.dim("(3R)")}`);
883
+ console.log(` │ ${chalk_1.default.green("TP3:")} ${formatPrice(result.takeProfit3)} ${chalk_1.default.dim("(5R)")}`);
884
+ console.log(` │`);
885
+ console.log(` │ ${chalk_1.default.cyan("R:R Ratio:")} ${chalk_1.default.cyan.bold(result.riskRewardRatio.toFixed(1) + ":1")}`);
886
+ console.log(chalk_1.default.green(" └─────────────────────────────────────────────────────────────────────┘"));
887
+ console.log("");
888
+ }
889
+ // Confluence Factors
890
+ if (result.confluenceFactors.length > 0) {
891
+ console.log(chalk_1.default.cyan(" ┌─────────────────────────────────────────────────────────────────────┐"));
892
+ console.log(chalk_1.default.cyan(" │ ✓ CONFLUENCE FACTORS │"));
893
+ console.log(chalk_1.default.cyan(" ├─────────────────────────────────────────────────────────────────────┤"));
894
+ result.confluenceFactors.forEach((cf) => {
895
+ console.log(` │ ${chalk_1.default.green("✓")} ${cf}`);
896
+ });
897
+ console.log(chalk_1.default.cyan(" └─────────────────────────────────────────────────────────────────────┘"));
898
+ console.log("");
899
+ }
900
+ // Reasoning
901
+ console.log(chalk_1.default.gray(" ┌─────────────────────────────────────────────────────────────────────┐"));
902
+ console.log(chalk_1.default.gray(" │ 🔍 ANALYSIS │"));
903
+ console.log(chalk_1.default.gray(" ├─────────────────────────────────────────────────────────────────────┤"));
904
+ result.reasoning.forEach((r) => {
905
+ console.log(` │ ${chalk_1.default.dim("•")} ${r}`);
906
+ });
907
+ console.log(chalk_1.default.gray(" └─────────────────────────────────────────────────────────────────────┘"));
908
+ console.log("");
909
+ // Warnings
910
+ if (result.warnings.length > 0) {
911
+ console.log(chalk_1.default.red(" ┌─────────────────────────────────────────────────────────────────────┐"));
912
+ console.log(chalk_1.default.red(" │ ⚠️ WARNINGS │"));
913
+ console.log(chalk_1.default.red(" ├─────────────────────────────────────────────────────────────────────┤"));
914
+ result.warnings.forEach((w) => {
915
+ console.log(` │ ${chalk_1.default.yellow("!")} ${chalk_1.default.yellow(w)}`);
916
+ });
917
+ console.log(chalk_1.default.red(" └─────────────────────────────────────────────────────────────────────┘"));
918
+ console.log("");
919
+ }
920
+ // ICT Concepts Legend
921
+ console.log(chalk_1.default.dim(" ┌─────────────────────────────────────────────────────────────────────┐"));
922
+ console.log(chalk_1.default.dim(" │ 📚 ICT CONCEPTS GUIDE │"));
923
+ console.log(chalk_1.default.dim(" ├─────────────────────────────────────────────────────────────────────┤"));
924
+ console.log(chalk_1.default.dim(" │ OTE: Optimal Trade Entry (62-79% Fib retracement) │"));
925
+ console.log(chalk_1.default.dim(" │ BOS: Break of Structure | CHoCH: Change of Character │"));
926
+ console.log(chalk_1.default.dim(" │ MSS: Market Structure Shift (trend reversal signal) │"));
927
+ console.log(chalk_1.default.dim(" │ AMD: Accumulation → Manipulation → Distribution │"));
928
+ console.log(chalk_1.default.dim(" │ Silver Bullet: High-probability 1hr entry windows │"));
929
+ console.log(chalk_1.default.dim(" │ Breaker: Failed OB that flips to support/resistance │"));
930
+ console.log(chalk_1.default.dim(" └─────────────────────────────────────────────────────────────────────┘"));
931
+ console.log("");
932
+ // Disclaimer
933
+ console.log(chalk_1.default.dim.italic(" ⚠️ This is not financial advice. Always manage your risk and DYOR."));
934
+ console.log("");
935
+ }
936
+ // ============================================
937
+ // RUNNER
938
+ // ============================================
939
+ async function runICTStrategy(symbol, interval = "1h") {
940
+ try {
941
+ const result = await generateICTSignal(symbol, interval);
942
+ displayICTSignal(result);
943
+ return result;
944
+ }
945
+ catch (error) {
946
+ console.log("");
947
+ console.log(chalk_1.default.red(" ╔═══════════════════════════════════════════════════════════════════════╗"));
948
+ console.log(chalk_1.default.red(" ║ ❌ ICT STRATEGY ERROR ║"));
949
+ console.log(chalk_1.default.red(" ╚═══════════════════════════════════════════════════════════════════════╝"));
950
+ console.log("");
951
+ console.log(chalk_1.default.yellow(` ⚠️ ${error.message || "An unexpected error occurred"}`));
952
+ console.log("");
953
+ return null;
954
+ }
955
+ }