prab-cli 1.2.0 → 1.2.4
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 +175 -4
- package/dist/lib/config.js +60 -1
- 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 +47 -0
- package/dist/lib/crypto/indicators.js +390 -0
- package/dist/lib/crypto/market-analyzer.js +497 -0
- package/dist/lib/crypto/signal-generator.js +559 -0
- package/dist/lib/crypto/smc-analyzer.js +418 -0
- package/dist/lib/crypto/smc-indicators.js +512 -0
- package/dist/lib/slash-commands.js +24 -0
- package/dist/lib/ui.js +70 -8
- package/package.json +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* global fetch */
|
|
3
|
+
/**
|
|
4
|
+
* Cryptocurrency Data Fetcher
|
|
5
|
+
* Fetches OHLCV data from Binance public API (no API key required)
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.normalizeSymbol = normalizeSymbol;
|
|
9
|
+
exports.fetchOHLCV = fetchOHLCV;
|
|
10
|
+
exports.fetch24hTicker = fetch24hTicker;
|
|
11
|
+
exports.fetchCryptoData = fetchCryptoData;
|
|
12
|
+
exports.getSupportedSymbols = getSupportedSymbols;
|
|
13
|
+
exports.isValidSymbol = isValidSymbol;
|
|
14
|
+
// Common crypto symbols mapping (user-friendly -> Binance format)
|
|
15
|
+
const SYMBOL_MAP = {
|
|
16
|
+
btc: "BTCUSDT",
|
|
17
|
+
bitcoin: "BTCUSDT",
|
|
18
|
+
eth: "ETHUSDT",
|
|
19
|
+
ethereum: "ETHUSDT",
|
|
20
|
+
sol: "SOLUSDT",
|
|
21
|
+
solana: "SOLUSDT",
|
|
22
|
+
xrp: "XRPUSDT",
|
|
23
|
+
ripple: "XRPUSDT",
|
|
24
|
+
doge: "DOGEUSDT",
|
|
25
|
+
dogecoin: "DOGEUSDT",
|
|
26
|
+
ada: "ADAUSDT",
|
|
27
|
+
cardano: "ADAUSDT",
|
|
28
|
+
bnb: "BNBUSDT",
|
|
29
|
+
dot: "DOTUSDT",
|
|
30
|
+
polkadot: "DOTUSDT",
|
|
31
|
+
matic: "MATICUSDT",
|
|
32
|
+
polygon: "MATICUSDT",
|
|
33
|
+
link: "LINKUSDT",
|
|
34
|
+
chainlink: "LINKUSDT",
|
|
35
|
+
avax: "AVAXUSDT",
|
|
36
|
+
avalanche: "AVAXUSDT",
|
|
37
|
+
ltc: "LTCUSDT",
|
|
38
|
+
litecoin: "LTCUSDT",
|
|
39
|
+
atom: "ATOMUSDT",
|
|
40
|
+
cosmos: "ATOMUSDT",
|
|
41
|
+
uni: "UNIUSDT",
|
|
42
|
+
uniswap: "UNIUSDT",
|
|
43
|
+
xlm: "XLMUSDT",
|
|
44
|
+
stellar: "XLMUSDT",
|
|
45
|
+
algo: "ALGOUSDT",
|
|
46
|
+
algorand: "ALGOUSDT",
|
|
47
|
+
near: "NEARUSDT",
|
|
48
|
+
apt: "APTUSDT",
|
|
49
|
+
aptos: "APTUSDT",
|
|
50
|
+
arb: "ARBUSDT",
|
|
51
|
+
arbitrum: "ARBUSDT",
|
|
52
|
+
op: "OPUSDT",
|
|
53
|
+
optimism: "OPUSDT",
|
|
54
|
+
sui: "SUIUSDT",
|
|
55
|
+
pepe: "PEPEUSDT",
|
|
56
|
+
shib: "SHIBUSDT",
|
|
57
|
+
wif: "WIFUSDT",
|
|
58
|
+
};
|
|
59
|
+
const BINANCE_API_BASE = "https://api.binance.com/api/v3";
|
|
60
|
+
/**
|
|
61
|
+
* Normalize symbol to Binance format
|
|
62
|
+
*/
|
|
63
|
+
function normalizeSymbol(input) {
|
|
64
|
+
const lower = input.toLowerCase().trim();
|
|
65
|
+
// Check if it's in our mapping
|
|
66
|
+
if (SYMBOL_MAP[lower]) {
|
|
67
|
+
return SYMBOL_MAP[lower];
|
|
68
|
+
}
|
|
69
|
+
// If already in USDT format, return uppercase
|
|
70
|
+
if (lower.endsWith("usdt")) {
|
|
71
|
+
return lower.toUpperCase();
|
|
72
|
+
}
|
|
73
|
+
// If ends with USD (not USDT), replace with USDT
|
|
74
|
+
if (lower.endsWith("usd")) {
|
|
75
|
+
return lower.slice(0, -3).toUpperCase() + "USDT";
|
|
76
|
+
}
|
|
77
|
+
// If ends with other quote currencies, keep as is
|
|
78
|
+
if (lower.endsWith("btc") || lower.endsWith("eth") || lower.endsWith("bnb")) {
|
|
79
|
+
return lower.toUpperCase();
|
|
80
|
+
}
|
|
81
|
+
// Default: append USDT
|
|
82
|
+
return lower.toUpperCase() + "USDT";
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fetch OHLCV candle data from Binance
|
|
86
|
+
*/
|
|
87
|
+
async function fetchOHLCV(symbol, interval = "1h", limit = 100) {
|
|
88
|
+
const normalizedSymbol = normalizeSymbol(symbol);
|
|
89
|
+
const url = `${BINANCE_API_BASE}/klines?symbol=${normalizedSymbol}&interval=${interval}&limit=${limit}`;
|
|
90
|
+
const response = await fetch(url);
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const error = await response.text();
|
|
93
|
+
throw new Error(`Failed to fetch data for ${normalizedSymbol}: ${error}`);
|
|
94
|
+
}
|
|
95
|
+
const data = await response.json();
|
|
96
|
+
// Binance klines format:
|
|
97
|
+
// [0] Open time, [1] Open, [2] High, [3] Low, [4] Close, [5] Volume, ...
|
|
98
|
+
return data.map((candle) => ({
|
|
99
|
+
timestamp: candle[0],
|
|
100
|
+
open: parseFloat(candle[1]),
|
|
101
|
+
high: parseFloat(candle[2]),
|
|
102
|
+
low: parseFloat(candle[3]),
|
|
103
|
+
close: parseFloat(candle[4]),
|
|
104
|
+
volume: parseFloat(candle[5]),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Fetch 24h ticker data for price change info
|
|
109
|
+
*/
|
|
110
|
+
async function fetch24hTicker(symbol) {
|
|
111
|
+
const normalizedSymbol = normalizeSymbol(symbol);
|
|
112
|
+
const url = `${BINANCE_API_BASE}/ticker/24hr?symbol=${normalizedSymbol}`;
|
|
113
|
+
const response = await fetch(url);
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
const error = await response.text();
|
|
116
|
+
throw new Error(`Failed to fetch ticker for ${normalizedSymbol}: ${error}`);
|
|
117
|
+
}
|
|
118
|
+
const data = await response.json();
|
|
119
|
+
return {
|
|
120
|
+
price: parseFloat(data.lastPrice),
|
|
121
|
+
priceChange: parseFloat(data.priceChange),
|
|
122
|
+
priceChangePercent: parseFloat(data.priceChangePercent),
|
|
123
|
+
high24h: parseFloat(data.highPrice),
|
|
124
|
+
low24h: parseFloat(data.lowPrice),
|
|
125
|
+
volume24h: parseFloat(data.volume),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Fetch complete crypto data for analysis
|
|
130
|
+
*/
|
|
131
|
+
async function fetchCryptoData(symbol, interval = "1h", limit = 100) {
|
|
132
|
+
const normalizedSymbol = normalizeSymbol(symbol);
|
|
133
|
+
// Fetch both OHLCV and 24h ticker in parallel
|
|
134
|
+
const [candles, ticker] = await Promise.all([
|
|
135
|
+
fetchOHLCV(symbol, interval, limit),
|
|
136
|
+
fetch24hTicker(symbol),
|
|
137
|
+
]);
|
|
138
|
+
return {
|
|
139
|
+
symbol: normalizedSymbol,
|
|
140
|
+
interval,
|
|
141
|
+
candles,
|
|
142
|
+
currentPrice: ticker.price,
|
|
143
|
+
priceChange24h: ticker.priceChange,
|
|
144
|
+
priceChangePercent24h: ticker.priceChangePercent,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get list of supported symbols
|
|
149
|
+
*/
|
|
150
|
+
function getSupportedSymbols() {
|
|
151
|
+
return [...new Set(Object.keys(SYMBOL_MAP))];
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Check if a symbol is valid/supported
|
|
155
|
+
*/
|
|
156
|
+
async function isValidSymbol(symbol) {
|
|
157
|
+
try {
|
|
158
|
+
const normalizedSymbol = normalizeSymbol(symbol);
|
|
159
|
+
const url = `${BINANCE_API_BASE}/ticker/price?symbol=${normalizedSymbol}`;
|
|
160
|
+
const response = await fetch(url);
|
|
161
|
+
return response.ok;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Crypto Trading Signal Module
|
|
4
|
+
* Exports all crypto-related functionality
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.displaySMCAnalysis = exports.runSMCAnalysis = exports.calculatePremiumDiscount = exports.findLiquidityZones = exports.findFairValueGaps = exports.findOrderBlocks = exports.findSwingPoints = exports.analyzeSMC = exports.analyzeTrend = exports.calculateSupportResistance = exports.analyzeVolume = exports.calculateATR = exports.calculateBollingerBands = exports.calculateMACD = exports.calculateRSI = exports.analyzeMarket = exports.displayComprehensiveAnalysis = exports.comprehensiveAnalysis = exports.fullSignal = exports.quickSignal = exports.displaySignal = exports.generateTradingSignal = exports.formatSignalSummary = exports.generateSignal = exports.calculateIndicators = exports.calculateAllEMAs = exports.calculateEMA = exports.isValidSymbol = exports.getSupportedSymbols = exports.normalizeSymbol = exports.fetch24hTicker = exports.fetchOHLCV = exports.fetchCryptoData = void 0;
|
|
8
|
+
var data_fetcher_1 = require("./data-fetcher");
|
|
9
|
+
Object.defineProperty(exports, "fetchCryptoData", { enumerable: true, get: function () { return data_fetcher_1.fetchCryptoData; } });
|
|
10
|
+
Object.defineProperty(exports, "fetchOHLCV", { enumerable: true, get: function () { return data_fetcher_1.fetchOHLCV; } });
|
|
11
|
+
Object.defineProperty(exports, "fetch24hTicker", { enumerable: true, get: function () { return data_fetcher_1.fetch24hTicker; } });
|
|
12
|
+
Object.defineProperty(exports, "normalizeSymbol", { enumerable: true, get: function () { return data_fetcher_1.normalizeSymbol; } });
|
|
13
|
+
Object.defineProperty(exports, "getSupportedSymbols", { enumerable: true, get: function () { return data_fetcher_1.getSupportedSymbols; } });
|
|
14
|
+
Object.defineProperty(exports, "isValidSymbol", { enumerable: true, get: function () { return data_fetcher_1.isValidSymbol; } });
|
|
15
|
+
var analyzer_1 = require("./analyzer");
|
|
16
|
+
Object.defineProperty(exports, "calculateEMA", { enumerable: true, get: function () { return analyzer_1.calculateEMA; } });
|
|
17
|
+
Object.defineProperty(exports, "calculateAllEMAs", { enumerable: true, get: function () { return analyzer_1.calculateAllEMAs; } });
|
|
18
|
+
Object.defineProperty(exports, "calculateIndicators", { enumerable: true, get: function () { return analyzer_1.calculateIndicators; } });
|
|
19
|
+
Object.defineProperty(exports, "generateSignal", { enumerable: true, get: function () { return analyzer_1.generateSignal; } });
|
|
20
|
+
Object.defineProperty(exports, "formatSignalSummary", { enumerable: true, get: function () { return analyzer_1.formatSignalSummary; } });
|
|
21
|
+
var signal_generator_1 = require("./signal-generator");
|
|
22
|
+
Object.defineProperty(exports, "generateTradingSignal", { enumerable: true, get: function () { return signal_generator_1.generateTradingSignal; } });
|
|
23
|
+
Object.defineProperty(exports, "displaySignal", { enumerable: true, get: function () { return signal_generator_1.displaySignal; } });
|
|
24
|
+
Object.defineProperty(exports, "quickSignal", { enumerable: true, get: function () { return signal_generator_1.quickSignal; } });
|
|
25
|
+
Object.defineProperty(exports, "fullSignal", { enumerable: true, get: function () { return signal_generator_1.fullSignal; } });
|
|
26
|
+
Object.defineProperty(exports, "comprehensiveAnalysis", { enumerable: true, get: function () { return signal_generator_1.comprehensiveAnalysis; } });
|
|
27
|
+
Object.defineProperty(exports, "displayComprehensiveAnalysis", { enumerable: true, get: function () { return signal_generator_1.displayComprehensiveAnalysis; } });
|
|
28
|
+
var market_analyzer_1 = require("./market-analyzer");
|
|
29
|
+
Object.defineProperty(exports, "analyzeMarket", { enumerable: true, get: function () { return market_analyzer_1.analyzeMarket; } });
|
|
30
|
+
var indicators_1 = require("./indicators");
|
|
31
|
+
Object.defineProperty(exports, "calculateRSI", { enumerable: true, get: function () { return indicators_1.calculateRSI; } });
|
|
32
|
+
Object.defineProperty(exports, "calculateMACD", { enumerable: true, get: function () { return indicators_1.calculateMACD; } });
|
|
33
|
+
Object.defineProperty(exports, "calculateBollingerBands", { enumerable: true, get: function () { return indicators_1.calculateBollingerBands; } });
|
|
34
|
+
Object.defineProperty(exports, "calculateATR", { enumerable: true, get: function () { return indicators_1.calculateATR; } });
|
|
35
|
+
Object.defineProperty(exports, "analyzeVolume", { enumerable: true, get: function () { return indicators_1.analyzeVolume; } });
|
|
36
|
+
Object.defineProperty(exports, "calculateSupportResistance", { enumerable: true, get: function () { return indicators_1.calculateSupportResistance; } });
|
|
37
|
+
Object.defineProperty(exports, "analyzeTrend", { enumerable: true, get: function () { return indicators_1.analyzeTrend; } });
|
|
38
|
+
var smc_indicators_1 = require("./smc-indicators");
|
|
39
|
+
Object.defineProperty(exports, "analyzeSMC", { enumerable: true, get: function () { return smc_indicators_1.analyzeSMC; } });
|
|
40
|
+
Object.defineProperty(exports, "findSwingPoints", { enumerable: true, get: function () { return smc_indicators_1.findSwingPoints; } });
|
|
41
|
+
Object.defineProperty(exports, "findOrderBlocks", { enumerable: true, get: function () { return smc_indicators_1.findOrderBlocks; } });
|
|
42
|
+
Object.defineProperty(exports, "findFairValueGaps", { enumerable: true, get: function () { return smc_indicators_1.findFairValueGaps; } });
|
|
43
|
+
Object.defineProperty(exports, "findLiquidityZones", { enumerable: true, get: function () { return smc_indicators_1.findLiquidityZones; } });
|
|
44
|
+
Object.defineProperty(exports, "calculatePremiumDiscount", { enumerable: true, get: function () { return smc_indicators_1.calculatePremiumDiscount; } });
|
|
45
|
+
var smc_analyzer_1 = require("./smc-analyzer");
|
|
46
|
+
Object.defineProperty(exports, "runSMCAnalysis", { enumerable: true, get: function () { return smc_analyzer_1.runSMCAnalysis; } });
|
|
47
|
+
Object.defineProperty(exports, "displaySMCAnalysis", { enumerable: true, get: function () { return smc_analyzer_1.displaySMCAnalysis; } });
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* global fetch */
|
|
3
|
+
/**
|
|
4
|
+
* Technical Indicators Module
|
|
5
|
+
* Comprehensive technical analysis calculations
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.calculateSMA = calculateSMA;
|
|
9
|
+
exports.calculateEMA = calculateEMA;
|
|
10
|
+
exports.calculateRSI = calculateRSI;
|
|
11
|
+
exports.calculateMACD = calculateMACD;
|
|
12
|
+
exports.calculateBollingerBands = calculateBollingerBands;
|
|
13
|
+
exports.calculateATR = calculateATR;
|
|
14
|
+
exports.analyzeVolume = analyzeVolume;
|
|
15
|
+
exports.calculateSupportResistance = calculateSupportResistance;
|
|
16
|
+
exports.analyzeTrend = analyzeTrend;
|
|
17
|
+
// ============================================
|
|
18
|
+
// MOVING AVERAGES
|
|
19
|
+
// ============================================
|
|
20
|
+
/**
|
|
21
|
+
* Calculate Simple Moving Average
|
|
22
|
+
*/
|
|
23
|
+
function calculateSMA(prices, period) {
|
|
24
|
+
if (prices.length < period)
|
|
25
|
+
return [];
|
|
26
|
+
const sma = [];
|
|
27
|
+
for (let i = period - 1; i < prices.length; i++) {
|
|
28
|
+
let sum = 0;
|
|
29
|
+
for (let j = 0; j < period; j++) {
|
|
30
|
+
sum += prices[i - j];
|
|
31
|
+
}
|
|
32
|
+
sma.push(sum / period);
|
|
33
|
+
}
|
|
34
|
+
return sma;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Calculate Exponential Moving Average
|
|
38
|
+
*/
|
|
39
|
+
function calculateEMA(prices, period) {
|
|
40
|
+
if (prices.length < period)
|
|
41
|
+
return [];
|
|
42
|
+
const ema = [];
|
|
43
|
+
const multiplier = 2 / (period + 1);
|
|
44
|
+
// First EMA is SMA
|
|
45
|
+
let sum = 0;
|
|
46
|
+
for (let i = 0; i < period; i++) {
|
|
47
|
+
sum += prices[i];
|
|
48
|
+
}
|
|
49
|
+
ema.push(sum / period);
|
|
50
|
+
for (let i = period; i < prices.length; i++) {
|
|
51
|
+
const currentEMA = (prices[i] - ema[ema.length - 1]) * multiplier + ema[ema.length - 1];
|
|
52
|
+
ema.push(currentEMA);
|
|
53
|
+
}
|
|
54
|
+
return ema;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Calculate RSI
|
|
58
|
+
*/
|
|
59
|
+
function calculateRSI(prices, period = 14) {
|
|
60
|
+
if (prices.length < period + 1) {
|
|
61
|
+
return { values: [], current: 50, condition: "neutral", divergence: "none" };
|
|
62
|
+
}
|
|
63
|
+
const gains = [];
|
|
64
|
+
const losses = [];
|
|
65
|
+
// Calculate price changes
|
|
66
|
+
for (let i = 1; i < prices.length; i++) {
|
|
67
|
+
const change = prices[i] - prices[i - 1];
|
|
68
|
+
gains.push(change > 0 ? change : 0);
|
|
69
|
+
losses.push(change < 0 ? Math.abs(change) : 0);
|
|
70
|
+
}
|
|
71
|
+
const rsiValues = [];
|
|
72
|
+
// First average
|
|
73
|
+
let avgGain = gains.slice(0, period).reduce((a, b) => a + b, 0) / period;
|
|
74
|
+
let avgLoss = losses.slice(0, period).reduce((a, b) => a + b, 0) / period;
|
|
75
|
+
for (let i = period; i < gains.length; i++) {
|
|
76
|
+
avgGain = (avgGain * (period - 1) + gains[i]) / period;
|
|
77
|
+
avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
|
|
78
|
+
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
|
79
|
+
const rsi = 100 - 100 / (1 + rs);
|
|
80
|
+
rsiValues.push(rsi);
|
|
81
|
+
}
|
|
82
|
+
const current = rsiValues[rsiValues.length - 1] || 50;
|
|
83
|
+
// Determine condition
|
|
84
|
+
let condition = "neutral";
|
|
85
|
+
if (current >= 70)
|
|
86
|
+
condition = "overbought";
|
|
87
|
+
else if (current <= 30)
|
|
88
|
+
condition = "oversold";
|
|
89
|
+
// Check for divergence (simplified)
|
|
90
|
+
let divergence = "none";
|
|
91
|
+
if (rsiValues.length >= 10) {
|
|
92
|
+
const recentRSI = rsiValues.slice(-10);
|
|
93
|
+
const recentPrices = prices.slice(-10);
|
|
94
|
+
const rsiTrend = recentRSI[recentRSI.length - 1] - recentRSI[0];
|
|
95
|
+
const priceTrend = recentPrices[recentPrices.length - 1] - recentPrices[0];
|
|
96
|
+
// Bullish divergence: price making lower lows, RSI making higher lows
|
|
97
|
+
if (priceTrend < 0 && rsiTrend > 0 && current < 40) {
|
|
98
|
+
divergence = "bullish";
|
|
99
|
+
}
|
|
100
|
+
// Bearish divergence: price making higher highs, RSI making lower highs
|
|
101
|
+
if (priceTrend > 0 && rsiTrend < 0 && current > 60) {
|
|
102
|
+
divergence = "bearish";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { values: rsiValues, current, condition, divergence };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Calculate MACD
|
|
109
|
+
*/
|
|
110
|
+
function calculateMACD(prices, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
|
|
111
|
+
const ema12 = calculateEMA(prices, fastPeriod);
|
|
112
|
+
const ema26 = calculateEMA(prices, slowPeriod);
|
|
113
|
+
// Align arrays
|
|
114
|
+
const offset = slowPeriod - fastPeriod;
|
|
115
|
+
const macdLine = [];
|
|
116
|
+
for (let i = 0; i < ema26.length; i++) {
|
|
117
|
+
macdLine.push(ema12[i + offset] - ema26[i]);
|
|
118
|
+
}
|
|
119
|
+
const signalLine = calculateEMA(macdLine, signalPeriod);
|
|
120
|
+
// Align MACD and Signal
|
|
121
|
+
const signalOffset = signalPeriod - 1;
|
|
122
|
+
const histogram = [];
|
|
123
|
+
for (let i = 0; i < signalLine.length; i++) {
|
|
124
|
+
histogram.push(macdLine[i + signalOffset] - signalLine[i]);
|
|
125
|
+
}
|
|
126
|
+
const current = {
|
|
127
|
+
macd: macdLine[macdLine.length - 1] || 0,
|
|
128
|
+
signal: signalLine[signalLine.length - 1] || 0,
|
|
129
|
+
histogram: histogram[histogram.length - 1] || 0,
|
|
130
|
+
};
|
|
131
|
+
// Detect crossover
|
|
132
|
+
let crossover = "none";
|
|
133
|
+
if (histogram.length >= 2) {
|
|
134
|
+
const prevHist = histogram[histogram.length - 2];
|
|
135
|
+
const currHist = histogram[histogram.length - 1];
|
|
136
|
+
if (prevHist <= 0 && currHist > 0)
|
|
137
|
+
crossover = "bullish";
|
|
138
|
+
if (prevHist >= 0 && currHist < 0)
|
|
139
|
+
crossover = "bearish";
|
|
140
|
+
}
|
|
141
|
+
// Momentum direction
|
|
142
|
+
let momentum = "neutral";
|
|
143
|
+
if (histogram.length >= 3) {
|
|
144
|
+
const recent = histogram.slice(-3);
|
|
145
|
+
if (recent[2] > recent[1] && recent[1] > recent[0])
|
|
146
|
+
momentum = "increasing";
|
|
147
|
+
if (recent[2] < recent[1] && recent[1] < recent[0])
|
|
148
|
+
momentum = "decreasing";
|
|
149
|
+
}
|
|
150
|
+
return { macdLine, signalLine, histogram, current, crossover, momentum };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Calculate Bollinger Bands
|
|
154
|
+
*/
|
|
155
|
+
function calculateBollingerBands(prices, period = 20, stdDev = 2) {
|
|
156
|
+
const middle = calculateSMA(prices, period);
|
|
157
|
+
const upper = [];
|
|
158
|
+
const lower = [];
|
|
159
|
+
for (let i = period - 1; i < prices.length; i++) {
|
|
160
|
+
const slice = prices.slice(i - period + 1, i + 1);
|
|
161
|
+
const mean = middle[i - period + 1];
|
|
162
|
+
// Calculate standard deviation
|
|
163
|
+
const squaredDiffs = slice.map((p) => Math.pow(p - mean, 2));
|
|
164
|
+
const variance = squaredDiffs.reduce((a, b) => a + b, 0) / period;
|
|
165
|
+
const std = Math.sqrt(variance);
|
|
166
|
+
upper.push(mean + stdDev * std);
|
|
167
|
+
lower.push(mean - stdDev * std);
|
|
168
|
+
}
|
|
169
|
+
const currentUpper = upper[upper.length - 1] || 0;
|
|
170
|
+
const currentMiddle = middle[middle.length - 1] || 0;
|
|
171
|
+
const currentLower = lower[lower.length - 1] || 0;
|
|
172
|
+
const currentPrice = prices[prices.length - 1];
|
|
173
|
+
// Bandwidth (volatility measure)
|
|
174
|
+
const bandwidth = ((currentUpper - currentLower) / currentMiddle) * 100;
|
|
175
|
+
// %B (where price is within the bands)
|
|
176
|
+
const percentB = ((currentPrice - currentLower) / (currentUpper - currentLower)) * 100;
|
|
177
|
+
// Squeeze detection (low volatility)
|
|
178
|
+
const squeeze = bandwidth < 5; // Adjust threshold as needed
|
|
179
|
+
// Price position
|
|
180
|
+
let pricePosition;
|
|
181
|
+
if (currentPrice > currentUpper)
|
|
182
|
+
pricePosition = "above_upper";
|
|
183
|
+
else if (currentPrice > currentMiddle)
|
|
184
|
+
pricePosition = "above_middle";
|
|
185
|
+
else if (currentPrice > currentLower)
|
|
186
|
+
pricePosition = "below_middle";
|
|
187
|
+
else
|
|
188
|
+
pricePosition = "below_lower";
|
|
189
|
+
return {
|
|
190
|
+
upper,
|
|
191
|
+
middle,
|
|
192
|
+
lower,
|
|
193
|
+
current: {
|
|
194
|
+
upper: currentUpper,
|
|
195
|
+
middle: currentMiddle,
|
|
196
|
+
lower: currentLower,
|
|
197
|
+
bandwidth,
|
|
198
|
+
percentB,
|
|
199
|
+
},
|
|
200
|
+
squeeze,
|
|
201
|
+
pricePosition,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Calculate ATR
|
|
206
|
+
*/
|
|
207
|
+
function calculateATR(candles, period = 14) {
|
|
208
|
+
if (candles.length < period + 1) {
|
|
209
|
+
return { values: [], current: 0, volatility: "medium", percentOfPrice: 0 };
|
|
210
|
+
}
|
|
211
|
+
const trueRanges = [];
|
|
212
|
+
for (let i = 1; i < candles.length; i++) {
|
|
213
|
+
const high = candles[i].high;
|
|
214
|
+
const low = candles[i].low;
|
|
215
|
+
const prevClose = candles[i - 1].close;
|
|
216
|
+
const tr = Math.max(high - low, Math.abs(high - prevClose), Math.abs(low - prevClose));
|
|
217
|
+
trueRanges.push(tr);
|
|
218
|
+
}
|
|
219
|
+
// Calculate ATR using EMA
|
|
220
|
+
const atrValues = [];
|
|
221
|
+
let atr = trueRanges.slice(0, period).reduce((a, b) => a + b, 0) / period;
|
|
222
|
+
atrValues.push(atr);
|
|
223
|
+
for (let i = period; i < trueRanges.length; i++) {
|
|
224
|
+
atr = (atr * (period - 1) + trueRanges[i]) / period;
|
|
225
|
+
atrValues.push(atr);
|
|
226
|
+
}
|
|
227
|
+
const current = atrValues[atrValues.length - 1] || 0;
|
|
228
|
+
const currentPrice = candles[candles.length - 1].close;
|
|
229
|
+
const percentOfPrice = (current / currentPrice) * 100;
|
|
230
|
+
// Volatility classification
|
|
231
|
+
let volatility = "medium";
|
|
232
|
+
if (percentOfPrice < 1.5)
|
|
233
|
+
volatility = "low";
|
|
234
|
+
else if (percentOfPrice > 3)
|
|
235
|
+
volatility = "high";
|
|
236
|
+
return { values: atrValues, current, volatility, percentOfPrice };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Analyze Volume
|
|
240
|
+
*/
|
|
241
|
+
function analyzeVolume(candles, period = 20) {
|
|
242
|
+
if (candles.length < period) {
|
|
243
|
+
return {
|
|
244
|
+
averageVolume: 0,
|
|
245
|
+
currentVolume: 0,
|
|
246
|
+
volumeRatio: 1,
|
|
247
|
+
trend: "stable",
|
|
248
|
+
confirmation: true,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const volumes = candles.map((c) => c.volume);
|
|
252
|
+
const recentVolumes = volumes.slice(-period);
|
|
253
|
+
const averageVolume = recentVolumes.reduce((a, b) => a + b, 0) / period;
|
|
254
|
+
const currentVolume = volumes[volumes.length - 1];
|
|
255
|
+
const volumeRatio = currentVolume / averageVolume;
|
|
256
|
+
// Volume trend
|
|
257
|
+
const recentVols = volumes.slice(-5);
|
|
258
|
+
const oldVols = volumes.slice(-10, -5);
|
|
259
|
+
const recentAvg = recentVols.reduce((a, b) => a + b, 0) / 5;
|
|
260
|
+
const oldAvg = oldVols.reduce((a, b) => a + b, 0) / 5;
|
|
261
|
+
let trend = "stable";
|
|
262
|
+
if (recentAvg > oldAvg * 1.2)
|
|
263
|
+
trend = "increasing";
|
|
264
|
+
else if (recentAvg < oldAvg * 0.8)
|
|
265
|
+
trend = "decreasing";
|
|
266
|
+
// Check if volume confirms price movement
|
|
267
|
+
const priceChange = candles[candles.length - 1].close - candles[candles.length - 2].close;
|
|
268
|
+
const confirmation = (priceChange > 0 && volumeRatio > 1) || (priceChange < 0 && volumeRatio > 1);
|
|
269
|
+
return { averageVolume, currentVolume, volumeRatio, trend, confirmation };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Calculate Support and Resistance levels
|
|
273
|
+
*/
|
|
274
|
+
function calculateSupportResistance(candles, lookback = 50) {
|
|
275
|
+
const prices = candles.slice(-lookback);
|
|
276
|
+
const currentPrice = candles[candles.length - 1].close;
|
|
277
|
+
// Find pivot points (local highs and lows)
|
|
278
|
+
const pivotHighs = [];
|
|
279
|
+
const pivotLows = [];
|
|
280
|
+
for (let i = 2; i < prices.length - 2; i++) {
|
|
281
|
+
const high = prices[i].high;
|
|
282
|
+
const low = prices[i].low;
|
|
283
|
+
// Pivot high
|
|
284
|
+
if (high > prices[i - 1].high &&
|
|
285
|
+
high > prices[i - 2].high &&
|
|
286
|
+
high > prices[i + 1].high &&
|
|
287
|
+
high > prices[i + 2].high) {
|
|
288
|
+
pivotHighs.push(high);
|
|
289
|
+
}
|
|
290
|
+
// Pivot low
|
|
291
|
+
if (low < prices[i - 1].low &&
|
|
292
|
+
low < prices[i - 2].low &&
|
|
293
|
+
low < prices[i + 1].low &&
|
|
294
|
+
low < prices[i + 2].low) {
|
|
295
|
+
pivotLows.push(low);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Cluster nearby levels (within 1%)
|
|
299
|
+
const clusterLevels = (levels) => {
|
|
300
|
+
if (levels.length === 0)
|
|
301
|
+
return [];
|
|
302
|
+
levels.sort((a, b) => a - b);
|
|
303
|
+
const clustered = [];
|
|
304
|
+
let cluster = [levels[0]];
|
|
305
|
+
for (let i = 1; i < levels.length; i++) {
|
|
306
|
+
if ((levels[i] - levels[i - 1]) / levels[i - 1] < 0.01) {
|
|
307
|
+
cluster.push(levels[i]);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
clustered.push(cluster.reduce((a, b) => a + b, 0) / cluster.length);
|
|
311
|
+
cluster = [levels[i]];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
clustered.push(cluster.reduce((a, b) => a + b, 0) / cluster.length);
|
|
315
|
+
return clustered;
|
|
316
|
+
};
|
|
317
|
+
const resistances = clusterLevels(pivotHighs).filter((r) => r > currentPrice);
|
|
318
|
+
const supports = clusterLevels(pivotLows).filter((s) => s < currentPrice);
|
|
319
|
+
const nearestResistance = resistances.length > 0 ? Math.min(...resistances) : currentPrice * 1.05;
|
|
320
|
+
const nearestSupport = supports.length > 0 ? Math.max(...supports) : currentPrice * 0.95;
|
|
321
|
+
const distanceToResistance = ((nearestResistance - currentPrice) / currentPrice) * 100;
|
|
322
|
+
const distanceToSupport = ((currentPrice - nearestSupport) / currentPrice) * 100;
|
|
323
|
+
return {
|
|
324
|
+
supports: supports.slice(-3),
|
|
325
|
+
resistances: resistances.slice(0, 3),
|
|
326
|
+
nearestSupport,
|
|
327
|
+
nearestResistance,
|
|
328
|
+
distanceToSupport,
|
|
329
|
+
distanceToResistance,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Analyze trend structure
|
|
334
|
+
*/
|
|
335
|
+
function analyzeTrend(candles) {
|
|
336
|
+
if (candles.length < 20) {
|
|
337
|
+
return {
|
|
338
|
+
direction: "sideways",
|
|
339
|
+
strength: 50,
|
|
340
|
+
phase: "consolidating",
|
|
341
|
+
higherHighs: false,
|
|
342
|
+
higherLows: false,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const recent = candles.slice(-20);
|
|
346
|
+
const highs = recent.map((c) => c.high);
|
|
347
|
+
const lows = recent.map((c) => c.low);
|
|
348
|
+
// Check for higher highs and higher lows
|
|
349
|
+
let hhCount = 0;
|
|
350
|
+
let hlCount = 0;
|
|
351
|
+
let lhCount = 0;
|
|
352
|
+
let llCount = 0;
|
|
353
|
+
for (let i = 5; i < highs.length; i += 5) {
|
|
354
|
+
const prevHigh = Math.max(...highs.slice(i - 5, i));
|
|
355
|
+
const currHigh = Math.max(...highs.slice(i, Math.min(i + 5, highs.length)));
|
|
356
|
+
const prevLow = Math.min(...lows.slice(i - 5, i));
|
|
357
|
+
const currLow = Math.min(...lows.slice(i, Math.min(i + 5, lows.length)));
|
|
358
|
+
if (currHigh > prevHigh)
|
|
359
|
+
hhCount++;
|
|
360
|
+
else
|
|
361
|
+
lhCount++;
|
|
362
|
+
if (currLow > prevLow)
|
|
363
|
+
hlCount++;
|
|
364
|
+
else
|
|
365
|
+
llCount++;
|
|
366
|
+
}
|
|
367
|
+
const higherHighs = hhCount > lhCount;
|
|
368
|
+
const higherLows = hlCount > llCount;
|
|
369
|
+
// Determine direction
|
|
370
|
+
let direction = "sideways";
|
|
371
|
+
if (higherHighs && higherLows)
|
|
372
|
+
direction = "bullish";
|
|
373
|
+
else if (!higherHighs && !higherLows)
|
|
374
|
+
direction = "bearish";
|
|
375
|
+
// Calculate strength
|
|
376
|
+
const totalSwings = hhCount + lhCount + hlCount + llCount;
|
|
377
|
+
const trendingSwings = direction === "bullish" ? hhCount + hlCount : lhCount + llCount;
|
|
378
|
+
const strength = totalSwings > 0 ? Math.round((trendingSwings / totalSwings) * 100) : 50;
|
|
379
|
+
// Determine phase
|
|
380
|
+
const prices = candles.slice(-10).map((c) => c.close);
|
|
381
|
+
const priceRange = (Math.max(...prices) - Math.min(...prices)) / prices[0];
|
|
382
|
+
let phase = "trending";
|
|
383
|
+
if (priceRange < 0.02)
|
|
384
|
+
phase = "consolidating";
|
|
385
|
+
else if ((direction === "bullish" && prices[prices.length - 1] < prices[0]) ||
|
|
386
|
+
(direction === "bearish" && prices[prices.length - 1] > prices[0])) {
|
|
387
|
+
phase = "reversing";
|
|
388
|
+
}
|
|
389
|
+
return { direction, strength, phase, higherHighs, higherLows };
|
|
390
|
+
}
|