diff --git a/apps/stock/core/index.node b/apps/stock/core/index.node index 42b2603..e647a4e 100755 Binary files a/apps/stock/core/index.node and b/apps/stock/core/index.node differ diff --git a/apps/stock/orchestrator/debug-performance-values.ts b/apps/stock/orchestrator/debug-performance-values.ts new file mode 100644 index 0000000..a184559 --- /dev/null +++ b/apps/stock/orchestrator/debug-performance-values.ts @@ -0,0 +1,149 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function debugPerformanceValues() { + console.log('Debugging Performance Calculation Values...\n'); + + // Create minimal service container with more logging + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => { + // Log everything related to P&L and portfolio + if (msg.includes('P&L') || msg.includes('portfolio') || msg.includes('Portfolio') || + msg.includes('equity') || msg.includes('Total') || msg.includes('pnl')) { + console.log('[INFO]', msg, ...args); + } + }, + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args), + debug: (msg: string, ...args: any[]) => { + if (msg.includes('P&L') || msg.includes('portfolio') || msg.includes('pnl')) { + console.log('[DEBUG]', msg, ...args); + } + }, + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Initialize backtest mode + await modeManager.initializeMode({ + mode: 'backtest', + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-15T00:00:00Z', // Just 15 days + speed: 'max', + symbols: ['TEST'], + initialCapital: 100000, + dataFrequency: '1d', + strategy: 'sma-crossover' + }); + + // Create backtest engine + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + // Run backtest + const config = { + mode: 'backtest', + name: 'Debug Performance Values', + strategy: 'sma-crossover', + symbols: ['TEST'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-15T00:00:00Z', + initialCapital: 100000, + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d', + speed: 'max' + }; + + console.log('Running backtest...'); + const result = await backtestEngine.runBacktest(config); + + // Debug values + console.log('\n=== RAW VALUES DEBUG ==='); + console.log(`Initial Capital: $${config.initialCapital}`); + console.log(`Reported Metrics:`); + console.log(` - Total Return: ${result.metrics.totalReturn}%`); + console.log(` - Sharpe Ratio: ${result.metrics.sharpeRatio}`); + console.log(` - Max Drawdown: ${result.metrics.maxDrawdown}%`); + console.log(` - Win Rate: ${result.metrics.winRate}%`); + console.log(` - Total Trades: ${result.metrics.totalTrades}`); + + console.log('\n=== EQUITY CURVE VALUES ==='); + console.log(`Equity Points: ${result.equity.length}`); + if (result.equity.length > 0) { + const first = result.equity[0]; + const last = result.equity[result.equity.length - 1]; + console.log(`First: ${first.date} => $${first.value}`); + console.log(`Last: ${last.date} => $${last.value}`); + + // Manual calculation + const manualReturn = ((last.value - first.value) / first.value) * 100; + console.log(`\nManual Total Return: ${manualReturn.toFixed(2)}%`); + console.log(`Difference from reported: ${Math.abs(manualReturn - result.metrics.totalReturn).toFixed(2)}%`); + } + + console.log('\n=== TRADE ANALYSIS ==='); + console.log(`Closed Trades: ${result.trades.length}`); + if (result.trades.length > 0) { + const wins = result.trades.filter(t => t.pnl > 0); + const losses = result.trades.filter(t => t.pnl < 0); + const manualWinRate = (wins.length / result.trades.length) * 100; + + console.log(`Wins: ${wins.length}`); + console.log(`Losses: ${losses.length}`); + console.log(`Manual Win Rate: ${manualWinRate.toFixed(2)}%`); + + // Show P&L values + const totalPnL = result.trades.reduce((sum, t) => sum + t.pnl, 0); + console.log(`\nTotal P&L from trades: $${totalPnL.toFixed(2)}`); + + // Show first few trades + console.log('\nFirst 3 trades:'); + result.trades.slice(0, 3).forEach((t, i) => { + console.log(` ${i+1}. ${t.side} ${t.quantity} @ ${t.exitPrice} | P&L: $${t.pnl.toFixed(2)}`); + }); + } + + // Check trading engine P&L + const tradingEngine = strategyManager.getTradingEngine(); + if (tradingEngine) { + try { + const [realized, unrealized] = tradingEngine.getTotalPnl(); + console.log('\n=== TRADING ENGINE P&L ==='); + console.log(`Realized P&L: $${realized.toFixed(2)}`); + console.log(`Unrealized P&L: $${unrealized.toFixed(2)}`); + console.log(`Total P&L: $${(realized + unrealized).toFixed(2)}`); + console.log(`Portfolio Value: $${(config.initialCapital + realized + unrealized).toFixed(2)}`); + } catch (e) { + console.error('Failed to get P&L from trading engine:', e); + } + } + + console.log('\n=== TEST COMPLETE ==='); + process.exit(0); +} + +debugPerformanceValues().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/src/analytics/PerformanceAnalyzer.ts b/apps/stock/orchestrator/src/analytics/PerformanceAnalyzer.ts index f268d37..4a016c7 100644 --- a/apps/stock/orchestrator/src/analytics/PerformanceAnalyzer.ts +++ b/apps/stock/orchestrator/src/analytics/PerformanceAnalyzer.ts @@ -101,7 +101,7 @@ export class PerformanceAnalyzer { addEquityPoint(date: Date, value: number): void { this.equityCurve.push({ date, value }); - this.calculateDailyReturns(); + // Don't calculate daily returns on every point - wait until analyze() is called } addTrade(trade: Trade): void { @@ -117,6 +117,9 @@ export class PerformanceAnalyzer { return this.getEmptyMetrics(); } + // Calculate daily returns before analysis + this.calculateDailyReturns(); + // Calculate returns const totalReturn = this.calculateTotalReturn(); const annualizedReturn = this.calculateAnnualizedReturn(); @@ -131,7 +134,7 @@ export class PerformanceAnalyzer { // Risk-adjusted returns const sharpeRatio = this.calculateSharpeRatio(annualizedReturn, volatility); const sortinoRatio = this.calculateSortinoRatio(annualizedReturn, downVolatility); - const calmarRatio = annualizedReturn / Math.abs(drawdownAnalysis.maxDrawdown); + const calmarRatio = drawdownAnalysis.maxDrawdown > 0 ? annualizedReturn / (drawdownAnalysis.maxDrawdown * 100) : 0; const informationRatio = this.calculateInformationRatio(); // Trade statistics @@ -148,8 +151,8 @@ export class PerformanceAnalyzer { totalReturn, annualizedReturn, cagr, - volatility, - downVolatility, + volatility: volatility * 100, // Convert to percentage for display + downVolatility: downVolatility * 100, // Convert to percentage for display maxDrawdown: drawdownAnalysis.maxDrawdown, maxDrawdownDuration: drawdownAnalysis.maxDrawdownDuration, var95, @@ -289,10 +292,26 @@ export class PerformanceAnalyzer { private calculateDailyReturns(): void { this.dailyReturns = []; - for (let i = 1; i < this.equityCurve.length; i++) { - const prevValue = this.equityCurve[i - 1].value; - const currValue = this.equityCurve[i].value; - this.dailyReturns.push((currValue - prevValue) / prevValue); + + // First, aggregate equity curve by day (taking last value of each day) + const dailyEquity = new Map(); + + for (const point of this.equityCurve) { + const dateKey = point.date.toISOString().split('T')[0]; + dailyEquity.set(dateKey, point); // This will keep the last value of each day + } + + // Convert to sorted array + const dailyPoints = Array.from(dailyEquity.values()) + .sort((a, b) => a.date.getTime() - b.date.getTime()); + + // Calculate returns from daily points + for (let i = 1; i < dailyPoints.length; i++) { + const prevValue = dailyPoints[i - 1].value; + const currValue = dailyPoints[i].value; + if (prevValue > 0) { + this.dailyReturns.push((currValue - prevValue) / prevValue); + } } } @@ -315,13 +334,15 @@ export class PerformanceAnalyzer { private calculateVolatility(): number { if (this.dailyReturns.length === 0) return 0; - return stats.standardDeviation(this.dailyReturns) * Math.sqrt(252) * 100; + // Return volatility as a decimal percentage (not multiplied by 100) + return stats.standardDeviation(this.dailyReturns) * Math.sqrt(252); } private calculateDownsideVolatility(): number { const negativeReturns = this.dailyReturns.filter(r => r < 0); if (negativeReturns.length === 0) return 0; - return stats.standardDeviation(negativeReturns) * Math.sqrt(252) * 100; + // Return volatility as a decimal percentage (not multiplied by 100) + return stats.standardDeviation(negativeReturns) * Math.sqrt(252); } private calculateVaR(): { var95: number; cvar95: number } { @@ -330,20 +351,30 @@ export class PerformanceAnalyzer { const sortedReturns = [...this.dailyReturns].sort((a, b) => a - b); const index95 = Math.floor(sortedReturns.length * 0.05); + // Handle edge case where we don't have enough data points + if (index95 === 0 || sortedReturns.length < 20) { + return { var95: 0, cvar95: 0 }; + } + const var95 = Math.abs(sortedReturns[index95]) * 100; - const cvar95 = Math.abs(stats.mean(sortedReturns.slice(0, index95))) * 100; + const tailReturns = sortedReturns.slice(0, index95); + const cvar95 = tailReturns.length > 0 ? Math.abs(stats.mean(tailReturns)) * 100 : 0; return { var95, cvar95 }; } private calculateSharpeRatio(annualReturn: number, volatility: number, riskFree: number = 2): number { if (volatility === 0) return 0; - return (annualReturn - riskFree) / volatility; + // Both annualReturn and volatility should be in percentage form + const volatilityPct = volatility * 100; + return (annualReturn - riskFree) / volatilityPct; } private calculateSortinoRatio(annualReturn: number, downVolatility: number, riskFree: number = 2): number { if (downVolatility === 0) return 0; - return (annualReturn - riskFree) / downVolatility; + // Both annualReturn and downVolatility should be in percentage form + const downVolatilityPct = downVolatility * 100; + return (annualReturn - riskFree) / downVolatilityPct; } private calculateInformationRatio(): number { @@ -380,8 +411,13 @@ export class PerformanceAnalyzer { }; } - const wins = this.trades.filter(t => t.pnl > 0); - const losses = this.trades.filter(t => t.pnl < 0); + // Filter out trades with 0 or undefined P&L + const validTrades = this.trades.filter(t => t.pnl !== undefined && t.pnl !== null); + const wins = validTrades.filter(t => t.pnl > 0); + const losses = validTrades.filter(t => t.pnl < 0); + + // Log trade analysis for debugging + logger.debug(`Trade Analysis: Total=${this.trades.length}, Valid=${validTrades.length}, Wins=${wins.length}, Losses=${losses.length}`); const totalWins = wins.reduce((sum, t) => sum + t.pnl, 0); const totalLosses = Math.abs(losses.reduce((sum, t) => sum + t.pnl, 0)); @@ -389,7 +425,7 @@ export class PerformanceAnalyzer { const avgWin = wins.length > 0 ? totalWins / wins.length : 0; const avgLoss = losses.length > 0 ? totalLosses / losses.length : 0; - const winRate = (wins.length / this.trades.length) * 100; + const winRate = validTrades.length > 0 ? (wins.length / validTrades.length) * 100 : 0; const profitFactor = totalLosses > 0 ? totalWins / totalLosses : totalWins > 0 ? Infinity : 0; const expectancy = (winRate / 100 * avgWin) - ((100 - winRate) / 100 * avgLoss); const payoffRatio = avgLoss > 0 ? avgWin / avgLoss : 0; diff --git a/apps/stock/orchestrator/src/backtest/BacktestEngine.ts b/apps/stock/orchestrator/src/backtest/BacktestEngine.ts index e8141f6..d4eb924 100644 --- a/apps/stock/orchestrator/src/backtest/BacktestEngine.ts +++ b/apps/stock/orchestrator/src/backtest/BacktestEngine.ts @@ -99,6 +99,8 @@ export class BacktestEngine extends EventEmitter { private microstructures: Map = new Map(); private container: IServiceContainer; private initialCapital: number = 100000; + private commission: number = 0.001; // Default 0.1% + private slippage: number = 0.0001; // Default 0.01% private pendingOrders: Map = new Map(); private ordersListenerSetup = false; @@ -116,7 +118,8 @@ export class BacktestEngine extends EventEmitter { includeDarkPools: true, latencyMs: 1 }); - this.performanceAnalyzer = new PerformanceAnalyzer(); + // Don't create performance analyzer here - wait for runBacktest + // this.performanceAnalyzer = new PerformanceAnalyzer(this.initialCapital); // Set up order listener immediately this.setupOrderListener(); @@ -132,6 +135,11 @@ export class BacktestEngine extends EventEmitter { this.reset(); this.isRunning = true; this.initialCapital = validatedConfig.initialCapital; + this.commission = validatedConfig.commission || 0.001; + this.slippage = validatedConfig.slippage || 0.0001; + + // Recreate performance analyzer with correct initial capital + this.performanceAnalyzer = new PerformanceAnalyzer(this.initialCapital); // Initialize equity curve with starting capital this.equityCurve.push({ @@ -145,6 +153,8 @@ export class BacktestEngine extends EventEmitter { this.initialCapital ); + this.container.logger.info(`[BacktestEngine] Initial capital set to ${this.initialCapital}, equity curve initialized with ${this.equityCurve.length} points`); + // Generate backtest ID const backtestId = `backtest_${Date.now()}`; @@ -668,12 +678,16 @@ export class BacktestEngine extends EventEmitter { private async updateEquityCurve(): Promise { const totalEquity = await this.getPortfolioValue(); + this.container.logger.debug(`[updateEquityCurve] currentTime=${this.currentTime}, totalEquity=${totalEquity}, initialCapital=${this.initialCapital}`); + // Don't add duplicate points at the same timestamp const lastPoint = this.equityCurve[this.equityCurve.length - 1]; if (lastPoint && lastPoint.timestamp === this.currentTime) { // Update the value instead of adding a duplicate + this.container.logger.debug(`[updateEquityCurve] Updating existing point at ${this.currentTime}: ${lastPoint.value} -> ${totalEquity}`); lastPoint.value = totalEquity; } else { + this.container.logger.debug(`[updateEquityCurve] Adding new point at ${this.currentTime}: ${totalEquity}`); this.equityCurve.push({ timestamp: this.currentTime, value: totalEquity @@ -684,6 +698,7 @@ export class BacktestEngine extends EventEmitter { private async getPortfolioValue(): Promise { const tradingEngine = this.strategyManager.getTradingEngine(); if (!tradingEngine) { + this.container.logger.debug(`[getPortfolioValue] No trading engine, returning initialCapital: ${this.initialCapital}`); return this.initialCapital; } @@ -692,9 +707,12 @@ export class BacktestEngine extends EventEmitter { const [realized, unrealized] = tradingEngine.getTotalPnl(); const totalValue = this.initialCapital + realized + unrealized; + this.container.logger.debug(`[getPortfolioValue] P&L: realized=${realized}, unrealized=${unrealized}, initialCapital=${this.initialCapital}, totalValue=${totalValue}`); + // Ensure we never return 0 or negative values that would cause spikes // This handles the case where getTotalPnl might not be initialized yet if (totalValue <= 0 || isNaN(totalValue)) { + this.container.logger.debug(`[getPortfolioValue] Invalid totalValue, returning initialCapital: ${this.initialCapital}`); return this.initialCapital; } @@ -941,13 +959,6 @@ export class BacktestEngine extends EventEmitter { return profile.map(v => v / sum); } - private getPortfolioValue(): number { - const tradingEngine = this.strategyManager.getTradingEngine(); - if (!tradingEngine) {return 100000;} // Default initial capital - - const [realized, unrealized] = tradingEngine.getTotalPnl(); - return 100000 + realized + unrealized; - } async stopBacktest(): Promise { this.isRunning = false; @@ -1160,8 +1171,8 @@ export class BacktestEngine extends EventEmitter { if (!orderEvent) continue; const fillPrice = orderEvent.order.side === 'buy' ? - currentPrice * (1 + (this.marketSimulator.config.slippage || 0.0001)) : - currentPrice * (1 - (this.marketSimulator.config.slippage || 0.0001)); + currentPrice * (1 + this.slippage) : + currentPrice * (1 - this.slippage); const fill = { orderId, @@ -1170,7 +1181,7 @@ export class BacktestEngine extends EventEmitter { quantity: orderEvent.order.quantity, price: fillPrice, timestamp: this.currentTime, - commission: orderEvent.order.quantity * fillPrice * 0.001, // 0.1% commission + commission: orderEvent.order.quantity * fillPrice * this.commission, strategyId: orderEvent.strategyId }; diff --git a/apps/stock/orchestrator/src/core/ModeManager.ts b/apps/stock/orchestrator/src/core/ModeManager.ts index bd57a07..8102bed 100644 --- a/apps/stock/orchestrator/src/core/ModeManager.ts +++ b/apps/stock/orchestrator/src/core/ModeManager.ts @@ -64,7 +64,8 @@ export class ModeManager extends EventEmitter { return { startTime: new Date(config.startDate).getTime(), endTime: new Date(config.endDate).getTime(), - speedMultiplier: this.getSpeedMultiplier(config.speed) + speedMultiplier: this.getSpeedMultiplier(config.speed), + initialCapital: config.initialCapital }; case 'paper': return { diff --git a/apps/stock/orchestrator/src/strategies/BaseStrategy.ts b/apps/stock/orchestrator/src/strategies/BaseStrategy.ts index be298c2..e9e8bc0 100644 --- a/apps/stock/orchestrator/src/strategies/BaseStrategy.ts +++ b/apps/stock/orchestrator/src/strategies/BaseStrategy.ts @@ -202,8 +202,9 @@ export abstract class BaseStrategy extends EventEmitter { } protected getEquity(): number { - // Simplified - in reality would calculate based on positions and market values - return 100000 + this.performance.totalPnl; // Assuming 100k starting capital + // Get initial capital from config parameters or default + const initialCapital = this.config.parameters?.initialCapital || 100000; + return initialCapital + this.performance.totalPnl; } protected async signalToOrder(signal: Signal): Promise { diff --git a/apps/stock/orchestrator/src/strategies/examples/SimpleMovingAverageCrossoverFixed.ts b/apps/stock/orchestrator/src/strategies/examples/SimpleMovingAverageCrossoverFixed.ts index 8562770..f68cd1e 100644 --- a/apps/stock/orchestrator/src/strategies/examples/SimpleMovingAverageCrossoverFixed.ts +++ b/apps/stock/orchestrator/src/strategies/examples/SimpleMovingAverageCrossoverFixed.ts @@ -272,13 +272,14 @@ export class SimpleMovingAverageCrossover extends BaseStrategy { } // Try to get account balance from trading engine - let accountBalance = 100000; // Default + // Use initial capital from config parameters if available + let accountBalance = this.config.parameters?.initialCapital || 100000; try { if (tradingEngine.getAccountBalance) { accountBalance = tradingEngine.getAccountBalance(); } else if (tradingEngine.getTotalPnl) { const [realized, unrealized] = tradingEngine.getTotalPnl(); - accountBalance = 100000 + realized + unrealized; // Assuming 100k initial + accountBalance = (this.config.parameters?.initialCapital || 100000) + realized + unrealized; } } catch (error) { logger.warn('Could not get account balance:', error); diff --git a/apps/stock/orchestrator/test-api-response.ts b/apps/stock/orchestrator/test-api-response.ts new file mode 100644 index 0000000..485dd1e --- /dev/null +++ b/apps/stock/orchestrator/test-api-response.ts @@ -0,0 +1,69 @@ +import { createContainer } from './src/simple-container'; +import fetch from 'node-fetch'; + +async function testApiResponse() { + console.log('Testing API Response Values...\n'); + + // First, create the container and start the server + const container = await createContainer({ + database: { + mongodb: { enabled: false, uri: '' }, + postgres: { enabled: false, uri: '' }, + questdb: { enabled: false, host: '', port: 0 }, + dragonfly: { enabled: false } + } + }); + + // Run a simple backtest via API + const backtestRequest = { + strategy: 'sma-crossover', + symbols: ['AAPL'], + startDate: '2023-01-01', + endDate: '2023-02-01', + initialCapital: 100000, + config: { + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d' + } + }; + + try { + console.log('Sending backtest request...'); + const response = await fetch('http://localhost:2003/api/backtest/run', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(backtestRequest) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + console.log('\n=== API RESPONSE ==='); + console.log(JSON.stringify(result, null, 2)); + + if (result.metrics) { + console.log('\n=== METRICS VALUES ==='); + console.log(`Total Return: ${result.metrics.totalReturn}`); + console.log(`Sharpe Ratio: ${result.metrics.sharpeRatio}`); + console.log(`Max Drawdown: ${result.metrics.maxDrawdown}`); + console.log(`Win Rate: ${result.metrics.winRate}`); + console.log(`Total Trades: ${result.metrics.totalTrades}`); + } + + } catch (error) { + console.error('API call failed:', error); + } + + console.log('\n=== TEST COMPLETE ==='); + process.exit(0); +} + +testApiResponse().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-commission-detailed.ts b/apps/stock/orchestrator/test-commission-detailed.ts new file mode 100644 index 0000000..3fc353b --- /dev/null +++ b/apps/stock/orchestrator/test-commission-detailed.ts @@ -0,0 +1,120 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function testCommissionDetailed() { + // Create service container with minimal logging + const container: IServiceContainer = { + logger: { + info: () => {}, + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: () => {}, + debug: () => {}, + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Test with moderate commission and slippage + const config = { + mode: 'backtest', + name: 'Commission Test', + strategy: 'sma-crossover', + symbols: ['AAPL'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-02-28T00:00:00Z', // 2 months + initialCapital: 10000, + commission: 0.002, // 0.2% commission + slippage: 0.001, // 0.1% slippage + dataFrequency: '1d', + speed: 'max' + }; + + await modeManager.initializeMode(config); + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + console.log('\n=== COMMISSION & SLIPPAGE TEST ==='); + console.log(`Initial Capital: $${config.initialCapital}`); + console.log(`Commission: ${(config.commission * 100).toFixed(2)}%`); + console.log(`Slippage: ${(config.slippage * 100).toFixed(2)}%`); + + const result = await backtestEngine.runBacktest(config); + + // Get P&L from Rust + const tradingEngine = strategyManager.getTradingEngine(); + const [realized, unrealized] = tradingEngine.getTotalPnl(); + + console.log('\n=== RESULTS ==='); + console.log(`Total Trades: ${result.trades.length}`); + console.log(`Final Portfolio Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`); + console.log(`Realized P&L: $${realized.toFixed(2)}`); + console.log(`Unrealized P&L: $${unrealized.toFixed(2)}`); + + if (result.trades.length > 0) { + console.log('\n=== TRADE DETAILS ==='); + let totalCommission = 0; + let totalSlippageCost = 0; + + result.trades.slice(0, 5).forEach((trade, idx) => { + const entryValue = trade.quantity * trade.entryPrice; + const exitValue = trade.quantity * trade.exitPrice; + + // Calculate expected slippage + // For a buy entry, we pay more (positive slippage) + // For a sell entry (short), we receive less (negative slippage) + const entrySlippage = trade.side === 'buy' ? + entryValue * config.slippage : + -entryValue * config.slippage; + + // For exit, it's opposite + const exitSlippage = trade.side === 'buy' ? + -exitValue * config.slippage : + exitValue * config.slippage; + + const expectedCommission = (entryValue + exitValue) * config.commission; + const slippageCost = entrySlippage + exitSlippage; + + totalCommission += trade.commission; + totalSlippageCost += slippageCost; + + console.log(`\nTrade ${idx + 1}: ${trade.symbol} ${trade.side}`); + console.log(` Quantity: ${trade.quantity} shares`); + console.log(` Entry: $${trade.entryPrice.toFixed(2)} (value: $${entryValue.toFixed(2)})`); + console.log(` Exit: $${trade.exitPrice.toFixed(2)} (value: $${exitValue.toFixed(2)})`); + console.log(` Commission: $${trade.commission.toFixed(2)} (expected: $${expectedCommission.toFixed(2)})`); + console.log(` Slippage Cost: $${slippageCost.toFixed(2)}`); + console.log(` Gross P&L: $${(exitValue - entryValue).toFixed(2)}`); + console.log(` Net P&L: $${trade.pnl.toFixed(2)}`); + }); + + console.log(`\n=== TOTALS ===`); + console.log(`Total Commission Paid: $${totalCommission.toFixed(2)}`); + console.log(`Total Slippage Cost: $${totalSlippageCost.toFixed(2)}`); + console.log(`Total Trading Costs: $${(totalCommission + totalSlippageCost).toFixed(2)}`); + } + + process.exit(0); +} + +testCommissionDetailed().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-commission-slippage.ts b/apps/stock/orchestrator/test-commission-slippage.ts new file mode 100644 index 0000000..0b1b1d2 --- /dev/null +++ b/apps/stock/orchestrator/test-commission-slippage.ts @@ -0,0 +1,123 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function testCommissionSlippage() { + console.log('Testing Commission and Slippage...\n'); + + // Create service container + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args), + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args), + debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args), + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Test with high commission and slippage + const config = { + mode: 'backtest', + name: 'Commission Test', + strategy: 'sma-crossover', + symbols: ['AAPL'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-31T00:00:00Z', + initialCapital: 10000, + commission: 0.01, // 1% commission (very high for testing) + slippage: 0.005, // 0.5% slippage (very high for testing) + dataFrequency: '1d', + speed: 'max' + }; + + // Initialize backtest mode + await modeManager.initializeMode(config); + + // Create backtest engine + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + console.log('Running backtest with:'); + console.log(` Initial Capital: $${config.initialCapital}`); + console.log(` Commission: ${(config.commission * 100).toFixed(1)}%`); + console.log(` Slippage: ${(config.slippage * 100).toFixed(1)}%`); + + try { + const result = await backtestEngine.runBacktest(config); + + console.log('\n=== RESULTS ==='); + console.log(`Initial Capital: $${config.initialCapital}`); + console.log(`Final Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`); + console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`); + console.log(`Total Trades: ${result.metrics.totalTrades}`); + + // Check trades for commission + if (result.trades.length > 0) { + console.log('\nFirst 3 trades with commission:'); + result.trades.slice(0, 3).forEach((t, idx) => { + const tradeValue = t.quantity * t.entryPrice; + const expectedCommission = tradeValue * config.commission; + const totalCost = tradeValue + expectedCommission; + + console.log(`\n${idx + 1}. ${t.symbol} ${t.side} ${t.quantity} shares`); + console.log(` Entry Price: $${t.entryPrice.toFixed(2)}`); + console.log(` Exit Price: $${t.exitPrice.toFixed(2)}`); + console.log(` Trade Value: $${tradeValue.toFixed(2)}`); + console.log(` Commission: $${t.commission.toFixed(2)} (expected: $${expectedCommission.toFixed(2)})`); + console.log(` P&L: $${t.pnl.toFixed(2)}`); + }); + } + + // Compare with zero commission/slippage + const zeroConfig = { + ...config, + commission: 0, + slippage: 0 + }; + + await modeManager.initializeMode(zeroConfig); + const zeroResult = await backtestEngine.runBacktest(zeroConfig); + + console.log('\n=== COMPARISON ==='); + console.log('With commission/slippage:'); + console.log(` Final Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`); + console.log(` Total Return: ${result.metrics.totalReturn.toFixed(2)}%`); + + console.log('\nWithout commission/slippage:'); + console.log(` Final Value: $${zeroResult.equity[zeroResult.equity.length - 1].value.toFixed(2)}`); + console.log(` Total Return: ${zeroResult.metrics.totalReturn.toFixed(2)}%`); + + const difference = zeroResult.equity[zeroResult.equity.length - 1].value - result.equity[result.equity.length - 1].value; + console.log(`\nCost of commission/slippage: $${difference.toFixed(2)}`); + + } catch (error) { + console.error('Backtest failed:', error); + } + + console.log('\n=== TEST COMPLETE ==='); + process.exit(0); +} + +testCommissionSlippage().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-debug-returns.ts b/apps/stock/orchestrator/test-debug-returns.ts new file mode 100644 index 0000000..0f73c15 --- /dev/null +++ b/apps/stock/orchestrator/test-debug-returns.ts @@ -0,0 +1,41 @@ +import { PerformanceAnalyzer } from './src/analytics/PerformanceAnalyzer'; + +// Test with simple data +const analyzer = new PerformanceAnalyzer(100000); + +// Add some equity points over 30 days +const startDate = new Date('2023-01-01'); +const values = [ + 100000, 101000, 100500, 102000, 101500, + 103000, 102500, 104000, 103500, 105000, + 104500, 106000, 105500, 107000, 106500, + 108000, 107500, 109000, 108500, 110000 +]; + +for (let i = 0; i < values.length; i++) { + const date = new Date(startDate); + date.setDate(date.getDate() + i); + analyzer.addEquityPoint(date, values[i]); + console.log(`Day ${i + 1}: ${date.toISOString().split('T')[0]} => $${values[i]}`); +} + +console.log('\n=== Analyzing Performance ==='); +const metrics = analyzer.analyze(); + +console.log(`\nTotal Return: ${metrics.totalReturn.toFixed(2)}%`); +console.log(`Annualized Return: ${metrics.annualizedReturn.toFixed(2)}%`); +console.log(`Volatility: ${metrics.volatility.toFixed(2)}%`); +console.log(`Sharpe Ratio: ${metrics.sharpeRatio.toFixed(3)}`); + +// Debug calculations +const finalValue = 110000; +const initialValue = 100000; +const totalReturn = ((finalValue - initialValue) / initialValue) * 100; +const days = 19; +const years = days / 365; +const annualizedReturn = (Math.pow(1 + totalReturn/100, 1/years) - 1) * 100; + +console.log('\n=== Manual Calculations ==='); +console.log(`Total Return: ${totalReturn.toFixed(2)}%`); +console.log(`Years: ${years.toFixed(4)}`); +console.log(`Annualized Return: ${annualizedReturn.toFixed(2)}%`); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-equity-curve-debug.ts b/apps/stock/orchestrator/test-equity-curve-debug.ts new file mode 100644 index 0000000..7b236a8 --- /dev/null +++ b/apps/stock/orchestrator/test-equity-curve-debug.ts @@ -0,0 +1,73 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function debugEquityCurve() { + // Create service container + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args), + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args), + debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args), + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Test with 5000 initial capital + const config = { + mode: 'backtest', + name: 'Debug', + strategy: 'sma-crossover', + symbols: ['AAPL'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-05T00:00:00Z', // Just 5 days + initialCapital: 5000, + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d', + speed: 'max' + }; + + await modeManager.initializeMode(config); + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + console.log('Before runBacktest - checking backtestEngine state...'); + + const result = await backtestEngine.runBacktest(config); + + console.log('\n=== EQUITY CURVE DEBUG ==='); + console.log(`Config Initial Capital: $${config.initialCapital}`); + console.log(`Number of equity points: ${result.equity.length}`); + + // Show all equity points + result.equity.forEach((point, idx) => { + console.log(` ${idx}: ${point.date} -> $${point.value.toFixed(2)}`); + }); + + process.exit(0); +} + +debugEquityCurve().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-full-backtest.ts b/apps/stock/orchestrator/test-full-backtest.ts new file mode 100644 index 0000000..a233a11 --- /dev/null +++ b/apps/stock/orchestrator/test-full-backtest.ts @@ -0,0 +1,130 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function testFullBacktest() { + console.log('Testing Full Backtest with Real Data...\n'); + + // Create service container + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args), + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args), + debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args), + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Test configuration matching what frontend might send + const config = { + mode: 'backtest', + name: 'Full Test', + strategy: 'sma-crossover', + symbols: ['AAPL', 'MSFT', 'GOOGL'], // Multiple symbols + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-12-31T00:00:00Z', // Full year + initialCapital: 100000, + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d', + speed: 'max' + }; + + // Initialize backtest mode + await modeManager.initializeMode(config); + + // Create backtest engine + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + console.log('Running full year backtest with multiple symbols...'); + const startTime = Date.now(); + + try { + const result = await backtestEngine.runBacktest(config); + + const duration = (Date.now() - startTime) / 1000; + console.log(`\nBacktest completed in ${duration.toFixed(2)} seconds`); + + // Check for data anomalies + console.log('\n=== RESULT ANALYSIS ==='); + console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`); + console.log(`Sharpe Ratio: ${result.metrics.sharpeRatio.toFixed(3)}`); + console.log(`Max Drawdown: ${(result.metrics.maxDrawdown * 100).toFixed(2)}%`); + console.log(`Win Rate: ${result.metrics.winRate.toFixed(1)}%`); + console.log(`Total Trades: ${result.metrics.totalTrades}`); + console.log(`Trades Array Length: ${result.trades.length}`); + + // Check equity curve + console.log('\n=== EQUITY CURVE CHECK ==='); + console.log(`Equity Points: ${result.equity.length}`); + if (result.equity.length > 0) { + const first = result.equity[0]; + const last = result.equity[result.equity.length - 1]; + const max = Math.max(...result.equity.map(e => e.value)); + const min = Math.min(...result.equity.map(e => e.value)); + + console.log(`First: $${first.value.toFixed(2)}`); + console.log(`Last: $${last.value.toFixed(2)}`); + console.log(`Max: $${max.toFixed(2)}`); + console.log(`Min: $${min.toFixed(2)}`); + + // Check for exponential growth + const growthRatio = last.value / first.value; + if (growthRatio > 10) { + console.log('\n⚠️ WARNING: Detected possible exponential growth!'); + console.log(`Growth ratio: ${growthRatio.toFixed(2)}x`); + } + } + + // Sample some trades + console.log('\n=== TRADE SAMPLES ==='); + if (result.trades.length > 0) { + console.log('First 3 trades:'); + result.trades.slice(0, 3).forEach((t, i) => { + console.log(` ${i+1}. ${t.symbol} ${t.side} ${t.quantity} @ ${t.exitPrice.toFixed(2)} | P&L: $${t.pnl.toFixed(2)} (${t.pnlPercent.toFixed(2)}%)`); + }); + + if (result.trades.length > 3) { + console.log('\nLast 3 trades:'); + result.trades.slice(-3).forEach((t, i) => { + console.log(` ${i+1}. ${t.symbol} ${t.side} ${t.quantity} @ ${t.exitPrice.toFixed(2)} | P&L: $${t.pnl.toFixed(2)} (${t.pnlPercent.toFixed(2)}%)`); + }); + } + } + + // Check raw metrics object + console.log('\n=== RAW METRICS ==='); + console.log(JSON.stringify(result.metrics, null, 2)); + + } catch (error) { + console.error('Backtest failed:', error); + } + + console.log('\n=== TEST COMPLETE ==='); + process.exit(0); +} + +testFullBacktest().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-initial-capital-debug.ts b/apps/stock/orchestrator/test-initial-capital-debug.ts new file mode 100644 index 0000000..62e5f85 --- /dev/null +++ b/apps/stock/orchestrator/test-initial-capital-debug.ts @@ -0,0 +1,122 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function testInitialCapitalDebug() { + console.log('Debugging Initial Capital Issue...\n'); + + // Create service container + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args), + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args), + debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args), + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Test with LOW initial capital + const config = { + mode: 'backtest', + name: 'Initial Capital Debug', + strategy: 'sma-crossover', + symbols: ['AAPL'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-31T00:00:00Z', // Just one month + initialCapital: 1000, // LOW CAPITAL + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d', + speed: 'max' + }; + + // Initialize backtest mode + await modeManager.initializeMode(config); + + // Create backtest engine + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + console.log('Running backtest with initial capital: $' + config.initialCapital); + + try { + const result = await backtestEngine.runBacktest(config); + + console.log('\n=== DEBUG INFO ==='); + console.log(`Initial Capital: $${config.initialCapital}`); + + // Check equity curve + console.log('\nEquity Curve:'); + result.equity.forEach((point, idx) => { + if (idx < 3 || idx >= result.equity.length - 3) { + console.log(` ${new Date(point.timestamp).toISOString()}: $${point.value.toFixed(2)}`); + } + if (idx === 3 && result.equity.length > 6) { + console.log(' ...'); + } + }); + + // Get trading engine to check P&L + const tradingEngine = strategyManager.getTradingEngine(); + if (tradingEngine) { + const [realized, unrealized] = tradingEngine.getTotalPnl(); + console.log(`\nRust Core P&L:`); + console.log(` Realized: $${realized.toFixed(2)}`); + console.log(` Unrealized: $${unrealized.toFixed(2)}`); + console.log(` Total P&L: $${(realized + unrealized).toFixed(2)}`); + console.log(` Expected Final Value: $${(config.initialCapital + realized + unrealized).toFixed(2)}`); + } + + // Check trades + console.log('\nTrades:'); + result.trades.forEach((t, idx) => { + console.log(` ${idx + 1}. ${t.symbol} ${t.side} ${t.quantity} @ $${t.entryPrice} -> $${t.exitPrice}`); + console.log(` P&L: $${t.pnl.toFixed(2)} (${t.pnlPercent.toFixed(2)}%)`); + }); + + // The issue calculation + const finalValue = result.equity[result.equity.length - 1].value; + console.log('\n=== ISSUE ANALYSIS ==='); + console.log(`Final portfolio value: $${finalValue.toFixed(2)}`); + console.log(`Initial capital: $${config.initialCapital}`); + console.log(`Difference: $${(finalValue - config.initialCapital).toFixed(2)}`); + console.log(`Return %: ${((finalValue - config.initialCapital) / config.initialCapital * 100).toFixed(2)}%`); + + // Check if it's using 100k + const assumedInitial = 100000; + const assumedReturn = (finalValue - assumedInitial) / assumedInitial * 100; + console.log(`\nIf initial was $100k:`); + console.log(` Return would be: ${assumedReturn.toFixed(2)}%`); + console.log(` P&L would be: $${(finalValue - assumedInitial).toFixed(2)}`); + + } catch (error) { + console.error('Backtest failed:', error); + } + + console.log('\n=== TEST COMPLETE ==='); + process.exit(0); +} + +testInitialCapitalDebug().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-initial-capital-simple.ts b/apps/stock/orchestrator/test-initial-capital-simple.ts new file mode 100644 index 0000000..f9a58c3 --- /dev/null +++ b/apps/stock/orchestrator/test-initial-capital-simple.ts @@ -0,0 +1,87 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function testSimple() { + // Create service container + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => {}, + error: (msg: string, ...args: any[]) => {}, + warn: (msg: string, ...args: any[]) => {}, + debug: (msg: string, ...args: any[]) => {}, + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Test with 1000 initial capital + const config = { + mode: 'backtest', + name: 'Test', + strategy: 'sma-crossover', + symbols: ['AAPL'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-31T00:00:00Z', + initialCapital: 1000, + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d', + speed: 'max' + }; + + await modeManager.initializeMode(config); + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + const result = await backtestEngine.runBacktest(config); + + // Get P&L from Rust + const tradingEngine = strategyManager.getTradingEngine(); + const [realized, unrealized] = tradingEngine.getTotalPnl(); + + console.log('\n=== RESULTS ==='); + console.log(`Config Initial Capital: $${config.initialCapital}`); + console.log(`First Equity Value: $${result.equity[0].value.toFixed(2)}`); + console.log(`Final Equity Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`); + console.log(`\nRust Core:`); + console.log(` Realized P&L: $${realized.toFixed(2)}`); + console.log(` Unrealized P&L: $${unrealized.toFixed(2)}`); + console.log(` Total P&L: $${(realized + unrealized).toFixed(2)}`); + console.log(`\nCalculated Final Value:`); + console.log(` $${config.initialCapital} + $${(realized + unrealized).toFixed(2)} = $${(config.initialCapital + realized + unrealized).toFixed(2)}`); + console.log(`\nActual vs Expected:`); + const actualFinal = result.equity[result.equity.length - 1].value; + const expectedFinal = config.initialCapital + realized + unrealized; + console.log(` Actual: $${actualFinal.toFixed(2)}`); + console.log(` Expected: $${expectedFinal.toFixed(2)}`); + console.log(` Difference: $${(actualFinal - expectedFinal).toFixed(2)}`); + + // Check if it's using 100k base + if (Math.abs(actualFinal - (100000 + realized + unrealized)) < 1) { + console.log('\n⚠️ BUG CONFIRMED: Using 100000 as base instead of ' + config.initialCapital); + } + + process.exit(0); +} + +testSimple().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-initial-capital.ts b/apps/stock/orchestrator/test-initial-capital.ts new file mode 100644 index 0000000..f620529 --- /dev/null +++ b/apps/stock/orchestrator/test-initial-capital.ts @@ -0,0 +1,104 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function testInitialCapital() { + console.log('Testing Initial Capital Issue...\n'); + + // Create service container + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args), + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args), + debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args), + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Test with LOW initial capital + const config = { + mode: 'backtest', + name: 'Initial Capital Test', + strategy: 'sma-crossover', + symbols: ['AAPL'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-31T00:00:00Z', // Just one month + initialCapital: 1000, // LOW CAPITAL + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d', + speed: 'max' + }; + + // Initialize backtest mode + await modeManager.initializeMode(config); + + // Create backtest engine + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + console.log('Running backtest with initial capital: $' + config.initialCapital); + + try { + const result = await backtestEngine.runBacktest(config); + + console.log('\n=== RESULTS ==='); + console.log(`Initial Capital: $${config.initialCapital}`); + console.log(`Final Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`); + console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`); + console.log(`Total Trades: ${result.metrics.totalTrades}`); + + // Check if portfolio grew from 1000 + const finalValue = result.equity[result.equity.length - 1].value; + const expectedRange = config.initialCapital * 0.5; // Within 50% of initial + + if (Math.abs(finalValue - config.initialCapital) > expectedRange && + Math.abs(finalValue - 100000) < expectedRange) { + console.log('\n⚠️ WARNING: Portfolio appears to be using 100000 as initial capital!'); + } else { + console.log('\n✅ Portfolio appears to be using correct initial capital'); + } + + // Check first few trades + if (result.trades.length > 0) { + console.log('\nFirst trade:'); + const t = result.trades[0]; + console.log(` ${t.symbol} ${t.side} ${t.quantity} shares @ $${t.entryPrice}`); + console.log(` Trade value: $${(t.quantity * t.entryPrice).toFixed(2)}`); + + if (t.quantity * t.entryPrice > config.initialCapital) { + console.log(' ⚠️ WARNING: Trade size exceeds initial capital!'); + } + } + + } catch (error) { + console.error('Backtest failed:', error); + } + + console.log('\n=== TEST COMPLETE ==='); + process.exit(0); +} + +testInitialCapital().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-performance-metrics.ts b/apps/stock/orchestrator/test-performance-metrics.ts new file mode 100644 index 0000000..696a08d --- /dev/null +++ b/apps/stock/orchestrator/test-performance-metrics.ts @@ -0,0 +1,134 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function testPerformanceMetrics() { + console.log('Testing Performance Metrics Calculation...\n'); + + // Create minimal service container + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args), + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args), + debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args), + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Initialize backtest mode + await modeManager.initializeMode({ + mode: 'backtest', + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-03-31T00:00:00Z', // 3 months for more trades + speed: 'max', + symbols: ['AAPL'], + initialCapital: 100000, + dataFrequency: '1d', + strategy: 'sma-crossover' + }); + + // Create backtest engine + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + // Run backtest + const config = { + mode: 'backtest', + name: 'Performance Metrics Test', + strategy: 'sma-crossover', + symbols: ['AAPL'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-03-31T00:00:00Z', + initialCapital: 100000, + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d', + speed: 'max' + }; + + console.log('Running backtest...'); + const result = await backtestEngine.runBacktest(config); + + // Analyze metrics + console.log('\n=== PERFORMANCE METRICS ANALYSIS ==='); + console.log('\n📊 Returns:'); + console.log(` Total Return: ${result.metrics.totalReturn.toFixed(2)}%`); + console.log(` Sharpe Ratio: ${result.metrics.sharpeRatio.toFixed(3)}`); + console.log(` Max Drawdown: ${(result.metrics.maxDrawdown * 100).toFixed(2)}%`); + + console.log('\n📈 Trading Statistics:'); + console.log(` Total Trades: ${result.metrics.totalTrades}`); + console.log(` Win Rate: ${result.metrics.winRate.toFixed(2)}%`); + console.log(` Profit Factor: ${result.metrics.profitFactor.toFixed(2)}`); + console.log(` Average Win: $${result.metrics.avgWin.toFixed(2)}`); + console.log(` Average Loss: $${result.metrics.avgLoss.toFixed(2)}`); + + console.log('\n🎯 Trade Details:'); + console.log(` Closed Trades: ${result.trades.length}`); + if (result.trades.length > 0) { + const wins = result.trades.filter(t => t.pnl > 0); + const losses = result.trades.filter(t => t.pnl < 0); + console.log(` Winning Trades: ${wins.length}`); + console.log(` Losing Trades: ${losses.length}`); + console.log(` Calculated Win Rate: ${(wins.length / result.trades.length * 100).toFixed(2)}%`); + + // Show first few trades + console.log('\n First 3 trades:'); + result.trades.slice(0, 3).forEach((trade, i) => { + console.log(` ${i + 1}. ${trade.side} ${trade.quantity} @ ${trade.exitPrice.toFixed(2)} | P&L: $${trade.pnl.toFixed(2)} (${trade.pnlPercent.toFixed(2)}%)`); + }); + } + + console.log('\n💰 Equity Curve:'); + console.log(` Starting Capital: $${config.initialCapital.toLocaleString()}`); + console.log(` Final Value: $${result.equity[result.equity.length - 1].value.toFixed(2)}`); + console.log(` Equity Points: ${result.equity.length}`); + + // Show first and last few equity points + console.log('\n First 3 equity points:'); + result.equity.slice(0, 3).forEach((point, i) => { + console.log(` ${i + 1}. ${point.date} => $${point.value.toFixed(2)}`); + }); + + console.log('\n Last 3 equity points:'); + result.equity.slice(-3).forEach((point, i) => { + console.log(` ${i + 1}. ${point.date} => $${point.value.toFixed(2)}`); + }); + + // Diagnostic checks + console.log('\n🔍 Diagnostics:'); + const sharpeOK = result.metrics.sharpeRatio !== 0; + const drawdownOK = result.metrics.maxDrawdown > 0 && result.metrics.maxDrawdown < 0.99; + const winRateOK = result.metrics.winRate > 0 && result.metrics.winRate < 100; + + console.log(` ✅ Sharpe Ratio calculated: ${sharpeOK ? 'YES' : 'NO'}`); + console.log(` ✅ Max Drawdown reasonable: ${drawdownOK ? 'YES' : 'NO (suspicious value)'}`); + console.log(` ✅ Win Rate reasonable: ${winRateOK ? 'YES' : 'NO (check trade P&L)'}`); + + console.log('\n=== TEST COMPLETE ==='); + process.exit(0); +} + +testPerformanceMetrics().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/orchestrator/test-portfolio-tracking.ts b/apps/stock/orchestrator/test-portfolio-tracking.ts new file mode 100644 index 0000000..6777a50 --- /dev/null +++ b/apps/stock/orchestrator/test-portfolio-tracking.ts @@ -0,0 +1,109 @@ +import { BacktestEngine } from './src/backtest/BacktestEngine'; +import { StrategyManager } from './src/strategies/StrategyManager'; +import { StorageService } from './src/services/StorageService'; +import { ModeManager } from './src/core/ModeManager'; +import { MarketDataService } from './src/services/MarketDataService'; +import { ExecutionService } from './src/services/ExecutionService'; +import { IServiceContainer } from '@stock-bot/di'; + +async function testPortfolioTracking() { + console.log('Testing Portfolio Value Tracking...\n'); + + // Create minimal service container + const container: IServiceContainer = { + logger: { + info: (msg: string, ...args: any[]) => { + // Filter for portfolio value messages + if (msg.includes('portfolio') || msg.includes('Portfolio') || msg.includes('equity')) { + console.log('[INFO]', msg, ...args); + } + }, + error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args), + debug: (msg: string, ...args: any[]) => {}, + } as any, + custom: {} + }; + + // Initialize services + const storageService = new StorageService(); + const marketDataService = new MarketDataService(container); + const executionService = new ExecutionService(container); + const modeManager = new ModeManager(container, marketDataService, executionService, storageService); + const strategyManager = new StrategyManager(container); + + // Set services in container + container.custom = { + MarketDataService: marketDataService, + ExecutionService: executionService, + ModeManager: modeManager, + StorageService: storageService + }; + + // Initialize backtest mode + await modeManager.initializeMode({ + mode: 'backtest', + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-10T00:00:00Z', // Just 10 days + speed: 'max', + symbols: ['TEST'], + initialCapital: 100000, + dataFrequency: '1d', + strategy: 'sma-crossover' + }); + + // Create backtest engine + const backtestEngine = new BacktestEngine(container, storageService, strategyManager); + + // Run backtest + const config = { + mode: 'backtest', + name: 'Portfolio Tracking Test', + strategy: 'sma-crossover', + symbols: ['TEST'], + startDate: '2023-01-01T00:00:00Z', + endDate: '2023-01-10T00:00:00Z', + initialCapital: 100000, + commission: 0.001, + slippage: 0.0001, + dataFrequency: '1d', + speed: 'max' + }; + + console.log('Running backtest...'); + const result = await backtestEngine.runBacktest(config); + + // Analyze equity curve + console.log('\n=== EQUITY CURVE ANALYSIS ==='); + console.log(`Initial Capital: $${config.initialCapital.toLocaleString()}`); + console.log(`Equity Points: ${result.equity.length}`); + + // Show all equity points + console.log('\nAll equity points:'); + result.equity.forEach((point, i) => { + console.log(` ${i + 1}. ${point.date} => $${point.value.toFixed(2)}`); + }); + + // Check for anomalies + const firstValue = result.equity[0].value; + const lastValue = result.equity[result.equity.length - 1].value; + const totalReturn = ((lastValue - firstValue) / firstValue) * 100; + + console.log(`\nFirst Value: $${firstValue.toFixed(2)}`); + console.log(`Last Value: $${lastValue.toFixed(2)}`); + console.log(`Calculated Total Return: ${totalReturn.toFixed(2)}%`); + console.log(`Reported Total Return: ${result.metrics.totalReturn.toFixed(2)}%`); + + // Check if values match + if (Math.abs(totalReturn - result.metrics.totalReturn) > 0.01) { + console.log('\n❌ WARNING: Calculated return does not match reported return!'); + } + + console.log('\n=== TEST COMPLETE ==='); + process.exit(0); +} + +testPortfolioTracking().catch(error => { + console.error('Test failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/components/BacktestResults.tsx b/apps/stock/web-app/src/features/backtest/components/BacktestResults.tsx index 67f8764..858b270 100644 --- a/apps/stock/web-app/src/features/backtest/components/BacktestResults.tsx +++ b/apps/stock/web-app/src/features/backtest/components/BacktestResults.tsx @@ -91,7 +91,7 @@ export function BacktestResults({ status, results, currentTime }: BacktestResult />