UNPKG

stock-indicator

Version:

Stock Indicator Library

384 lines (362 loc) 11.6 kB
export type TMixed = number | null; // - mixed type /** * - check if the parameter is an array of numbers or null values * @param {number[]} arr - array to be checked * @param {string} parameName - name of the parameter */ export function checkArrayOfNumbers(arr: number[], parameName: string) { if (!Array.isArray(arr) || arr.some((item) => typeof item !== "number")) { throw new Error(`The ${parameName} parameter must be an array of numbers`); } } /** * - check if the parameter is a number between min and max * @param {number} num - number to be checked * @param {number} min - minimum value * @param {number} max - maximum value * @param {string} parameName - name of the parameter * @param {string} maxName - name of the array */ export function checkNumber( num: number, min: number, max: TMixed[] | number, parameName: string, maxName: string ) { const maxStr = Array.isArray(max) ? `the length of the ${maxName} array` : maxName; if ( typeof num !== "number" || num < min || num > (Array.isArray(max) ? max.length : max) ) { throw new Error( `The ${parameName} parameter must be an array of numbers between ${min} and ${maxStr}` ); } } /** * - Sum of n-period moving averages (SUM) * @param {TMixed[]} arr - closing prices of the stock * @param {number} n - number of periods to calculate the SUM, must be greater than 1 and less than the length of the CLOSE array */ export function funMS(arr: TMixed[], n: number): TMixed[] { const doSum = (tArr: TMixed[]): TMixed => { if (tArr.every((item) => item === null || isNaN(item))) { return null; } return tArr.reduce( (acc: number, cur) => (cur !== null && !isNaN(cur) ? acc + cur : acc), 0 ); }; // calculate the SUM for each period, starting from the second period const result: TMixed[] = []; for (let i = 0; i < arr.length; i++) { result.push(doSum(arr.slice(Math.max(0, i - n + 1), i + 1))); } return result; } /** * - Moving Average (MA) * @param {TMixed[]} arr - - closing prices of the stock * @param {number} n - - number of periods to calculate the moving average, must be between 2 and the length of the CLOSE array * @returns { TMixed[] } } - array of moving average values */ export function funMA(arr: TMixed[], n: number): TMixed[] { const result: TMixed[] = []; //对给定的数组arr进行检查,如果包含null,则返回null,否则计算n个周期的移动平均值 const doMA = (iarr: TMixed[], count: number) => { if (iarr.length < count) { return null; } let sum = 0; for (let i = 0; i < iarr.length; i++) { if (typeof iarr[i] === "number" && !isNaN(iarr[i] as number)) { sum += iarr[i] as number; } else { return null; } } return sum / count; }; for (let i = 0; i < arr.length; i++) { result.push(doMA(arr.slice(Math.max(0, i - n + 1), i + 1), n)); } return result; } /** * SMA (Simple Moving Average) * @param {TMixed[]} arr - An array of the closing price of the stock * @param {number} n - The number of periods to calculate the SMA for. Must be between 2 and the length of the CLOSE array. * @param {number} m - The number of periods to calculate the SMA for. Must be between 0 and N-1. * @returns {{ SMA: TMixed[] }} - The SMA values for the given data and parameters */ export function funSMA(arr: TMixed[], n: number, m: number): TMixed[] { return arr.slice(1).reduce( (acc, cur, i) => { if (cur === null) { acc.push(null); } else if (i === 0 && (acc[0] === null || acc[0] === 0)) { acc.push(cur); } else { acc.push((cur * m + (acc[i] as number) * (n - m)) / n); } return acc; }, [arr[0]] ); } /** * - Exponential Moving Average (EMA) * @param {TMixed[]} arr - closing prices of the stock * @param {number} n - number of periods to calculate the EMA,must be greater than 1 and less than the length of the CLOSE array * @param {number} m - number of periods to calculate the initial EMA, default is 1,must be less than N * @returns {{ EMA: TMixed[] }} - object with the EMA values */ export function funEMA(arr: TMixed[], n: number, m: number = 1): TMixed[] { return arr.slice(1).reduce( (acc, cur) => { acc.push( cur ? (cur * (m + 1) + (acc.slice(-1)[0] ?? cur) * (n - m)) / (n + 1) : null ); return acc; }, [arr[0]] ); } /** * WMA - Weighted Moving Average * @param {TMixed[]} arr should be an array of numbers or null values * @param {number} n should be a number between 2 and the length of the data array * @returns {TMixed[]} WMA values */ export function funWMA(arr: TMixed[], n: number): TMixed[] { // calculate WMA const cumsum = (arr: TMixed[]): TMixed => { const sum = arr .slice(1) .reduce( (acc, cur, i) => (acc && cur ? acc + cur * (i + 1) : null), arr[0] ); return sum ? sum / ((arr.length * (arr.length + 1)) / 2) : null; }; return arr.reduce((acc: TMixed[], cur, i) => { if (i < n - 1) { acc.push(null); } else { const res = cumsum(arr.slice(i - n + 1, i + 1)); acc.push(res); } return acc; }, []); } /** * - Standard Deviation (STD) * @param {TMixed[]} arr - closing prices of the stock * @param {number} n - number of periods to calculate the STD, must be greater than 1 and less than the length of the CLOSE array * @returns { TMixed[] } - array of standard deviation values */ export function funSTD(arr: TMixed[], n: number): TMixed[] { return arr.reduce((acc: TMixed[], cur, i) => { if (i < n - 1) { acc.push(null); } else { const mean = arr .slice(i - n + 1, i + 1) .reduce((acc: number, cur) => acc + (cur ?? 0), 0) / n; const variance = arr .slice(i - n + 1, i + 1) .reduce( (acc: number, cur) => acc + ((cur ?? 0) - mean) ** 2 / (n - 1), 0 ); const std = Math.sqrt(variance); acc.push(std); } return acc; }, []); } /** * - Average Variance Enhanced (AVEDEV) * @param {TMixed[]} arr - closing prices of the stock * @param {number} n - number of periods to calculate the AVEDEV, must be greater than 1 and less than the length of the CLOSE array * @returns { TMixed[] } - array of average variance enhanced values */ export function funAVEDEV(arr: TMixed[], n: number): TMixed[] { const result: TMixed[] = []; for (let i = 0; i < arr.length; i++) { const curArr = arr.slice(Math.max(0, i - n + 1), i + 1); if ( curArr.some((item) => item === null || isNaN(item)) || curArr.length < n ) { result.push(null); continue; } const mean: number = curArr.reduce((acc: number, cur) => acc + (cur ?? 0), 0) / n; result.push( (curArr as number[]).reduce( (acc: number, cur: number) => acc + Math.abs(cur - mean), 0 ) / n ); } return result; } /** * - subtract two arrays element by element * @param {TMixed[]} subtractor - array to be subtracted from * @param {TMixed[]} minuend - array to be subtracted * @returns {TMixed[]} - array of subtracted values */ export function funArrSub(subtractor: TMixed[], minuend: TMixed[]): TMixed[] { const count = Math.max(subtractor.length, minuend.length); const results: TMixed[] = []; for (let i = 0; i < count; i++) { results.push( subtractor[i] !== null && minuend[i] !== null ? (subtractor[i] ?? 0) - (minuend[i] ?? 0) : null ); } return results; } /** * - add two or more arrays element by element * @param {TMixed[]}...args - arrays to be added * @returns {TMixed[]} - array of added values */ export function funArrAdd(...args: TMixed[][]): TMixed[] { const result: number[] = []; const maxLength = args.reduce((p, v) => Math.max(p, v.length), 0); for (let i = 0; i < maxLength; i++) { let sum = 0; for (let j = 0; j < args.length; j++) { if (i < args[j].length) { sum += args[j][i] ?? 0; } } result.push(sum); } return result; } /** * - divide two arrays element by element * @param {TMixed[]} divisor - array to be divided * @param {TMixed[]} dividend - array to divide by * @returns {TMixed[]}- array of divided values */ export function funArrDiv(divisor: TMixed[], dividend: TMixed[]): TMixed[] { const count = Math.max(divisor.length, dividend.length); const results: TMixed[] = []; for (let i = 0; i < count; i++) { if (dividend[i] === null || divisor[i] === null || dividend[i] === 0) { results.push(null); continue; } else { results.push((divisor[i] as number) / (dividend[i] as number)); } } return results; } /** * - multiply two arrays element by element * @param {TMixed[]} arr1 - first array to be multiplied * @param {TMixed[]} arr2 - second array to be multiplied * @returns {TMixed[]} - array of multiplied values */ export function funArrMul(arr1: TMixed[], arr2: TMixed[]): TMixed[] { const count = Math.max(arr1.length, arr2.length); const results: TMixed[] = []; for (let i = 0; i < count; i++) { if (arr1[i] === null || arr2[i] === null) { results.push(null); continue; } else { results.push((arr1[i] as number) * (arr2[i] as number)); } } return results; } /** * - absolute value of an array * @param {TMixed[]} arr - array to be absolute value of * @returns {TMixed[]} - array of absolute values */ export function funArrAbs(arr: TMixed[]): TMixed[] { return arr.map((v) => (v === null ? null : Math.abs(v))); } /** * - rounded to n decimal places * @param {number} num - number to be rounded * @param {number} n - number of decimal places to round to * @returns {number} - rounded number */ export function funRound(num: TMixed, n: number): TMixed { if (typeof num !== "number" || isNaN(num)) return null; return Math.round(num * Math.pow(10, n)) / Math.pow(10, n); } /** * - check if a is greater than b with a given precision * @param {number} a - first number * @param {number} b - second number * @param {number} precision - precision to compare the numbers * @returns {boolean} - true if a is greater than b, false otherwise */ export function aGtB( a: number, b: number, precision: number = 0.0001 ): boolean { return a - b > precision; } /** * - check if a is less than b with a given precision * @param {number} a - first number * @param {number} b - second number * @param {number} precision - precision to compare the numbers * @returns {boolean} - true if a is less than b, false otherwise */ export function aLtB( a: number, b: number, precision: number = 0.0001 ): boolean { return b - a > precision; } /** * - check if a is equal to b with a given precision * @param {number} a - first number * @param {number} b - second number * @param {number} precision - precision to compare the numbers * @returns {boolean} - true if a is equal to b, false otherwise */ export function aEqB( a: number, b: number, precision: number = 0.0001 ): boolean { return Math.abs(a - b) <= precision; } /** * - check if a is not equal to b with a given precision * @param {number} a - first number * @param {number} b - second number * @param {number} precision - precision to compare the numbers * @returns {boolean} - true if a is not equal to b, false otherwise */ export function aNeqB( a: number, b: number, precision: number = 0.0001 ): boolean { return Math.abs(a - b) > precision; }