prab-cli 1.2.1 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +181 -2
- package/dist/lib/chat-handler.js +39 -5
- package/dist/lib/crypto/analyzer.js +275 -0
- package/dist/lib/crypto/chart-visual.js +548 -0
- package/dist/lib/crypto/data-fetcher.js +166 -0
- package/dist/lib/crypto/index.js +57 -0
- package/dist/lib/crypto/indicators.js +390 -0
- package/dist/lib/crypto/market-analyzer.js +497 -0
- package/dist/lib/crypto/market-scanner.js +569 -0
- package/dist/lib/crypto/news-fetcher.js +394 -0
- package/dist/lib/crypto/signal-generator.js +625 -0
- package/dist/lib/crypto/smc-analyzer.js +450 -0
- package/dist/lib/crypto/smc-indicators.js +512 -0
- package/dist/lib/crypto/whale-tracker.js +508 -0
- package/dist/lib/slash-commands.js +36 -0
- package/dist/lib/ui.js +45 -1
- package/package.json +1 -1
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Smart Money Concepts (SMC) Indicators
|
|
4
|
+
* Institutional trading strategy analysis
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.findSwingPoints = findSwingPoints;
|
|
8
|
+
exports.detectStructureBreaks = detectStructureBreaks;
|
|
9
|
+
exports.findOrderBlocks = findOrderBlocks;
|
|
10
|
+
exports.findFairValueGaps = findFairValueGaps;
|
|
11
|
+
exports.findLiquidityZones = findLiquidityZones;
|
|
12
|
+
exports.calculatePremiumDiscount = calculatePremiumDiscount;
|
|
13
|
+
exports.analyzeSMC = analyzeSMC;
|
|
14
|
+
// ============================================
|
|
15
|
+
// SWING POINT DETECTION
|
|
16
|
+
// ============================================
|
|
17
|
+
/**
|
|
18
|
+
* Find swing highs and lows
|
|
19
|
+
*/
|
|
20
|
+
function findSwingPoints(candles, lookback = 3) {
|
|
21
|
+
const swingPoints = [];
|
|
22
|
+
for (let i = lookback; i < candles.length - lookback; i++) {
|
|
23
|
+
const current = candles[i];
|
|
24
|
+
// Check for swing high
|
|
25
|
+
let isSwingHigh = true;
|
|
26
|
+
let isSwingLow = true;
|
|
27
|
+
for (let j = 1; j <= lookback; j++) {
|
|
28
|
+
if (candles[i - j].high >= current.high || candles[i + j].high >= current.high) {
|
|
29
|
+
isSwingHigh = false;
|
|
30
|
+
}
|
|
31
|
+
if (candles[i - j].low <= current.low || candles[i + j].low <= current.low) {
|
|
32
|
+
isSwingLow = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (isSwingHigh) {
|
|
36
|
+
swingPoints.push({
|
|
37
|
+
index: i,
|
|
38
|
+
price: current.high,
|
|
39
|
+
type: "high",
|
|
40
|
+
timestamp: current.timestamp,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (isSwingLow) {
|
|
44
|
+
swingPoints.push({
|
|
45
|
+
index: i,
|
|
46
|
+
price: current.low,
|
|
47
|
+
type: "low",
|
|
48
|
+
timestamp: current.timestamp,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return swingPoints.sort((a, b) => a.index - b.index);
|
|
53
|
+
}
|
|
54
|
+
// ============================================
|
|
55
|
+
// MARKET STRUCTURE ANALYSIS
|
|
56
|
+
// ============================================
|
|
57
|
+
/**
|
|
58
|
+
* Detect Break of Structure (BOS) and Change of Character (CHoCH)
|
|
59
|
+
*/
|
|
60
|
+
function detectStructureBreaks(candles, swingPoints) {
|
|
61
|
+
const breaks = [];
|
|
62
|
+
if (swingPoints.length < 4)
|
|
63
|
+
return breaks;
|
|
64
|
+
// Get recent swing highs and lows
|
|
65
|
+
const highs = swingPoints.filter((p) => p.type === "high");
|
|
66
|
+
const lows = swingPoints.filter((p) => p.type === "low");
|
|
67
|
+
// Track current trend
|
|
68
|
+
let currentTrend = "unknown";
|
|
69
|
+
// Analyze structure
|
|
70
|
+
for (let i = 2; i < highs.length; i++) {
|
|
71
|
+
const prevHigh = highs[i - 1];
|
|
72
|
+
const currHigh = highs[i];
|
|
73
|
+
// Find lows between these highs
|
|
74
|
+
const lowsBetween = lows.filter((l) => l.index > prevHigh.index && l.index < currHigh.index);
|
|
75
|
+
if (lowsBetween.length > 0) {
|
|
76
|
+
const lowestLow = lowsBetween.reduce((a, b) => (a.price < b.price ? a : b));
|
|
77
|
+
// Check for BOS or CHoCH
|
|
78
|
+
for (let j = currHigh.index; j < candles.length; j++) {
|
|
79
|
+
const candle = candles[j];
|
|
80
|
+
// Bullish BOS: Price breaks above previous high in uptrend
|
|
81
|
+
if (candle.close > prevHigh.price) {
|
|
82
|
+
if (currentTrend === "bullish") {
|
|
83
|
+
breaks.push({
|
|
84
|
+
type: "BOS",
|
|
85
|
+
direction: "bullish",
|
|
86
|
+
level: prevHigh.price,
|
|
87
|
+
index: j,
|
|
88
|
+
timestamp: candle.timestamp,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else if (currentTrend === "bearish") {
|
|
92
|
+
breaks.push({
|
|
93
|
+
type: "CHoCH",
|
|
94
|
+
direction: "bullish",
|
|
95
|
+
level: prevHigh.price,
|
|
96
|
+
index: j,
|
|
97
|
+
timestamp: candle.timestamp,
|
|
98
|
+
});
|
|
99
|
+
currentTrend = "bullish";
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
currentTrend = "bullish";
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
// Bearish BOS: Price breaks below previous low in downtrend
|
|
107
|
+
if (candle.close < lowestLow.price) {
|
|
108
|
+
if (currentTrend === "bearish") {
|
|
109
|
+
breaks.push({
|
|
110
|
+
type: "BOS",
|
|
111
|
+
direction: "bearish",
|
|
112
|
+
level: lowestLow.price,
|
|
113
|
+
index: j,
|
|
114
|
+
timestamp: candle.timestamp,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else if (currentTrend === "bullish") {
|
|
118
|
+
breaks.push({
|
|
119
|
+
type: "CHoCH",
|
|
120
|
+
direction: "bearish",
|
|
121
|
+
level: lowestLow.price,
|
|
122
|
+
index: j,
|
|
123
|
+
timestamp: candle.timestamp,
|
|
124
|
+
});
|
|
125
|
+
currentTrend = "bearish";
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
currentTrend = "bearish";
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return breaks;
|
|
136
|
+
}
|
|
137
|
+
// ============================================
|
|
138
|
+
// ORDER BLOCKS
|
|
139
|
+
// ============================================
|
|
140
|
+
/**
|
|
141
|
+
* Detect Order Blocks (institutional buying/selling zones)
|
|
142
|
+
*/
|
|
143
|
+
function findOrderBlocks(candles, lookback = 50) {
|
|
144
|
+
const orderBlocks = [];
|
|
145
|
+
const currentPrice = candles[candles.length - 1].close;
|
|
146
|
+
for (let i = 2; i < Math.min(candles.length - 1, lookback); i++) {
|
|
147
|
+
const idx = candles.length - 1 - i;
|
|
148
|
+
const candle = candles[idx];
|
|
149
|
+
const nextCandle = candles[idx + 1];
|
|
150
|
+
const prevCandle = candles[idx - 1];
|
|
151
|
+
// Bullish Order Block: Last bearish candle before strong bullish move
|
|
152
|
+
if (candle.close < candle.open && // Bearish candle
|
|
153
|
+
nextCandle.close > nextCandle.open && // Followed by bullish
|
|
154
|
+
nextCandle.close > candle.high && // Strong bullish move
|
|
155
|
+
nextCandle.close - nextCandle.open > (candle.open - candle.close) * 1.5 // Momentum
|
|
156
|
+
) {
|
|
157
|
+
const ob = {
|
|
158
|
+
type: "bullish",
|
|
159
|
+
top: candle.open,
|
|
160
|
+
bottom: candle.low,
|
|
161
|
+
mitigated: currentPrice < candle.low,
|
|
162
|
+
strength: "moderate",
|
|
163
|
+
index: idx,
|
|
164
|
+
timestamp: candle.timestamp,
|
|
165
|
+
};
|
|
166
|
+
// Check strength based on displacement
|
|
167
|
+
const displacement = (nextCandle.close - candle.high) / candle.high;
|
|
168
|
+
if (displacement > 0.02)
|
|
169
|
+
ob.strength = "strong";
|
|
170
|
+
if (displacement < 0.01)
|
|
171
|
+
ob.strength = "weak";
|
|
172
|
+
// Check if mitigated
|
|
173
|
+
for (let j = idx + 2; j < candles.length; j++) {
|
|
174
|
+
if (candles[j].low <= ob.bottom) {
|
|
175
|
+
ob.mitigated = true;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!ob.mitigated) {
|
|
180
|
+
orderBlocks.push(ob);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Bearish Order Block: Last bullish candle before strong bearish move
|
|
184
|
+
if (candle.close > candle.open && // Bullish candle
|
|
185
|
+
nextCandle.close < nextCandle.open && // Followed by bearish
|
|
186
|
+
nextCandle.close < candle.low && // Strong bearish move
|
|
187
|
+
nextCandle.open - nextCandle.close > (candle.close - candle.open) * 1.5 // Momentum
|
|
188
|
+
) {
|
|
189
|
+
const ob = {
|
|
190
|
+
type: "bearish",
|
|
191
|
+
top: candle.high,
|
|
192
|
+
bottom: candle.open,
|
|
193
|
+
mitigated: currentPrice > candle.high,
|
|
194
|
+
strength: "moderate",
|
|
195
|
+
index: idx,
|
|
196
|
+
timestamp: candle.timestamp,
|
|
197
|
+
};
|
|
198
|
+
// Check strength
|
|
199
|
+
const displacement = (candle.low - nextCandle.close) / candle.low;
|
|
200
|
+
if (displacement > 0.02)
|
|
201
|
+
ob.strength = "strong";
|
|
202
|
+
if (displacement < 0.01)
|
|
203
|
+
ob.strength = "weak";
|
|
204
|
+
// Check if mitigated
|
|
205
|
+
for (let j = idx + 2; j < candles.length; j++) {
|
|
206
|
+
if (candles[j].high >= ob.top) {
|
|
207
|
+
ob.mitigated = true;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!ob.mitigated) {
|
|
212
|
+
orderBlocks.push(ob);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return orderBlocks;
|
|
217
|
+
}
|
|
218
|
+
// ============================================
|
|
219
|
+
// FAIR VALUE GAPS (FVG)
|
|
220
|
+
// ============================================
|
|
221
|
+
/**
|
|
222
|
+
* Detect Fair Value Gaps (imbalances)
|
|
223
|
+
*/
|
|
224
|
+
function findFairValueGaps(candles, lookback = 50) {
|
|
225
|
+
const fvgs = [];
|
|
226
|
+
const currentPrice = candles[candles.length - 1].close;
|
|
227
|
+
for (let i = 2; i < Math.min(candles.length, lookback); i++) {
|
|
228
|
+
const idx = candles.length - 1 - i;
|
|
229
|
+
if (idx < 1)
|
|
230
|
+
continue;
|
|
231
|
+
const candle1 = candles[idx - 1];
|
|
232
|
+
const candle2 = candles[idx];
|
|
233
|
+
const candle3 = candles[idx + 1];
|
|
234
|
+
// Bullish FVG: Gap between candle 1 high and candle 3 low
|
|
235
|
+
if (candle3.low > candle1.high) {
|
|
236
|
+
const fvg = {
|
|
237
|
+
type: "bullish",
|
|
238
|
+
top: candle3.low,
|
|
239
|
+
bottom: candle1.high,
|
|
240
|
+
filled: false,
|
|
241
|
+
fillPercentage: 0,
|
|
242
|
+
index: idx,
|
|
243
|
+
timestamp: candle2.timestamp,
|
|
244
|
+
};
|
|
245
|
+
// Check if filled
|
|
246
|
+
let lowestReach = candle3.low;
|
|
247
|
+
for (let j = idx + 2; j < candles.length; j++) {
|
|
248
|
+
if (candles[j].low < lowestReach) {
|
|
249
|
+
lowestReach = candles[j].low;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (lowestReach <= candle1.high) {
|
|
253
|
+
fvg.filled = true;
|
|
254
|
+
fvg.fillPercentage = 100;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const gapSize = candle3.low - candle1.high;
|
|
258
|
+
const filledSize = candle3.low - lowestReach;
|
|
259
|
+
fvg.fillPercentage = (filledSize / gapSize) * 100;
|
|
260
|
+
}
|
|
261
|
+
fvgs.push(fvg);
|
|
262
|
+
}
|
|
263
|
+
// Bearish FVG: Gap between candle 1 low and candle 3 high
|
|
264
|
+
if (candle3.high < candle1.low) {
|
|
265
|
+
const fvg = {
|
|
266
|
+
type: "bearish",
|
|
267
|
+
top: candle1.low,
|
|
268
|
+
bottom: candle3.high,
|
|
269
|
+
filled: false,
|
|
270
|
+
fillPercentage: 0,
|
|
271
|
+
index: idx,
|
|
272
|
+
timestamp: candle2.timestamp,
|
|
273
|
+
};
|
|
274
|
+
// Check if filled
|
|
275
|
+
let highestReach = candle3.high;
|
|
276
|
+
for (let j = idx + 2; j < candles.length; j++) {
|
|
277
|
+
if (candles[j].high > highestReach) {
|
|
278
|
+
highestReach = candles[j].high;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (highestReach >= candle1.low) {
|
|
282
|
+
fvg.filled = true;
|
|
283
|
+
fvg.fillPercentage = 100;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
const gapSize = candle1.low - candle3.high;
|
|
287
|
+
const filledSize = highestReach - candle3.high;
|
|
288
|
+
fvg.fillPercentage = (filledSize / gapSize) * 100;
|
|
289
|
+
}
|
|
290
|
+
fvgs.push(fvg);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return fvgs;
|
|
294
|
+
}
|
|
295
|
+
// ============================================
|
|
296
|
+
// LIQUIDITY ZONES
|
|
297
|
+
// ============================================
|
|
298
|
+
/**
|
|
299
|
+
* Find liquidity zones (stop loss clusters)
|
|
300
|
+
*/
|
|
301
|
+
function findLiquidityZones(candles, swingPoints) {
|
|
302
|
+
const buySide = [];
|
|
303
|
+
const sellSide = [];
|
|
304
|
+
const currentPrice = candles[candles.length - 1].close;
|
|
305
|
+
// Group swing highs (buy-side liquidity - stops above)
|
|
306
|
+
const highs = swingPoints.filter((p) => p.type === "high" && p.price > currentPrice);
|
|
307
|
+
const lows = swingPoints.filter((p) => p.type === "low" && p.price < currentPrice);
|
|
308
|
+
// Cluster nearby highs
|
|
309
|
+
const clusterThreshold = 0.005; // 0.5%
|
|
310
|
+
highs.forEach((high) => {
|
|
311
|
+
const existing = buySide.find((z) => Math.abs(z.level - high.price) / high.price < clusterThreshold);
|
|
312
|
+
if (existing) {
|
|
313
|
+
existing.strength++;
|
|
314
|
+
existing.level = (existing.level + high.price) / 2;
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
buySide.push({
|
|
318
|
+
type: "buy_side",
|
|
319
|
+
level: high.price,
|
|
320
|
+
strength: 1,
|
|
321
|
+
swept: false,
|
|
322
|
+
index: high.index,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
// Cluster nearby lows
|
|
327
|
+
lows.forEach((low) => {
|
|
328
|
+
const existing = sellSide.find((z) => Math.abs(z.level - low.price) / low.price < clusterThreshold);
|
|
329
|
+
if (existing) {
|
|
330
|
+
existing.strength++;
|
|
331
|
+
existing.level = (existing.level + low.price) / 2;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
sellSide.push({
|
|
335
|
+
type: "sell_side",
|
|
336
|
+
level: low.price,
|
|
337
|
+
strength: 1,
|
|
338
|
+
swept: false,
|
|
339
|
+
index: low.index,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
// Sort by proximity to current price
|
|
344
|
+
buySide.sort((a, b) => a.level - b.level);
|
|
345
|
+
sellSide.sort((a, b) => b.level - a.level);
|
|
346
|
+
return { buySide: buySide.slice(0, 5), sellSide: sellSide.slice(0, 5) };
|
|
347
|
+
}
|
|
348
|
+
// ============================================
|
|
349
|
+
// PREMIUM/DISCOUNT ZONES
|
|
350
|
+
// ============================================
|
|
351
|
+
/**
|
|
352
|
+
* Calculate Premium/Discount zones using recent range
|
|
353
|
+
*/
|
|
354
|
+
function calculatePremiumDiscount(candles, lookback = 50) {
|
|
355
|
+
const recentCandles = candles.slice(-lookback);
|
|
356
|
+
const rangeHigh = Math.max(...recentCandles.map((c) => c.high));
|
|
357
|
+
const rangeLow = Math.min(...recentCandles.map((c) => c.low));
|
|
358
|
+
const equilibrium = (rangeHigh + rangeLow) / 2;
|
|
359
|
+
const currentPrice = candles[candles.length - 1].close;
|
|
360
|
+
// Calculate Fibonacci level (0 = low, 0.5 = equilibrium, 1 = high)
|
|
361
|
+
const fibLevel = (currentPrice - rangeLow) / (rangeHigh - rangeLow);
|
|
362
|
+
let zone;
|
|
363
|
+
if (fibLevel > 0.618) {
|
|
364
|
+
zone = "premium";
|
|
365
|
+
}
|
|
366
|
+
else if (fibLevel < 0.382) {
|
|
367
|
+
zone = "discount";
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
zone = "equilibrium";
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
zone,
|
|
374
|
+
fibLevel: fibLevel * 100,
|
|
375
|
+
rangeHigh,
|
|
376
|
+
rangeLow,
|
|
377
|
+
equilibrium,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
// ============================================
|
|
381
|
+
// MAIN SMC ANALYSIS
|
|
382
|
+
// ============================================
|
|
383
|
+
/**
|
|
384
|
+
* Perform complete SMC analysis
|
|
385
|
+
*/
|
|
386
|
+
function analyzeSMC(candles) {
|
|
387
|
+
// Find swing points
|
|
388
|
+
const swingPoints = findSwingPoints(candles, 3);
|
|
389
|
+
const swingHighs = swingPoints.filter((p) => p.type === "high");
|
|
390
|
+
const swingLows = swingPoints.filter((p) => p.type === "low");
|
|
391
|
+
// Detect structure breaks
|
|
392
|
+
const structureBreaks = detectStructureBreaks(candles, swingPoints);
|
|
393
|
+
const lastBOS = structureBreaks.filter((b) => b.type === "BOS").pop() || null;
|
|
394
|
+
const lastCHoCH = structureBreaks.filter((b) => b.type === "CHoCH").pop() || null;
|
|
395
|
+
// Determine market structure trend
|
|
396
|
+
let marketTrend = "ranging";
|
|
397
|
+
if (lastBOS) {
|
|
398
|
+
marketTrend = lastBOS.direction;
|
|
399
|
+
}
|
|
400
|
+
if (lastCHoCH && (!lastBOS || lastCHoCH.index > lastBOS.index)) {
|
|
401
|
+
marketTrend = lastCHoCH.direction;
|
|
402
|
+
}
|
|
403
|
+
// Find order blocks
|
|
404
|
+
const orderBlocks = findOrderBlocks(candles);
|
|
405
|
+
const bullishOBs = orderBlocks.filter((ob) => ob.type === "bullish");
|
|
406
|
+
const bearishOBs = orderBlocks.filter((ob) => ob.type === "bearish");
|
|
407
|
+
// Find nearest unmitigated order block
|
|
408
|
+
const currentPrice = candles[candles.length - 1].close;
|
|
409
|
+
const nearestOB = orderBlocks
|
|
410
|
+
.filter((ob) => !ob.mitigated)
|
|
411
|
+
.sort((a, b) => {
|
|
412
|
+
const distA = Math.min(Math.abs(currentPrice - a.top), Math.abs(currentPrice - a.bottom));
|
|
413
|
+
const distB = Math.min(Math.abs(currentPrice - b.top), Math.abs(currentPrice - b.bottom));
|
|
414
|
+
return distA - distB;
|
|
415
|
+
})[0] || null;
|
|
416
|
+
// Find FVGs
|
|
417
|
+
const fvgs = findFairValueGaps(candles);
|
|
418
|
+
const bullishFVGs = fvgs.filter((f) => f.type === "bullish");
|
|
419
|
+
const bearishFVGs = fvgs.filter((f) => f.type === "bearish");
|
|
420
|
+
const unfilledFVGs = fvgs.filter((f) => !f.filled);
|
|
421
|
+
// Find liquidity
|
|
422
|
+
const liquidity = findLiquidityZones(candles, swingPoints);
|
|
423
|
+
const nextLiqTarget = marketTrend === "bullish"
|
|
424
|
+
? liquidity.buySide[0] || null
|
|
425
|
+
: marketTrend === "bearish"
|
|
426
|
+
? liquidity.sellSide[0] || null
|
|
427
|
+
: null;
|
|
428
|
+
// Premium/Discount
|
|
429
|
+
const premiumDiscount = calculatePremiumDiscount(candles);
|
|
430
|
+
// Generate bias
|
|
431
|
+
const reasoning = [];
|
|
432
|
+
let biasScore = 0;
|
|
433
|
+
// Structure-based bias
|
|
434
|
+
if (marketTrend === "bullish") {
|
|
435
|
+
biasScore += 30;
|
|
436
|
+
reasoning.push(`Bullish market structure (${lastBOS ? "BOS" : "CHoCH"} to upside)`);
|
|
437
|
+
}
|
|
438
|
+
else if (marketTrend === "bearish") {
|
|
439
|
+
biasScore -= 30;
|
|
440
|
+
reasoning.push(`Bearish market structure (${lastBOS ? "BOS" : "CHoCH"} to downside)`);
|
|
441
|
+
}
|
|
442
|
+
// Order block proximity
|
|
443
|
+
if (nearestOB) {
|
|
444
|
+
if (nearestOB.type === "bullish" && currentPrice <= nearestOB.top * 1.01) {
|
|
445
|
+
biasScore += 20;
|
|
446
|
+
reasoning.push(`Price at bullish order block ($${nearestOB.bottom.toFixed(2)} - $${nearestOB.top.toFixed(2)})`);
|
|
447
|
+
}
|
|
448
|
+
else if (nearestOB.type === "bearish" && currentPrice >= nearestOB.bottom * 0.99) {
|
|
449
|
+
biasScore -= 20;
|
|
450
|
+
reasoning.push(`Price at bearish order block ($${nearestOB.bottom.toFixed(2)} - $${nearestOB.top.toFixed(2)})`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Premium/Discount zone
|
|
454
|
+
if (premiumDiscount.zone === "discount") {
|
|
455
|
+
biasScore += 15;
|
|
456
|
+
reasoning.push(`Price in discount zone (${premiumDiscount.fibLevel.toFixed(0)}% of range)`);
|
|
457
|
+
}
|
|
458
|
+
else if (premiumDiscount.zone === "premium") {
|
|
459
|
+
biasScore -= 15;
|
|
460
|
+
reasoning.push(`Price in premium zone (${premiumDiscount.fibLevel.toFixed(0)}% of range)`);
|
|
461
|
+
}
|
|
462
|
+
// Unfilled FVGs
|
|
463
|
+
const bullishUnfilled = unfilledFVGs.filter((f) => f.type === "bullish" && f.bottom > currentPrice);
|
|
464
|
+
const bearishUnfilled = unfilledFVGs.filter((f) => f.type === "bearish" && f.top < currentPrice);
|
|
465
|
+
if (bullishUnfilled.length > 0) {
|
|
466
|
+
reasoning.push(`${bullishUnfilled.length} unfilled bullish FVG(s) above`);
|
|
467
|
+
}
|
|
468
|
+
if (bearishUnfilled.length > 0) {
|
|
469
|
+
reasoning.push(`${bearishUnfilled.length} unfilled bearish FVG(s) below`);
|
|
470
|
+
}
|
|
471
|
+
// Liquidity targets
|
|
472
|
+
if (liquidity.buySide.length > 0) {
|
|
473
|
+
const nearestBuySide = liquidity.buySide[0];
|
|
474
|
+
reasoning.push(`Buy-side liquidity at $${nearestBuySide.level.toFixed(2)} (${nearestBuySide.strength} touches)`);
|
|
475
|
+
}
|
|
476
|
+
if (liquidity.sellSide.length > 0) {
|
|
477
|
+
const nearestSellSide = liquidity.sellSide[0];
|
|
478
|
+
reasoning.push(`Sell-side liquidity at $${nearestSellSide.level.toFixed(2)} (${nearestSellSide.strength} touches)`);
|
|
479
|
+
}
|
|
480
|
+
const biasDirection = biasScore > 20 ? "bullish" : biasScore < -20 ? "bearish" : "neutral";
|
|
481
|
+
const confidence = Math.min(Math.abs(biasScore), 100);
|
|
482
|
+
return {
|
|
483
|
+
marketStructure: {
|
|
484
|
+
trend: marketTrend,
|
|
485
|
+
lastBOS,
|
|
486
|
+
lastCHoCH,
|
|
487
|
+
swingHighs: swingHighs.slice(-10),
|
|
488
|
+
swingLows: swingLows.slice(-10),
|
|
489
|
+
},
|
|
490
|
+
orderBlocks: {
|
|
491
|
+
bullish: bullishOBs.slice(0, 5),
|
|
492
|
+
bearish: bearishOBs.slice(0, 5),
|
|
493
|
+
nearest: nearestOB,
|
|
494
|
+
},
|
|
495
|
+
fairValueGaps: {
|
|
496
|
+
bullish: bullishFVGs.slice(0, 5),
|
|
497
|
+
bearish: bearishFVGs.slice(0, 5),
|
|
498
|
+
unfilled: unfilledFVGs.slice(0, 5),
|
|
499
|
+
},
|
|
500
|
+
liquidity: {
|
|
501
|
+
buySide: liquidity.buySide,
|
|
502
|
+
sellSide: liquidity.sellSide,
|
|
503
|
+
nextTarget: nextLiqTarget,
|
|
504
|
+
},
|
|
505
|
+
premiumDiscount,
|
|
506
|
+
bias: {
|
|
507
|
+
direction: biasDirection,
|
|
508
|
+
confidence,
|
|
509
|
+
reasoning,
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
}
|