chore: add AGENTS.md with build, lint, test commands and style guidelines
This commit is contained in:
141
js/indicators/rsi.js
Normal file
141
js/indicators/rsi.js
Normal file
@ -0,0 +1,141 @@
|
||||
// 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 };
|
||||
Reference in New Issue
Block a user