141 lines
4.6 KiB
JavaScript
141 lines
4.6 KiB
JavaScript
// Self-contained RSI indicator
|
|
// Includes math, metadata, signal calculation, and base class
|
|
|
|
// Signal constants (defined in each indicator file)
|
|
const SIGNAL_TYPES = {
|
|
BUY: 'buy',
|
|
SELL: 'sell',
|
|
HOLD: 'hold'
|
|
};
|
|
|
|
const SIGNAL_COLORS = {
|
|
buy: '#26a69a',
|
|
hold: '#787b86',
|
|
sell: '#ef5350'
|
|
};
|
|
|
|
// Base class (inline replacement for BaseIndicator)
|
|
class BaseIndicator {
|
|
constructor(config) {
|
|
this.id = config.id;
|
|
this.type = config.type;
|
|
this.name = config.name;
|
|
this.params = config.params || {};
|
|
this.timeframe = config.timeframe || '1m';
|
|
this.series = [];
|
|
this.visible = config.visible !== false;
|
|
this.cachedResults = null;
|
|
this.cachedMeta = null;
|
|
this.lastSignalTimestamp = null;
|
|
this.lastSignalType = null;
|
|
}
|
|
}
|
|
|
|
// Signal calculation for RSI
|
|
function calculateRSISignal(indicator, lastCandle, prevCandle, values, prevValues) {
|
|
const rsi = values?.rsi;
|
|
const prevRsi = prevValues?.rsi;
|
|
const overbought = indicator.params?.overbought || 70;
|
|
const oversold = indicator.params?.oversold || 30;
|
|
|
|
if (rsi === undefined || rsi === null || prevRsi === undefined || prevRsi === null) {
|
|
return null;
|
|
}
|
|
|
|
// BUY when RSI crosses UP through oversold level
|
|
if (prevRsi < oversold && rsi >= oversold) {
|
|
return {
|
|
type: SIGNAL_TYPES.BUY,
|
|
strength: 75,
|
|
value: rsi,
|
|
reasoning: `RSI crossed UP through oversold level (${oversold})`
|
|
};
|
|
}
|
|
// SELL when RSI crosses DOWN through overbought level
|
|
else if (prevRsi > overbought && rsi <= overbought) {
|
|
return {
|
|
type: SIGNAL_TYPES.SELL,
|
|
strength: 75,
|
|
value: rsi,
|
|
reasoning: `RSI crossed DOWN through overbought level (${overbought})`
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// RSI Indicator class
|
|
export class RSIIndicator extends BaseIndicator {
|
|
constructor(config) {
|
|
super(config);
|
|
this.lastSignalTimestamp = null;
|
|
this.lastSignalType = null;
|
|
}
|
|
|
|
calculate(candles) {
|
|
const period = this.params.period || 14;
|
|
const overbought = this.params.overbought || 70;
|
|
const oversold = this.params.oversold || 30;
|
|
|
|
// 1. Calculate RSI using RMA (Wilder's Smoothing)
|
|
let rsiValues = new Array(candles.length).fill(null);
|
|
let upSum = 0;
|
|
let downSum = 0;
|
|
const rmaAlpha = 1 / period;
|
|
|
|
for (let i = 1; i < candles.length; i++) {
|
|
const diff = candles[i].close - candles[i-1].close;
|
|
const up = diff > 0 ? diff : 0;
|
|
const down = diff < 0 ? -diff : 0;
|
|
|
|
if (i < period) {
|
|
upSum += up;
|
|
downSum += down;
|
|
} else if (i === period) {
|
|
upSum += up;
|
|
downSum += down;
|
|
const avgUp = upSum / period;
|
|
const avgDown = downSum / period;
|
|
rsiValues[i] = avgDown === 0 ? 100 : (avgUp === 0 ? 0 : 100 - (100 / (1 + avgUp / avgDown)));
|
|
upSum = avgUp;
|
|
downSum = avgDown;
|
|
} else {
|
|
upSum = (up - upSum) * rmaAlpha + upSum;
|
|
downSum = (down - downSum) * rmaAlpha + downSum;
|
|
rsiValues[i] = downSum === 0 ? 100 : (upSum === 0 ? 0 : 100 - (100 / (1 + upSum / downSum)));
|
|
}
|
|
}
|
|
|
|
// Combine results
|
|
return rsiValues.map((rsi, i) => {
|
|
return {
|
|
paneBg: 80,
|
|
rsi: rsi,
|
|
overboughtBand: overbought,
|
|
oversoldBand: oversold
|
|
};
|
|
});
|
|
}
|
|
|
|
getMetadata() {
|
|
return {
|
|
name: 'RSI',
|
|
description: 'Relative Strength Index',
|
|
inputs: [
|
|
{ name: 'period', label: 'RSI Length', type: 'number', default: 14, min: 1, max: 100 },
|
|
{ name: 'overbought', label: 'Overbought Level', type: 'number', default: 70, min: 50, max: 95 },
|
|
{ name: 'oversold', label: 'Oversold Level', type: 'number', default: 30, min: 5, max: 50 }
|
|
],
|
|
plots: [
|
|
{ id: 'rsi', color: '#7E57C2', title: '', style: 'solid', width: 1, lastValueVisible: true },
|
|
{ id: 'overboughtBand', color: '#787B86', title: '', style: 'dashed', width: 1, lastValueVisible: false },
|
|
{ id: 'oversoldBand', color: '#787B86', title: '', style: 'dashed', width: 1, lastValueVisible: false }
|
|
],
|
|
displayMode: 'pane',
|
|
paneMin: 0,
|
|
paneMax: 100
|
|
};
|
|
}
|
|
}
|
|
|
|
export { calculateRSISignal }; |