/** * Trading Utilities for autonoma Agent Core * * Common utility functions for trading calculations and operations. */ import { Order, Position, Portfolio, OrderSide, YieldOpportunity, TimeFrame, PriceRange, OHLCV } from './types.js'; // ============================================================================= // Price & Market Calculations // ============================================================================= /** * Calculate percentage change between two prices */ export function calculatePriceChange(oldPrice: number, newPrice: number): number { if (oldPrice === 0) return 0; return ((newPrice - oldPrice) / oldPrice) * 100; } /** * Calculate simple moving average */ export function calculateSMA(prices: number[], period: number): number { if (prices.length < period) return 0; const sum = prices.slice(-period).reduce((acc, price) => acc + price, 0); return sum / period; } /** * Calculate exponential moving average */ export function calculateEMA(prices: number[], period: number): number { if (prices.length === 0) return 0; if (prices.length === 1) return prices[0]!; const multiplier = 2 / (period + 1); let ema = prices[0]!; for (let i = 1; i < prices.length; i++) { const price = prices[i]; if (price !== undefined) { ema = (price * multiplier) + (ema * (1 - multiplier)); } } return ema; } /** * Calculate volatility (standard deviation) of price returns */ export function calculateVolatility(prices: number[]): number { if (prices.length < 2) return 0; const returns = []; for (let i = 1; i < prices.length; i++) { const current = prices[i]; const previous = prices[i - 1]; if (current !== undefined && previous !== undefined && previous !== 0) { returns.push((current - previous) / previous); } } const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length; return Math.sqrt(variance) * Math.sqrt(252); // Annualized volatility } /** * Calculate support and resistance levels */ export function findSupportResistance(ohlcv: OHLCV[], lookback: number = 20): { support: number; resistance: number } { if (ohlcv.length < lookback) return { support: 0, resistance: 0 }; const recent = ohlcv.slice(-lookback); const highs = recent.map(candle => candle.high); const lows = recent.map(candle => candle.low); const support = lows.reduce((min, low) => Math.min(min, low), lows[0] ?? 0); const resistance = highs.reduce((max, high) => Math.max(max, high), highs[0] ?? 0); return { support, resistance }; } // ============================================================================= // Portfolio & Risk Calculations // ============================================================================= /** * Calculate portfolio total value */ export function calculatePortfolioValue(portfolio: Portfolio): number { const positionsValue = portfolio.positions.reduce((total, position) => { return total + position.marketValue; }, 0); return portfolio.cash + positionsValue; } /** * Calculate position size as percentage of portfolio */ export function calculatePositionWeight(position: Position, portfolioValue: number): number { if (portfolioValue === 0) return 0; return (position.marketValue / portfolioValue) * 100; } /** * Calculate unrealized P&L for a position */ export function calculateUnrealizedPnL(position: Position, currentPrice: number): number { return (currentPrice - position.averagePrice) * position.quantity; } /** * Calculate Sharpe ratio */ export function calculateSharpeRatio(returns: number[], riskFreeRate: number = 0.02): number { if (returns.length === 0) return 0; const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - avgReturn, 2), 0) / returns.length; const stdDev = Math.sqrt(variance); if (stdDev === 0) return 0; return (avgReturn - riskFreeRate) / stdDev; } /** * Calculate maximum drawdown */ export function calculateMaxDrawdown(portfolioValues: number[]): number { if (portfolioValues.length < 2) return 0; let maxDrawdown = 0; let peak = portfolioValues[0] ?? 0; for (let i = 1; i < portfolioValues.length; i++) { const value = portfolioValues[i]; if (value !== undefined) { if (value > peak) { peak = value; } else if (peak > 0) { const drawdown = (peak - value) / peak; maxDrawdown = Math.max(maxDrawdown, drawdown); } } } return maxDrawdown * 100; // Return as percentage } /** * Calculate Value at Risk (VaR) using historical simulation */ export function calculateVaR(returns: number[], confidence: number = 0.05): number { if (returns.length === 0) return 0; const sortedReturns = [...returns].sort((a, b) => a - b); const index = Math.floor(returns.length * confidence); return Math.abs(sortedReturns[index] || 0); } // ============================================================================= // Order & Trading Calculations // ============================================================================= /** * Calculate order fill ratio */ export function calculateFillRatio(order: Order): number { if (order.quantity === 0) return 0; return (order.filledQuantity / order.quantity) * 100; } /** * Calculate slippage */ export function calculateSlippage(expectedPrice: number, executedPrice: number, side: OrderSide): number { if (expectedPrice === 0) return 0; const diff = side === 'buy' ? executedPrice - expectedPrice : expectedPrice - executedPrice; return (diff / expectedPrice) * 100; } /** * Calculate optimal order size based on market impact */ export function calculateOptimalOrderSize( availableLiquidity: number, targetSize: number, maxMarketImpact: number = 0.5 // 0.5% max impact ): number { const maxSafeSize = availableLiquidity * (maxMarketImpact / 100); return Math.min(targetSize, maxSafeSize); } // ============================================================================= // DeFi Calculations // ============================================================================= /** * Calculate APY from APR */ export function aprToApy(apr: number, compoundingPeriods: number = 365): number { return (Math.pow(1 + (apr / compoundingPeriods), compoundingPeriods) - 1) * 100; } /** * Calculate yield farming returns */ export function calculateYieldFarmingReturns( principal: number, apy: number, days: number, compounding: boolean = true ): { totalReturn: number; profit: number } { const periods = compounding ? days : 1; const rate = apy / 100 / (compounding ? 365 : 1); const totalReturn = compounding ? principal * Math.pow(1 + rate, periods) : principal + (principal * rate * days); return { totalReturn, profit: totalReturn - principal }; } /** * Calculate impermanent loss for liquidity provision */ export function calculateImpermanentLoss( initialPrice1: number, initialPrice2: number, currentPrice1: number, currentPrice2: number ): number { const priceRatio = (currentPrice1 / currentPrice2) / (initialPrice1 / initialPrice2); const impermanentLoss = (2 * Math.sqrt(priceRatio)) / (1 + priceRatio) - 1; return Math.abs(impermanentLoss) * 100; } /** * Compare yield opportunities */ export function rankYieldOpportunities(opportunities: YieldOpportunity[]): YieldOpportunity[] { return opportunities.sort((a, b) => { // Risk-adjusted APY calculation (simple risk penalty) const riskPenalty = { low: 1, medium: 0.8, high: 0.6 }; const adjustedApyA = a.apy * riskPenalty[a.risk]; const adjustedApyB = b.apy * riskPenalty[b.risk]; return adjustedApyB - adjustedApyA; }); } // ============================================================================= // Utility Functions // ============================================================================= /** * Convert timeframe to milliseconds */ export function timeFrameToMs(timeFrame: TimeFrame): number { const units = { second: 1000, minute: 60 * 1000, hour: 60 * 60 * 1000, day: 24 * 60 * 60 * 1000, week: 7 * 24 * 60 * 60 * 1000, month: 30 * 24 * 60 * 60 * 1000 }; return timeFrame.value * units[timeFrame.unit]; } /** * Format currency with appropriate decimal places */ export function formatCurrency(amount: number, decimals: number = 2): string { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(amount); } /** * Format percentage with sign */ export function formatPercentage(value: number, decimals: number = 2): string { const sign = value >= 0 ? '+' : ''; return `${sign}${value.toFixed(decimals)}%`; } /** * Check if price is within range */ export function isPriceInRange(price: number, range: PriceRange): boolean { return price >= range.min && price <= range.max; } /** * Calculate distance from price to range bounds */ export function distanceToRange(price: number, range: PriceRange): { toMin: number; toMax: number } { return { toMin: Math.abs(price - range.min), toMax: Math.abs(price - range.max) }; } /** * Generate trading session ID */ export function generateSessionId(): string { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substr(2, 5); return `${timestamp}-${random}`; } /** * Validate trading symbol format */ export function isValidSymbol(symbol: string): boolean { // Basic validation for crypto trading pairs const regex = /^[A-Z]{2,10}[\/\-][A-Z]{2,10}$/; return regex.test(symbol); } /** * Normalize symbol format */ export function normalizeSymbol(symbol: string): string { return symbol.toUpperCase().replace('-', '/'); }