- 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.
217 lines
6.3 KiB
JavaScript
217 lines
6.3 KiB
JavaScript
// Self-contained Moving Average indicator with SMA/EMA/RMA/WMA/VWMA support
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Moving Average math (SMA/EMA/RMA/WMA/VWMA)
|
|
function calculateSMA(candles, period, source = 'close') {
|
|
const results = new Array(candles.length).fill(null);
|
|
let sum = 0;
|
|
for (let i = 0; i < candles.length; i++) {
|
|
sum += candles[i][source];
|
|
if (i >= period) sum -= candles[i - period][source];
|
|
if (i >= period - 1) results[i] = sum / period;
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function calculateEMA(candles, period, source = 'close') {
|
|
const multiplier = 2 / (period + 1);
|
|
const results = new Array(candles.length).fill(null);
|
|
let ema = 0;
|
|
let sum = 0;
|
|
for (let i = 0; i < candles.length; i++) {
|
|
if (i < period) {
|
|
sum += candles[i][source];
|
|
if (i === period - 1) {
|
|
ema = sum / period;
|
|
results[i] = ema;
|
|
}
|
|
} else {
|
|
ema = (candles[i][source] - ema) * multiplier + ema;
|
|
results[i] = ema;
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function calculateRMA(candles, period, source = 'close') {
|
|
const multiplier = 1 / period;
|
|
const results = new Array(candles.length).fill(null);
|
|
let rma = 0;
|
|
let sum = 0;
|
|
|
|
for (let i = 0; i < candles.length; i++) {
|
|
if (i < period) {
|
|
sum += candles[i][source];
|
|
if (i === period - 1) {
|
|
rma = sum / period;
|
|
results[i] = rma;
|
|
}
|
|
} else {
|
|
rma = (candles[i][source] - rma) * multiplier + rma;
|
|
results[i] = rma;
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function calculateWMA(candles, period, source = 'close') {
|
|
const results = new Array(candles.length).fill(null);
|
|
const weightSum = (period * (period + 1)) / 2;
|
|
|
|
for (let i = period - 1; i < candles.length; i++) {
|
|
let sum = 0;
|
|
for (let j = 0; j < period; j++) {
|
|
sum += candles[i - j][source] * (period - j);
|
|
}
|
|
results[i] = sum / weightSum;
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function calculateVWMA(candles, period, source = 'close') {
|
|
const results = new Array(candles.length).fill(null);
|
|
|
|
for (let i = period - 1; i < candles.length; i++) {
|
|
let sumPV = 0;
|
|
let sumV = 0;
|
|
for (let j = 0; j < period; j++) {
|
|
sumPV += candles[i - j][source] * candles[i - j].volume;
|
|
sumV += candles[i - j].volume;
|
|
}
|
|
results[i] = sumV !== 0 ? sumPV / sumV : null;
|
|
}
|
|
return results;
|
|
}
|
|
|
|
// Signal calculation for Moving Average
|
|
function calculateMASignal(indicator, lastCandle, prevCandle, values) {
|
|
const close = lastCandle.close;
|
|
const ma = values?.ma;
|
|
|
|
if (!ma && ma !== 0) {
|
|
return null;
|
|
}
|
|
|
|
if (close > ma) {
|
|
return {
|
|
type: SIGNAL_TYPES.BUY,
|
|
strength: Math.min(60 + ((close - ma) / ma) * 500, 100),
|
|
value: close,
|
|
reasoning: `Price (${close.toFixed(2)}) is above MA (${ma.toFixed(2)})`
|
|
};
|
|
} else if (close < ma) {
|
|
return {
|
|
type: SIGNAL_TYPES.SELL,
|
|
strength: Math.min(60 + ((ma - close) / ma) * 500, 100),
|
|
value: close,
|
|
reasoning: `Price (${close.toFixed(2)}) is below MA (${ma.toFixed(2)})`
|
|
};
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// MA Indicator class
|
|
export class MAIndicator extends BaseIndicator {
|
|
constructor(config) {
|
|
super(config);
|
|
this.lastSignalTimestamp = null;
|
|
this.lastSignalType = null;
|
|
}
|
|
|
|
calculate(candles) {
|
|
const maType = (this.params.maType || 'SMA').toLowerCase();
|
|
const period = this.params.period || 44;
|
|
|
|
let maValues;
|
|
|
|
switch (maType) {
|
|
case 'sma':
|
|
maValues = calculateSMA(candles, period, this.params.source || 'close');
|
|
break;
|
|
case 'ema':
|
|
maValues = calculateEMA(candles, period, this.params.source || 'close');
|
|
break;
|
|
case 'rma':
|
|
maValues = calculateRMA(candles, period, this.params.source || 'close');
|
|
break;
|
|
case 'wma':
|
|
maValues = calculateWMA(candles, period, this.params.source || 'close');
|
|
break;
|
|
case 'vwma':
|
|
maValues = calculateVWMA(candles, period, this.params.source || 'close');
|
|
break;
|
|
default:
|
|
maValues = calculateSMA(candles, period, this.params.source || 'close');
|
|
}
|
|
|
|
return maValues.map(ma => ({ ma }));
|
|
}
|
|
|
|
getMetadata() {
|
|
return {
|
|
name: 'MA',
|
|
description: 'Moving Average (SMA/EMA/RMA/WMA/VWMA)',
|
|
inputs: [
|
|
{
|
|
name: 'period',
|
|
label: 'Period',
|
|
type: 'number',
|
|
default: 44,
|
|
min: 1,
|
|
max: 500
|
|
},
|
|
{
|
|
name: 'maType',
|
|
label: 'MA Type',
|
|
type: 'select',
|
|
options: ['SMA', 'EMA', 'RMA', 'WMA', 'VWMA'],
|
|
default: 'SMA'
|
|
}
|
|
],
|
|
plots: [
|
|
{
|
|
id: 'value',
|
|
color: '#2962ff',
|
|
title: 'MA',
|
|
style: 'solid',
|
|
width: 1
|
|
}
|
|
],
|
|
displayMode: 'overlay'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Export signal function for external use
|
|
export { calculateMASignal }; |