UNPKG

quantitivecalc

Version:

A TypeScript library providing advanced quantitative finance functions for risk analysis, performance metrics, and technical indicators. (Currently in development)

245 lines (244 loc) 9.19 kB
"use strict"; /** * Technical Indicators Utilities * * Functions: * - calculateMovingAverage: Calculates simple or exponential moving averages for a given data series. * - calculateRSI: Calculates Relative Strength Index momentum oscillator * - calculateMACD: Calculates Moving Average Convergence Divergence * - calculateBollingerBands: Calculates price channels based on standard deviation * - calculateStochasticOscillator: Calculates %K and %D momentum indicators * * The functions operate on arrays of objects (list of dicts) and allow you to specify source/result columns and parameters. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateMovingAverage = calculateMovingAverage; exports.calculateRSI = calculateRSI; exports.calculateMACD = calculateMACD; exports.calculateBollingerBands = calculateBollingerBands; exports.calculateStochasticOscillator = calculateStochasticOscillator; function calculateMovingAverage(data, sourceColumn, resultColumn, windowSize = 20, type = 'simple') { if (!data || data.length === 0) { return []; } const result = data.map(row => ({ ...row })); if (type === 'simple') { // Simple Moving Average for (let i = 0; i < result.length; i++) { if (i < windowSize - 1) { result[i][resultColumn] = null; } else { let sum = 0; let count = 0; for (let j = i - windowSize + 1; j <= i; j++) { const value = result[j][sourceColumn]; if (typeof value === 'number' && !isNaN(value)) { sum += value; count++; } } result[i][resultColumn] = count > 0 ? sum / count : null; } } } else { // Exponential Moving Average const multiplier = 2 / (windowSize + 1); let ema = null; for (let i = 0; i < result.length; i++) { const value = result[i][sourceColumn]; if (typeof value === 'number' && !isNaN(value)) { if (ema === null) { ema = value; // First valid value becomes initial EMA } else { ema = (value * multiplier) + (ema * (1 - multiplier)); } result[i][resultColumn] = ema; } else { result[i][resultColumn] = ema; // Keep previous EMA if current value is invalid } } } return result; } function calculateRSI(data, sourceColumn, resultColumn = 'rsi', windowSize = 14) { if (!data || data.length === 0) { return []; } const result = data.map(row => ({ ...row })); for (let i = 0; i < result.length; i++) { if (i < windowSize) { result[i][resultColumn] = null; continue; } let gains = 0; let losses = 0; let gainCount = 0; let lossCount = 0; // Calculate gains and losses over the window for (let j = i - windowSize + 1; j <= i; j++) { const currentValue = result[j][sourceColumn]; const previousValue = result[j - 1][sourceColumn]; if (typeof currentValue === 'number' && typeof previousValue === 'number' && !isNaN(currentValue) && !isNaN(previousValue)) { const change = currentValue - previousValue; if (change > 0) { gains += change; gainCount++; } else if (change < 0) { losses += Math.abs(change); lossCount++; } } } if (gainCount === 0 && lossCount === 0) { result[i][resultColumn] = null; continue; } const avgGain = gainCount > 0 ? gains / windowSize : 0; const avgLoss = lossCount > 0 ? losses / windowSize : 0; if (avgLoss === 0) { result[i][resultColumn] = 100; } else { const rs = avgGain / avgLoss; result[i][resultColumn] = 100 - (100 / (1 + rs)); } } return result; } function calculateMACD(data, sourceColumn, macdColumn = 'macd', signalColumn = 'macd_signal', histogramColumn = 'macd_histogram', fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) { if (!data || data.length === 0) { return []; } // Calculate fast and slow EMAs const withFastEMA = calculateMovingAverage(data, sourceColumn, '__fast_ema', fastPeriod, 'exponential'); const withBothEMAs = calculateMovingAverage(withFastEMA, sourceColumn, '__slow_ema', slowPeriod, 'exponential'); // Calculate MACD line const result = withBothEMAs.map(row => { const macdValue = (row.__fast_ema !== null && row.__slow_ema !== null) ? row.__fast_ema - row.__slow_ema : null; return { ...row, [macdColumn]: macdValue, __temp_macd: macdValue }; }); // Calculate signal line (EMA of MACD) const withSignal = calculateMovingAverage(result, '__temp_macd', signalColumn, signalPeriod, 'exponential'); // Calculate histogram and clean up temporary columns return withSignal.map(row => { const histogramValue = (row[macdColumn] !== null && row[signalColumn] !== null) ? row[macdColumn] - row[signalColumn] : null; const cleanRow = { ...row }; delete cleanRow.__fast_ema; delete cleanRow.__slow_ema; delete cleanRow.__temp_macd; return { ...cleanRow, [histogramColumn]: histogramValue }; }); } function calculateBollingerBands(data, sourceColumn, upperColumn = 'bb_upper', middleColumn = 'bb_middle', lowerColumn = 'bb_lower', windowSize = 20, numStdDev = 2) { if (!data || data.length === 0) { return []; } // Calculate the middle band (Simple Moving Average) const withMA = calculateMovingAverage(data, sourceColumn, middleColumn, windowSize, 'simple'); return withMA.map((row, i) => { if (i < windowSize - 1 || row[middleColumn] === null) { return { ...row, [upperColumn]: null, [lowerColumn]: null }; } // Calculate standard deviation for the window let sumSquaredDeviations = 0; let count = 0; const mean = row[middleColumn]; for (let j = i - windowSize + 1; j <= i; j++) { const value = withMA[j][sourceColumn]; if (typeof value === 'number' && !isNaN(value)) { sumSquaredDeviations += Math.pow(value - mean, 2); count++; } } if (count === 0) { return { ...row, [upperColumn]: null, [lowerColumn]: null }; } const variance = sumSquaredDeviations / count; const stdDev = Math.sqrt(variance); return { ...row, [upperColumn]: mean + (numStdDev * stdDev), [lowerColumn]: mean - (numStdDev * stdDev) }; }); } function calculateStochasticOscillator(data, highColumn, lowColumn, closeColumn, kColumn = 'stoch_k', dColumn = 'stoch_d', kPeriod = 14, dPeriod = 3) { if (!data || data.length === 0) { return []; } // Calculate %K const withK = data.map((row, i) => { if (i < kPeriod - 1) { return { ...row, [kColumn]: null }; } let highestHigh = -Infinity; let lowestLow = Infinity; let validData = false; // Find highest high and lowest low over the period for (let j = i - kPeriod + 1; j <= i; j++) { const high = data[j][highColumn]; const low = data[j][lowColumn]; if (typeof high === 'number' && typeof low === 'number' && !isNaN(high) && !isNaN(low)) { highestHigh = Math.max(highestHigh, high); lowestLow = Math.min(lowestLow, low); validData = true; } } if (!validData || highestHigh === lowestLow) { return { ...row, [kColumn]: null }; } const currentClose = row[closeColumn]; if (typeof currentClose !== 'number' || isNaN(currentClose)) { return { ...row, [kColumn]: null }; } const kValue = ((currentClose - lowestLow) / (highestHigh - lowestLow)) * 100; return { ...row, [kColumn]: kValue, __temp_k: kValue }; }); // Calculate %D (Simple Moving Average of %K) const withD = calculateMovingAverage(withK, '__temp_k', dColumn, dPeriod, 'simple'); // Clean up temporary column return withD.map(row => { const cleanRow = { ...row }; delete cleanRow.__temp_k; return cleanRow; }); }