chore: add AGENTS.md with build, lint, test commands and style guidelines
This commit is contained in:
221
js/indicators/moving_average.js
Normal file
221
js/indicators/moving_average.js
Normal file
@ -0,0 +1,221 @@
|
||||
// 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, prevValues) {
|
||||
const close = lastCandle.close;
|
||||
const prevClose = prevCandle?.close;
|
||||
const ma = values?.ma;
|
||||
const prevMa = prevValues?.ma;
|
||||
|
||||
if (!ma && ma !== 0) return null;
|
||||
if (prevClose === undefined || prevMa === undefined || prevMa === null) return null;
|
||||
|
||||
// BUY: Price crosses UP through MA
|
||||
if (prevClose <= prevMa && close > ma) {
|
||||
return {
|
||||
type: SIGNAL_TYPES.BUY,
|
||||
strength: 80,
|
||||
value: close,
|
||||
reasoning: `Price crossed UP through MA`
|
||||
};
|
||||
}
|
||||
// SELL: Price crosses DOWN through MA
|
||||
else if (prevClose >= prevMa && close < ma) {
|
||||
return {
|
||||
type: SIGNAL_TYPES.SELL,
|
||||
strength: 80,
|
||||
value: close,
|
||||
reasoning: `Price crossed DOWN through MA`
|
||||
};
|
||||
}
|
||||
|
||||
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: 'ma',
|
||||
color: '#2962ff',
|
||||
title: 'MA',
|
||||
style: 'solid',
|
||||
width: 1
|
||||
}
|
||||
],
|
||||
displayMode: 'overlay'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export signal function for external use
|
||||
export { calculateMASignal };
|
||||
Reference in New Issue
Block a user