adding data-services

This commit is contained in:
Bojan Kucera 2025-06-03 07:42:48 -04:00
parent e3bfd05b90
commit 405b818c86
139 changed files with 55943 additions and 416 deletions

68
libs/api-client/README.md Normal file
View file

@ -0,0 +1,68 @@
# API Client Library
Type-safe HTTP clients for inter-service communication in the stock-bot project.
## Available Clients
### BacktestClient
Client for interacting with the Backtest Engine service:
```typescript
import { createBacktestClient } from '@stock-bot/api-client';
// Create a client instance
const backtestClient = createBacktestClient('http://localhost:4002');
// Run a backtest
const result = await backtestClient.runBacktest({
strategyId: '123',
startDate: new Date('2023-01-01'),
endDate: new Date('2023-12-31'),
symbols: ['AAPL', 'MSFT', 'GOOG'],
initialCapital: 100000,
parameters: {
riskFactor: 0.5,
positionSizeLimit: 0.1
}
});
```
### StrategyClient
Client for interacting with the Strategy Orchestrator service:
```typescript
import { createStrategyClient } from '@stock-bot/api-client';
// Create a client instance
const strategyClient = createStrategyClient('http://localhost:4001');
// Get a strategy by ID
const strategy = await strategyClient.getStrategy('123');
// Update a strategy
await strategyClient.updateStrategy('123', {
parameters: {
lookbackPeriod: 20,
threshold: 0.02
}
});
// Enable a strategy
await strategyClient.enableStrategy('123');
```
## Creating Custom Clients
Extend the `BaseApiClient` to create clients for other services:
```typescript
import { BaseApiClient } from '@stock-bot/api-client';
class RiskGuardianClient extends BaseApiClient {
async getRiskLimits(portfolioId: string) {
return this.client.get(`/api/risk/limits/${portfolioId}`);
}
}
```

View file

@ -0,0 +1,22 @@
{
"name": "@stock-bot/api-client",
"version": "1.0.0",
"description": "API clients for inter-service communication",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist",
"test": "jest"
},
"dependencies": {
"@stock-bot/shared-types": "workspace:*",
"axios": "^1.6.0"
},
"devDependencies": {
"@types/jest": "^29.5.2",
"jest": "^29.5.0",
"typescript": "^5.4.5"
}
}

View file

@ -0,0 +1,30 @@
import { BaseApiClient } from './BaseApiClient';
import { ApiResponse, BacktestConfig, BacktestResult } from '@stock-bot/shared-types';
/**
* Client for interacting with the Backtest Engine service
*/
export class BacktestClient extends BaseApiClient {
/**
* Run a backtest
*/
async runBacktest(config: BacktestConfig): Promise<ApiResponse<BacktestResult>> {
return this.client.post('/api/backtest/run', config);
}
/**
* Get a backtest by ID
*/
async getBacktest(id: string): Promise<ApiResponse<BacktestResult>> {
return this.client.get(`/api/backtest/${id}`);
}
/**
* List all backtests for a strategy
*/
async listBacktests(strategyId: string): Promise<ApiResponse<BacktestResult[]>> {
return this.client.get(`/api/backtest`, {
params: { strategyId }
});
}
}

View file

@ -0,0 +1,39 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { ApiResponse } from '@stock-bot/shared-types';
/**
* Base API client that all service clients extend
*/
export abstract class BaseApiClient {
protected client: AxiosInstance;
constructor(baseURL: string, config?: AxiosRequestConfig) {
this.client = axios.create({
baseURL,
timeout: 10000, // 10 seconds timeout
...config
});
// Add response interceptor for consistent error handling
this.client.interceptors.response.use(
(response) => response.data,
(error) => {
// Format error for consistent error handling
const formattedError = {
status: error.response?.status || 500,
message: error.response?.data?.error || error.message || 'Unknown error',
originalError: error
};
return Promise.reject(formattedError);
}
);
}
/**
* Get the health status of a service
*/
async getHealth(): Promise<ApiResponse<{ status: string }>> {
return this.client.get('/api/health');
}
}

View file

@ -0,0 +1,56 @@
import { BaseApiClient } from './BaseApiClient';
import { ApiResponse, Strategy } from '@stock-bot/shared-types';
/**
* Client for interacting with the Strategy Orchestrator service
*/
export class StrategyClient extends BaseApiClient {
/**
* Get a strategy by ID
*/
async getStrategy(id: string): Promise<ApiResponse<Strategy>> {
return this.client.get(`/api/strategy/${id}`);
}
/**
* List all strategies
*/
async listStrategies(): Promise<ApiResponse<Strategy[]>> {
return this.client.get('/api/strategy');
}
/**
* Create a new strategy
*/
async createStrategy(strategy: Omit<Strategy, 'id'>): Promise<ApiResponse<Strategy>> {
return this.client.post('/api/strategy', strategy);
}
/**
* Update a strategy
*/
async updateStrategy(id: string, strategy: Partial<Strategy>): Promise<ApiResponse<Strategy>> {
return this.client.put(`/api/strategy/${id}`, strategy);
}
/**
* Delete a strategy
*/
async deleteStrategy(id: string): Promise<ApiResponse<void>> {
return this.client.delete(`/api/strategy/${id}`);
}
/**
* Enable a strategy
*/
async enableStrategy(id: string): Promise<ApiResponse<Strategy>> {
return this.client.post(`/api/strategy/${id}/enable`);
}
/**
* Disable a strategy
*/
async disableStrategy(id: string): Promise<ApiResponse<Strategy>> {
return this.client.post(`/api/strategy/${id}/disable`);
}
}

View file

@ -0,0 +1,14 @@
import { BaseApiClient } from './BaseApiClient';
import { BacktestClient } from './BacktestClient';
import { StrategyClient } from './StrategyClient';
export { BaseApiClient, BacktestClient, StrategyClient };
// Factory functions
export function createBacktestClient(baseUrl: string = 'http://localhost:4002'): BacktestClient {
return new BacktestClient(baseUrl);
}
export function createStrategyClient(baseUrl: string = 'http://localhost:4001'): StrategyClient {
return new StrategyClient(baseUrl);
}

View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"declaration": true,
"outDir": "dist",
"strict": true,
"sourceMap": true
},
"include": ["src/**/*"]
}

42
libs/event-bus/README.md Normal file
View file

@ -0,0 +1,42 @@
# Event Bus Library
A Redis-based event bus implementation for inter-service communication in the stock-bot project.
## Features
- Publish/subscribe pattern for asynchronous messaging
- Support for typed events based on `@stock-bot/shared-types`
- Reliable message delivery
- Channel-based subscriptions
## Usage
```typescript
import { createEventBus } from '@stock-bot/event-bus';
import { MarketDataEvent } from '@stock-bot/shared-types';
// Create an event bus instance
const eventBus = createEventBus({
redisHost: 'localhost',
redisPort: 6379
});
// Subscribe to market data events
eventBus.subscribe('market.data', async (event: MarketDataEvent) => {
console.log(`Received price update for ${event.data.symbol}: ${event.data.price}`);
});
// Publish an event
await eventBus.publish('market.data', {
type: 'MARKET_DATA',
data: {
symbol: 'AAPL',
price: 150.25,
bid: 150.20,
ask: 150.30,
volume: 1000000,
timestamp: new Date()
},
timestamp: new Date()
});
```

View file

@ -0,0 +1,22 @@
{
"name": "@stock-bot/event-bus",
"version": "1.0.0",
"description": "Event bus implementation for inter-service communication",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist",
"test": "jest"
},
"dependencies": {
"@stock-bot/shared-types": "workspace:*",
"ioredis": "^5.3.2"
},
"devDependencies": {
"@types/jest": "^29.5.2",
"jest": "^29.5.0",
"typescript": "^5.4.5"
}
}

View file

@ -0,0 +1,131 @@
import Redis from 'ioredis';
import { Event } from '@stock-bot/shared-types';
export type EventHandler<T extends Event = Event> = (event: T) => Promise<void> | void;
export interface EventBusConfig {
redisHost: string;
redisPort: number;
redisPassword?: string;
}
/**
* Event Bus for publishing and subscribing to events in the system
* Provides reliable message delivery and pattern-based subscriptions
*/
export class EventBus {
private publisher: Redis;
private subscriber: Redis;
private handlers: Map<string, EventHandler[]>;
private isConnected: boolean = false;
constructor(private config: EventBusConfig) {
this.publisher = new Redis({
host: config.redisHost,
port: config.redisPort,
password: config.redisPassword,
});
this.subscriber = new Redis({
host: config.redisHost,
port: config.redisPort,
password: config.redisPassword,
});
this.handlers = new Map<string, EventHandler[]>();
this.setupConnectionHandlers();
}
/**
* Set up Redis connection event handlers
*/
private setupConnectionHandlers(): void {
this.publisher.on('connect', () => {
console.log('Publisher connected to Redis');
this.isConnected = true;
});
this.publisher.on('error', (err) => {
console.error('Publisher Redis error', err);
this.isConnected = false;
});
this.subscriber.on('connect', () => {
console.log('Subscriber connected to Redis');
});
this.subscriber.on('error', (err) => {
console.error('Subscriber Redis error', err);
});
this.subscriber.on('message', (channel, message) => {
try {
const event = JSON.parse(message) as Event;
this.processEvent(channel, event);
} catch (err) {
console.error('Error processing event', err);
}
});
}
/**
* Process an incoming event by calling all registered handlers
*/
private processEvent(channel: string, event: Event): void {
const handlers = this.handlers.get(channel) || [];
handlers.forEach(handler => {
try {
handler(event);
} catch (err) {
console.error(`Error in event handler for ${channel}:`, err);
}
});
}
/**
* Publish an event to the specified channel
*/
public async publish(channel: string, event: Event): Promise<void> {
if (!this.isConnected) {
throw new Error('Not connected to Redis');
}
await this.publisher.publish(channel, JSON.stringify(event));
}
/**
* Subscribe to events on a specific channel
*/
public subscribe(channel: string, handler: EventHandler): () => void {
if (!this.handlers.has(channel)) {
this.handlers.set(channel, []);
this.subscriber.subscribe(channel);
}
const handlers = this.handlers.get(channel)!;
handlers.push(handler);
// Return unsubscribe function
return () => {
const index = handlers.indexOf(handler);
if (index >= 0) {
handlers.splice(index, 1);
}
if (handlers.length === 0) {
this.handlers.delete(channel);
this.subscriber.unsubscribe(channel);
}
};
}
/**
* Close connections
*/
public async close(): Promise<void> {
await this.publisher.quit();
await this.subscriber.quit();
this.isConnected = false;
}
}

View file

@ -0,0 +1,8 @@
import { EventBus, EventBusConfig, EventHandler } from './EventBus';
export { EventBus, EventBusConfig, EventHandler };
// Convenience function to create an event bus with the default configuration
export function createEventBus(config: EventBusConfig): EventBus {
return new EventBus(config);
}

View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"declaration": true,
"outDir": "dist",
"strict": true,
"sourceMap": true
},
"include": ["src/**/*"]
}

View file

@ -0,0 +1,28 @@
# Shared Types Library
This library contains domain-specific TypeScript type definitions used across the stock-bot project.
## Directory Structure
- `market/` - Market data structures (OHLCV, OrderBook, etc.)
- `trading/` - Trading types (Orders, Positions, etc.)
- `strategy/` - Strategy and signal types
- `events/` - Event definitions for the event bus
- `api/` - Common API request/response types
- `config/` - Configuration type definitions
## Usage
```typescript
import { OHLCV, MarketData } from '@stock-bot/shared-types';
// Use the types
const marketData: MarketData = {
symbol: 'AAPL',
price: 150.25,
bid: 150.20,
ask: 150.30,
volume: 1000000,
timestamp: new Date()
};
```

View file

@ -0,0 +1,23 @@
{
"name": "@stock-bot/shared-types",
"version": "1.0.0",
"description": "Domain-specific shared TypeScript definitions for the trading bot",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist",
"test": "echo \"No tests yet\""
},
"devDependencies": {
"typescript": "^5.4.5"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.js"
}
}
}

View file

@ -0,0 +1,44 @@
// Service Types
export interface ServiceConfig {
name: string;
version: string;
environment: 'development' | 'staging' | 'production';
port?: number;
dependencies?: string[];
}
export interface HealthStatus {
service: string;
status: 'healthy' | 'unhealthy' | 'degraded';
timestamp: Date;
details?: Record<string, any>;
}
// API Response Types
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
timestamp: Date;
}
export interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
hasNext: boolean;
}
// HTTP Response standards
export interface ErrorResponse {
status: number;
code: string;
message: string;
details?: any;
}
export interface SuccessResponse<T> {
status: number;
data: T;
}

View file

@ -0,0 +1,69 @@
// Configuration Types
export interface DatabaseConfig {
questdb: {
host: string;
port: number;
database: string;
};
postgres: {
host: string;
port: number;
database: string;
username: string;
password: string;
};
dragonfly: {
host: string;
port: number;
password?: string;
};
mongodb?: {
uri: string;
database: string;
};
}
export interface BrokerConfig {
name: string;
apiKey: string;
secretKey: string;
baseUrl: string;
sandbox: boolean;
rateLimit?: {
maxRequestsPerSecond: number;
maxRequestsPerMinute: number;
};
}
export interface DataProviderConfig {
name: string;
apiKey: string;
baseUrl: string;
rateLimits: {
requestsPerSecond: number;
requestsPerDay: number;
};
}
export interface LoggingConfig {
level: 'debug' | 'info' | 'warn' | 'error';
format: 'json' | 'text';
destination: 'console' | 'file' | 'both';
filePath?: string;
}
export interface SystemConfig {
databases: DatabaseConfig;
brokers: BrokerConfig[];
dataProviders: DataProviderConfig[];
logging: LoggingConfig;
services: {
[key: string]: ServiceConfig;
};
}
interface ServiceConfig {
port: number;
host: string;
dependencies: string[];
}

View file

@ -0,0 +1,42 @@
import { MarketData } from '../market/market-data';
import { Order } from '../trading/orders';
import { TradingSignal } from '../strategy/strategy';
// Event Types
export interface MarketDataEvent {
type: 'MARKET_DATA';
data: MarketData;
timestamp: Date;
}
export interface OrderEvent {
type: 'ORDER_CREATED' | 'ORDER_FILLED' | 'ORDER_CANCELLED';
order: Order;
timestamp: Date;
}
export interface SignalEvent {
type: 'SIGNAL_GENERATED';
signal: TradingSignal;
timestamp: Date;
}
// Define more specific events
export interface RiskAlertEvent {
type: 'RISK_ALERT';
alertLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
message: string;
portfolioId?: string;
strategyId?: string;
timestamp: Date;
}
export interface SystemEvent {
type: 'SYSTEM_STARTUP' | 'SYSTEM_SHUTDOWN' | 'SERVICE_ERROR';
serviceName: string;
message?: string;
timestamp: Date;
}
export type TradingEvent = MarketDataEvent | OrderEvent | SignalEvent;
export type Event = TradingEvent | RiskAlertEvent | SystemEvent;

View file

@ -0,0 +1,20 @@
// Export all types from their respective domains
// Market Data Types
export * from './market/market-data';
// Trading Types
export * from './trading/orders';
// Strategy Types
export * from './strategy/strategy';
export * from './strategy/backtest';
// Events
export * from './events/events';
// API Types
export * from './api/api';
// Configuration Types
export * from './config/config';

View file

@ -0,0 +1,48 @@
// Market Data Types
export interface OHLCV {
symbol: string;
timestamp: Date;
open: number;
high: number;
low: number;
close: number;
volume: number;
}
export interface MarketData {
symbol: string;
price: number;
bid: number;
ask: number;
volume: number;
timestamp: Date;
}
export interface OrderBook {
symbol: string;
bids: [number, number][]; // [price, size]
asks: [number, number][]; // [price, size]
timestamp: Date;
}
// Fundamental Data Types
export interface CompanyFundamentals {
symbol: string;
marketCap: number;
peRatio?: number;
pbRatio?: number;
roe?: number;
revenue: number;
netIncome: number;
lastUpdated: Date;
}
export interface NewsItem {
id: string;
headline: string;
summary: string;
sentiment: number; // -1 to 1
symbols: string[];
source: string;
publishedAt: Date;
}

View file

@ -0,0 +1,53 @@
// Trading Signal Types
import { SignalType } from './strategy';
export interface Signal {
symbol: string;
type: SignalType;
strength: number; // 0-1
price: number;
timestamp: Date;
strategyId: string;
metadata?: Record<string, any>;
}
export interface BacktestConfig {
strategyId: string;
startDate: Date;
endDate: Date;
symbols: string[];
initialCapital: number;
parameters: Record<string, any>;
}
export interface BacktestResult {
id: string;
strategyId: string;
startDate: Date;
endDate: Date;
symbols: string[];
initialCapital: number;
finalCapital: number;
totalReturn: number;
annualizedReturn: number;
maxDrawdown: number;
sharpeRatio: number;
trades: BacktestTrade[];
dailyPerformance: DailyPerformance[];
}
export interface BacktestTrade {
symbol: string;
side: 'BUY' | 'SELL';
quantity: number;
price: number;
timestamp: Date;
pnl?: number;
}
export interface DailyPerformance {
date: Date;
portfolioValue: number;
dailyPnL: number;
dailyReturn: number;
}

View file

@ -0,0 +1,28 @@
// Strategy Types
export type SignalType = 'BUY' | 'SELL' | 'HOLD';
export interface TradingSignal {
symbol: string;
type: SignalType;
strength: number; // 0-1
price: number;
timestamp: Date;
strategyId: string;
metadata?: Record<string, any>;
}
export interface Strategy {
id: string;
name: string;
description: string;
isActive: boolean;
riskLimits: RiskLimits;
parameters: Record<string, any>;
}
export interface RiskLimits {
maxPositionSize: number;
maxDailyLoss: number;
maxDrawdown: number;
allowedSymbols?: string[];
}

View file

@ -0,0 +1,35 @@
// Trading Types
export type OrderSide = 'BUY' | 'SELL';
export type OrderType = 'MARKET' | 'LIMIT' | 'STOP' | 'STOP_LIMIT';
export type OrderStatus = 'PENDING' | 'FILLED' | 'PARTIALLY_FILLED' | 'CANCELLED' | 'REJECTED';
export interface Order {
id: string;
symbol: string;
side: OrderSide;
type: OrderType;
quantity: number;
price?: number;
stopPrice?: number;
status: OrderStatus;
timestamp: Date;
strategyId: string;
}
export interface Position {
symbol: string;
quantity: number;
averagePrice: number;
marketValue: number;
unrealizedPnL: number;
timestamp: Date;
}
export interface Portfolio {
cash: number;
totalValue: number;
positions: Position[];
dayPnL: number;
totalPnL: number;
timestamp: Date;
}

View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

51
libs/utils/README.md Normal file
View file

@ -0,0 +1,51 @@
# Utils Library
Common utility functions shared across services in the stock-bot project.
## Included Utilities
### Date Utilities
Helper functions for working with market dates:
```typescript
import { dateUtils } from '@stock-bot/utils';
// Check if a date is a trading day
const isTradingDay = dateUtils.isTradingDay(new Date());
// Get the next trading day
const nextTradingDay = dateUtils.getNextTradingDay(new Date());
```
### Financial Utilities
Mathematical functions for financial calculations:
```typescript
import { financialUtils } from '@stock-bot/utils';
// Calculate Sharpe ratio
const returns = [0.05, 0.03, -0.01, 0.04, 0.02];
const sharpeRatio = financialUtils.calculateSharpeRatio(returns, 0.02);
// Calculate maximum drawdown
const equityCurve = [10000, 10500, 10200, 10800, 10300];
const maxDrawdown = financialUtils.calculateMaxDrawdown(equityCurve);
```
### Logger
Standardized logging service:
```typescript
import { createLogger, LogLevel } from '@stock-bot/utils';
// Create a logger for your service
const logger = createLogger('strategy-orchestrator', LogLevel.INFO);
// Log at different levels
logger.info('Strategy initialized');
logger.warn('Position size exceeds recommended limit');
logger.error('Failed to execute order', { orderId: '123', reason: 'Insufficient funds' });
```

22
libs/utils/package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "@stock-bot/utils",
"version": "1.0.0",
"description": "Common utility functions for stock-bot services",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist",
"test": "jest"
},
"dependencies": {
"@stock-bot/shared-types": "workspace:*",
"date-fns": "^2.30.0"
},
"devDependencies": {
"@types/jest": "^29.5.2",
"jest": "^29.5.0",
"typescript": "^5.4.5"
}
}

View file

@ -0,0 +1,55 @@
/**
* Date and time utilities for working with market data
*/
export const dateUtils = {
/**
* Check if a date is a trading day (Monday-Friday, non-holiday)
* This is a simplified implementation - a real version would check market holidays
*/
isTradingDay(date: Date): boolean {
const day = date.getDay();
return day > 0 && day < 6; // Mon-Fri
},
/**
* Get the next trading day from a given date
*/
getNextTradingDay(date: Date): Date {
const nextDay = new Date(date);
nextDay.setDate(nextDay.getDate() + 1);
while (!this.isTradingDay(nextDay)) {
nextDay.setDate(nextDay.getDate() + 1);
}
return nextDay;
},
/**
* Get the previous trading day from a given date
*/
getPreviousTradingDay(date: Date): Date {
const prevDay = new Date(date);
prevDay.setDate(prevDay.getDate() - 1);
while (!this.isTradingDay(prevDay)) {
prevDay.setDate(prevDay.getDate() - 1);
}
return prevDay;
},
/**
* Format a date as YYYY-MM-DD
*/
formatDate(date: Date): string {
return date.toISOString().split('T')[0];
},
/**
* Parse a date string in YYYY-MM-DD format
*/
parseDate(dateStr: string): Date {
return new Date(dateStr);
}
};

View file

@ -0,0 +1,67 @@
/**
* Financial calculation utilities
*/
export const financialUtils = {
/**
* Calculate the Sharpe ratio
* @param returns Array of period returns
* @param riskFreeRate The risk-free rate (e.g. 0.02 for 2%)
*/
calculateSharpeRatio(returns: number[], riskFreeRate: number = 0.02): number {
if (returns.length < 2) {
return 0;
}
// Calculate average return
const avgReturn = returns.reduce((sum, val) => sum + val, 0) / returns.length;
// Calculate standard deviation
const squaredDiffs = returns.map(val => Math.pow(val - avgReturn, 2));
const avgSquaredDiff = squaredDiffs.reduce((sum, val) => sum + val, 0) / squaredDiffs.length;
const stdDev = Math.sqrt(avgSquaredDiff);
// Avoid division by zero
if (stdDev === 0) return 0;
// Calculate Sharpe ratio
return (avgReturn - riskFreeRate) / stdDev;
},
/**
* Calculate the maximum drawdown
* @param equityCurve Array of equity values over time
*/
calculateMaxDrawdown(equityCurve: number[]): number {
if (equityCurve.length < 2) {
return 0;
}
let maxDrawdown = 0;
let peak = equityCurve[0];
for (let i = 1; i < equityCurve.length; i++) {
if (equityCurve[i] > peak) {
peak = equityCurve[i];
} else {
const drawdown = (peak - equityCurve[i]) / peak;
maxDrawdown = Math.max(maxDrawdown, drawdown);
}
}
return maxDrawdown;
},
/**
* Calculate the Compound Annual Growth Rate (CAGR)
* @param startValue Initial investment value
* @param endValue Final investment value
* @param years Number of years
*/
calculateCAGR(startValue: number, endValue: number, years: number): number {
if (years <= 0 || startValue <= 0 || endValue <= 0) {
return 0;
}
return Math.pow(endValue / startValue, 1 / years) - 1;
}
};

3
libs/utils/src/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from './dateUtils';
export * from './financialUtils';
export * from './logger';

65
libs/utils/src/logger.ts Normal file
View file

@ -0,0 +1,65 @@
/**
* Logger utility with consistent formatting and log levels
*/
export class Logger {
constructor(private serviceName: string, private level: LogLevel = LogLevel.INFO) {}
debug(message: string, ...args: any[]): void {
this.log(LogLevel.DEBUG, message, ...args);
}
info(message: string, ...args: any[]): void {
this.log(LogLevel.INFO, message, ...args);
}
warn(message: string, ...args: any[]): void {
this.log(LogLevel.WARN, message, ...args);
}
error(message: string, ...args: any[]): void {
this.log(LogLevel.ERROR, message, ...args);
}
private log(level: LogLevel, message: string, ...args: any[]): void {
if (level < this.level) return;
const timestamp = new Date().toISOString();
const levelStr = LogLevel[level].padEnd(5);
const logMessage = `[${timestamp}] [${levelStr}] [${this.serviceName}] ${message}`;
switch (level) {
case LogLevel.ERROR:
console.error(logMessage, ...args);
break;
case LogLevel.WARN:
console.warn(logMessage, ...args);
break;
case LogLevel.INFO:
console.info(logMessage, ...args);
break;
case LogLevel.DEBUG:
default:
console.debug(logMessage, ...args);
break;
}
}
setLevel(level: LogLevel): void {
this.level = level;
}
}
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3
}
/**
* Create a new logger instance
*/
export function createLogger(serviceName: string, level: LogLevel = LogLevel.INFO): Logger {
return new Logger(serviceName, level);
}

13
libs/utils/tsconfig.json Normal file
View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"declaration": true,
"outDir": "dist",
"strict": true,
"sourceMap": true
},
"include": ["src/**/*"]
}