meridianalgo-js
Version:
Advanced algorithmic trading library for Node.js & TypeScript with 100+ technical indicators, pattern recognition, and risk management tools.
391 lines • 15.2 kB
JavaScript
"use strict";
/**
* Momentum Technical Indicators
*
* This module provides momentum-based technical analysis indicators including
* Rate of Change, Momentum, Chande Momentum Oscillator, and other momentum metrics.
*
* @fileoverview Momentum indicators for technical analysis
* @author MeridianAlgo
* @version 1.0.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MomentumIndicators = exports.kst = exports.coppockCurve = exports.chandeForecastOscillator = exports.dpo = exports.pvo = exports.ppo = exports.rvi = exports.cmo = exports.momentum = exports.roc = void 0;
const indicators_1 = require("./indicators");
/**
* Rate of Change (ROC)
*
* ROC measures the percentage change in price over a specified period.
* It's a pure momentum oscillator that shows the speed of price change.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 10)
* @returns Array of ROC values as percentages
*
* @example
* ```typescript
* const prices = [100, 102, 101, 103, 105, 104, 106];
* const roc = MomentumIndicators.roc(prices, 5);
* ```
*/
function roc(prices, period = 10) {
if (prices.length < period + 1)
return [];
const rocValues = [];
for (let i = period; i < prices.length; i++) {
const currentPrice = prices[i];
const pastPrice = prices[i - period];
const roc = pastPrice !== 0 ? ((currentPrice - pastPrice) / pastPrice) * 100 : 0;
rocValues.push(roc);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - rocValues.length;
return new Array(padLength).fill(NaN).concat(rocValues);
}
exports.roc = roc;
/**
* Momentum
*
* Momentum measures the rate of change in prices over a specified period.
* It's similar to ROC but shows the actual price difference rather than percentage.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 10)
* @returns Array of momentum values
*/
function momentum(prices, period = 10) {
if (prices.length < period + 1)
return [];
const momentumValues = [];
for (let i = period; i < prices.length; i++) {
momentumValues.push(prices[i] - prices[i - period]);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - momentumValues.length;
return new Array(padLength).fill(NaN).concat(momentumValues);
}
exports.momentum = momentum;
/**
* Chande Momentum Oscillator (CMO)
*
* CMO is a momentum oscillator that measures the momentum on both up and down days.
* It ranges from -100 to +100 and can identify overbought/oversold conditions.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 14)
* @returns Array of CMO values
*/
function cmo(prices, period = 14) {
if (prices.length < period + 1)
return [];
const gains = [];
const losses = [];
for (let i = 1; i < prices.length; i++) {
const change = prices[i] - prices[i - 1];
gains.push(Math.max(0, change));
losses.push(Math.max(0, -change));
}
const sumGains = [];
const sumLosses = [];
for (let i = period - 1; i < gains.length; i++) {
let gainSum = 0;
let lossSum = 0;
for (let j = i - period + 1; j <= i; j++) {
gainSum += gains[j];
lossSum += losses[j];
}
sumGains.push(gainSum);
sumLosses.push(lossSum);
}
const cmoValues = [];
for (let i = 0; i < sumGains.length; i++) {
const total = sumGains[i] + sumLosses[i];
const cmo = total !== 0 ? ((sumGains[i] - sumLosses[i]) / total) * 100 : 0;
cmoValues.push(cmo);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - cmoValues.length;
return new Array(padLength).fill(NaN).concat(cmoValues);
}
exports.cmo = cmo;
/**
* Relative Vigor Index (RVI)
*
* RVI measures the conviction of a recent price action by comparing closing prices
* to the trading range and smoothing the result with a moving average.
*
* @param open - Array of opening prices
* @param high - Array of high prices
* @param low - Array of low prices
* @param close - Array of closing prices
* @param period - Number of periods for calculation (default: 10)
* @returns Object containing RVI and signal line
*/
function rvi(open, high, low, close, period = 10) {
if (open.length !== high.length || open.length !== low.length || open.length !== close.length) {
throw new Error('Open, high, low, and close arrays must have the same length');
}
if (open.length < period) {
return { rvi: [], signal: [] };
}
const numerator = [];
const denominator = [];
for (let i = 0; i < open.length; i++) {
const closeOpen = close[i] - open[i];
const highLow = high[i] - low[i];
numerator.push(closeOpen);
denominator.push(highLow);
}
const numeratorMA = indicators_1.Indicators.sma(numerator, period);
const denominatorMA = indicators_1.Indicators.sma(denominator, period);
const rviValues = [];
for (let i = 0; i < numeratorMA.length; i++) {
rviValues.push(denominatorMA[i] !== 0 ? numeratorMA[i] / denominatorMA[i] : 0);
}
const signal = indicators_1.Indicators.sma(rviValues, 4);
// Pad the beginning with NaN to match input length
const padLength = open.length - rviValues.length;
const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr);
return {
rvi: padArray(rviValues),
signal: padArray(signal)
};
}
exports.rvi = rvi;
/**
* Percentage Price Oscillator (PPO)
*
* PPO is similar to MACD but uses percentage instead of absolute values.
* It shows the percentage difference between two moving averages.
*
* @param prices - Array of price data
* @param fastPeriod - Fast moving average period (default: 12)
* @param slowPeriod - Slow moving average period (default: 26)
* @param signalPeriod - Signal line period (default: 9)
* @returns Object containing PPO line, signal line, and histogram
*/
function ppo(prices, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
if (fastPeriod >= slowPeriod) {
throw new Error('Fast period must be less than slow period');
}
const fastMA = indicators_1.Indicators.ema(prices, fastPeriod);
const slowMA = indicators_1.Indicators.ema(prices, slowPeriod);
const ppoValues = [];
const offset = Math.abs(fastMA.length - slowMA.length);
for (let i = 0; i < Math.min(fastMA.length, slowMA.length); i++) {
const fast = fastMA[i + (fastMA.length > slowMA.length ? offset : 0)];
const slow = slowMA[i + (slowMA.length > fastMA.length ? offset : 0)];
const ppo = slow !== 0 ? ((fast - slow) / slow) * 100 : 0;
ppoValues.push(ppo);
}
const signal = indicators_1.Indicators.sma(ppoValues, signalPeriod);
const histogram = [];
for (let i = 0; i < signal.length; i++) {
const signalOffset = ppoValues.length - signal.length;
histogram.push(ppoValues[i + signalOffset] - signal[i]);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - ppoValues.length;
const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr);
return {
ppo: padArray(ppoValues),
signal: padArray(signal),
histogram: padArray(histogram)
};
}
exports.ppo = ppo;
/**
* Percentage Volume Oscillator (PVO)
*
* PVO measures the percentage difference between two volume moving averages.
* It helps identify volume trends and potential reversals.
*
* @param volume - Array of volume values
* @param fastPeriod - Fast moving average period (default: 12)
* @param slowPeriod - Slow moving average period (default: 26)
* @param signalPeriod - Signal line period (default: 9)
* @returns Object containing PVO line, signal line, and histogram
*/
function pvo(volume, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
if (fastPeriod >= slowPeriod) {
throw new Error('Fast period must be less than slow period');
}
const fastMA = indicators_1.Indicators.ema(volume, fastPeriod);
const slowMA = indicators_1.Indicators.ema(volume, slowPeriod);
const pvoValues = [];
const offset = Math.abs(fastMA.length - slowMA.length);
for (let i = 0; i < Math.min(fastMA.length, slowMA.length); i++) {
const fast = fastMA[i + (fastMA.length > slowMA.length ? offset : 0)];
const slow = slowMA[i + (slowMA.length > fastMA.length ? offset : 0)];
const pvo = slow !== 0 ? ((fast - slow) / slow) * 100 : 0;
pvoValues.push(pvo);
}
const signal = indicators_1.Indicators.sma(pvoValues, signalPeriod);
const histogram = [];
for (let i = 0; i < signal.length; i++) {
const signalOffset = pvoValues.length - signal.length;
histogram.push(pvoValues[i + signalOffset] - signal[i]);
}
// Pad the beginning with NaN to match input length
const padLength = volume.length - pvoValues.length;
const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr);
return {
pvo: padArray(pvoValues),
signal: padArray(signal),
histogram: padArray(histogram)
};
}
exports.pvo = pvo;
/**
* Detrended Price Oscillator (DPO)
*
* DPO removes the trend from prices, making it easier to identify cycles
* and overbought/oversold conditions.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 20)
* @returns Array of DPO values
*/
function dpo(prices, period = 20) {
if (prices.length < period)
return [];
const sma = indicators_1.Indicators.sma(prices, period);
const dpoValues = [];
for (let i = 0; i < sma.length; i++) {
const priceIndex = i + period - 1;
const shiftedPrice = prices[priceIndex - Math.floor(period / 2) - 1];
dpoValues.push(shiftedPrice - sma[i]);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - dpoValues.length;
return new Array(padLength).fill(NaN).concat(dpoValues);
}
exports.dpo = dpo;
/**
* Chande Forecast Oscillator
*
* The Chande Forecast Oscillator is based on linear regression analysis.
* It helps identify trend changes and potential reversals.
*
* @param prices - Array of price data
* @param period - Number of periods for calculation (default: 14)
* @returns Array of forecast oscillator values
*/
function chandeForecastOscillator(prices, period = 14) {
if (prices.length < period)
return [];
const forecastValues = [];
for (let i = period - 1; i < prices.length; i++) {
const slice = prices.slice(i - period + 1, i + 1);
// Calculate linear regression
let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
for (let j = 0; j < slice.length; j++) {
const x = j;
const y = slice[j];
sumX += x;
sumY += y;
sumXY += x * y;
sumXX += x * x;
}
const n = slice.length;
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
const intercept = (sumY - slope * sumX) / n;
// Forecast for the next period
const forecast = slope * n + intercept;
const actual = prices[i];
const forecastOscillator = actual - forecast;
forecastValues.push(forecastOscillator);
}
// Pad the beginning with NaN to match input length
const padLength = prices.length - forecastValues.length;
return new Array(padLength).fill(NaN).concat(forecastValues);
}
exports.chandeForecastOscillator = chandeForecastOscillator;
/**
* Coppock Curve
*
* The Coppock Curve is a momentum indicator that uses rate of change
* to identify long-term buying opportunities.
*
* @param prices - Array of price data
* @param roc1Period - First ROC period (default: 14)
* @param roc2Period - Second ROC period (default: 11)
* @param wmaPeriod - WMA smoothing period (default: 10)
* @returns Array of Coppock Curve values
*/
function coppockCurve(prices, roc1Period = 14, roc2Period = 11, wmaPeriod = 10) {
const roc1 = roc(prices, roc1Period);
const roc2 = roc(prices, roc2Period);
const coppockValues = [];
const maxLength = Math.max(roc1.length, roc2.length);
for (let i = 0; i < maxLength; i++) {
const r1 = i < roc1.length ? roc1[i] : 0;
const r2 = i < roc2.length ? roc2[i] : 0;
coppockValues.push(r1 + r2);
}
// Apply WMA smoothing - filter out NaN values
const validValues = coppockValues.filter(val => !isNaN(val));
if (validValues.length < wmaPeriod) {
return new Array(prices.length).fill(NaN);
}
return indicators_1.Indicators.sma(validValues, wmaPeriod);
}
exports.coppockCurve = coppockCurve;
/**
* KST Oscillator (Know Sure Thing)
*
* KST is a momentum oscillator that combines multiple rate of change indicators
* with different time periods to create a comprehensive momentum signal.
*
* @param prices - Array of price data
* @param roc1 - First ROC period (default: 10)
* @param roc2 - Second ROC period (default: 15)
* @param roc3 - Third ROC period (default: 20)
* @param roc4 - Fourth ROC period (default: 30)
* @param sma1 - First SMA period (default: 10)
* @param sma2 - Second SMA period (default: 10)
* @param sma3 - Third SMA period (default: 10)
* @param sma4 - Fourth SMA period (default: 15)
* @returns Object containing KST and signal line
*/
function kst(prices, roc1 = 10, roc2 = 15, roc3 = 20, roc4 = 30, sma1 = 10, sma2 = 10, sma3 = 10, sma4 = 15) {
const roc1Values = roc(prices, roc1);
const roc2Values = roc(prices, roc2);
const roc3Values = roc(prices, roc3);
const roc4Values = roc(prices, roc4);
const sma1Values = indicators_1.Indicators.sma(roc1Values.filter(val => !isNaN(val)), sma1);
const sma2Values = indicators_1.Indicators.sma(roc2Values.filter(val => !isNaN(val)), sma2);
const sma3Values = indicators_1.Indicators.sma(roc3Values.filter(val => !isNaN(val)), sma3);
const sma4Values = indicators_1.Indicators.sma(roc4Values.filter(val => !isNaN(val)), sma4);
const kstValues = [];
const minLength = Math.min(sma1Values.length, sma2Values.length, sma3Values.length, sma4Values.length);
for (let i = 0; i < minLength; i++) {
const kst = sma1Values[i] + (2 * sma2Values[i]) + (3 * sma3Values[i]) + (4 * sma4Values[i]);
kstValues.push(kst);
}
const signal = indicators_1.Indicators.sma(kstValues, 9);
// Pad the beginning with NaN to match input length
const padLength = prices.length - kstValues.length;
const padArray = (arr) => new Array(padLength).fill(NaN).concat(arr);
return {
kst: padArray(kstValues),
signal: padArray(signal)
};
}
exports.kst = kst;
/**
* Collection of momentum-based technical indicators
*/
exports.MomentumIndicators = {
roc,
momentum,
cmo,
rvi,
ppo,
pvo,
dpo,
chandeForecastOscillator,
coppockCurve,
kst
};
//# sourceMappingURL=momentum.js.map