added calcs

This commit is contained in:
Bojan Kucera 2025-06-03 14:54:02 -04:00
parent ef12c9d308
commit 7886b7cfa5
10 changed files with 4331 additions and 0 deletions

View file

@ -0,0 +1,470 @@
/**
* Technical Indicators
* Comprehensive set of technical analysis indicators
*/
import { OHLCVData } from './index';
/**
* Simple Moving Average
*/
export function sma(values: number[], period: number): number[] {
if (period > values.length) return [];
const result: number[] = [];
for (let i = period - 1; i < values.length; i++) {
const sum = values.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0);
result.push(sum / period);
}
return result;
}
/**
* Exponential Moving Average
*/
export function ema(values: number[], period: number): number[] {
if (period > values.length) return [];
const result: number[] = [];
const multiplier = 2 / (period + 1);
// Start with SMA for first value
let ema = values.slice(0, period).reduce((a, b) => a + b, 0) / period;
result.push(ema);
for (let i = period; i < values.length; i++) {
ema = (values[i] * multiplier) + (ema * (1 - multiplier));
result.push(ema);
}
return result;
}
/**
* Relative Strength Index (RSI)
*/
export function rsi(prices: number[], period: number = 14): number[] {
if (period >= prices.length) return [];
const gains: number[] = [];
const losses: number[] = [];
// Calculate gains and losses
for (let i = 1; i < prices.length; i++) {
const change = prices[i] - prices[i - 1];
gains.push(change > 0 ? change : 0);
losses.push(change < 0 ? Math.abs(change) : 0);
}
const result: number[] = [];
// Calculate RSI
for (let i = period - 1; i < gains.length; i++) {
const avgGain = gains.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0) / period;
const avgLoss = losses.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0) / period;
if (avgLoss === 0) {
result.push(100);
} else {
const rs = avgGain / avgLoss;
const rsiValue = 100 - (100 / (1 + rs));
result.push(rsiValue);
}
}
return result;
}
/**
* Moving Average Convergence Divergence (MACD)
*/
export function macd(
prices: number[],
fastPeriod: number = 12,
slowPeriod: number = 26,
signalPeriod: number = 9
): { macd: number[], signal: number[], histogram: number[] } {
const fastEMA = ema(prices, fastPeriod);
const slowEMA = ema(prices, slowPeriod);
const macdLine: number[] = [];
const startIndex = slowPeriod - fastPeriod;
for (let i = 0; i < fastEMA.length - startIndex; i++) {
macdLine.push(fastEMA[i + startIndex] - slowEMA[i]);
}
const signalLine = ema(macdLine, signalPeriod);
const histogram: number[] = [];
const signalStartIndex = signalPeriod - 1;
for (let i = 0; i < signalLine.length; i++) {
histogram.push(macdLine[i + signalStartIndex] - signalLine[i]);
}
return {
macd: macdLine,
signal: signalLine,
histogram: histogram
};
}
/**
* Bollinger Bands
*/
export function bollingerBands(
prices: number[],
period: number = 20,
standardDeviations: number = 2
): { upper: number[], middle: number[], lower: number[] } {
const middle = sma(prices, period);
const upper: number[] = [];
const lower: number[] = [];
for (let i = period - 1; i < prices.length; i++) {
const slice = prices.slice(i - period + 1, i + 1);
const mean = slice.reduce((a, b) => a + b, 0) / period;
const variance = slice.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / period;
const stdDev = Math.sqrt(variance);
const middleValue = middle[i - period + 1];
upper.push(middleValue + (standardDeviations * stdDev));
lower.push(middleValue - (standardDeviations * stdDev));
}
return { upper, middle, lower };
}
/**
* Average True Range (ATR)
*/
export function atr(ohlcv: OHLCVData[], period: number = 14): number[] {
if (period >= ohlcv.length) return [];
const trueRanges: number[] = [];
for (let i = 1; i < ohlcv.length; i++) {
const high = ohlcv[i].high;
const low = ohlcv[i].low;
const prevClose = ohlcv[i - 1].close;
const tr = Math.max(
high - low,
Math.abs(high - prevClose),
Math.abs(low - prevClose)
);
trueRanges.push(tr);
}
return sma(trueRanges, period);
}
/**
* Stochastic Oscillator
*/
export function stochastic(
ohlcv: OHLCVData[],
kPeriod: number = 14,
dPeriod: number = 3
): { k: number[], d: number[] } {
if (kPeriod >= ohlcv.length) return { k: [], d: [] };
const kValues: number[] = [];
for (let i = kPeriod - 1; i < ohlcv.length; i++) {
const slice = ohlcv.slice(i - kPeriod + 1, i + 1);
const highest = Math.max(...slice.map(d => d.high));
const lowest = Math.min(...slice.map(d => d.low));
const currentClose = ohlcv[i].close;
if (highest === lowest) {
kValues.push(50); // Avoid division by zero
} else {
const kValue = ((currentClose - lowest) / (highest - lowest)) * 100;
kValues.push(kValue);
}
}
const dValues = sma(kValues, dPeriod);
return { k: kValues, d: dValues };
}
/**
* Williams %R
*/
export function williamsR(ohlcv: OHLCVData[], period: number = 14): number[] {
if (period >= ohlcv.length) return [];
const result: number[] = [];
for (let i = period - 1; i < ohlcv.length; i++) {
const slice = ohlcv.slice(i - period + 1, i + 1);
const highest = Math.max(...slice.map(d => d.high));
const lowest = Math.min(...slice.map(d => d.low));
const currentClose = ohlcv[i].close;
if (highest === lowest) {
result.push(-50); // Avoid division by zero
} else {
const wrValue = ((highest - currentClose) / (highest - lowest)) * -100;
result.push(wrValue);
}
}
return result;
}
/**
* Commodity Channel Index (CCI)
*/
export function cci(ohlcv: OHLCVData[], period: number = 20): number[] {
if (period >= ohlcv.length) return [];
const typicalPrices = ohlcv.map(d => (d.high + d.low + d.close) / 3);
const smaTP = sma(typicalPrices, period);
const result: number[] = [];
for (let i = 0; i < smaTP.length; i++) {
const slice = typicalPrices.slice(i, i + period);
const mean = smaTP[i];
const meanDeviation = slice.reduce((sum, value) => sum + Math.abs(value - mean), 0) / period;
if (meanDeviation === 0) {
result.push(0);
} else {
const cciValue = (typicalPrices[i + period - 1] - mean) / (0.015 * meanDeviation);
result.push(cciValue);
}
}
return result;
}
/**
* Momentum
*/
export function momentum(prices: number[], period: number = 10): number[] {
if (period >= prices.length) return [];
const result: number[] = [];
for (let i = period; i < prices.length; i++) {
result.push(prices[i] - prices[i - period]);
}
return result;
}
/**
* Rate of Change (ROC)
*/
export function rateOfChange(prices: number[], period: number = 10): number[] {
if (period >= prices.length) return [];
const result: number[] = [];
for (let i = period; i < prices.length; i++) {
if (prices[i - period] === 0) {
result.push(0);
} else {
const rocValue = ((prices[i] - prices[i - period]) / prices[i - period]) * 100;
result.push(rocValue);
}
}
return result;
}
/**
* Money Flow Index (MFI)
*/
export function moneyFlowIndex(ohlcv: OHLCVData[], period: number = 14): number[] {
if (period >= ohlcv.length) return [];
const typicalPrices = ohlcv.map(d => (d.high + d.low + d.close) / 3);
const rawMoneyFlows = ohlcv.map((d, i) => typicalPrices[i] * d.volume);
const result: number[] = [];
for (let i = 1; i < ohlcv.length - period + 1; i++) {
let positiveFlow = 0;
let negativeFlow = 0;
for (let j = 0; j < period; j++) {
const currentIndex = i + j;
if (typicalPrices[currentIndex] > typicalPrices[currentIndex - 1]) {
positiveFlow += rawMoneyFlows[currentIndex];
} else if (typicalPrices[currentIndex] < typicalPrices[currentIndex - 1]) {
negativeFlow += rawMoneyFlows[currentIndex];
}
}
if (negativeFlow === 0) {
result.push(100);
} else {
const moneyRatio = positiveFlow / negativeFlow;
const mfiValue = 100 - (100 / (1 + moneyRatio));
result.push(mfiValue);
}
}
return result;
}
/**
* On Balance Volume (OBV)
*/
export function onBalanceVolume(ohlcv: OHLCVData[]): number[] {
if (ohlcv.length === 0) return [];
const result: number[] = [ohlcv[0].volume];
for (let i = 1; i < ohlcv.length; i++) {
let obvValue = result[i - 1];
if (ohlcv[i].close > ohlcv[i - 1].close) {
obvValue += ohlcv[i].volume;
} else if (ohlcv[i].close < ohlcv[i - 1].close) {
obvValue -= ohlcv[i].volume;
}
result.push(obvValue);
}
return result;
}
/**
* Accumulation/Distribution Line
*/
export function accumulationDistribution(ohlcv: OHLCVData[]): number[] {
if (ohlcv.length === 0) return [];
const result: number[] = [];
let adLine = 0;
for (const candle of ohlcv) {
if (candle.high === candle.low) {
// Avoid division by zero
result.push(adLine);
continue;
}
const moneyFlowMultiplier = ((candle.close - candle.low) - (candle.high - candle.close)) / (candle.high - candle.low);
const moneyFlowVolume = moneyFlowMultiplier * candle.volume;
adLine += moneyFlowVolume;
result.push(adLine);
}
return result;
}
/**
* Chaikin Money Flow (CMF)
*/
export function chaikinMoneyFlow(ohlcv: OHLCVData[], period: number = 20): number[] {
if (period >= ohlcv.length) return [];
const adValues: number[] = [];
for (const candle of ohlcv) {
if (candle.high === candle.low) {
adValues.push(0);
} else {
const moneyFlowMultiplier = ((candle.close - candle.low) - (candle.high - candle.close)) / (candle.high - candle.low);
const moneyFlowVolume = moneyFlowMultiplier * candle.volume;
adValues.push(moneyFlowVolume);
}
}
const result: number[] = [];
for (let i = period - 1; i < ohlcv.length; i++) {
const sumAD = adValues.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0);
const sumVolume = ohlcv.slice(i - period + 1, i + 1).reduce((a, b) => a + b.volume, 0);
if (sumVolume === 0) {
result.push(0);
} else {
result.push(sumAD / sumVolume);
}
}
return result;
}
/**
* Parabolic SAR
*/
export function parabolicSAR(
ohlcv: OHLCVData[],
step: number = 0.02,
maximum: number = 0.2
): number[] {
if (ohlcv.length < 2) return [];
const result: number[] = [];
let isUptrend = ohlcv[1].close > ohlcv[0].close;
let sar = isUptrend ? ohlcv[0].low : ohlcv[0].high;
let ep = isUptrend ? ohlcv[1].high : ohlcv[1].low;
let af = step;
result.push(sar);
for (let i = 1; i < ohlcv.length; i++) {
const currentHigh = ohlcv[i].high;
const currentLow = ohlcv[i].low;
const currentClose = ohlcv[i].close;
// Calculate new SAR
sar = sar + af * (ep - sar);
if (isUptrend) {
// Uptrend logic
if (currentLow <= sar) {
// Trend reversal
isUptrend = false;
sar = ep;
ep = currentLow;
af = step;
} else {
// Continue uptrend
if (currentHigh > ep) {
ep = currentHigh;
af = Math.min(af + step, maximum);
}
// Ensure SAR doesn't go above previous two lows
if (i >= 2) {
sar = Math.min(sar, ohlcv[i - 1].low, ohlcv[i - 2].low);
}
}
} else {
// Downtrend logic
if (currentHigh >= sar) {
// Trend reversal
isUptrend = true;
sar = ep;
ep = currentHigh;
af = step;
} else {
// Continue downtrend
if (currentLow < ep) {
ep = currentLow;
af = Math.min(af + step, maximum);
}
// Ensure SAR doesn't go below previous two highs
if (i >= 2) {
sar = Math.max(sar, ohlcv[i - 1].high, ohlcv[i - 2].high);
}
}
}
result.push(sar);
}
return result;
}