added calcs
This commit is contained in:
parent
ef12c9d308
commit
7886b7cfa5
10 changed files with 4331 additions and 0 deletions
470
libs/utils/src/calculations/technical-indicators.ts
Normal file
470
libs/utils/src/calculations/technical-indicators.ts
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue