Bot & Automation
Market AI
/root/hermes-projects/Market AI
src/utils/math.ts
text
import type { Candle } from "../domain/types";
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
export function roundTo(value: number, digits = 5): number {
const factor = 10 ** digits;
return Math.round(value * factor) / factor;
}
export function simpleMovingAverage(values: number[], period: number): number {
if (values.length < period) {
return average(values);
}
return average(values.slice(-period));
}
export function exponentialMovingAverage(values: number[], period: number): number {
if (values.length === 0) {
return 0;
}
const smoothing = 2 / (period + 1);
const seed = simpleMovingAverage(values.slice(0, Math.min(period, values.length)), period);
return values.slice(1).reduce((previous, current) => {
return current * smoothing + previous * (1 - smoothing);
}, seed);
}
export function relativeStrengthIndex(values: number[], period = 14): number {
if (values.length <= period) {
return 50;
}
const changes = values.slice(1).map((value, index) => value - values[index]);
const recent = changes.slice(-period);
const gains = recent.filter((value) => value > 0).reduce((sum, value) => sum + value, 0);
const losses = Math.abs(recent.filter((value) => value < 0).reduce((sum, value) => sum + value, 0));
if (losses === 0) {
return 100;
}
const relativeStrength = gains / losses;
return 100 - 100 / (1 + relativeStrength);
}
export function averageTrueRange(candles: Candle[], period = 14): number {
if (candles.length < 2) {
return 0;
}
const trueRanges = candles.slice(1).map((candle, index) => {
const previousClose = candles[index].close;
return Math.max(
candle.high - candle.low,
Math.abs(candle.high - previousClose),
Math.abs(candle.low - previousClose)
);
});
return simpleMovingAverage(trueRanges, Math.min(period, trueRanges.length));
}
export function macdHistogram(values: number[]): number {
const ema12 = exponentialMovingAverage(values, 12);
const ema26 = exponentialMovingAverage(values, 26);
const macdLine = ema12 - ema26;
const recentMacdValues = values.slice(-9).map((_, index, recent) => {
const sliceEnd = values.length - recent.length + index + 1;
const slice = values.slice(0, sliceEnd);
return exponentialMovingAverage(slice, 12) - exponentialMovingAverage(slice, 26);
});
const signalLine = exponentialMovingAverage(recentMacdValues, 9);
return macdLine - signalLine;
}
export function supportResistance(candles: Candle[], lookback = 40): { support: number; resistance: number } {
const recent = candles.slice(-lookback);
return {
support: Math.min(...recent.map((candle) => candle.low)),
resistance: Math.max(...recent.map((candle) => candle.high))
};
}
function average(values: number[]): number {
if (values.length === 0) {
return 0;
}
return values.reduce((sum, value) => sum + value, 0) / values.length;
}