db = $db; } public function getFullAnalytics(int $accountId, ?string $from = null, ?string $to = null, ?string $symbol = null, ?string $strategy = null): array { // 1. Fetch Account Details $stmtAcc = $this->db->prepare("SELECT * FROM accounts WHERE id = ?"); $stmtAcc->execute([$accountId]); $account = $stmtAcc->fetch(); if (!$account) { return ['error' => 'Account not found']; } $initialBalance = (float)$account['initial_balance']; // 2. Fetch Trades Query Filtered $sql = "SELECT * FROM trades WHERE account_id = ?"; $params = [$accountId]; if ($from) { $sql .= " AND open_time >= ?"; $params[] = $from . ' 00:00:00'; } if ($to) { $sql .= " AND open_time <= ?"; $params[] = $to . ' 23:59:59'; } if ($symbol && $symbol !== 'ALL') { $sql .= " AND symbol = ?"; $params[] = $symbol; } if ($strategy && $strategy !== 'ALL') { $sql .= " AND strategy = ?"; $params[] = $strategy; } $sql .= " ORDER BY open_time ASC, id ASC"; $stmtTrades = $this->db->prepare($sql); $stmtTrades->execute($params); $trades = $stmtTrades->fetchAll(); // 3. Process Metrics & Aggregations $totalTrades = count($trades); $closedTrades = []; $openTrades = []; $grossProfit = 0.0; $grossLoss = 0.0; $totalCommission = 0.0; $totalSwap = 0.0; $totalPips = 0.0; $wonTradesCount = 0; $lostTradesCount = 0; $beTradesCount = 0; $longTradesCount = 0; $longWonCount = 0; $longProfit = 0.0; $shortTradesCount = 0; $shortWonCount = 0; $shortProfit = 0.0; $bestTradeProfit = 0.0; $worstTradeProfit = 0.0; $bestTradePips = 0.0; $worstTradePips = 0.0; $winPipsSum = 0.0; $lossPipsSum = 0.0; $currentBalance = $initialBalance; $peakBalance = $initialBalance; $maxDrawdownAmount = 0.0; $maxDrawdownPercent = 0.0; $growthCurve = []; $drawdownCurve = []; $consecutiveWins = 0; $consecutiveLosses = 0; $maxConsecutiveWins = 0; $maxConsecutiveLosses = 0; $totalDurationSec = 0; $winDurationSec = 0; $lossDurationSec = 0; $dailyReturns = []; // date => pnl $symbolStats = []; $strategyStats = []; $emotionStats = []; $sessionStats = ['London' => ['count' => 0, 'profit' => 0, 'wins' => 0], 'New York' => ['count' => 0, 'profit' => 0, 'wins' => 0], 'Asian' => ['count' => 0, 'profit' => 0, 'wins' => 0], 'Overlap' => ['count' => 0, 'profit' => 0, 'wins' => 0]]; $dayOfWeekStats = [ 1 => ['name' => 'Monday', 'fa' => 'دوشنبه', 'count' => 0, 'profit' => 0, 'wins' => 0], 2 => ['name' => 'Tuesday', 'fa' => 'سه‌شنبه', 'count' => 0, 'profit' => 0, 'wins' => 0], 3 => ['name' => 'Wednesday', 'fa' => 'چهارشنبه', 'count' => 0, 'profit' => 0, 'wins' => 0], 4 => ['name' => 'Thursday', 'fa' => 'پنج‌شنبه', 'count' => 0, 'profit' => 0, 'wins' => 0], 5 => ['name' => 'Friday', 'fa' => 'جمعه', 'count' => 0, 'profit' => 0, 'wins' => 0], 6 => ['name' => 'Saturday', 'fa' => 'شنبه', 'count' => 0, 'profit' => 0, 'wins' => 0], 7 => ['name' => 'Sunday', 'fa' => 'یک‌شنبه', 'count' => 0, 'profit' => 0, 'wins' => 0], ]; $hourlyStats = []; for ($h = 0; $h < 24; $h++) { $hourlyStats[$h] = ['hour' => $h, 'count' => 0, 'profit' => 0, 'wins' => 0]; } // Add starting point to growth curve $growthCurve[] = [ 'date' => date('Y-m-d H:i', strtotime($account['created_at'] ?? 'now')), 'trade_index' => 0, 'balance' => round($initialBalance, 2), 'equity' => round($initialBalance, 2), 'profit' => 0.0, 'gain_percent' => 0.0, 'symbol' => 'START', 'ticket' => '-' ]; $tradeIndex = 0; foreach ($trades as $t) { $profit = (float)$t['profit']; $pips = (float)$t['pips']; $comm = (float)$t['commission']; $swap = (float)$t['swap']; $sym = $t['symbol']; $strat = $t['strategy'] ?: 'Price Action'; $emo = $t['emotion'] ?: 'Disciplined'; $sess = $t['session'] ?: 'London'; if ($t['status'] === 'open') { $openTrades[] = $t; continue; } $closedTrades[] = $t; $tradeIndex++; $totalCommission += $comm; $totalSwap += $swap; $totalPips += $pips; // Balance & Equity Tracking $netTradeProfit = $profit + $comm + $swap; $currentBalance += $netTradeProfit; if ($currentBalance > $peakBalance) { $peakBalance = $currentBalance; } $currentDDAmount = $peakBalance - $currentBalance; $currentDDPercent = $peakBalance > 0 ? ($currentDDAmount / $peakBalance) * 100 : 0; if ($currentDDAmount > $maxDrawdownAmount) { $maxDrawdownAmount = $currentDDAmount; } if ($currentDDPercent > $maxDrawdownPercent) { $maxDrawdownPercent = $currentDDPercent; } $gainPercent = $initialBalance > 0 ? (($currentBalance - $initialBalance) / $initialBalance) * 100 : 0; $closeDate = $t['close_time'] ?: $t['open_time']; $growthCurve[] = [ 'date' => date('Y-m-d H:i', strtotime($closeDate)), 'trade_index' => $tradeIndex, 'balance' => round($currentBalance, 2), 'equity' => round($currentBalance, 2), 'profit' => round($netTradeProfit, 2), 'gain_percent' => round($gainPercent, 2), 'symbol' => $sym, 'ticket' => $t['ticket'] ?: "#$tradeIndex" ]; $drawdownCurve[] = [ 'date' => date('Y-m-d H:i', strtotime($closeDate)), 'trade_index' => $tradeIndex, 'drawdown_percent' => round(-$currentDDPercent, 2), 'drawdown_amount' => round($currentDDAmount, 2) ]; // Win / Loss classification if ($profit > 0) { $wonTradesCount++; $grossProfit += $profit; $winPipsSum += $pips; $consecutiveWins++; $consecutiveLosses = 0; if ($consecutiveWins > $maxConsecutiveWins) { $maxConsecutiveWins = $consecutiveWins; } if ($profit > $bestTradeProfit) { $bestTradeProfit = $profit; } if ($pips > $bestTradePips) { $bestTradePips = $pips; } } elseif ($profit < 0) { $lostTradesCount++; $grossLoss += abs($profit); $lossPipsSum += abs($pips); $consecutiveLosses++; $consecutiveWins = 0; if ($consecutiveLosses > $maxConsecutiveLosses) { $maxConsecutiveLosses = $consecutiveLosses; } if ($profit < $worstTradeProfit) { $worstTradeProfit = $profit; } if ($pips < $worstTradePips) { $worstTradePips = $pips; } } else { $beTradesCount++; } // Long / Short stats if (strtolower($t['trade_type']) === 'buy') { $longTradesCount++; $longProfit += $netTradeProfit; if ($profit > 0) $longWonCount++; } else { $shortTradesCount++; $shortProfit += $netTradeProfit; if ($profit > 0) $shortWonCount++; } // Duration calculation if ($t['open_time'] && $t['close_time']) { $dur = max(0, strtotime($t['close_time']) - strtotime($t['open_time'])); $totalDurationSec += $dur; if ($profit > 0) $winDurationSec += $dur; elseif ($profit < 0) $lossDurationSec += $dur; } // Daily return aggregation $dayKey = date('Y-m-d', strtotime($t['open_time'])); if (!isset($dailyReturns[$dayKey])) { $dailyReturns[$dayKey] = ['profit' => 0.0, 'pips' => 0.0, 'trades' => 0, 'wins' => 0, 'losses' => 0]; } $dailyReturns[$dayKey]['profit'] += $netTradeProfit; $dailyReturns[$dayKey]['pips'] += $pips; $dailyReturns[$dayKey]['trades']++; if ($profit > 0) $dailyReturns[$dayKey]['wins']++; elseif ($profit < 0) $dailyReturns[$dayKey]['losses']++; // By Symbol Breakdown if (!isset($symbolStats[$sym])) { $symbolStats[$sym] = ['symbol' => $sym, 'count' => 0, 'wins' => 0, 'losses' => 0, 'profit' => 0.0, 'pips' => 0.0, 'longs' => 0, 'shorts' => 0]; } $symbolStats[$sym]['count']++; $symbolStats[$sym]['profit'] += $netTradeProfit; $symbolStats[$sym]['pips'] += $pips; if ($profit > 0) $symbolStats[$sym]['wins']++; elseif ($profit < 0) $symbolStats[$sym]['losses']++; if (strtolower($t['trade_type']) === 'buy') $symbolStats[$sym]['longs']++; else $symbolStats[$sym]['shorts']++; // By Strategy Breakdown if (!isset($strategyStats[$strat])) { $strategyStats[$strat] = ['strategy' => $strat, 'count' => 0, 'wins' => 0, 'losses' => 0, 'profit' => 0.0, 'gross_profit' => 0.0, 'gross_loss' => 0.0]; } $strategyStats[$strat]['count']++; $strategyStats[$strat]['profit'] += $netTradeProfit; if ($profit > 0) { $strategyStats[$strat]['wins']++; $strategyStats[$strat]['gross_profit'] += $profit; } elseif ($profit < 0) { $strategyStats[$strat]['losses']++; $strategyStats[$strat]['gross_loss'] += abs($profit); } // By Emotion Breakdown if (!isset($emotionStats[$emo])) { $emotionStats[$emo] = ['emotion' => $emo, 'count' => 0, 'wins' => 0, 'profit' => 0.0, 'pips' => 0.0]; } $emotionStats[$emo]['count']++; $emotionStats[$emo]['profit'] += $netTradeProfit; $emotionStats[$emo]['pips'] += $pips; if ($profit > 0) $emotionStats[$emo]['wins']++; // By Session Breakdown if (isset($sessionStats[$sess])) { $sessionStats[$sess]['count']++; $sessionStats[$sess]['profit'] += $netTradeProfit; if ($profit > 0) $sessionStats[$sess]['wins']++; } // By Day of Week & Hour $openTs = strtotime($t['open_time']); $dow = (int)date('N', $openTs); // 1 = Monday .. 7 = Sunday if (isset($dayOfWeekStats[$dow])) { $dayOfWeekStats[$dow]['count']++; $dayOfWeekStats[$dow]['profit'] += $netTradeProfit; if ($profit > 0) $dayOfWeekStats[$dow]['wins']++; } $hr = (int)date('G', $openTs); // 0 .. 23 if (isset($hourlyStats[$hr])) { $hourlyStats[$hr]['count']++; $hourlyStats[$hr]['profit'] += $netTradeProfit; if ($profit > 0) $hourlyStats[$hr]['wins']++; } } // Open floating calculation $openFloatingProfit = 0.0; foreach ($openTrades as $ot) { $openFloatingProfit += (float)$ot['profit'] + (float)$ot['commission'] + (float)$ot['swap']; } $currentEquity = $currentBalance + $openFloatingProfit; // Total Net Closed Profit $totalClosedProfit = $currentBalance - $initialBalance; $totalGainPercent = $initialBalance > 0 ? (($currentEquity - $initialBalance) / $initialBalance) * 100 : 0; $closedGainPercent = $initialBalance > 0 ? ($totalClosedProfit / $initialBalance) * 100 : 0; $closedCount = count($closedTrades); $winRate = $closedCount > 0 ? ($wonTradesCount / $closedCount) * 100 : 0; $lossRate = $closedCount > 0 ? ($lostTradesCount / $closedCount) * 100 : 0; $profitFactor = $grossLoss > 0 ? round($grossProfit / $grossLoss, 2) : ($grossProfit > 0 ? 99.9 : 0.0); $avgWin = $wonTradesCount > 0 ? $grossProfit / $wonTradesCount : 0.0; $avgLoss = $lostTradesCount > 0 ? $grossLoss / $lostTradesCount : 0.0; $winLossRatio = $avgLoss > 0 ? round($avgWin / $avgLoss, 2) : 0.0; $expectancy = ($winRate / 100 * $avgWin) - ($lossRate / 100 * $avgLoss); $avgPipsWin = $wonTradesCount > 0 ? $winPipsSum / $wonTradesCount : 0.0; $avgPipsLoss = $lostTradesCount > 0 ? $lossPipsSum / $lostTradesCount : 0.0; $longWinRate = $longTradesCount > 0 ? ($longWonCount / $longTradesCount) * 100 : 0; $shortWinRate = $shortTradesCount > 0 ? ($shortWonCount / $shortTradesCount) * 100 : 0; $avgTradeDuration = $closedCount > 0 ? round($totalDurationSec / $closedCount) : 0; $avgWinDuration = $wonTradesCount > 0 ? round($winDurationSec / $wonTradesCount) : 0; $avgLossDuration = $lostTradesCount > 0 ? round($lossDurationSec / $lostTradesCount) : 0; // Sharpe & Sortino Ratio estimation $dailyPnLValues = array_column($dailyReturns, 'profit'); $sharpeRatio = 0.0; $sortinoRatio = 0.0; if (count($dailyPnLValues) > 2) { $meanDaily = array_sum($dailyPnLValues) / count($dailyPnLValues); $variance = 0.0; $downsideVariance = 0.0; foreach ($dailyPnLValues as $dp) { $diff = $dp - $meanDaily; $variance += ($diff * $diff); if ($dp < 0) { $downsideVariance += ($dp * $dp); } } $stdDev = sqrt($variance / count($dailyPnLValues)); $downsideStdDev = sqrt($downsideVariance / count($dailyPnLValues)); if ($stdDev > 0) { $sharpeRatio = round(($meanDaily / $stdDev) * sqrt(252), 2); // Annualized (252 trading days) } if ($downsideStdDev > 0) { $sortinoRatio = round(($meanDaily / $downsideStdDev) * sqrt(252), 2); } } // Today, This Week, This Month profit $todayStr = date('Y-m-d'); $thisWeekStart = date('Y-m-d', strtotime('monday this week')); $thisMonthStart = date('Y-m-01'); $todayProfit = $dailyReturns[$todayStr]['profit'] ?? 0.0; $thisWeekProfit = 0.0; $thisMonthProfit = 0.0; foreach ($dailyReturns as $dDate => $dData) { if ($dDate >= $thisWeekStart) { $thisWeekProfit += $dData['profit']; } if ($dDate >= $thisMonthStart) { $thisMonthProfit += $dData['profit']; } } // Monthly Returns Table (Year x Months Heatmap) $monthlyReturns = []; foreach ($dailyReturns as $dDate => $dData) { $yr = (int)date('Y', strtotime($dDate)); $mo = (int)date('n', strtotime($dDate)); if (!isset($monthlyReturns[$yr])) { $monthlyReturns[$yr] = [ 'year' => $yr, 'months' => array_fill(1, 12, ['profit' => 0.0, 'gain_percent' => 0.0, 'trades' => 0]), 'total_profit' => 0.0, 'total_gain_percent' => 0.0, 'total_trades' => 0 ]; } $monthlyReturns[$yr]['months'][$mo]['profit'] += $dData['profit']; $monthlyReturns[$yr]['months'][$mo]['trades'] += $dData['trades']; $monthlyReturns[$yr]['total_profit'] += $dData['profit']; $monthlyReturns[$yr]['total_trades'] += $dData['trades']; } foreach ($monthlyReturns as $yr => &$yData) { foreach ($yData['months'] as $mo => &$mVal) { if ($initialBalance > 0) { $mVal['gain_percent'] = round(($mVal['profit'] / $initialBalance) * 100, 2); } } if ($initialBalance > 0) { $yData['total_gain_percent'] = round(($yData['total_profit'] / $initialBalance) * 100, 2); } } unset($yData, $mVal); // Format Breakdown Lists $symbolsList = array_values($symbolStats); usort($symbolsList, fn($a, $b) => $b['profit'] <=> $a['profit']); foreach ($symbolsList as &$sItem) { $sItem['win_rate'] = $sItem['count'] > 0 ? round(($sItem['wins'] / $sItem['count']) * 100, 1) : 0; $sItem['avg_profit'] = $sItem['count'] > 0 ? round($sItem['profit'] / $sItem['count'], 2) : 0; } $strategiesList = array_values($strategyStats); usort($strategiesList, fn($a, $b) => $b['profit'] <=> $a['profit']); foreach ($strategiesList as &$stItem) { $stItem['win_rate'] = $stItem['count'] > 0 ? round(($stItem['wins'] / $stItem['count']) * 100, 1) : 0; $stItem['profit_factor'] = $stItem['gross_loss'] > 0 ? round($stItem['gross_profit'] / $stItem['gross_loss'], 2) : ($stItem['gross_profit'] > 0 ? 99.9 : 0.0); } $emotionsList = array_values($emotionStats); usort($emotionsList, fn($a, $b) => $b['profit'] <=> $a['profit']); foreach ($emotionsList as &$emItem) { $emItem['win_rate'] = $emItem['count'] > 0 ? round(($emItem['wins'] / $emItem['count']) * 100, 1) : 0; } $sessionsList = []; foreach ($sessionStats as $sessKey => $sessData) { $sessionsList[] = [ 'session' => $sessKey, 'count' => $sessData['count'], 'profit' => round($sessData['profit'], 2), 'win_rate' => $sessData['count'] > 0 ? round(($sessData['wins'] / $sessData['count']) * 100, 1) : 0 ]; } $daysOfWeekList = []; foreach ($dayOfWeekStats as $dowKey => $dowData) { $daysOfWeekList[] = [ 'day_num' => $dowKey, 'name' => $dowData['name'], 'name_fa' => $dowData['fa'], 'count' => $dowData['count'], 'profit' => round($dowData['profit'], 2), 'win_rate' => $dowData['count'] > 0 ? round(($dowData['wins'] / $dowData['count']) * 100, 1) : 0 ]; } $hourlyList = array_values($hourlyStats); foreach ($hourlyList as &$hrItem) { $hrItem['profit'] = round($hrItem['profit'], 2); $hrItem['win_rate'] = $hrItem['count'] > 0 ? round(($hrItem['wins'] / $hrItem['count']) * 100, 1) : 0; } return [ 'account' => $account, 'summary' => [ 'initial_balance' => round($initialBalance, 2), 'current_balance' => round($currentBalance, 2), 'current_equity' => round($currentEquity, 2), 'open_floating_profit' => round($openFloatingProfit, 2), 'total_closed_profit' => round($totalClosedProfit, 2), 'total_gain_percent' => round($totalGainPercent, 2), 'closed_gain_percent' => round($closedGainPercent, 2), 'today_profit' => round($todayProfit, 2), 'this_week_profit' => round($thisWeekProfit, 2), 'this_month_profit' => round($thisMonthProfit, 2), 'total_trades' => $totalTrades, 'closed_trades' => $closedCount, 'open_trades' => count($openTrades), 'won_trades' => $wonTradesCount, 'lost_trades' => $lostTradesCount, 'breakeven_trades' => $beTradesCount, 'win_rate' => round($winRate, 1), 'loss_rate' => round($lossRate, 1), 'profit_factor' => $profitFactor, 'gross_profit' => round($grossProfit, 2), 'gross_loss' => round($grossLoss, 2), 'avg_win' => round($avgWin, 2), 'avg_loss' => round($avgLoss, 2), 'win_loss_ratio' => $winLossRatio, 'expectancy' => round($expectancy, 2), 'total_pips' => round($totalPips, 1), 'avg_pips_win' => round($avgPipsWin, 1), 'avg_pips_loss' => round($avgPipsLoss, 1), 'best_trade_profit' => round($bestTradeProfit, 2), 'worst_trade_profit' => round($worstTradeProfit, 2), 'best_trade_pips' => round($bestTradePips, 1), 'worst_trade_pips' => round($worstTradePips, 1), 'max_consecutive_wins' => $maxConsecutiveWins, 'max_consecutive_losses' => $maxConsecutiveLosses, 'max_drawdown_amount' => round($maxDrawdownAmount, 2), 'max_drawdown_percent' => round($maxDrawdownPercent, 2), 'sharpe_ratio' => $sharpeRatio, 'sortino_ratio' => $sortinoRatio, 'total_commission' => round($totalCommission, 2), 'total_swap' => round($totalSwap, 2), 'long_trades' => $longTradesCount, 'long_won' => $longWonCount, 'long_win_rate' => round($longWinRate, 1), 'long_profit' => round($longProfit, 2), 'short_trades' => $shortTradesCount, 'short_won' => $shortWonCount, 'short_win_rate' => round($shortWinRate, 1), 'short_profit' => round($shortProfit, 2), 'avg_trade_duration_seconds' => $avgTradeDuration, 'avg_win_duration_seconds' => $avgWinDuration, 'avg_loss_duration_seconds' => $avgLossDuration, ], 'growth_curve' => $growthCurve, 'drawdown_curve' => $drawdownCurve, 'monthly_returns' => array_values($monthlyReturns), 'daily_pnl' => $dailyReturns, 'by_symbol' => $symbolsList, 'by_strategy' => $strategiesList, 'by_emotion' => $emotionsList, 'by_session' => $sessionsList, 'by_day_of_week' => $daysOfWeekList, 'by_hour' => $hourlyList, 'open_trades' => $openTrades ]; } }