initial data-service started

This commit is contained in:
Bojan Kucera 2025-06-08 13:46:03 -04:00
parent 8681c34529
commit f68e620c76
6 changed files with 531 additions and 178 deletions

View file

@ -0,0 +1,179 @@
import { Logger } from '@stock-bot/logger';
import { HttpClient } from '@stock-bot/http';
import createCache, { type CacheProvider } from '@stock-bot/cache';
export interface MarketDataResponse {
symbol: string;
price: number;
timestamp: Date;
volume?: number;
change?: number;
open?: number;
high?: number;
low?: number;
close?: number;
}
export class MarketDataProvider {
private logger = new Logger('market-data-provider');
private httpClient: HttpClient;
private cache: CacheProvider = createCache('hybrid');
private readonly CACHE_TTL = 60; // 1 minute
constructor() {
this.httpClient = new HttpClient({
timeout: 10000,
}, this.logger);
}
async getLiveData(symbol: string): Promise<MarketDataResponse> {
const cacheKey = `market-data:${symbol}`;
try {
// Check cache first
const cached = await this.cache.get(cacheKey) as MarketDataResponse | null;
if (cached) {
this.logger.debug('Returning cached market data', { symbol });
return cached;
}
// Generate simulated data for demo
const data = this.generateSimulatedData(symbol);
// Cache the result
await this.cache.set(cacheKey, data, this.CACHE_TTL);
this.logger.info('Generated live market data', { symbol, price: data.price });
return data;
} catch (error) {
this.logger.error('Error fetching market data', { symbol, error });
throw error;
}
}
async getHistoricalData(symbol: string, from: Date, to: Date, interval: string = '1m'): Promise<MarketDataResponse[]> {
const cacheKey = `historical:${symbol}:${from.toISOString()}:${to.toISOString()}:${interval}`;
try {
const cached = await this.cache.get(cacheKey) as MarketDataResponse[] | null;
if (cached) {
this.logger.debug('Returning cached historical data', { symbol, from, to });
return cached;
}
// Generate simulated historical data
const data = this.generateHistoricalData(symbol, from, to, interval);
// Cache for longer time (1 hour)
await this.cache.set(cacheKey, data, 3600);
this.logger.info('Generated historical market data', { symbol, from, to, count: data.length });
return data;
} catch (error) {
this.logger.error('Error fetching historical data', { symbol, error });
throw error;
}
}
private generateSimulatedData(symbol: string): MarketDataResponse {
// Base prices for different symbols
const basePrices: { [key: string]: number } = {
'AAPL': 150,
'GOOGL': 2500,
'MSFT': 300,
'TSLA': 200,
'AMZN': 3000,
'NVDA': 400,
'META': 250,
'NFLX': 400
};
const basePrice = basePrices[symbol] || 100;
// Add some randomness (+/- 2%)
const variation = (Math.random() - 0.5) * 0.04; // ±2%
const price = basePrice * (1 + variation);
const change = variation * basePrice;
return {
symbol,
price: Math.round(price * 100) / 100,
timestamp: new Date(),
volume: Math.floor(Math.random() * 1000000) + 500000,
change: Math.round(change * 100) / 100,
open: Math.round((price - change) * 100) / 100,
high: Math.round((price + Math.abs(change * 0.5)) * 100) / 100,
low: Math.round((price - Math.abs(change * 0.5)) * 100) / 100,
close: Math.round(price * 100) / 100
};
}
private generateHistoricalData(symbol: string, from: Date, to: Date, interval: string): MarketDataResponse[] {
const data: MarketDataResponse[] = [];
const intervalMs = this.parseInterval(interval);
let currentTime = new Date(from);
const endTime = new Date(to);
// Base price for the symbol
const basePrices: { [key: string]: number } = {
'AAPL': 150,
'GOOGL': 2500,
'MSFT': 300,
'TSLA': 200,
'AMZN': 3000,
'NVDA': 400,
'META': 250,
'NFLX': 400
};
let basePrice = basePrices[symbol] || 100;
while (currentTime <= endTime) {
// Add some trend and randomness
const trend = (Math.random() - 0.5) * 0.01; // Small trend
const variation = (Math.random() - 0.5) * 0.02; // Random variation
basePrice = basePrice * (1 + trend + variation);
const change = basePrice * variation;
data.push({
symbol,
price: Math.round(basePrice * 100) / 100,
timestamp: new Date(currentTime),
volume: Math.floor(Math.random() * 1000000) + 500000,
change: Math.round(change * 100) / 100,
open: Math.round((basePrice - change) * 100) / 100,
high: Math.round((basePrice + Math.abs(change * 0.5)) * 100) / 100,
low: Math.round((basePrice - Math.abs(change * 0.5)) * 100) / 100,
close: Math.round(basePrice * 100) / 100
});
currentTime = new Date(currentTime.getTime() + intervalMs);
}
return data;
}
private parseInterval(interval: string): number {
const value = parseInt(interval.slice(0, -1));
const unit = interval.slice(-1).toLowerCase();
switch (unit) {
case 's': return value * 1000;
case 'm': return value * 60 * 1000;
case 'h': return value * 60 * 60 * 1000;
case 'd': return value * 24 * 60 * 60 * 1000;
default: return 60 * 1000; // Default to 1 minute
}
}
async clearCache(): Promise<void> {
this.logger.info('Clearing market data cache');
// Note: Cache provider limitations - would need proper key tracking
}
async shutdown(): Promise<void> {
this.logger.info('Shutting down MarketDataProvider');
}
}
export const marketDataProvider = new MarketDataProvider();