Initial commit: BTC/USDT Trading View with technical indicators
This commit is contained in:
376
indicators.js
Normal file
376
indicators.js
Normal file
@@ -0,0 +1,376 @@
|
||||
// Technical Indicators Calculator
|
||||
|
||||
class TechnicalIndicators {
|
||||
|
||||
// Simple Moving Average (SMA)
|
||||
static SMA(data, period) {
|
||||
const result = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (i < period - 1) {
|
||||
result.push({ time: data[i].time, value: null });
|
||||
} else {
|
||||
let sum = 0;
|
||||
for (let j = 0; j < period; j++) {
|
||||
sum += data[i - j].close;
|
||||
}
|
||||
result.push({ time: data[i].time, value: sum / period });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Exponential Moving Average (EMA)
|
||||
static EMA(data, period) {
|
||||
const result = [];
|
||||
const multiplier = 2 / (period + 1);
|
||||
|
||||
// First EMA uses SMA
|
||||
let sum = 0;
|
||||
for (let i = 0; i < period; i++) {
|
||||
sum += data[i].close;
|
||||
result.push({ time: data[i].time, value: null });
|
||||
}
|
||||
result[period - 1] = { time: data[period - 1].time, value: sum / period };
|
||||
|
||||
// Calculate EMA for remaining data
|
||||
for (let i = period; i < data.length; i++) {
|
||||
const ema = (data[i].close - result[i - 1].value) * multiplier + result[i - 1].value;
|
||||
result.push({ time: data[i].time, value: ema });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bollinger Bands
|
||||
static BollingerBands(data, period = 20, stdDev = 2) {
|
||||
const sma = this.SMA(data, period);
|
||||
const upper = [];
|
||||
const lower = [];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (i < period - 1) {
|
||||
upper.push({ time: data[i].time, value: null });
|
||||
lower.push({ time: data[i].time, value: null });
|
||||
} else {
|
||||
// Calculate standard deviation
|
||||
let sumSquares = 0;
|
||||
for (let j = 0; j < period; j++) {
|
||||
sumSquares += Math.pow(data[i - j].close - sma[i].value, 2);
|
||||
}
|
||||
const std = Math.sqrt(sumSquares / period);
|
||||
|
||||
upper.push({ time: data[i].time, value: sma[i].value + stdDev * std });
|
||||
lower.push({ time: data[i].time, value: sma[i].value - stdDev * std });
|
||||
}
|
||||
}
|
||||
|
||||
return { middle: sma, upper, lower };
|
||||
}
|
||||
|
||||
// MACD (Moving Average Convergence Divergence)
|
||||
static MACD(data, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
|
||||
const emaFast = this.EMA(data, fastPeriod);
|
||||
const emaSlow = this.EMA(data, slowPeriod);
|
||||
|
||||
// MACD Line = Fast EMA - Slow EMA
|
||||
const macdLine = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (emaFast[i].value === null || emaSlow[i].value === null) {
|
||||
macdLine.push({ time: data[i].time, value: null, close: null });
|
||||
} else {
|
||||
const value = emaFast[i].value - emaSlow[i].value;
|
||||
macdLine.push({ time: data[i].time, value, close: value });
|
||||
}
|
||||
}
|
||||
|
||||
// Signal Line = EMA of MACD Line
|
||||
const validMacdData = macdLine.filter(d => d.value !== null);
|
||||
const signalEma = this.EMA(validMacdData, signalPeriod);
|
||||
|
||||
// Map signal back to full timeline
|
||||
const signalLine = [];
|
||||
let signalIndex = 0;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (macdLine[i].value === null || signalIndex >= signalEma.length) {
|
||||
signalLine.push({ time: data[i].time, value: null });
|
||||
} else if (signalEma[signalIndex] && signalEma[signalIndex].time === data[i].time) {
|
||||
signalLine.push({ time: data[i].time, value: signalEma[signalIndex].value });
|
||||
signalIndex++;
|
||||
} else {
|
||||
signalLine.push({ time: data[i].time, value: null });
|
||||
}
|
||||
}
|
||||
|
||||
// Histogram = MACD Line - Signal Line
|
||||
const histogram = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (macdLine[i].value === null || signalLine[i].value === null) {
|
||||
histogram.push({ time: data[i].time, value: 0, color: '#363a45' });
|
||||
} else {
|
||||
const value = macdLine[i].value - signalLine[i].value;
|
||||
histogram.push({
|
||||
time: data[i].time,
|
||||
value,
|
||||
color: value >= 0 ? '#26a69a' : '#ef5350'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { macdLine, signalLine, histogram };
|
||||
}
|
||||
|
||||
// RSI (Relative Strength Index)
|
||||
static RSI(data, period = 14) {
|
||||
const result = [];
|
||||
const gains = [];
|
||||
const losses = [];
|
||||
|
||||
// Calculate price changes
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (i === 0) {
|
||||
gains.push(0);
|
||||
losses.push(0);
|
||||
result.push({ time: data[i].time, value: null });
|
||||
continue;
|
||||
}
|
||||
|
||||
const change = data[i].close - data[i - 1].close;
|
||||
gains.push(change > 0 ? change : 0);
|
||||
losses.push(change < 0 ? Math.abs(change) : 0);
|
||||
|
||||
if (i < period) {
|
||||
result.push({ time: data[i].time, value: null });
|
||||
} else if (i === period) {
|
||||
// First RSI calculation uses simple average
|
||||
let avgGain = 0, avgLoss = 0;
|
||||
for (let j = 1; j <= period; j++) {
|
||||
avgGain += gains[j];
|
||||
avgLoss += losses[j];
|
||||
}
|
||||
avgGain /= period;
|
||||
avgLoss /= period;
|
||||
|
||||
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
||||
const rsi = 100 - (100 / (1 + rs));
|
||||
result.push({ time: data[i].time, value: rsi, avgGain, avgLoss });
|
||||
} else {
|
||||
// Subsequent RSI uses smoothed average
|
||||
const prevAvgGain = result[i - 1].avgGain;
|
||||
const prevAvgLoss = result[i - 1].avgLoss;
|
||||
const avgGain = (prevAvgGain * (period - 1) + gains[i]) / period;
|
||||
const avgLoss = (prevAvgLoss * (period - 1) + losses[i]) / period;
|
||||
|
||||
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
||||
const rsi = 100 - (100 / (1 + rs));
|
||||
result.push({ time: data[i].time, value: rsi, avgGain, avgLoss });
|
||||
}
|
||||
}
|
||||
|
||||
return result.map(d => ({ time: d.time, value: d.value }));
|
||||
}
|
||||
|
||||
// Stochastic Oscillator
|
||||
static Stochastic(data, kPeriod = 14, dPeriod = 3, smooth = 3) {
|
||||
const kValues = [];
|
||||
|
||||
// Calculate %K
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (i < kPeriod - 1) {
|
||||
kValues.push({ time: data[i].time, value: null, close: null });
|
||||
} else {
|
||||
let highest = -Infinity;
|
||||
let lowest = Infinity;
|
||||
|
||||
for (let j = 0; j < kPeriod; j++) {
|
||||
highest = Math.max(highest, data[i - j].high);
|
||||
lowest = Math.min(lowest, data[i - j].low);
|
||||
}
|
||||
|
||||
const k = highest === lowest ? 50 : ((data[i].close - lowest) / (highest - lowest)) * 100;
|
||||
kValues.push({ time: data[i].time, value: k, close: k });
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth %K (Fast %K -> Slow %K)
|
||||
const slowK = [];
|
||||
for (let i = 0; i < kValues.length; i++) {
|
||||
if (i < kPeriod - 1 + smooth - 1) {
|
||||
slowK.push({ time: kValues[i].time, value: null, close: null });
|
||||
} else {
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
for (let j = 0; j < smooth; j++) {
|
||||
if (kValues[i - j].value !== null) {
|
||||
sum += kValues[i - j].value;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
const value = count > 0 ? sum / count : null;
|
||||
slowK.push({ time: kValues[i].time, value, close: value });
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate %D (SMA of Slow %K)
|
||||
const dValues = [];
|
||||
for (let i = 0; i < slowK.length; i++) {
|
||||
if (i < kPeriod - 1 + smooth - 1 + dPeriod - 1) {
|
||||
dValues.push({ time: slowK[i].time, value: null });
|
||||
} else {
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
for (let j = 0; j < dPeriod; j++) {
|
||||
if (slowK[i - j].value !== null) {
|
||||
sum += slowK[i - j].value;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
dValues.push({ time: slowK[i].time, value: count > 0 ? sum / count : null });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
k: slowK.map(d => ({ time: d.time, value: d.value })),
|
||||
d: dValues
|
||||
};
|
||||
}
|
||||
|
||||
// Detect Golden Cross / Dead Cross
|
||||
static detectCrosses(shortMA, longMA) {
|
||||
const signals = [];
|
||||
|
||||
for (let i = 1; i < shortMA.length; i++) {
|
||||
if (shortMA[i].value === null || longMA[i].value === null ||
|
||||
shortMA[i - 1].value === null || longMA[i - 1].value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const prevShort = shortMA[i - 1].value;
|
||||
const prevLong = longMA[i - 1].value;
|
||||
const currShort = shortMA[i].value;
|
||||
const currLong = longMA[i].value;
|
||||
|
||||
// Golden Cross: Short MA crosses above Long MA
|
||||
if (prevShort <= prevLong && currShort > currLong) {
|
||||
signals.push({
|
||||
time: shortMA[i].time,
|
||||
type: 'golden',
|
||||
description: 'Golden Cross'
|
||||
});
|
||||
}
|
||||
|
||||
// Dead Cross: Short MA crosses below Long MA
|
||||
if (prevShort >= prevLong && currShort < currLong) {
|
||||
signals.push({
|
||||
time: shortMA[i].time,
|
||||
type: 'dead',
|
||||
description: 'Dead Cross'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
// Detect RSI signals
|
||||
static detectRSISignals(rsi, oversold = 30, overbought = 70) {
|
||||
const signals = [];
|
||||
|
||||
for (let i = 1; i < rsi.length; i++) {
|
||||
if (rsi[i].value === null || rsi[i - 1].value === null) continue;
|
||||
|
||||
// Oversold -> Recovery (Buy signal)
|
||||
if (rsi[i - 1].value < oversold && rsi[i].value >= oversold) {
|
||||
signals.push({
|
||||
time: rsi[i].time,
|
||||
type: 'buy',
|
||||
description: 'RSI Oversold Recovery'
|
||||
});
|
||||
}
|
||||
|
||||
// Overbought -> Decline (Sell signal)
|
||||
if (rsi[i - 1].value > overbought && rsi[i].value <= overbought) {
|
||||
signals.push({
|
||||
time: rsi[i].time,
|
||||
type: 'sell',
|
||||
description: 'RSI Overbought Decline'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
// Detect MACD signals
|
||||
static detectMACDSignals(macd) {
|
||||
const signals = [];
|
||||
const { macdLine, signalLine } = macd;
|
||||
|
||||
for (let i = 1; i < macdLine.length; i++) {
|
||||
if (macdLine[i].value === null || signalLine[i].value === null ||
|
||||
macdLine[i - 1].value === null || signalLine[i - 1].value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const prevMacd = macdLine[i - 1].value;
|
||||
const prevSignal = signalLine[i - 1].value;
|
||||
const currMacd = macdLine[i].value;
|
||||
const currSignal = signalLine[i].value;
|
||||
|
||||
// MACD crosses above Signal (Buy)
|
||||
if (prevMacd <= prevSignal && currMacd > currSignal) {
|
||||
signals.push({
|
||||
time: macdLine[i].time,
|
||||
type: 'buy',
|
||||
description: 'MACD Bullish Cross'
|
||||
});
|
||||
}
|
||||
|
||||
// MACD crosses below Signal (Sell)
|
||||
if (prevMacd >= prevSignal && currMacd < currSignal) {
|
||||
signals.push({
|
||||
time: macdLine[i].time,
|
||||
type: 'sell',
|
||||
description: 'MACD Bearish Cross'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
// Detect Stochastic signals
|
||||
static detectStochasticSignals(stochastic, oversold = 20, overbought = 80) {
|
||||
const signals = [];
|
||||
const { k, d } = stochastic;
|
||||
|
||||
for (let i = 1; i < k.length; i++) {
|
||||
if (k[i].value === null || d[i].value === null ||
|
||||
k[i - 1].value === null || d[i - 1].value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// %K crosses above %D in oversold zone (Buy)
|
||||
if (k[i - 1].value <= d[i - 1].value && k[i].value > d[i].value && k[i].value < oversold) {
|
||||
signals.push({
|
||||
time: k[i].time,
|
||||
type: 'buy',
|
||||
description: 'Stochastic Oversold Cross'
|
||||
});
|
||||
}
|
||||
|
||||
// %K crosses below %D in overbought zone (Sell)
|
||||
if (k[i - 1].value >= d[i - 1].value && k[i].value < d[i].value && k[i].value > overbought) {
|
||||
signals.push({
|
||||
time: k[i].time,
|
||||
type: 'sell',
|
||||
description: 'Stochastic Overbought Cross'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in app.js
|
||||
window.TechnicalIndicators = TechnicalIndicators;
|
||||
Reference in New Issue
Block a user