fast-technical-indicators
Version:
High-performance technical indicators with zero dependencies - compatible with technicalindicators package
1,391 lines (1,374 loc) • 274 kB
JavaScript
'use strict';
class CandleData {
constructor(open, high, low, close, volume) {
this.open = open;
this.high = high;
this.low = low;
this.close = close;
this.volume = volume;
}
}
function sma(input) {
const { period, values } = input;
if (period <= 0 || period > values.length) {
return [];
}
const result = [];
for (let i = period - 1; i < values.length; i++) {
let sum = 0;
for (let j = i - period + 1; j <= i; j++) {
sum += values[j];
}
result.push(sum / period);
}
return result;
}
class SMA {
constructor(input) {
this.values = [];
this.sum = 0;
this.period = input.period;
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
nextValue(value) {
this.values.push(value);
this.sum += value;
if (this.values.length > this.period) {
this.sum -= this.values.shift();
}
if (this.values.length === this.period) {
return this.sum / this.period;
}
return undefined;
}
getResult() {
if (this.values.length < this.period) {
return [];
}
return [this.sum / this.period];
}
}
SMA.calculate = sma;
function ema(input) {
const { period, values } = input;
if (period <= 0 || values.length < period) {
return [];
}
const result = [];
const multiplier = 2 / (period + 1);
// Use SMA for the first EMA value
const firstSMA = sma({ period, values: values.slice(0, period) });
let previousEMA = firstSMA[0];
result.push(previousEMA);
for (let i = period; i < values.length; i++) {
const currentEMA = (values[i] - previousEMA) * multiplier + previousEMA;
result.push(currentEMA);
previousEMA = currentEMA;
}
return result;
}
class EMA {
constructor(input) {
this.values = [];
this.initialized = false;
this.period = input.period;
this.multiplier = 2 / (input.period + 1);
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
nextValue(value) {
this.values.push(value);
if (!this.initialized) {
if (this.values.length === this.period) {
// Initialize with SMA
const sum = this.values.reduce((acc, val) => acc + val, 0);
this.previousEMA = sum / this.period;
this.initialized = true;
return this.previousEMA;
}
return undefined;
}
// Calculate EMA
const currentEMA = (value - this.previousEMA) * this.multiplier + this.previousEMA;
this.previousEMA = currentEMA;
return currentEMA;
}
getResult() {
if (!this.initialized || !this.previousEMA) {
return [];
}
return [this.previousEMA];
}
}
EMA.calculate = ema;
function wma(input) {
const { period, values } = input;
if (period <= 0 || period > values.length) {
return [];
}
const result = [];
for (let i = period - 1; i < values.length; i++) {
let weightedSum = 0;
let weightSum = 0;
for (let j = 0; j < period; j++) {
const weight = j + 1;
weightedSum += values[i - period + 1 + j] * weight;
weightSum += weight;
}
result.push(weightedSum / weightSum);
}
return result;
}
class WMA {
constructor(input) {
this.values = [];
this.period = input.period;
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
nextValue(value) {
this.values.push(value);
if (this.values.length > this.period) {
this.values.shift();
}
if (this.values.length === this.period) {
let weightedSum = 0;
let weightSum = 0;
for (let i = 0; i < this.period; i++) {
const weight = i + 1;
weightedSum += this.values[i] * weight;
weightSum += weight;
}
return weightedSum / weightSum;
}
return undefined;
}
getResult() {
if (this.values.length < this.period) {
return [];
}
let weightedSum = 0;
let weightSum = 0;
for (let i = 0; i < this.period; i++) {
const weight = i + 1;
weightedSum += this.values[i] * weight;
weightSum += weight;
}
return [weightedSum / weightSum];
}
}
WMA.calculate = wma;
function wema(input) {
const { period, values } = input;
if (period <= 0 || values.length < period) {
return [];
}
const result = [];
const alpha = 1 / period;
// Initialize with SMA of first period values
let sum = 0;
for (let i = 0; i < period; i++) {
sum += values[i];
}
let previousWEMA = sum / period;
result.push(previousWEMA);
// Calculate subsequent WEMA values
for (let i = period; i < values.length; i++) {
const currentWEMA = alpha * values[i] + (1 - alpha) * previousWEMA;
result.push(currentWEMA);
previousWEMA = currentWEMA;
}
return result;
}
class WEMA {
constructor(input) {
this.values = [];
this.initialized = false;
this.period = input.period;
this.alpha = 1 / input.period;
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
nextValue(value) {
this.values.push(value);
if (!this.initialized) {
if (this.values.length === this.period) {
// Initialize with SMA
const sum = this.values.reduce((acc, val) => acc + val, 0);
this.previousWEMA = sum / this.period;
this.initialized = true;
return this.previousWEMA;
}
return undefined;
}
// Calculate WEMA using Wilder's smoothing
const currentWEMA = this.alpha * value + (1 - this.alpha) * this.previousWEMA;
this.previousWEMA = currentWEMA;
return currentWEMA;
}
getResult() {
if (!this.initialized || !this.previousWEMA) {
return [];
}
return [this.previousWEMA];
}
}
WEMA.calculate = wema;
function macd(input) {
const { values = [], fastPeriod = 12, slowPeriod = 26, signalPeriod = 9, SimpleMAOscillator = false, SimpleMASignal = false } = input;
if (values.length < slowPeriod) {
return [];
}
const result = [];
// Calculate fast and slow moving averages
const fastMA = SimpleMAOscillator
? sma({ period: fastPeriod, values })
: ema({ period: fastPeriod, values });
const slowMA = SimpleMAOscillator
? sma({ period: slowPeriod, values })
: ema({ period: slowPeriod, values });
// Calculate MACD line
const macdLine = [];
const startIndex = slowPeriod - fastPeriod;
for (let i = 0; i < slowMA.length; i++) {
macdLine.push(fastMA[i + startIndex] - slowMA[i]);
}
// Calculate signal line
const signalLine = SimpleMASignal
? sma({ period: signalPeriod, values: macdLine })
: ema({ period: signalPeriod, values: macdLine });
// Create result array
for (let i = 0; i < macdLine.length; i++) {
const macdValue = macdLine[i];
const signalValue = i >= signalPeriod - 1 ? signalLine[i - signalPeriod + 1] : undefined;
const histogramValue = signalValue !== undefined ? macdValue - signalValue : undefined;
result.push({
MACD: macdValue,
signal: signalValue,
histogram: histogramValue
});
}
return result;
}
class MACD {
constructor(input) {
this.macdHistory = [];
this.initialized = false;
this.count = 0;
this.fastPeriod = input.fastPeriod || 12;
this.slowPeriod = input.slowPeriod || 26;
this.signalPeriod = input.signalPeriod || 9;
const EMACls = input.SimpleMAOscillator ? SMA : EMA;
const SignalCls = input.SimpleMASignal ? SMA : EMA;
this.fastEMA = new EMACls({ period: this.fastPeriod, values: [] });
this.slowEMA = new EMACls({ period: this.slowPeriod, values: [] });
this.signalEMA = new SignalCls({ period: this.signalPeriod, values: [] });
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
nextValue(value) {
this.count++;
const fastValue = this.fastEMA.nextValue(value);
const slowValue = this.slowEMA.nextValue(value);
if (fastValue === undefined || slowValue === undefined) {
return undefined;
}
const macdValue = fastValue - slowValue;
this.macdHistory.push(macdValue);
const signalValue = this.signalEMA.nextValue(macdValue);
const histogramValue = signalValue !== undefined ? macdValue - signalValue : undefined;
return {
MACD: macdValue,
signal: signalValue,
histogram: histogramValue
};
}
getResult() {
if (this.macdHistory.length === 0) {
return [];
}
const lastMACD = this.macdHistory[this.macdHistory.length - 1];
const signalResult = this.signalEMA.getResult();
const lastSignal = signalResult.length > 0 ? signalResult[0] : undefined;
const histogram = lastSignal !== undefined ? lastMACD - lastSignal : undefined;
return [{
MACD: lastMACD,
signal: lastSignal,
histogram: histogram
}];
}
}
MACD.calculate = macd;
function rsi(input) {
const { period = 14, values } = input;
if (period <= 0 || values.length <= period) {
return [];
}
const result = [];
const gains = [];
const losses = [];
// Calculate initial gains and losses
for (let i = 1; i < values.length; i++) {
const difference = values[i] - values[i - 1];
gains.push(difference > 0 ? difference : 0);
losses.push(difference < 0 ? Math.abs(difference) : 0);
}
// Calculate initial average gain and loss (SMA)
let avgGain = gains.slice(0, period).reduce((sum, gain) => sum + gain, 0) / period;
let avgLoss = losses.slice(0, period).reduce((sum, loss) => sum + loss, 0) / period;
// Calculate first RSI
let rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
result.push(parseFloat((100 - (100 / (1 + rs))).toFixed(2)));
// Calculate subsequent RSI using Wilder's smoothing
for (let i = period; i < gains.length; i++) {
avgGain = ((avgGain * (period - 1)) + gains[i]) / period;
avgLoss = ((avgLoss * (period - 1)) + losses[i]) / period;
rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
result.push(parseFloat((100 - (100 / (1 + rs))).toFixed(2)));
}
return result;
}
class RSI {
constructor(input) {
this.values = [];
this.gains = [];
this.losses = [];
this.avgGain = 0;
this.avgLoss = 0;
this.initialized = false;
this.count = 0;
this.period = input.period || 14;
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
nextValue(value) {
this.values.push(value);
this.count++;
if (this.values.length === 1) {
return undefined; // Need at least 2 values to calculate difference
}
const difference = value - this.values[this.values.length - 2];
const gain = difference > 0 ? difference : 0;
const loss = difference < 0 ? Math.abs(difference) : 0;
this.gains.push(gain);
this.losses.push(loss);
if (!this.initialized) {
if (this.gains.length === this.period) {
// Calculate initial average using SMA
this.avgGain = this.gains.reduce((sum, g) => sum + g, 0) / this.period;
this.avgLoss = this.losses.reduce((sum, l) => sum + l, 0) / this.period;
this.initialized = true;
const rs = this.avgLoss === 0 ? 100 : this.avgGain / this.avgLoss;
return parseFloat((100 - (100 / (1 + rs))).toFixed(2));
}
return undefined;
}
// Use Wilder's smoothing for subsequent values
this.avgGain = ((this.avgGain * (this.period - 1)) + gain) / this.period;
this.avgLoss = ((this.avgLoss * (this.period - 1)) + loss) / this.period;
const rs = this.avgLoss === 0 ? 100 : this.avgGain / this.avgLoss;
return parseFloat((100 - (100 / (1 + rs))).toFixed(2));
}
getResult() {
if (!this.initialized) {
return [];
}
const rs = this.avgLoss === 0 ? 100 : this.avgGain / this.avgLoss;
return [parseFloat((100 - (100 / (1 + rs))).toFixed(2))];
}
}
RSI.calculate = rsi;
function cci(input) {
const { period = 20, high, low, close } = input;
if (high.length !== low.length || low.length !== close.length || close.length < period) {
return [];
}
const result = [];
for (let i = period - 1; i < close.length; i++) {
// Calculate typical prices for the period
const typicalPrices = [];
for (let j = i - period + 1; j <= i; j++) {
typicalPrices.push((high[j] + low[j] + close[j]) / 3);
}
// Calculate SMA of typical prices
const smaTP = typicalPrices.reduce((sum, tp) => sum + tp, 0) / period;
// Calculate mean deviation
const meanDeviation = typicalPrices.reduce((sum, tp) => sum + Math.abs(tp - smaTP), 0) / period;
// Calculate CCI
const currentTypicalPrice = (high[i] + low[i] + close[i]) / 3;
let cciValue;
if (meanDeviation === 0) {
cciValue = 0; // Avoid division by zero
}
else {
cciValue = (currentTypicalPrice - smaTP) / (0.015 * meanDeviation);
}
result.push(cciValue);
}
return result;
}
class CCI {
constructor(input) {
this.highValues = [];
this.lowValues = [];
this.closeValues = [];
this.period = input.period || 20;
}
nextValue(high, low, close) {
this.highValues.push(high);
this.lowValues.push(low);
this.closeValues.push(close);
if (this.highValues.length > this.period) {
this.highValues.shift();
this.lowValues.shift();
this.closeValues.shift();
}
if (this.highValues.length === this.period) {
// Calculate typical prices for the period
const typicalPrices = [];
for (let i = 0; i < this.period; i++) {
typicalPrices.push((this.highValues[i] + this.lowValues[i] + this.closeValues[i]) / 3);
}
// Calculate SMA of typical prices
const smaTP = typicalPrices.reduce((sum, tp) => sum + tp, 0) / this.period;
// Calculate mean deviation
const meanDeviation = typicalPrices.reduce((sum, tp) => sum + Math.abs(tp - smaTP), 0) / this.period;
// Calculate CCI
const currentTypicalPrice = (high + low + close) / 3;
if (meanDeviation === 0) {
return 0;
}
return (currentTypicalPrice - smaTP) / (0.015 * meanDeviation);
}
return undefined;
}
getResult() {
if (this.highValues.length < this.period) {
return [];
}
// Calculate typical prices
const typicalPrices = [];
for (let i = 0; i < this.period; i++) {
typicalPrices.push((this.highValues[i] + this.lowValues[i] + this.closeValues[i]) / 3);
}
// Calculate SMA of typical prices
const smaTP = typicalPrices.reduce((sum, tp) => sum + tp, 0) / this.period;
// Calculate mean deviation
const meanDeviation = typicalPrices.reduce((sum, tp) => sum + Math.abs(tp - smaTP), 0) / this.period;
// Calculate CCI
const currentTypicalPrice = typicalPrices[typicalPrices.length - 1];
if (meanDeviation === 0) {
return [0];
}
return [(currentTypicalPrice - smaTP) / (0.015 * meanDeviation)];
}
}
CCI.calculate = cci;
function awesomeoscillator(input) {
const { high, low, fastPeriod = 5, slowPeriod = 34 } = input;
if (high.length !== low.length || high.length < slowPeriod) {
return [];
}
// Calculate midpoint prices (HL2)
const midpoints = [];
for (let i = 0; i < high.length; i++) {
midpoints.push((high[i] + low[i]) / 2);
}
// Calculate fast and slow SMAs of midpoints
const fastSMA = sma({ period: fastPeriod, values: midpoints });
const slowSMA = sma({ period: slowPeriod, values: midpoints });
// Calculate AO (Fast SMA - Slow SMA)
const result = [];
const startIndex = slowPeriod - fastPeriod;
for (let i = 0; i < slowSMA.length; i++) {
const aoValue = fastSMA[i + startIndex] - slowSMA[i];
result.push(aoValue);
}
return result;
}
class AwesomeOscillator {
constructor(input) {
this.highValues = [];
this.lowValues = [];
this.midpoints = [];
this.fastPeriod = input.fastPeriod || 5;
this.slowPeriod = input.slowPeriod || 34;
// Create SMA calculators
this.fastSMACalculator = {
values: [],
period: this.fastPeriod,
sum: 0,
nextValue: function (value) {
this.values.push(value);
this.sum += value;
if (this.values.length > this.period) {
this.sum -= this.values.shift();
}
if (this.values.length === this.period) {
return this.sum / this.period;
}
return undefined;
}
};
this.slowSMACalculator = {
values: [],
period: this.slowPeriod,
sum: 0,
nextValue: function (value) {
this.values.push(value);
this.sum += value;
if (this.values.length > this.period) {
this.sum -= this.values.shift();
}
if (this.values.length === this.period) {
return this.sum / this.period;
}
return undefined;
}
};
}
nextValue(high, low) {
this.highValues.push(high);
this.lowValues.push(low);
const midpoint = (high + low) / 2;
this.midpoints.push(midpoint);
const fastSMA = this.fastSMACalculator.nextValue(midpoint);
const slowSMA = this.slowSMACalculator.nextValue(midpoint);
if (fastSMA !== undefined && slowSMA !== undefined) {
return fastSMA - slowSMA;
}
return undefined;
}
getResult() {
if (this.midpoints.length === 0) {
return [];
}
this.midpoints[this.midpoints.length - 1];
const fastSMA = this.fastSMACalculator.values.length === this.fastPeriod
? this.fastSMACalculator.sum / this.fastPeriod
: undefined;
const slowSMA = this.slowSMACalculator.values.length === this.slowPeriod
? this.slowSMACalculator.sum / this.slowPeriod
: undefined;
if (fastSMA !== undefined && slowSMA !== undefined) {
return [fastSMA - slowSMA];
}
return [];
}
}
AwesomeOscillator.calculate = awesomeoscillator;
function roc(input) {
const { period, values } = input;
if (period <= 0 || values.length <= period) {
return [];
}
const result = [];
for (let i = period; i < values.length; i++) {
const currentPrice = values[i];
const pastPrice = values[i - period];
if (pastPrice === 0) {
result.push(0);
}
else {
const rocValue = ((currentPrice - pastPrice) / pastPrice) * 100;
result.push(rocValue);
}
}
return result;
}
class ROC {
constructor(input) {
this.values = [];
this.period = input.period;
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
nextValue(value) {
this.values.push(value);
if (this.values.length > this.period + 1) {
this.values.shift();
}
if (this.values.length === this.period + 1) {
const currentPrice = this.values[this.values.length - 1];
const pastPrice = this.values[0];
if (pastPrice === 0) {
return 0;
}
return ((currentPrice - pastPrice) / pastPrice) * 100;
}
return undefined;
}
getResult() {
if (this.values.length < this.period + 1) {
return [];
}
const currentPrice = this.values[this.values.length - 1];
const pastPrice = this.values[0];
if (pastPrice === 0) {
return [0];
}
return [((currentPrice - pastPrice) / pastPrice) * 100];
}
}
ROC.calculate = roc;
function stochastic(input) {
const { period = 14, signalPeriod = 3, high, low, close } = input;
if (high.length !== low.length || low.length !== close.length || close.length < period) {
return [];
}
const result = [];
// Calculate %K values
const kValues = [];
for (let i = period - 1; i < close.length; i++) {
const highestHigh = Math.max(...high.slice(i - period + 1, i + 1));
const lowestLow = Math.min(...low.slice(i - period + 1, i + 1));
let kValue;
if (highestHigh === lowestLow) {
kValue = 50; // Avoid division by zero
}
else {
kValue = ((close[i] - lowestLow) / (highestHigh - lowestLow)) * 100;
}
kValues.push(kValue);
}
// Calculate %D (SMA of %K)
const dValues = sma({ period: signalPeriod, values: kValues });
// Combine results
for (let i = 0; i < kValues.length; i++) {
const dValue = i >= signalPeriod - 1 ? dValues[i - signalPeriod + 1] : undefined;
result.push({
k: kValues[i],
d: dValue
});
}
return result;
}
class Stochastic {
constructor(input) {
this.highValues = [];
this.lowValues = [];
this.closeValues = [];
this.kValues = [];
this.period = input.period || 14;
this.signalPeriod = input.signalPeriod || 3;
// Import SMA class for D calculation - we'll handle this inline to avoid circular imports
this.dCalculator = {
values: [],
period: this.signalPeriod,
nextValue: (value) => {
this.dCalculator.values.push(value);
if (this.dCalculator.values.length > this.dCalculator.period) {
this.dCalculator.values.shift();
}
if (this.dCalculator.values.length === this.dCalculator.period) {
return this.dCalculator.values.reduce((sum, val) => sum + val, 0) / this.dCalculator.period;
}
return undefined;
}
};
}
nextValue(high, low, close) {
this.highValues.push(high);
this.lowValues.push(low);
this.closeValues.push(close);
// Keep only the required period
if (this.highValues.length > this.period) {
this.highValues.shift();
this.lowValues.shift();
this.closeValues.shift();
}
if (this.highValues.length === this.period) {
const highestHigh = Math.max(...this.highValues);
const lowestLow = Math.min(...this.lowValues);
const currentClose = close;
let kValue;
if (highestHigh === lowestLow) {
kValue = 50;
}
else {
kValue = ((currentClose - lowestLow) / (highestHigh - lowestLow)) * 100;
}
this.kValues.push(kValue);
const dValue = this.dCalculator.nextValue(kValue);
return {
k: kValue,
d: dValue
};
}
return undefined;
}
getResult() {
if (this.kValues.length === 0) {
return [];
}
const lastK = this.kValues[this.kValues.length - 1];
const lastD = this.dCalculator.values.length === this.signalPeriod
? this.dCalculator.values.reduce((sum, val) => sum + val, 0) / this.signalPeriod
: undefined;
return [{
k: lastK,
d: lastD
}];
}
}
Stochastic.calculate = stochastic;
function williamsr(input) {
const { period = 14, high, low, close } = input;
if (high.length !== low.length || low.length !== close.length || close.length < period) {
return [];
}
const result = [];
for (let i = period - 1; i < close.length; i++) {
const highestHigh = Math.max(...high.slice(i - period + 1, i + 1));
const lowestLow = Math.min(...low.slice(i - period + 1, i + 1));
let williamsR;
if (highestHigh === lowestLow) {
williamsR = -50; // Middle value when no range
}
else {
williamsR = ((highestHigh - close[i]) / (highestHigh - lowestLow)) * -100;
}
result.push(williamsR);
}
return result;
}
class WilliamsR {
constructor(input) {
this.highValues = [];
this.lowValues = [];
this.closeValues = [];
this.period = input.period || 14;
}
nextValue(high, low, close) {
this.highValues.push(high);
this.lowValues.push(low);
this.closeValues.push(close);
if (this.highValues.length > this.period) {
this.highValues.shift();
this.lowValues.shift();
this.closeValues.shift();
}
if (this.highValues.length === this.period) {
const highestHigh = Math.max(...this.highValues);
const lowestLow = Math.min(...this.lowValues);
const currentClose = close;
if (highestHigh === lowestLow) {
return -50;
}
return ((highestHigh - currentClose) / (highestHigh - lowestLow)) * -100;
}
return undefined;
}
getResult() {
if (this.highValues.length < this.period) {
return [];
}
const highestHigh = Math.max(...this.highValues);
const lowestLow = Math.min(...this.lowValues);
const currentClose = this.closeValues[this.closeValues.length - 1];
if (highestHigh === lowestLow) {
return [-50];
}
return [((highestHigh - currentClose) / (highestHigh - lowestLow)) * -100];
}
}
WilliamsR.calculate = williamsr;
function trix(input) {
const { period = 14, values } = input;
if (values.length < period * 3) {
return [];
}
// First EMA
const ema1 = ema({ period, values });
// Second EMA of first EMA
const ema2 = ema({ period, values: ema1 });
// Third EMA of second EMA
const ema3 = ema({ period, values: ema2 });
// Calculate TRIX: Rate of change of triple EMA
const result = [];
for (let i = 1; i < ema3.length; i++) {
const currentValue = ema3[i];
const previousValue = ema3[i - 1];
if (previousValue === 0) {
result.push(0);
}
else {
const trixValue = ((currentValue - previousValue) / previousValue) * 100;
result.push(trixValue);
}
}
return result;
}
class TRIX {
constructor(input) {
this.initialized = false;
this.period = input.period || 14;
// Create inline EMA calculators to avoid circular imports
this.ema1Calculator = this.createEMACalculator(this.period);
this.ema2Calculator = this.createEMACalculator(this.period);
this.ema3Calculator = this.createEMACalculator(this.period);
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
createEMACalculator(period) {
const multiplier = 2 / (period + 1);
return {
values: [],
period,
multiplier,
previousEMA: undefined,
initialized: false,
nextValue: function (value) {
this.values.push(value);
if (!this.initialized) {
if (this.values.length === this.period) {
const sum = this.values.reduce((acc, val) => acc + val, 0);
this.previousEMA = sum / this.period;
this.initialized = true;
return this.previousEMA;
}
return undefined;
}
const currentEMA = (value - this.previousEMA) * this.multiplier + this.previousEMA;
this.previousEMA = currentEMA;
return currentEMA;
}
};
}
nextValue(value) {
const ema1Value = this.ema1Calculator.nextValue(value);
if (ema1Value === undefined)
return undefined;
const ema2Value = this.ema2Calculator.nextValue(ema1Value);
if (ema2Value === undefined)
return undefined;
const ema3Value = this.ema3Calculator.nextValue(ema2Value);
if (ema3Value === undefined)
return undefined;
if (!this.initialized) {
this.previousTripleEMA = ema3Value;
this.initialized = true;
return undefined; // Need at least 2 values to calculate rate of change
}
const currentTripleEMA = ema3Value;
let trixValue;
if (this.previousTripleEMA === 0) {
trixValue = 0;
}
else {
trixValue = ((currentTripleEMA - this.previousTripleEMA) / this.previousTripleEMA) * 100;
}
this.previousTripleEMA = currentTripleEMA;
return trixValue;
}
getResult() {
if (!this.initialized || this.previousTripleEMA === undefined) {
return [];
}
// Return the last calculated TRIX value
const currentTripleEMA = this.ema3Calculator.previousEMA;
if (currentTripleEMA === undefined)
return [];
let trixValue;
if (this.previousTripleEMA === 0) {
trixValue = 0;
}
else {
trixValue = ((currentTripleEMA - this.previousTripleEMA) / this.previousTripleEMA) * 100;
}
return [trixValue];
}
}
TRIX.calculate = trix;
function stochasticrsi(input) {
const { rsiPeriod = 14, stochasticPeriod = 14, kPeriod = 3, dPeriod = 3, values } = input;
// Need enough data for full calculation
const requiredLength = rsiPeriod + stochasticPeriod + kPeriod + dPeriod - 1;
if (values.length < requiredLength) {
return [];
}
// Calculate RSI first
const rsiValues = rsi({ period: rsiPeriod, values });
if (rsiValues.length < stochasticPeriod) {
return [];
}
const result = [];
const stochRSIValues = [];
// Calculate Stochastic RSI
for (let i = stochasticPeriod - 1; i < rsiValues.length; i++) {
const rsiSlice = rsiValues.slice(i - stochasticPeriod + 1, i + 1);
const highestRSI = Math.max(...rsiSlice);
const lowestRSI = Math.min(...rsiSlice);
let stochRSI;
if (highestRSI === lowestRSI) {
stochRSI = 0; // Avoid division by zero
}
else {
stochRSI = ((rsiValues[i] - lowestRSI) / (highestRSI - lowestRSI)) * 100;
}
stochRSIValues.push(stochRSI);
}
// Calculate %K (SMA of Stochastic RSI)
const kValues = sma({ period: kPeriod, values: stochRSIValues });
// Calculate %D (SMA of %K)
const dValues = sma({ period: dPeriod, values: kValues });
// Only return results when we have complete D values
// This means we start from kPeriod + dPeriod - 2 index in stochRSI
const startIndex = kPeriod + dPeriod - 2;
for (let i = startIndex; i < stochRSIValues.length; i++) {
const kIndex = i - kPeriod + 1;
const dIndex = i - kPeriod - dPeriod + 2;
result.push({
stochRSI: stochRSIValues[i],
k: kValues[kIndex],
d: dValues[dIndex]
});
}
return result;
}
class StochasticRSI {
constructor(input) {
this.values = [];
this.rsiValues = [];
this.stochRSIValues = [];
this.rsiPeriod = input.rsiPeriod || 14;
this.stochasticPeriod = input.stochasticPeriod || 14;
this.kPeriod = input.kPeriod || 3;
this.dPeriod = input.dPeriod || 3;
// Create inline RSI calculator
this.rsiCalculator = this.createRSICalculator(this.rsiPeriod);
// Create SMA calculators for K and D
this.kCalculator = this.createSMACalculator(this.kPeriod);
this.dCalculator = this.createSMACalculator(this.dPeriod);
if (input.values?.length) {
input.values.forEach(value => this.nextValue(value));
}
}
createRSICalculator(period) {
return {
values: [],
gains: [],
losses: [],
avgGain: 0,
avgLoss: 0,
initialized: false,
period,
nextValue: function (value) {
this.values.push(value);
if (this.values.length === 1)
return undefined;
const diff = value - this.values[this.values.length - 2];
const gain = diff > 0 ? diff : 0;
const loss = diff < 0 ? Math.abs(diff) : 0;
this.gains.push(gain);
this.losses.push(loss);
if (!this.initialized) {
if (this.gains.length === this.period) {
this.avgGain = this.gains.reduce((sum, g) => sum + g, 0) / this.period;
this.avgLoss = this.losses.reduce((sum, l) => sum + l, 0) / this.period;
this.initialized = true;
const rs = this.avgLoss === 0 ? 100 : this.avgGain / this.avgLoss;
return parseFloat((100 - (100 / (1 + rs))).toFixed(2));
}
return undefined;
}
this.avgGain = ((this.avgGain * (this.period - 1)) + gain) / this.period;
this.avgLoss = ((this.avgLoss * (this.period - 1)) + loss) / this.period;
const rs = this.avgLoss === 0 ? 100 : this.avgGain / this.avgLoss;
return parseFloat((100 - (100 / (1 + rs))).toFixed(2));
}
};
}
createSMACalculator(period) {
return {
values: [],
period,
nextValue: function (value) {
this.values.push(value);
if (this.values.length > this.period) {
this.values.shift();
}
if (this.values.length === this.period) {
return this.values.reduce((sum, val) => sum + val, 0) / this.period;
}
return undefined;
}
};
}
nextValue(value) {
this.values.push(value);
// Calculate RSI
const rsiValue = this.rsiCalculator.nextValue(value);
if (rsiValue === undefined)
return undefined;
this.rsiValues.push(rsiValue);
// Keep only required RSI values
if (this.rsiValues.length > this.stochasticPeriod) {
this.rsiValues.shift();
}
if (this.rsiValues.length < this.stochasticPeriod) {
return undefined;
}
// Calculate Stochastic RSI
const highestRSI = Math.max(...this.rsiValues);
const lowestRSI = Math.min(...this.rsiValues);
let stochRSI;
if (highestRSI === lowestRSI) {
stochRSI = 0;
}
else {
stochRSI = ((rsiValue - lowestRSI) / (highestRSI - lowestRSI)) * 100;
}
this.stochRSIValues.push(stochRSI);
// Calculate %K and %D
const kValue = this.kCalculator.nextValue(stochRSI);
const dValue = kValue !== undefined ? this.dCalculator.nextValue(kValue) : undefined;
// Only return when we have complete %D values (same logic as functional version)
if (dValue === undefined) {
return undefined;
}
return {
stochRSI: stochRSI,
k: kValue,
d: dValue
};
}
getResult() {
if (this.stochRSIValues.length === 0) {
return [];
}
const lastStochRSI = this.stochRSIValues[this.stochRSIValues.length - 1];
const kResult = this.kCalculator.values.length === this.kPeriod
? this.kCalculator.values.reduce((sum, val) => sum + val, 0) / this.kPeriod
: undefined;
const dResult = this.dCalculator.values.length === this.dPeriod
? this.dCalculator.values.reduce((sum, val) => sum + val, 0) / this.dPeriod
: undefined;
return [{
stochRSI: lastStochRSI,
k: kResult,
d: dResult
}];
}
}
StochasticRSI.calculate = stochasticrsi;
function psar(input) {
const { high, low, step = 0.02, max = 0.2 } = input;
if (high.length !== low.length || high.length < 1) {
return [];
}
const result = [];
let curr;
let extreme;
let sar;
let furthest;
let up = true;
let accel = step;
let prev;
// Process each period
for (let i = 0; i < high.length; i++) {
curr = { high: high[i], low: low[i] };
if (prev && curr && sar !== undefined && extreme !== undefined && furthest !== undefined) {
sar = sar + accel * (extreme - sar);
if (up) {
sar = Math.min(sar, furthest.low, prev.low);
if (curr.high > extreme) {
extreme = curr.high;
accel = Math.min(accel + step, max);
}
}
else {
sar = Math.max(sar, furthest.high, prev.high);
if (curr.low < extreme) {
extreme = curr.low;
accel = Math.min(accel + step, max);
}
}
if ((up && curr.low < sar) || (!up && curr.high > sar)) {
accel = step;
sar = extreme;
up = !up;
extreme = !up ? curr.low : curr.high;
}
}
else {
// Initialize on first data point
sar = curr.low;
extreme = curr.high;
}
furthest = prev || curr;
if (curr) {
prev = curr;
}
result.push(sar);
}
return result;
}
class PSAR {
constructor(input) {
this.up = true;
this.results = [];
this.step = input.step || 0.02;
this.max = input.max || 0.2;
this.accel = this.step;
}
nextValue(high, low) {
this.curr = { high, low };
if (this.prev) {
if (this.prev && this.curr) {
this.sar = this.sar + this.accel * (this.extreme - this.sar);
if (this.up) {
this.sar = Math.min(this.sar, this.furthest.low, this.prev.low);
if (this.curr.high > this.extreme) {
this.extreme = this.curr.high;
this.accel = Math.min(this.accel + this.step, this.max);
}
}
else {
this.sar = Math.max(this.sar, this.furthest.high, this.prev.high);
if (this.curr.low < this.extreme) {
this.extreme = this.curr.low;
this.accel = Math.min(this.accel + this.step, this.max);
}
}
if ((this.up && this.curr.low < this.sar) || (!this.up && this.curr.high > this.sar)) {
this.accel = this.step;
this.sar = this.extreme;
this.up = !this.up;
this.extreme = !this.up ? this.curr.low : this.curr.high;
}
}
}
else {
// Initialize on first data point
this.sar = this.curr.low;
this.extreme = this.curr.high;
}
this.furthest = this.prev || this.curr;
if (this.curr) {
this.prev = this.curr;
}
this.results.push(this.sar);
return this.sar;
}
getResult() {
return this.results;
}
}
PSAR.calculate = psar;
function kst(input) {
const { values, ROCPer1 = 10, ROCPer2 = 15, ROCPer3 = 20, ROCPer4 = 30, SMAROCPer1 = 10, SMAROCPer2 = 10, SMAROCPer3 = 10, SMAROCPer4 = 15, signalPeriod = 9 } = input;
if (!values || values.length === 0) {
return [];
}
const result = [];
// Calculate minimum required data points for first result
const firstResult = Math.max(ROCPer1 + SMAROCPer1, ROCPer2 + SMAROCPer2, ROCPer3 + SMAROCPer3, ROCPer4 + SMAROCPer4);
if (values.length < firstResult) {
return [];
}
// Arrays to store ROC values for SMA calculation
const roc1Values = [];
const roc2Values = [];
const roc3Values = [];
const roc4Values = [];
// KST values for signal calculation
const kstValues = [];
for (let i = 0; i < values.length; i++) {
// Calculate ROC values
let roc1Value;
let roc2Value;
let roc3Value;
let roc4Value;
if (i >= ROCPer1) {
roc1Value = ((values[i] - values[i - ROCPer1]) / values[i - ROCPer1]) * 100;
roc1Values.push(roc1Value);
}
if (i >= ROCPer2) {
roc2Value = ((values[i] - values[i - ROCPer2]) / values[i - ROCPer2]) * 100;
roc2Values.push(roc2Value);
}
if (i >= ROCPer3) {
roc3Value = ((values[i] - values[i - ROCPer3]) / values[i - ROCPer3]) * 100;
roc3Values.push(roc3Value);
}
if (i >= ROCPer4) {
roc4Value = ((values[i] - values[i - ROCPer4]) / values[i - ROCPer4]) * 100;
roc4Values.push(roc4Value);
}
// Calculate SMA values
let rcma1;
let rcma2;
let rcma3;
let rcma4;
if (roc1Values.length >= SMAROCPer1) {
const sma1Sum = roc1Values.slice(-SMAROCPer1).reduce((sum, val) => sum + val, 0);
rcma1 = sma1Sum / SMAROCPer1;
}
if (roc2Values.length >= SMAROCPer2) {
const sma2Sum = roc2Values.slice(-SMAROCPer2).reduce((sum, val) => sum + val, 0);
rcma2 = sma2Sum / SMAROCPer2;
}
if (roc3Values.length >= SMAROCPer3) {
const sma3Sum = roc3Values.slice(-SMAROCPer3).reduce((sum, val) => sum + val, 0);
rcma3 = sma3Sum / SMAROCPer3;
}
if (roc4Values.length >= SMAROCPer4) {
const sma4Sum = roc4Values.slice(-SMAROCPer4).reduce((sum, val) => sum + val, 0);
rcma4 = sma4Sum / SMAROCPer4;
}
// Calculate KST if we have all components
if (i >= firstResult - 1 && rcma1 !== undefined && rcma2 !== undefined && rcma3 !== undefined && rcma4 !== undefined) {
const kstValue = (rcma1 * 1) + (rcma2 * 2) + (rcma3 * 3) + (rcma4 * 4);
kstValues.push(kstValue);
// Calculate signal (SMA of KST)
let signalValue;
if (kstValues.length >= signalPeriod) {
const signalSum = kstValues.slice(-signalPeriod).reduce((sum, val) => sum + val, 0);
signalValue = signalSum / signalPeriod;
}
result.push({
kst: kstValue,
signal: signalValue
});
}
}
return result;
}
class KST {
constructor(input) {
this.values = [];
// Arrays to store ROC values for SMA calculation
this.roc1Values = [];
this.roc2Values = [];
this.roc3Values = [];
this.roc4Values = [];
// KST values for signal calculation
this.kstValues = [];
this.results = [];
this.ROCPer1 = input.ROCPer1 || 10;
this.ROCPer2 = input.ROCPer2 || 15;
this.ROCPer3 = input.ROCPer3 || 20;
this.ROCPer4 = input.ROCPer4 || 30;
this.SMAROCPer1 = input.SMAROCPer1 || 10;
this.SMAROCPer2 = input.SMAROCPer2 || 10;
this.SMAROCPer3 = input.SMAROCPer3 || 10;
this.SMAROCPer4 = input.SMAROCPer4 || 15;
this.signalPeriod = input.signalPeriod || 9;
this.firstResult = Math.max(this.ROCPer1 + this.SMAROCPer1, this.ROCPer2 + this.SMAROCPer2, this.ROCPer3 + this.SMAROCPer3, this.ROCPer4 + this.SMAROCPer4);
if (input.values && input.values.length) {
input.values.forEach(value => this.nextValue(value));
}
}
nextValue(value) {
this.values.push(value);
const i = this.values.length - 1;
// Calculate ROC values
if (i >= this.ROCPer1) {
const roc1Value = ((value - this.values[i - this.ROCPer1]) / this.values[i - this.ROCPer1]) * 100;
this.roc1Values.push(roc1Value);
}
if (i >= this.ROCPer2) {
const roc2Value = ((value - this.values[i - this.ROCPer2]) / this.values[i - this.ROCPer2]) * 100;
this.roc2Values.push(roc2Value);
}
if (i >= this.ROCPer3) {
const roc3Value = ((value - this.values[i - this.ROCPer3]) / this.values[i - this.ROCPer3]) * 100;
this.roc3Values.push(roc3Value);
}
if (i >= this.ROCPer4) {
const roc4Value = ((value - this.values[i - this.ROCPer4]) / this.values[i - this.ROCPer4]) * 100;
this.roc4Values.push(roc4Value);
}
// Calculate SMA values
let rcma1;
let rcma2;
let rcma3;
let rcma4;
if (this.roc1Values.length >= this.SMAROCPer1) {
const sma1Sum = this.roc1Values.slice(-this.SMAROCPer1).reduce((sum, val) => sum + val, 0);
rcma1 = sma1Sum / this.SMAROCPer1;
}
if (this.roc2Values.length >= this.SMAROCPer2) {
const sma2Sum = this.roc2Values.slice(-this.SMAROCPer2).reduce((sum, val) => sum + val, 0);
rcma2 = sma2Sum / this.SMAROCPer2;
}
if (this.roc3Values.length >= this.SMAROCPer3) {
const sma3Sum = this.roc3Values.slice(-this.SMAROCPer3).reduce((sum, val) => sum + val, 0);
rcma3 = sma3Sum / this.SMAROCPer3;
}
if (this.roc4Values.length >= this.SMAROCPer4) {
const sma4Sum = this.roc4Values.slice(-this.SMAROCPer4).reduce((sum, val) => sum + val, 0);
rcma4 = sma4Sum / this.SMAROCPer4;
}
// Calculate KST if we have all components
if (i >= this.firstResult - 1 && rcma1 !== undefined && rcma2 !== undefined && rcma3 !== undefined && rcma4 !== undefined) {
const kstValue = (rcma1 * 1) + (rcma2 * 2) + (rcma3 * 3) + (rcma4 * 4);
this.kstValues.push(kstValue);
// Calculate signal (SMA of KST)
let signalValue;
if (this.kstValues.length >= this.signalPeriod) {
const signalSum = this.kstValues.slice(-this.signalPeriod).reduce((sum, val) => sum + val, 0);
signalValue = signalSum / this.signalPeriod;
}
const result = {
kst: kstValue,
signal: signalValue
};
this.results.push(result);
return result;
}
return undefined;
}
getResult() {
return this.results;
}
}
KST.calculate = kst;
function trueRange$2(high, low, previousClose) {
const highLow = high - low;
const highPrevClose = Math.abs(high - previousClose);
const lowPrevClose = Math.abs(low - previousClose);
return Math.max(highLow, highPrevClose, lowPrevClose);
}
function buyingPressure(high, low, close) {
return close - Math.min(low, close);
}
function calculateSum(values, period, startIndex) {
let sum = 0;
for (let i = Math.max(0, startIndex - period + 1); i <= startIndex; i++) {
sum += values[i];
}
return sum;
}
function ultimateoscillator(input) {
const { high, low, close, period1 = 7, period2 = 14, period3 = 28 } = input;
if (high.length !== low.length || low.length !== close.length || close.length < period3 + 1) {
return [];
}
const result = [];
const trueRanges = [];
const buyingPressures = [];
// Calculate true ranges and buying pressures (skip first value since we need previous close)
for (let i = 1; i < close.length; i++) {
const tr = trueRange$2(high[i], low[i], close[i - 1]);
const bp = buyingPressure(high[i], low[i], close[i]);
trueRanges.push(tr);
buyingPressures.push(bp);
}
// Calculate Ultimate Oscillator starting from period3
for (let i = period3 - 1; i < trueRanges.length; i++) {
// Calculate sums for each period
const bp1Sum = calculateSum(buyingPressures, period1, i);
const tr1Sum = calculateSum(trueRanges, period1, i);
const raw1 = bp1Sum / tr1Sum;
const