- 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.
145 lines
5.9 KiB
JavaScript
145 lines
5.9 KiB
JavaScript
// Signal Calculator - orchestrates signal calculation using indicator-specific functions
|
|
// Signal calculation logic is now in each indicator file
|
|
|
|
import { IndicatorRegistry, getSignalFunction } from '../indicators/index.js';
|
|
|
|
/**
|
|
* Calculate signal for a single indicator using its signal function
|
|
* @param {Object} indicator - Indicator object with type, params, etc.
|
|
* @param {Array} candles - Recent candle data
|
|
* @param {Object} indicatorValues - Computed indicator values for last candle
|
|
* @returns {Object} Signal object with type, strength, value, reasoning
|
|
*/
|
|
function calculateIndicatorSignal(indicator, candles, indicatorValues) {
|
|
const signalFunction = getSignalFunction(indicator.type);
|
|
|
|
if (!signalFunction) {
|
|
console.warn('[Signals] No signal function for indicator type:', indicator.type);
|
|
return null;
|
|
}
|
|
|
|
const lastCandle = candles[candles.length - 1];
|
|
const prevCandle = candles[candles.length - 2];
|
|
|
|
return signalFunction(indicator, lastCandle, prevCandle, indicatorValues);
|
|
}
|
|
|
|
/**
|
|
* Calculate signals for all active indicators
|
|
* @returns {Array} Array of indicator signals
|
|
*/
|
|
export function calculateAllIndicatorSignals() {
|
|
const activeIndicators = window.getActiveIndicators?.() || [];
|
|
const candles = window.dashboard?.allData?.get(window.dashboard?.currentInterval);
|
|
|
|
console.log('[Signals] ========== calculateAllIndicatorSignals START ==========');
|
|
console.log('[Signals] Active indicators:', activeIndicators.length, 'Candles:', candles?.length || 0);
|
|
|
|
if (!candles || candles.length < 2) {
|
|
console.log('[Signals] Insufficient candles available:', candles?.length || 0);
|
|
return [];
|
|
}
|
|
|
|
if (!activeIndicators || activeIndicators.length === 0) {
|
|
console.log('[Signals] No active indicators');
|
|
return [];
|
|
}
|
|
|
|
const signals = [];
|
|
|
|
for (const indicator of activeIndicators) {
|
|
const IndicatorClass = IndicatorRegistry[indicator.type];
|
|
if (!IndicatorClass) {
|
|
console.log('[Signals] No class for indicator type:', indicator.type);
|
|
continue;
|
|
}
|
|
|
|
// Use cached results if available, otherwise calculate
|
|
let results = indicator.cachedResults;
|
|
let meta = indicator.cachedMeta;
|
|
|
|
if (!results || !meta || results.length !== candles.length) {
|
|
const instance = new IndicatorClass(indicator);
|
|
meta = instance.getMetadata();
|
|
results = instance.calculate(candles);
|
|
indicator.cachedResults = results;
|
|
indicator.cachedMeta = meta;
|
|
}
|
|
|
|
if (!results || results.length === 0) {
|
|
console.log('[Signals] No results for indicator:', indicator.type);
|
|
continue;
|
|
}
|
|
|
|
const lastResult = results[results.length - 1];
|
|
if (lastResult === null || lastResult === undefined) {
|
|
console.log('[Signals] No valid last result for indicator:', indicator.type);
|
|
continue;
|
|
}
|
|
|
|
let values;
|
|
if (typeof lastResult === 'object' && lastResult !== null && !Array.isArray(lastResult)) {
|
|
values = lastResult;
|
|
} else if (typeof lastResult === 'number') {
|
|
values = { ma: lastResult };
|
|
} else {
|
|
console.log('[Signals] Unexpected result type for', indicator.type, ':', typeof lastResult);
|
|
continue;
|
|
}
|
|
|
|
const signal = calculateIndicatorSignal(indicator, candles, values);
|
|
|
|
let currentSignal = signal;
|
|
let lastSignalDate = indicator.lastSignalTimestamp || null;
|
|
let lastSignalType = indicator.lastSignalType || null;
|
|
|
|
if (!currentSignal || !currentSignal.type) {
|
|
console.log('[Signals] No valid signal for', indicator.type, '- Using last signal if available');
|
|
|
|
if (lastSignalType && lastSignalDate) {
|
|
currentSignal = {
|
|
type: lastSignalType,
|
|
strength: 50,
|
|
value: candles[candles.length - 1]?.close,
|
|
reasoning: `No crossover (price equals MA)`
|
|
};
|
|
} else {
|
|
console.log('[Signals] No previous signal available - Skipping');
|
|
continue;
|
|
}
|
|
} else {
|
|
const currentCandleTimestamp = candles[candles.length - 1].time;
|
|
|
|
if (currentSignal.type !== lastSignalType || !lastSignalType) {
|
|
console.log('[Signals] Signal changed for', indicator.type, ':', lastSignalType, '->', currentSignal.type);
|
|
lastSignalDate = currentCandleTimestamp;
|
|
lastSignalType = currentSignal.type;
|
|
indicator.lastSignalTimestamp = lastSignalDate;
|
|
indicator.lastSignalType = lastSignalType;
|
|
}
|
|
}
|
|
|
|
signals.push({
|
|
id: indicator.id,
|
|
name: meta?.name || indicator.type,
|
|
label: indicator.type?.toUpperCase(),
|
|
params: indicator.params && typeof indicator.params === 'object'
|
|
? Object.entries(indicator.params)
|
|
.filter(([k, v]) => !k.startsWith('_') && v !== undefined && v !== null)
|
|
.map(([k, v]) => `${k}=${v}`)
|
|
.join(', ')
|
|
: null,
|
|
type: indicator.type,
|
|
signal: currentSignal.type,
|
|
strength: Math.round(currentSignal.strength),
|
|
value: currentSignal.value,
|
|
reasoning: currentSignal.reasoning,
|
|
color: currentSignal.type === 'buy' ? '#26a69a' : currentSignal.type === 'sell' ? '#ef5350' : '#787b86',
|
|
lastSignalDate: lastSignalDate
|
|
});
|
|
}
|
|
|
|
console.log('[Signals] ========== calculateAllIndicatorSignals END ==========');
|
|
console.log('[Signals] Total signals calculated:', signals.length);
|
|
return signals;
|
|
} |