prab-cli 1.2.6 → 1.2.7
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 +41 -0
- package/dist/lib/crypto/analyzer.js +397 -93
- package/dist/lib/crypto/data-fetcher.js +342 -50
- package/dist/lib/crypto/index.js +12 -1
- package/dist/lib/crypto/signal-generator.js +278 -3
- package/dist/lib/crypto/strategy.js +673 -0
- package/dist/lib/slash-commands.js +6 -0
- package/dist/server/index.js +70 -0
- package/package.json +3 -3
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Technical Analysis Module
|
|
4
4
|
* Calculates EMA and generates trading signals
|
|
5
|
+
* Primary indicators: EMA 10 and EMA 20 with tilt detection
|
|
5
6
|
*/
|
|
6
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
8
|
exports.calculateEMA = calculateEMA;
|
|
8
9
|
exports.calculateAllEMAs = calculateAllEMAs;
|
|
10
|
+
exports.calculateEMATilt = calculateEMATilt;
|
|
11
|
+
exports.checkMarketConditions = checkMarketConditions;
|
|
9
12
|
exports.calculateIndicators = calculateIndicators;
|
|
10
13
|
exports.generateSignal = generateSignal;
|
|
11
14
|
exports.formatSignalSummary = formatSignalSummary;
|
|
@@ -32,19 +35,180 @@ function calculateEMA(prices, period) {
|
|
|
32
35
|
return ema;
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
35
|
-
* Calculate all EMA values
|
|
38
|
+
* Calculate all EMA values (primary: 10 and 20)
|
|
36
39
|
*/
|
|
37
40
|
function calculateAllEMAs(candles) {
|
|
38
41
|
const closePrices = candles.map((c) => c.close);
|
|
39
42
|
return {
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
ema10: calculateEMA(closePrices, 10),
|
|
44
|
+
ema20: calculateEMA(closePrices, 20),
|
|
42
45
|
ema50: calculateEMA(closePrices, 50),
|
|
43
46
|
ema200: calculateEMA(closePrices, 200),
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
/**
|
|
47
|
-
*
|
|
50
|
+
* Calculate EMA tilt/slope
|
|
51
|
+
* Looks at recent EMA values to determine direction and strength
|
|
52
|
+
* @param emaValues - Array of EMA values
|
|
53
|
+
* @param lookback - Number of periods to analyze (default 3)
|
|
54
|
+
* @returns Tilt information including direction, angle, and strength
|
|
55
|
+
*/
|
|
56
|
+
function calculateEMATilt(emaValues, lookback = 3) {
|
|
57
|
+
if (emaValues.length < lookback + 1) {
|
|
58
|
+
return { direction: "flat", angle: 0, strength: "LOW", slopePercent: 0 };
|
|
59
|
+
}
|
|
60
|
+
const current = emaValues[emaValues.length - 1];
|
|
61
|
+
const previous = emaValues[emaValues.length - 1 - lookback];
|
|
62
|
+
// Calculate percentage change (slope)
|
|
63
|
+
const slopePercent = ((current - previous) / previous) * 100;
|
|
64
|
+
// Normalize to angle-like value (-100 to 100)
|
|
65
|
+
// Multiply by factor to make small changes more visible
|
|
66
|
+
const angle = Math.max(-100, Math.min(100, slopePercent * 50));
|
|
67
|
+
// Determine direction
|
|
68
|
+
let direction;
|
|
69
|
+
if (slopePercent > 0.05) {
|
|
70
|
+
direction = "bullish";
|
|
71
|
+
}
|
|
72
|
+
else if (slopePercent < -0.05) {
|
|
73
|
+
direction = "bearish";
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
direction = "flat";
|
|
77
|
+
}
|
|
78
|
+
// Determine strength based on slope magnitude
|
|
79
|
+
let strength;
|
|
80
|
+
const absSlope = Math.abs(slopePercent);
|
|
81
|
+
if (absSlope > 0.3) {
|
|
82
|
+
strength = "HIGH";
|
|
83
|
+
}
|
|
84
|
+
else if (absSlope > 0.1) {
|
|
85
|
+
strength = "MEDIUM";
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
strength = "LOW";
|
|
89
|
+
}
|
|
90
|
+
return { direction, angle, strength, slopePercent };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check market conditions for trade validity
|
|
94
|
+
* Detects sideways markets, EMA intersections, and validates EMA angle
|
|
95
|
+
* Based on visual analysis: good trades have clear EMA separation and optimal angle
|
|
96
|
+
*
|
|
97
|
+
* @param ema10Values - Array of EMA10 values
|
|
98
|
+
* @param ema20Values - Array of EMA20 values
|
|
99
|
+
* @param ema10Tilt - EMA10 tilt information
|
|
100
|
+
* @param ema20Tilt - EMA20 tilt information
|
|
101
|
+
* @param currentPrice - Current price
|
|
102
|
+
* @returns Market condition check result
|
|
103
|
+
*/
|
|
104
|
+
function checkMarketConditions(ema10Values, ema20Values, ema10Tilt, ema20Tilt, currentPrice) {
|
|
105
|
+
const currentEMA10 = ema10Values[ema10Values.length - 1];
|
|
106
|
+
const currentEMA20 = ema20Values[ema20Values.length - 1];
|
|
107
|
+
// Calculate EMA separation percentage
|
|
108
|
+
const emaSeparationPercent = Math.abs((currentEMA10 - currentEMA20) / currentEMA20) * 100;
|
|
109
|
+
// Check for EMA intersection/convergence (EMAs too close together)
|
|
110
|
+
// If separation is less than 0.15%, EMAs are effectively intersecting
|
|
111
|
+
const emasIntersecting = emaSeparationPercent < 0.15;
|
|
112
|
+
// Check for sideways market: EMAs are flat or diverging in different directions
|
|
113
|
+
// Sideways when: both tilts are LOW strength, or tilts are in opposite directions
|
|
114
|
+
const bothTiltsFlat = ema10Tilt.strength === "LOW" && ema20Tilt.strength === "LOW";
|
|
115
|
+
const tiltsConflicting = ema10Tilt.direction !== "flat" &&
|
|
116
|
+
ema20Tilt.direction !== "flat" &&
|
|
117
|
+
ema10Tilt.direction !== ema20Tilt.direction;
|
|
118
|
+
// Check for recent crossovers (looking back 5 candles) - indicates choppy market
|
|
119
|
+
let recentCrossoverCount = 0;
|
|
120
|
+
const lookback = Math.min(5, ema10Values.length - 1, ema20Values.length - 1);
|
|
121
|
+
for (let i = 1; i <= lookback; i++) {
|
|
122
|
+
const prevIdx = ema10Values.length - 1 - i;
|
|
123
|
+
const currIdx = ema10Values.length - i;
|
|
124
|
+
if (prevIdx >= 0 && currIdx >= 0) {
|
|
125
|
+
const prevAbove = ema10Values[prevIdx] > ema20Values[prevIdx];
|
|
126
|
+
const currAbove = ema10Values[currIdx] > ema20Values[currIdx];
|
|
127
|
+
if (prevAbove !== currAbove) {
|
|
128
|
+
recentCrossoverCount++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Multiple crossovers indicate choppy/sideways market
|
|
133
|
+
const multipleCrossovers = recentCrossoverCount >= 2;
|
|
134
|
+
// Check if EMA10 angle is in optimal range for trading
|
|
135
|
+
// - Not too flat (abs slope < 0.05%) = no trend
|
|
136
|
+
// - Not too steep (abs slope > 1.5%) = extreme/overextended
|
|
137
|
+
// Optimal range: 0.1% to 0.8% slope (good momentum without being extreme)
|
|
138
|
+
const absSlope = Math.abs(ema10Tilt.slopePercent);
|
|
139
|
+
const tooFlat = absSlope < 0.08;
|
|
140
|
+
const tooSteep = absSlope > 1.2;
|
|
141
|
+
const emaAngleInOptimalRange = !tooFlat && !tooSteep;
|
|
142
|
+
// Determine if market is sideways
|
|
143
|
+
const isSideways = bothTiltsFlat ||
|
|
144
|
+
tiltsConflicting ||
|
|
145
|
+
multipleCrossovers ||
|
|
146
|
+
(emasIntersecting && !emaAngleInOptimalRange);
|
|
147
|
+
// Final decision: should we trade?
|
|
148
|
+
let shouldTrade = true;
|
|
149
|
+
let reason = "Market conditions favorable for trading";
|
|
150
|
+
if (emasIntersecting) {
|
|
151
|
+
shouldTrade = false;
|
|
152
|
+
reason = `EMAs intersecting (separation: ${emaSeparationPercent.toFixed(3)}%) - wait for clear trend`;
|
|
153
|
+
}
|
|
154
|
+
else if (isSideways) {
|
|
155
|
+
shouldTrade = false;
|
|
156
|
+
if (multipleCrossovers) {
|
|
157
|
+
reason = "Choppy market - multiple EMA crossovers detected, wait for trend to establish";
|
|
158
|
+
}
|
|
159
|
+
else if (tiltsConflicting) {
|
|
160
|
+
reason = "EMAs diverging in opposite directions - conflicting signals, wait for alignment";
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
reason = "Sideways market - EMAs showing no clear direction, wait for breakout";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (tooFlat) {
|
|
167
|
+
shouldTrade = false;
|
|
168
|
+
reason = `EMA10 angle too flat (${ema10Tilt.slopePercent.toFixed(3)}%) - no momentum, wait for trend`;
|
|
169
|
+
}
|
|
170
|
+
else if (tooSteep) {
|
|
171
|
+
shouldTrade = false;
|
|
172
|
+
reason = `EMA10 angle too steep (${ema10Tilt.slopePercent.toFixed(3)}%) - overextended, wait for pullback`;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
isSideways,
|
|
176
|
+
emasIntersecting,
|
|
177
|
+
emaSeparationPercent,
|
|
178
|
+
emaAngleInOptimalRange,
|
|
179
|
+
shouldTrade,
|
|
180
|
+
reason,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Check if EMA 10 is tilting towards the candle (high probability setup)
|
|
185
|
+
* @param currentPrice - Current candle close price
|
|
186
|
+
* @param ema10 - Current EMA 10 value
|
|
187
|
+
* @param ema10Tilt - EMA 10 tilt information
|
|
188
|
+
* @param ema20 - Current EMA 20 value
|
|
189
|
+
* @returns true if high probability setup detected
|
|
190
|
+
*/
|
|
191
|
+
function isHighProbabilitySetup(currentPrice, ema10, ema10Tilt, ema20) {
|
|
192
|
+
const priceAboveEMA10 = currentPrice > ema10;
|
|
193
|
+
const priceAboveEMA20 = currentPrice > ema20;
|
|
194
|
+
const ema10AboveEMA20 = ema10 > ema20;
|
|
195
|
+
// High probability BUY: Price above EMAs, EMA10 > EMA20, EMA10 tilting up
|
|
196
|
+
if (priceAboveEMA10 && ema10AboveEMA20 && ema10Tilt.direction === "bullish") {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
// High probability SELL: Price below EMAs, EMA10 < EMA20, EMA10 tilting down
|
|
200
|
+
if (!priceAboveEMA10 && !ema10AboveEMA20 && ema10Tilt.direction === "bearish") {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
// Reversal setup: Price touches EMA10 from above/below with strong tilt
|
|
204
|
+
const distanceToEMA10 = Math.abs((currentPrice - ema10) / ema10) * 100;
|
|
205
|
+
if (distanceToEMA10 < 0.5 && ema10Tilt.strength === "HIGH") {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Detect EMA crossover patterns (EMA10 vs EMA20)
|
|
48
212
|
*/
|
|
49
213
|
function detectCrossover(shortEMA, longEMA, lookback = 3) {
|
|
50
214
|
if (shortEMA.length < lookback + 1 || longEMA.length < lookback + 1) {
|
|
@@ -54,11 +218,11 @@ function detectCrossover(shortEMA, longEMA, lookback = 3) {
|
|
|
54
218
|
const currentLong = longEMA[longEMA.length - 1];
|
|
55
219
|
const prevShort = shortEMA[shortEMA.length - lookback];
|
|
56
220
|
const prevLong = longEMA[longEMA.length - lookback];
|
|
57
|
-
// Golden cross:
|
|
221
|
+
// Golden cross: EMA10 crosses above EMA20
|
|
58
222
|
if (prevShort <= prevLong && currentShort > currentLong) {
|
|
59
223
|
return "golden";
|
|
60
224
|
}
|
|
61
|
-
// Death cross:
|
|
225
|
+
// Death cross: EMA10 crosses below EMA20
|
|
62
226
|
if (prevShort >= prevLong && currentShort < currentLong) {
|
|
63
227
|
return "death";
|
|
64
228
|
}
|
|
@@ -67,8 +231,8 @@ function detectCrossover(shortEMA, longEMA, lookback = 3) {
|
|
|
67
231
|
/**
|
|
68
232
|
* Determine price position relative to EMAs
|
|
69
233
|
*/
|
|
70
|
-
function getPriceVsEMA(currentPrice,
|
|
71
|
-
const aboveCount = [
|
|
234
|
+
function getPriceVsEMA(currentPrice, ema10, ema20, ema50) {
|
|
235
|
+
const aboveCount = [ema10, ema20, ema50].filter((ema) => currentPrice > ema).length;
|
|
72
236
|
if (aboveCount === 3)
|
|
73
237
|
return "above_all";
|
|
74
238
|
if (aboveCount === 0)
|
|
@@ -76,68 +240,125 @@ function getPriceVsEMA(currentPrice, ema9, ema21, ema50) {
|
|
|
76
240
|
return "mixed";
|
|
77
241
|
}
|
|
78
242
|
/**
|
|
79
|
-
* Calculate trend strength based on EMA alignment
|
|
243
|
+
* Calculate trend strength based on EMA alignment and tilt
|
|
80
244
|
*/
|
|
81
|
-
function calculateTrendStrength(currentPrice,
|
|
245
|
+
function calculateTrendStrength(currentPrice, ema10, ema20, ema50, ema200, ema10Tilt) {
|
|
82
246
|
let strength = 0;
|
|
83
|
-
// Check EMA alignment (bullish:
|
|
84
|
-
if (
|
|
85
|
-
strength +=
|
|
86
|
-
if (
|
|
87
|
-
strength +=
|
|
247
|
+
// Check EMA alignment (bullish: 10 > 20 > 50 > 200)
|
|
248
|
+
if (ema10 > ema20)
|
|
249
|
+
strength += 15;
|
|
250
|
+
if (ema20 > ema50)
|
|
251
|
+
strength += 15;
|
|
88
252
|
if (ema200 !== undefined && ema50 > ema200)
|
|
89
|
-
strength +=
|
|
253
|
+
strength += 15;
|
|
90
254
|
// Check price vs EMAs
|
|
91
|
-
if (currentPrice >
|
|
255
|
+
if (currentPrice > ema10)
|
|
92
256
|
strength += 10;
|
|
93
|
-
if (currentPrice >
|
|
257
|
+
if (currentPrice > ema20)
|
|
94
258
|
strength += 10;
|
|
95
259
|
if (currentPrice > ema50)
|
|
96
260
|
strength += 10;
|
|
97
261
|
if (ema200 !== undefined && currentPrice > ema200)
|
|
98
262
|
strength += 10;
|
|
263
|
+
// Tilt adds significant strength
|
|
264
|
+
if (ema10Tilt.strength === "HIGH")
|
|
265
|
+
strength += 15;
|
|
266
|
+
else if (ema10Tilt.strength === "MEDIUM")
|
|
267
|
+
strength += 10;
|
|
268
|
+
else
|
|
269
|
+
strength += 5;
|
|
99
270
|
return Math.min(strength, 100);
|
|
100
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Determine overall probability level
|
|
274
|
+
*/
|
|
275
|
+
function determineProbability(ema10Tilt, ema20Tilt, crossover, priceVsEMA) {
|
|
276
|
+
let score = 0;
|
|
277
|
+
// Strong tilt in same direction = high probability
|
|
278
|
+
if (ema10Tilt.strength === "HIGH")
|
|
279
|
+
score += 3;
|
|
280
|
+
else if (ema10Tilt.strength === "MEDIUM")
|
|
281
|
+
score += 2;
|
|
282
|
+
else
|
|
283
|
+
score += 1;
|
|
284
|
+
if (ema20Tilt.strength === "HIGH")
|
|
285
|
+
score += 2;
|
|
286
|
+
else if (ema20Tilt.strength === "MEDIUM")
|
|
287
|
+
score += 1;
|
|
288
|
+
// Tilts aligned = higher probability
|
|
289
|
+
if (ema10Tilt.direction === ema20Tilt.direction && ema10Tilt.direction !== "flat") {
|
|
290
|
+
score += 2;
|
|
291
|
+
}
|
|
292
|
+
// Recent crossover = higher probability
|
|
293
|
+
if (crossover !== "none")
|
|
294
|
+
score += 2;
|
|
295
|
+
// Clear price position = higher probability
|
|
296
|
+
if (priceVsEMA !== "mixed")
|
|
297
|
+
score += 1;
|
|
298
|
+
if (score >= 7)
|
|
299
|
+
return "HIGH";
|
|
300
|
+
if (score >= 4)
|
|
301
|
+
return "MEDIUM";
|
|
302
|
+
return "LOW";
|
|
303
|
+
}
|
|
101
304
|
/**
|
|
102
305
|
* Calculate technical indicators
|
|
103
306
|
*/
|
|
104
307
|
function calculateIndicators(data) {
|
|
105
308
|
const ema = calculateAllEMAs(data.candles);
|
|
106
|
-
const
|
|
107
|
-
const
|
|
309
|
+
const currentEMA10 = ema.ema10[ema.ema10.length - 1] || 0;
|
|
310
|
+
const currentEMA20 = ema.ema20[ema.ema20.length - 1] || 0;
|
|
108
311
|
const currentEMA50 = ema.ema50[ema.ema50.length - 1] || 0;
|
|
109
312
|
const currentEMA200 = ema.ema200.length > 0 ? ema.ema200[ema.ema200.length - 1] : undefined;
|
|
110
|
-
//
|
|
111
|
-
const
|
|
313
|
+
// Calculate EMA tilts (key indicator for probability)
|
|
314
|
+
const ema10Tilt = calculateEMATilt(ema.ema10);
|
|
315
|
+
const ema20Tilt = calculateEMATilt(ema.ema20);
|
|
316
|
+
// Detect crossover between EMA10 and EMA20
|
|
317
|
+
const emaCrossover = detectCrossover(ema.ema10, ema.ema20);
|
|
112
318
|
// Price vs EMA position
|
|
113
|
-
const priceVsEMA = getPriceVsEMA(data.currentPrice,
|
|
114
|
-
// Determine trend
|
|
319
|
+
const priceVsEMA = getPriceVsEMA(data.currentPrice, currentEMA10, currentEMA20, currentEMA50);
|
|
320
|
+
// Determine trend based on EMA alignment
|
|
115
321
|
let trend;
|
|
116
|
-
if (
|
|
322
|
+
if (currentEMA10 > currentEMA20 && currentEMA20 > currentEMA50) {
|
|
117
323
|
trend = "bullish";
|
|
118
324
|
}
|
|
119
|
-
else if (
|
|
325
|
+
else if (currentEMA10 < currentEMA20 && currentEMA20 < currentEMA50) {
|
|
120
326
|
trend = "bearish";
|
|
121
327
|
}
|
|
122
328
|
else {
|
|
123
329
|
trend = "neutral";
|
|
124
330
|
}
|
|
125
|
-
// Calculate trend strength
|
|
126
|
-
const trendStrength = calculateTrendStrength(data.currentPrice,
|
|
331
|
+
// Calculate trend strength (includes tilt)
|
|
332
|
+
const trendStrength = calculateTrendStrength(data.currentPrice, currentEMA10, currentEMA20, currentEMA50, currentEMA200, ema10Tilt);
|
|
333
|
+
// Determine probability level
|
|
334
|
+
const probability = determineProbability(ema10Tilt, ema20Tilt, emaCrossover, priceVsEMA);
|
|
335
|
+
// Check for high probability alert trigger
|
|
336
|
+
const alertTrigger = isHighProbabilitySetup(data.currentPrice, currentEMA10, ema10Tilt, currentEMA20);
|
|
337
|
+
// Check market conditions (sideways, intersecting EMAs, angle validation)
|
|
338
|
+
const marketCondition = checkMarketConditions(ema.ema10, ema.ema20, ema10Tilt, ema20Tilt, data.currentPrice);
|
|
127
339
|
return {
|
|
128
340
|
ema,
|
|
129
|
-
|
|
130
|
-
|
|
341
|
+
currentEMA10,
|
|
342
|
+
currentEMA20,
|
|
131
343
|
currentEMA50,
|
|
132
344
|
currentEMA200: currentEMA200 || 0,
|
|
345
|
+
// Legacy aliases for compatibility
|
|
346
|
+
currentEMA9: currentEMA10,
|
|
347
|
+
currentEMA21: currentEMA20,
|
|
348
|
+
ema10Tilt,
|
|
349
|
+
ema20Tilt,
|
|
133
350
|
emaCrossover,
|
|
134
351
|
priceVsEMA,
|
|
135
352
|
trend,
|
|
136
353
|
trendStrength,
|
|
354
|
+
probability,
|
|
355
|
+
alertTrigger,
|
|
356
|
+
marketCondition,
|
|
137
357
|
};
|
|
138
358
|
}
|
|
139
359
|
/**
|
|
140
|
-
* Generate trading signal based on EMA analysis
|
|
360
|
+
* Generate trading signal based on EMA 10/20 analysis with tilt detection
|
|
361
|
+
* Now includes market condition filtering to avoid sideways/choppy markets
|
|
141
362
|
*/
|
|
142
363
|
function generateSignal(data) {
|
|
143
364
|
const indicators = calculateIndicators(data);
|
|
@@ -146,99 +367,162 @@ function generateSignal(data) {
|
|
|
146
367
|
let confidence = 50;
|
|
147
368
|
let stopLoss = 3;
|
|
148
369
|
let takeProfit = 6;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
reasoning.push(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
370
|
+
const { ema10Tilt, ema20Tilt, alertTrigger, probability, marketCondition } = indicators;
|
|
371
|
+
// === MARKET CONDITION CHECK (Priority Filter) ===
|
|
372
|
+
// Check for sideways market, intersecting EMAs, or suboptimal EMA angle
|
|
373
|
+
if (!marketCondition.shouldTrade) {
|
|
374
|
+
reasoning.push(`⚠️ ${marketCondition.reason}`);
|
|
375
|
+
// Add specific details about the market condition
|
|
376
|
+
if (marketCondition.emasIntersecting) {
|
|
377
|
+
reasoning.push(`EMA separation: ${marketCondition.emaSeparationPercent.toFixed(3)}% (too close)`);
|
|
378
|
+
}
|
|
379
|
+
if (marketCondition.isSideways) {
|
|
380
|
+
reasoning.push("Market showing ranging/sideways behavior");
|
|
381
|
+
}
|
|
382
|
+
if (!marketCondition.emaAngleInOptimalRange) {
|
|
383
|
+
const absSlope = Math.abs(ema10Tilt.slopePercent);
|
|
384
|
+
if (absSlope < 0.08) {
|
|
385
|
+
reasoning.push(`EMA10 slope: ${ema10Tilt.slopePercent.toFixed(3)}% (too flat for reliable signal)`);
|
|
386
|
+
}
|
|
387
|
+
else if (absSlope > 1.2) {
|
|
388
|
+
reasoning.push(`EMA10 slope: ${ema10Tilt.slopePercent.toFixed(3)}% (too steep, may reverse)`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Return HOLD with reduced confidence
|
|
392
|
+
return {
|
|
393
|
+
signal: "HOLD",
|
|
394
|
+
confidence: 25,
|
|
395
|
+
stopLoss: 3,
|
|
396
|
+
takeProfit: 6,
|
|
397
|
+
indicators,
|
|
398
|
+
reasoning,
|
|
399
|
+
};
|
|
159
400
|
}
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
401
|
+
// Add positive market condition note
|
|
402
|
+
reasoning.push(`✓ EMA separation: ${marketCondition.emaSeparationPercent.toFixed(2)}% - clear trend`);
|
|
403
|
+
// === PRIMARY SIGNAL: EMA 10 Tilt Analysis ===
|
|
404
|
+
if (ema10Tilt.direction === "bullish") {
|
|
405
|
+
if (ema10Tilt.strength === "HIGH") {
|
|
406
|
+
signal = "BUY";
|
|
407
|
+
confidence += 30;
|
|
408
|
+
reasoning.push(`EMA10 strongly tilted UP (${ema10Tilt.slopePercent.toFixed(3)}%) - HIGH probability`);
|
|
409
|
+
}
|
|
410
|
+
else if (ema10Tilt.strength === "MEDIUM") {
|
|
411
|
+
signal = "BUY";
|
|
412
|
+
confidence += 20;
|
|
413
|
+
reasoning.push(`EMA10 tilted UP (${ema10Tilt.slopePercent.toFixed(3)}%) - MEDIUM probability`);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
reasoning.push(`EMA10 slightly tilted UP - weak signal`);
|
|
165
417
|
}
|
|
166
|
-
reasoning.push("Price is above all major EMAs (9, 21, 50)");
|
|
167
418
|
}
|
|
168
|
-
else if (
|
|
169
|
-
if (
|
|
170
|
-
signal =
|
|
171
|
-
confidence +=
|
|
419
|
+
else if (ema10Tilt.direction === "bearish") {
|
|
420
|
+
if (ema10Tilt.strength === "HIGH") {
|
|
421
|
+
signal = "SELL";
|
|
422
|
+
confidence += 30;
|
|
423
|
+
reasoning.push(`EMA10 strongly tilted DOWN (${ema10Tilt.slopePercent.toFixed(3)}%) - HIGH probability`);
|
|
424
|
+
}
|
|
425
|
+
else if (ema10Tilt.strength === "MEDIUM") {
|
|
426
|
+
signal = "SELL";
|
|
427
|
+
confidence += 20;
|
|
428
|
+
reasoning.push(`EMA10 tilted DOWN (${ema10Tilt.slopePercent.toFixed(3)}%) - MEDIUM probability`);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
reasoning.push(`EMA10 slightly tilted DOWN - weak signal`);
|
|
172
432
|
}
|
|
173
|
-
reasoning.push("Price is below all major EMAs (9, 21, 50)");
|
|
174
433
|
}
|
|
175
434
|
else {
|
|
176
|
-
reasoning.push("
|
|
435
|
+
reasoning.push("EMA10 is flat - no directional bias");
|
|
436
|
+
}
|
|
437
|
+
// === EMA 20 Tilt Confirmation ===
|
|
438
|
+
if (ema20Tilt.direction === ema10Tilt.direction && ema10Tilt.direction !== "flat") {
|
|
439
|
+
confidence += 15;
|
|
440
|
+
reasoning.push(`EMA20 confirms direction (${ema20Tilt.direction}) - trend alignment`);
|
|
441
|
+
}
|
|
442
|
+
else if (ema20Tilt.direction !== ema10Tilt.direction && ema20Tilt.direction !== "flat") {
|
|
443
|
+
confidence -= 10;
|
|
444
|
+
reasoning.push(`Warning: EMA20 diverging from EMA10 - potential reversal`);
|
|
445
|
+
}
|
|
446
|
+
// === EMA Crossover signals ===
|
|
447
|
+
if (indicators.emaCrossover === "golden") {
|
|
448
|
+
if (signal !== "SELL")
|
|
449
|
+
signal = "BUY";
|
|
450
|
+
confidence += 20;
|
|
451
|
+
reasoning.push("Golden cross detected (EMA10 crossed above EMA20)");
|
|
177
452
|
}
|
|
178
|
-
|
|
179
|
-
|
|
453
|
+
else if (indicators.emaCrossover === "death") {
|
|
454
|
+
if (signal !== "BUY")
|
|
455
|
+
signal = "SELL";
|
|
456
|
+
confidence += 20;
|
|
457
|
+
reasoning.push("Death cross detected (EMA10 crossed below EMA20)");
|
|
458
|
+
}
|
|
459
|
+
// === Price position relative to EMAs ===
|
|
460
|
+
if (indicators.priceVsEMA === "above_all") {
|
|
180
461
|
if (signal !== "SELL") {
|
|
462
|
+
signal = signal === "HOLD" ? "BUY" : signal;
|
|
181
463
|
confidence += 10;
|
|
182
464
|
}
|
|
183
|
-
reasoning.push("
|
|
465
|
+
reasoning.push("Price above all EMAs (10, 20, 50) - bullish structure");
|
|
184
466
|
}
|
|
185
|
-
else if (indicators.
|
|
467
|
+
else if (indicators.priceVsEMA === "below_all") {
|
|
186
468
|
if (signal !== "BUY") {
|
|
469
|
+
signal = signal === "HOLD" ? "SELL" : signal;
|
|
187
470
|
confidence += 10;
|
|
188
471
|
}
|
|
189
|
-
reasoning.push("
|
|
472
|
+
reasoning.push("Price below all EMAs (10, 20, 50) - bearish structure");
|
|
190
473
|
}
|
|
191
474
|
else {
|
|
192
|
-
reasoning.push("
|
|
475
|
+
reasoning.push("Price mixed relative to EMAs - consolidation");
|
|
476
|
+
}
|
|
477
|
+
// === Alert Trigger (High Probability Setup) ===
|
|
478
|
+
if (alertTrigger) {
|
|
479
|
+
confidence += 15;
|
|
480
|
+
reasoning.push("⚡ HIGH PROBABILITY SETUP: EMA10 tilting towards price action");
|
|
193
481
|
}
|
|
194
|
-
// EMA200 analysis (
|
|
482
|
+
// === EMA200 analysis (long-term context) ===
|
|
195
483
|
if (indicators.currentEMA200 > 0) {
|
|
196
484
|
if (data.currentPrice > indicators.currentEMA200) {
|
|
197
485
|
if (signal === "BUY")
|
|
198
|
-
confidence +=
|
|
199
|
-
reasoning.push("
|
|
486
|
+
confidence += 5;
|
|
487
|
+
reasoning.push("Above EMA200 - long-term bullish context");
|
|
200
488
|
}
|
|
201
489
|
else {
|
|
202
490
|
if (signal === "SELL")
|
|
203
|
-
confidence +=
|
|
204
|
-
reasoning.push("
|
|
491
|
+
confidence += 5;
|
|
492
|
+
reasoning.push("Below EMA200 - long-term bearish context");
|
|
205
493
|
}
|
|
206
494
|
}
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
else if (Math.abs(data.priceChangePercent24h) > 10) {
|
|
218
|
-
reasoning.push(`Caution: High volatility (${data.priceChangePercent24h.toFixed(2)}% in 24h)`);
|
|
219
|
-
confidence -= 5;
|
|
220
|
-
}
|
|
495
|
+
// === Distance from EMA10 (entry quality) ===
|
|
496
|
+
const distanceFromEMA10 = ((data.currentPrice - indicators.currentEMA10) / indicators.currentEMA10) * 100;
|
|
497
|
+
if (Math.abs(distanceFromEMA10) < 0.3) {
|
|
498
|
+
confidence += 10;
|
|
499
|
+
reasoning.push(`Price near EMA10 (${distanceFromEMA10.toFixed(2)}%) - optimal entry zone`);
|
|
500
|
+
}
|
|
501
|
+
else if (Math.abs(distanceFromEMA10) > 2) {
|
|
502
|
+
confidence -= 5;
|
|
503
|
+
reasoning.push(`Price extended from EMA10 (${distanceFromEMA10.toFixed(2)}%) - wait for pullback`);
|
|
221
504
|
}
|
|
222
|
-
//
|
|
505
|
+
// === Stop-loss and Take-profit calculation ===
|
|
223
506
|
if (signal === "BUY") {
|
|
224
|
-
//
|
|
225
|
-
const
|
|
226
|
-
stopLoss = Math.max(
|
|
227
|
-
takeProfit = stopLoss * 2; // 2:1 risk-reward
|
|
507
|
+
// Stop-loss below EMA20
|
|
508
|
+
const distanceToEMA20 = ((data.currentPrice - indicators.currentEMA20) / data.currentPrice) * 100;
|
|
509
|
+
stopLoss = Math.max(1.5, Math.min(distanceToEMA20 + 0.5, 4));
|
|
510
|
+
takeProfit = stopLoss * 2.5; // 2.5:1 risk-reward
|
|
228
511
|
}
|
|
229
512
|
else if (signal === "SELL") {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
takeProfit = stopLoss * 2;
|
|
513
|
+
const distanceToEMA20 = ((indicators.currentEMA20 - data.currentPrice) / data.currentPrice) * 100;
|
|
514
|
+
stopLoss = Math.max(1.5, Math.min(Math.abs(distanceToEMA20) + 0.5, 4));
|
|
515
|
+
takeProfit = stopLoss * 2.5;
|
|
234
516
|
}
|
|
235
|
-
//
|
|
517
|
+
// === Final confidence adjustment ===
|
|
236
518
|
confidence = Math.min(confidence, 95);
|
|
237
519
|
confidence = Math.max(confidence, 10);
|
|
238
|
-
// If no clear
|
|
239
|
-
if (
|
|
520
|
+
// If no clear signal with weak tilt, recommend HOLD
|
|
521
|
+
if (signal === "HOLD" || (ema10Tilt.strength === "LOW" && indicators.emaCrossover === "none")) {
|
|
240
522
|
signal = "HOLD";
|
|
241
|
-
reasoning.
|
|
523
|
+
if (!reasoning.some((r) => r.includes("waiting"))) {
|
|
524
|
+
reasoning.push("No strong tilt detected - wait for EMA10 to show direction");
|
|
525
|
+
}
|
|
242
526
|
}
|
|
243
527
|
return {
|
|
244
528
|
signal,
|
|
@@ -259,17 +543,37 @@ function formatSignalSummary(signal, symbol, price) {
|
|
|
259
543
|
: signal.indicators.trend === "bearish"
|
|
260
544
|
? "\u{2198}\u{FE0F}"
|
|
261
545
|
: "\u{27A1}\u{FE0F}";
|
|
546
|
+
const tiltEmoji = signal.indicators.ema10Tilt.direction === "bullish"
|
|
547
|
+
? "\u{2191}"
|
|
548
|
+
: signal.indicators.ema10Tilt.direction === "bearish"
|
|
549
|
+
? "\u{2193}"
|
|
550
|
+
: "\u{2194}";
|
|
551
|
+
const probEmoji = signal.indicators.probability === "HIGH"
|
|
552
|
+
? "\u{1F525}"
|
|
553
|
+
: signal.indicators.probability === "MEDIUM"
|
|
554
|
+
? "\u{1F7E1}"
|
|
555
|
+
: "\u{26AA}";
|
|
556
|
+
const marketCondition = signal.indicators.marketCondition;
|
|
557
|
+
const marketStatus = marketCondition?.shouldTrade
|
|
558
|
+
? "✓ Tradeable"
|
|
559
|
+
: "⚠️ Avoid (Sideways/Intersecting)";
|
|
262
560
|
return `
|
|
263
561
|
${signalEmoji} Signal: ${signal.signal}
|
|
264
562
|
Confidence: ${signal.confidence}%
|
|
563
|
+
Probability: ${signal.indicators.probability} ${probEmoji}
|
|
564
|
+
Market: ${marketStatus}
|
|
265
565
|
Stop-Loss: ${signal.stopLoss}%
|
|
266
566
|
Take-Profit: ${signal.takeProfit}%
|
|
267
567
|
|
|
268
568
|
Current Price: $${price.toLocaleString()}
|
|
269
569
|
Trend: ${signal.indicators.trend} ${trendEmoji}
|
|
270
|
-
|
|
271
|
-
|
|
570
|
+
|
|
571
|
+
EMA10: $${signal.indicators.currentEMA10.toFixed(2)} ${tiltEmoji} (tilt: ${signal.indicators.ema10Tilt.slopePercent.toFixed(3)}%)
|
|
572
|
+
EMA20: $${signal.indicators.currentEMA20.toFixed(2)}
|
|
573
|
+
EMA Separation: ${marketCondition?.emaSeparationPercent.toFixed(2) || "N/A"}%
|
|
272
574
|
EMA50: $${signal.indicators.currentEMA50.toFixed(2)}
|
|
273
575
|
${signal.indicators.currentEMA200 > 0 ? `EMA200: $${signal.indicators.currentEMA200.toFixed(2)}` : ""}
|
|
576
|
+
${signal.indicators.alertTrigger ? "\n⚡ ALERT: High probability setup detected!" : ""}
|
|
577
|
+
${!marketCondition?.shouldTrade ? `\n⚠️ ${marketCondition?.reason || "Market conditions not favorable"}` : ""}
|
|
274
578
|
`;
|
|
275
579
|
}
|