189 lines
4.7 KiB
TypeScript
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),
|
|
}));
|
|
}
|