stock-bot/libs/utils/src/generic-functions.ts
2025-06-22 17:55:51 -04:00

189 lines
4.7 KiB
TypeScript

/**
* Generic utility functions that work with standardized types
* These functions demonstrate how to use generic types with OHLCV data
*/
import type { HasClose, HasOHLC, HasVolume, OHLCV } from '@stock-bot/types';
/**
* Extract close prices from any data structure that has a close field
* Works with OHLCV, MarketData, or any custom type with close price
*/
export function extractCloses<T extends HasClose>(data: T[]): number[] {
return data.map(item => item.close);
}
/**
* Extract OHLC prices from any data structure that has OHLC fields
*/
export function extractOHLC<T extends HasOHLC>(
data: T[]
): {
opens: number[];
highs: number[];
lows: number[];
closes: number[];
} {
return {
opens: data.map(item => item.open),
highs: data.map(item => item.high),
lows: data.map(item => item.low),
closes: data.map(item => item.close),
};
}
/**
* Extract volumes from any data structure that has a volume field
*/
export function extractVolumes<T extends HasVolume>(data: T[]): number[] {
return data.map(item => item.volume);
}
/**
* Calculate simple moving average using close prices from any compatible data type
*/
export function calculateSMA<T extends HasClose>(data: T[], period: number): number[] {
const closes = extractCloses(data);
const result: number[] = [];
for (let i = period - 1; i < closes.length; i++) {
const sum = closes.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0);
result.push(sum / period);
}
return result;
}
/**
* Calculate typical price (HLC/3) from any OHLC compatible data
*/
export function calculateTypicalPrice<T extends HasOHLC>(data: T[]): number[] {
return data.map(item => (item.high + item.low + item.close) / 3);
}
/**
* Calculate true range from OHLC data
*/
export function calculateTrueRange<T extends HasOHLC>(data: T[]): number[] {
const result: number[] = [];
for (let i = 0; i < data.length; i++) {
if (i === 0) {
result.push(data[i]!.high - data[i]!.low);
} else {
const current = data[i]!;
const previous = data[i - 1]!;
const tr = Math.max(
current.high - current.low,
Math.abs(current.high - previous.close),
Math.abs(current.low - previous.close)
);
result.push(tr);
}
}
return result;
}
/**
* Calculate returns from close prices
*/
export function calculateReturns<T extends HasClose>(data: T[]): number[] {
const closes = extractCloses(data);
const returns: number[] = [];
for (let i = 1; i < closes.length; i++) {
const current = closes[i]!;
const previous = closes[i - 1]!;
if (previous > 0) {
returns.push((current - previous) / previous);
} else {
returns.push(0);
}
}
return returns;
}
/**
* Calculate log returns from close prices
*/
export function calculateLogReturns<T extends HasClose>(data: T[]): number[] {
const closes = extractCloses(data);
const logReturns: number[] = [];
for (let i = 1; i < closes.length; i++) {
const current = closes[i]!;
const previous = closes[i - 1]!;
if (previous > 0 && current > 0) {
logReturns.push(Math.log(current / previous));
} else {
logReturns.push(0);
}
}
return logReturns;
}
/**
* Calculate volume-weighted average price (VWAP) from OHLC + Volume data
*/
export function calculateVWAP<T extends HasOHLC & HasVolume>(data: T[]): number[] {
const result: number[] = [];
let cumulativeVolumePrice = 0;
let cumulativeVolume = 0;
for (const item of data) {
const typicalPrice = (item.high + item.low + item.close) / 3;
cumulativeVolumePrice += typicalPrice * item.volume;
cumulativeVolume += item.volume;
if (cumulativeVolume > 0) {
result.push(cumulativeVolumePrice / cumulativeVolume);
} else {
result.push(typicalPrice);
}
}
return result;
}
/**
* Filter OHLCV data by symbol
*/
export function filterBySymbol(data: OHLCV[], symbol: string): OHLCV[] {
return data.filter(item => item.symbol === symbol);
}
/**
* Filter OHLCV data by time range
*/
export function filterByTimeRange(data: OHLCV[], startTime: number, endTime: number): OHLCV[] {
return data.filter(item => item.timestamp >= startTime && item.timestamp <= endTime);
}
/**
* Group OHLCV data by symbol
*/
export function groupBySymbol(data: OHLCV[]): Record<string, OHLCV[]> {
const grouped: Record<string, OHLCV[]> = {};
for (const item of data) {
if (!grouped[item.symbol]) {
grouped[item.symbol] = [];
}
grouped[item.symbol]!.push(item);
}
return grouped;
}
/**
* Convert timestamp to Date for OHLCV data
*/
export function convertTimestamps(data: OHLCV[]): Array<OHLCV & { date: Date }> {
return data.map(item => ({
...item,
date: new Date(item.timestamp),
}));
}