139 lines
4.3 KiB
JavaScript
139 lines
4.3 KiB
JavaScript
// Self-contained Stochastic Oscillator 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;
|
|
}
|
|
}
|
|
|
|
// Signal calculation for Stochastic
|
|
function calculateStochSignal(indicator, lastCandle, prevCandle, values, prevValues) {
|
|
const k = values?.k;
|
|
const d = values?.d;
|
|
const prevK = prevValues?.k;
|
|
const prevD = prevValues?.d;
|
|
const overbought = indicator.params?.overbought || 80;
|
|
const oversold = indicator.params?.oversold || 20;
|
|
|
|
if (k === undefined || d === undefined || prevK === undefined || prevD === undefined) {
|
|
return null;
|
|
}
|
|
|
|
// BUY: %K crosses UP through %D while both are oversold
|
|
if (prevK <= prevD && k > d && k < oversold) {
|
|
return {
|
|
type: SIGNAL_TYPES.BUY,
|
|
strength: 80,
|
|
value: k,
|
|
reasoning: `Stochastic %K crossed UP through %D in oversold zone`
|
|
};
|
|
}
|
|
// SELL: %K crosses DOWN through %D while both are overbought
|
|
else if (prevK >= prevD && k < d && k > overbought) {
|
|
return {
|
|
type: SIGNAL_TYPES.SELL,
|
|
strength: 80,
|
|
value: k,
|
|
reasoning: `Stochastic %K crossed DOWN through %D in overbought zone`
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Stochastic Oscillator Indicator class
|
|
export class StochasticIndicator extends BaseIndicator {
|
|
constructor(config) {
|
|
super(config);
|
|
this.lastSignalTimestamp = null;
|
|
this.lastSignalType = null;
|
|
}
|
|
|
|
calculate(candles) {
|
|
const kPeriod = this.params.kPeriod || 14;
|
|
const dPeriod = this.params.dPeriod || 3;
|
|
const results = new Array(candles.length).fill(null);
|
|
|
|
const kValues = new Array(candles.length).fill(null);
|
|
|
|
for (let i = kPeriod - 1; i < candles.length; i++) {
|
|
let lowest = Infinity;
|
|
let highest = -Infinity;
|
|
for (let j = 0; j < kPeriod; j++) {
|
|
lowest = Math.min(lowest, candles[i-j].low);
|
|
highest = Math.max(highest, candles[i-j].high);
|
|
}
|
|
const diff = highest - lowest;
|
|
kValues[i] = diff === 0 ? 50 : ((candles[i].close - lowest) / diff) * 100;
|
|
}
|
|
|
|
for (let i = kPeriod + dPeriod - 2; i < candles.length; i++) {
|
|
let sum = 0;
|
|
for (let j = 0; j < dPeriod; j++) sum += kValues[i-j];
|
|
results[i] = { k: kValues[i], d: sum / dPeriod };
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
getMetadata() {
|
|
return {
|
|
name: 'Stochastic',
|
|
description: 'Stochastic Oscillator - compares close to high-low range',
|
|
inputs: [
|
|
{
|
|
name: 'kPeriod',
|
|
label: '%K Period',
|
|
type: 'number',
|
|
default: 14,
|
|
min: 1,
|
|
max: 100,
|
|
description: 'Lookback period for %K calculation'
|
|
},
|
|
{
|
|
name: 'dPeriod',
|
|
label: '%D Period',
|
|
type: 'number',
|
|
default: 3,
|
|
min: 1,
|
|
max: 20,
|
|
description: 'Smoothing period for %D (SMA of %K)'
|
|
}
|
|
],
|
|
plots: [
|
|
{ id: 'k', color: '#3f51b5', title: '%K', style: 'solid', width: 1 },
|
|
{ id: 'd', color: '#ff9800', title: '%D', style: 'solid', width: 1 }
|
|
],
|
|
displayMode: 'pane',
|
|
paneMin: 0,
|
|
paneMax: 100
|
|
};
|
|
}
|
|
}
|
|
|
|
export { calculateStochSignal }; |