chore: add AGENTS.md with build, lint, test commands and style guidelines

This commit is contained in:
DiTus
2026-03-18 21:17:43 +01:00
parent e98c25efc4
commit 509f8033fa
32 changed files with 10087 additions and 133 deletions

141
js/indicators/rsi.js Normal file
View 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 };