added RSI crossover signals: BUY when crosses up oversold, SELL when crosses down overbought

This commit is contained in:
DiTus
2026-03-02 09:03:18 +01:00
parent 767c0bef67
commit a84a1c9091
2 changed files with 40 additions and 11 deletions

View File

@ -32,8 +32,9 @@ class BaseIndicator {
} }
// Signal calculation for RSI // Signal calculation for RSI
function calculateRSISignal(indicator, lastCandle, prevCandle, values) { function calculateRSISignal(indicator, lastCandle, prevCandle, values, prevValues) {
const rsi = values?.rsi; const rsi = values?.rsi;
const prevRsi = prevValues?.rsi;
const overbought = indicator.params?.overbought || 70; const overbought = indicator.params?.overbought || 70;
const oversold = indicator.params?.oversold || 30; const oversold = indicator.params?.oversold || 30;
@ -43,13 +44,30 @@ function calculateRSISignal(indicator, lastCandle, prevCandle, values) {
let signalType, strength, reasoning; let signalType, strength, reasoning;
if (rsi < oversold) { // BUY when RSI crosses UP through oversold band (bottom band)
// RSI was below oversold, now above oversold
if (prevRsi !== undefined && prevRsi !== null && prevRsi < oversold && rsi >= oversold) {
signalType = SIGNAL_TYPES.BUY; signalType = SIGNAL_TYPES.BUY;
strength = Math.min(50 + (oversold - rsi) * 2, 100); strength = Math.min(50 + (rsi - oversold) * 2, 100);
reasoning = `RSI (${rsi.toFixed(2)}) is oversold (<${oversold})`; reasoning = `RSI (${rsi.toFixed(2)}) crossed up through oversold level (${oversold})`;
} else if (rsi > overbought) { }
// SELL when RSI crosses DOWN through overbought band (top band)
// RSI was above overbought, now below overbought
else if (prevRsi !== undefined && prevRsi !== null && prevRsi > overbought && rsi <= overbought) {
signalType = SIGNAL_TYPES.SELL; signalType = SIGNAL_TYPES.SELL;
strength = Math.min(50 + (rsi - overbought) * 2, 100); strength = Math.min(50 + (overbought - rsi) * 2, 100);
reasoning = `RSI (${rsi.toFixed(2)}) crossed down through overbought level (${overbought})`;
}
// When RSI is in oversold territory but no crossover - strong BUY
else if (rsi < oversold) {
signalType = SIGNAL_TYPES.BUY;
strength = Math.min(40 + (oversold - rsi) * 1.5, 80);
reasoning = `RSI (${rsi.toFixed(2)}) is oversold (<${oversold})`;
}
// When RSI is in overbought territory but no crossover - strong SELL
else if (rsi > overbought) {
signalType = SIGNAL_TYPES.SELL;
strength = Math.min(40 + (rsi - overbought) * 1.5, 80);
reasoning = `RSI (${rsi.toFixed(2)}) is overbought (>${overbought})`; reasoning = `RSI (${rsi.toFixed(2)}) is overbought (>${overbought})`;
} else { } else {
return null; return null;

View File

@ -4,13 +4,14 @@
import { IndicatorRegistry, getSignalFunction } from '../indicators/index.js'; import { IndicatorRegistry, getSignalFunction } from '../indicators/index.js';
/** /**
* Calculate signal for a single indicator using its signal function * Calculate signal for an indicator
* @param {Object} indicator - Indicator object with type, params, etc. * @param {Object} indicator - Indicator configuration
* @param {Array} candles - Recent candle data * @param {Array} candles - Candle data array
* @param {Object} indicatorValues - Computed indicator values for last candle * @param {Object} indicatorValues - Computed indicator values for last candle
* @param {Object} prevIndicatorValues - Computed indicator values for previous candle
* @returns {Object} Signal object with type, strength, value, reasoning * @returns {Object} Signal object with type, strength, value, reasoning
*/ */
function calculateIndicatorSignal(indicator, candles, indicatorValues) { function calculateIndicatorSignal(indicator, candles, indicatorValues, prevIndicatorValues) {
const signalFunction = getSignalFunction(indicator.type); const signalFunction = getSignalFunction(indicator.type);
if (!signalFunction) { if (!signalFunction) {
@ -21,6 +22,12 @@ function calculateIndicatorSignal(indicator, candles, indicatorValues) {
const lastCandle = candles[candles.length - 1]; const lastCandle = candles[candles.length - 1];
const prevCandle = candles[candles.length - 2]; const prevCandle = candles[candles.length - 2];
return signalFunction(indicator, lastCandle, prevCandle, indicatorValues, prevIndicatorValues);
}
const lastCandle = candles[candles.length - 1];
const prevCandle = candles[candles.length - 2];
return signalFunction(indicator, lastCandle, prevCandle, indicatorValues); return signalFunction(indicator, lastCandle, prevCandle, indicatorValues);
} }
@ -216,22 +223,26 @@ export function calculateAllIndicatorSignals() {
} }
const lastResult = results[results.length - 1]; const lastResult = results[results.length - 1];
const prevResult = results[results.length - 2];
if (lastResult === null || lastResult === undefined) { if (lastResult === null || lastResult === undefined) {
console.log('[Signals] No valid last result for indicator:', indicator.type); console.log('[Signals] No valid last result for indicator:', indicator.type);
continue; continue;
} }
let values; let values;
let prevValues;
if (typeof lastResult === 'object' && lastResult !== null && !Array.isArray(lastResult)) { if (typeof lastResult === 'object' && lastResult !== null && !Array.isArray(lastResult)) {
values = lastResult; values = lastResult;
prevValues = prevResult;
} else if (typeof lastResult === 'number') { } else if (typeof lastResult === 'number') {
values = { ma: lastResult }; values = { ma: lastResult };
prevValues = prevResult ? { ma: prevResult } : undefined;
} else { } else {
console.log('[Signals] Unexpected result type for', indicator.type, ':', typeof lastResult); console.log('[Signals] Unexpected result type for', indicator.type, ':', typeof lastResult);
continue; continue;
} }
const signal = calculateIndicatorSignal(indicator, candles, values); const signal = calculateIndicatorSignal(indicator, candles, values, prevValues);
let currentSignal = signal; let currentSignal = signal;
let lastSignalDate = indicator.lastSignalTimestamp || null; let lastSignalDate = indicator.lastSignalTimestamp || null;