Refactor: Convert indicators to self-contained files

- Created moving_average.js consolidating ma.js, ma_indicator.js, sma.js, ema.js
- Made all indicators self-contained with embedded:
  * Math logic (no external dependencies)
  * Metadata (getMetadata())
  * Signal calculation (calculateXXXSignal)
  * Base class (inline BaseIndicator)
- Updated macd.js, hts.js to inline EMA/MA calculations
- Added signal functions to RSI, BB, Stochastic, ATR indicators
- Updated indicators/index.js to export both classes and signal functions
- Simplified signals-calculator.js to orchestrate using indicator signal functions
- Removed obsolete files: ma.js, base.js, ma_indicator.js, sma.js, ema.js

All indicators now fully self-contained with no external file dependencies
for math, signal calculation, or base class.
This commit is contained in:
DiTus
2026-03-01 19:39:28 +01:00
parent fdab0a3faa
commit a344a7f0da
14 changed files with 883 additions and 644 deletions

View File

@ -1,37 +1,123 @@
import { MA } from './ma.js';
import { BaseIndicator } from './base.js';
// Self-contained MACD 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;
}
}
// EMA calculation inline (needed for MACD)
function calculateEMAInline(data, period) {
const multiplier = 2 / (period + 1);
const ema = [];
for (let i = 0; i < data.length; i++) {
if (i < period - 1) {
ema.push(null);
} else if (i === period - 1) {
ema.push(data[i]);
} else {
ema.push((data[i] - ema[i - 1]) * multiplier + ema[i - 1]);
}
}
return ema;
}
// Signal calculation for MACD
function calculateMACDSignal(indicator, lastCandle, prevCandle, values) {
const macd = values?.macd;
const signal = values?.signal;
const histogram = values?.histogram;
if (!macd || macd === null || !signal || signal === null) {
return null;
}
let signalType, strength, reasoning;
const prevCandleHistogram = prevCandle ? values?.histogram : null;
if (macd > signal) {
signalType = SIGNAL_TYPES.BUY;
strength = Math.min(50 + histogram * 500, 100);
reasoning = `MACD (${macd.toFixed(2)}) is above Signal (${signal.toFixed(2)})`;
} else if (macd < signal) {
signalType = SIGNAL_TYPES.SELL;
strength = Math.min(50 + Math.abs(histogram) * 500, 100);
reasoning = `MACD (${macd.toFixed(2)}) is below Signal (${signal.toFixed(2)})`;
} else {
return null;
}
return { type: signalType, strength, value: macd, reasoning };
}
// MACD Indicator class
export class MACDIndicator extends BaseIndicator {
constructor(config) {
super(config);
this.lastSignalTimestamp = null;
this.lastSignalType = null;
}
calculate(candles) {
const fast = this.params.fast || 12;
const slow = this.params.slow || 26;
const signal = this.params.signal || 9;
const signalPeriod = this.params.signal || 9;
const fastEma = MA.ema(candles, fast, 'close');
const slowEma = MA.ema(candles, slow, 'close');
const closes = candles.map(c => c.close);
const macdLine = fastEma.map((f, i) => (f !== null && slowEma[i] !== null) ? f - slowEma[i] : null);
// Use inline EMA calculation instead of MA.ema()
const fastEMA = calculateEMAInline(closes, fast);
const slowEMA = calculateEMAInline(closes, slow);
const macdLine = fastEMA.map((f, i) => (f !== null && slowEMA[i] !== null) ? f - slowEMA[i] : null);
const signalLine = new Array(candles.length).fill(null);
const multiplier = 2 / (signal + 1);
let ema = 0;
let sum = 0;
let ema = 0;
let count = 0;
for (let i = 0; i < macdLine.length; i++) {
if (macdLine[i] === null) continue;
const signalLine = macdLine.map(m => {
if (m === null) return null;
count++;
if (count < signal) {
sum += macdLine[i];
} else if (count === signal) {
sum += macdLine[i];
ema = sum / signal;
signalLine[i] = ema;
if (count < signalPeriod) {
sum += m;
return null;
} else if (count === signalPeriod) {
sum += m;
ema = sum / signalPeriod;
return ema;
} else {
ema = (macdLine[i] - ema) * multiplier + ema;
signalLine[i] = ema;
ema = (m - ema) * (2 / (signalPeriod + 1)) + ema;
return ema;
}
}
});
return macdLine.map((m, i) => ({
macd: m,
@ -58,3 +144,5 @@ export class MACDIndicator extends BaseIndicator {
};
}
}
export { calculateMACDSignal };