query(" SELECT a.*, (SELECT COUNT(*) FROM trades t WHERE t.account_id = a.id) as total_trades, (SELECT COALESCE(SUM(profit + commission + swap), 0) FROM trades t WHERE t.account_id = a.id AND t.status = 'closed') as net_closed_profit, (SELECT COALESCE(SUM(profit + commission + swap), 0) FROM trades t WHERE t.account_id = a.id AND t.status = 'open') as net_open_profit FROM accounts a ORDER BY a.is_default DESC, a.id ASC "); $accounts = $stmt->fetchAll(); sendJson(['success' => true, 'accounts' => $accounts]); } elseif ($method === 'POST') { $input = getJsonInput(); $name = trim($input['name'] ?? 'حساب معاملاتی جدید'); $broker = trim($input['broker'] ?? 'IC Markets'); $accNum = trim($input['account_number'] ?? ''); $currency = trim($input['currency'] ?? 'USD'); $initBal = (float)($input['initial_balance'] ?? 10000.0); $leverage = (float)($input['leverage'] ?? 100.0); $accType = trim($input['account_type'] ?? 'Real'); $isDefault = !empty($input['is_default']) ? 1 : 0; if ($isDefault) { $pdo->exec("UPDATE accounts SET is_default = 0"); } $stmt = $pdo->prepare(" INSERT INTO accounts (name, broker, account_number, currency, initial_balance, leverage, account_type, is_default) VALUES (?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([$name, $broker, $accNum, $currency, $initBal, $leverage, $accType, $isDefault]); $newId = (int)$pdo->lastInsertId(); // Optionally generate sample data if (!empty($input['with_sample_data'])) { Database::generateSampleTrades($newId, $initBal); } sendJson(['success' => true, 'id' => $newId, 'message' => 'حساب معاملاتی با موفقیت ایجاد شد']); } } if (preg_match('#^/api/accounts/(\d+)$#', $uri, $matches)) { $accId = (int)$matches[1]; if ($method === 'GET') { $stmt = $pdo->prepare("SELECT * FROM accounts WHERE id = ?"); $stmt->execute([$accId]); $acc = $stmt->fetch(); if (!$acc) sendJson(['success' => false, 'error' => 'حساب یافت نشد'], 404); sendJson(['success' => true, 'account' => $acc]); } elseif ($method === 'PUT') { $input = getJsonInput(); $stmt = $pdo->prepare(" UPDATE accounts SET name = COALESCE(?, name), broker = COALESCE(?, broker), account_number = COALESCE(?, account_number), currency = COALESCE(?, currency), initial_balance = COALESCE(?, initial_balance), leverage = COALESCE(?, leverage), account_type = COALESCE(?, account_type), is_default = COALESCE(?, is_default), updated_at = CURRENT_TIMESTAMP WHERE id = ? "); $stmt->execute([ $input['name'] ?? null, $input['broker'] ?? null, $input['account_number'] ?? null, $input['currency'] ?? null, isset($input['initial_balance']) ? (float)$input['initial_balance'] : null, isset($input['leverage']) ? (float)$input['leverage'] : null, $input['account_type'] ?? null, isset($input['is_default']) ? (int)$input['is_default'] : null, $accId ]); sendJson(['success' => true, 'message' => 'حساب بهروزرسانی شد']); } elseif ($method === 'DELETE') { $stmt = $pdo->prepare("DELETE FROM accounts WHERE id = ?"); $stmt->execute([$accId]); sendJson(['success' => true, 'message' => 'حساب و معاملات آن حذف شدند']); } } // ------------------------------------------------------------- // 2. ANALYTICS ENDPOINT (Myfxbook Style) // ------------------------------------------------------------- if (preg_match('#^/api/analytics/?$#', $uri)) { if ($method === 'GET') { $accountId = (int)($_GET['account_id'] ?? 0); if ($accountId <= 0) { // Get default account $stmtDef = $pdo->query("SELECT id FROM accounts ORDER BY is_default DESC, id ASC LIMIT 1"); $accountId = (int)($stmtDef->fetchColumn() ?: 1); } $from = $_GET['from'] ?? null; $to = $_GET['to'] ?? null; $symbol = $_GET['symbol'] ?? null; $strategy = $_GET['strategy'] ?? null; $data = $analyticsEngine->getFullAnalytics($accountId, $from, $to, $symbol, $strategy); sendJson(['success' => true, 'data' => $data]); } } // ------------------------------------------------------------- // 3. TRADES CRUD ENDPOINTS // ------------------------------------------------------------- if (preg_match('#^/api/trades/?$#', $uri)) { if ($method === 'GET') { $accountId = (int)($_GET['account_id'] ?? 0); if ($accountId <= 0) { $stmtDef = $pdo->query("SELECT id FROM accounts ORDER BY is_default DESC, id ASC LIMIT 1"); $accountId = (int)($stmtDef->fetchColumn() ?: 1); } $page = max(1, (int)($_GET['page'] ?? 1)); $limit = max(5, min(100, (int)($_GET['limit'] ?? 50))); $offset = ($page - 1) * $limit; $status = $_GET['status'] ?? 'all'; $symbol = $_GET['symbol'] ?? 'all'; $strategy = $_GET['strategy'] ?? 'all'; $search = trim($_GET['search'] ?? ''); $sortBy = $_GET['sort_by'] ?? 'open_time'; $sortDir = strtolower($_GET['sort_dir'] ?? 'desc') === 'asc' ? 'ASC' : 'DESC'; $allowedSorts = ['open_time', 'close_time', 'profit', 'pips', 'lot_size', 'symbol', 'id']; if (!in_array($sortBy, $allowedSorts)) $sortBy = 'open_time'; $where = ["account_id = ?"]; $params = [$accountId]; if ($status !== 'all') { $where[] = "status = ?"; $params[] = $status; } if ($symbol !== 'all' && !empty($symbol)) { $where[] = "symbol = ?"; $params[] = $symbol; } if ($strategy !== 'all' && !empty($strategy)) { $where[] = "strategy = ?"; $params[] = $strategy; } if (!empty($search)) { $where[] = "(symbol LIKE ? OR ticket LIKE ? OR entry_notes LIKE ? OR exit_notes LIKE ? OR tags LIKE ?)"; $sParam = "%$search%"; $params[] = $sParam; $params[] = $sParam; $params[] = $sParam; $params[] = $sParam; $params[] = $sParam; } $whereClause = implode(" AND ", $where); // Total count $stmtCount = $pdo->prepare("SELECT COUNT(*) FROM trades WHERE $whereClause"); $stmtCount->execute($params); $totalCount = (int)$stmtCount->fetchColumn(); // Fetch records $sql = "SELECT * FROM trades WHERE $whereClause ORDER BY $sortBy $sortDir LIMIT $limit OFFSET $offset"; $stmtList = $pdo->prepare($sql); $stmtList->execute($params); $trades = $stmtList->fetchAll(); sendJson([ 'success' => true, 'trades' => $trades, 'pagination' => [ 'total' => $totalCount, 'page' => $page, 'limit' => $limit, 'total_pages' => ceil($totalCount / $limit) ] ]); } elseif ($method === 'POST') { $input = getJsonInput(); $accountId = (int)($input['account_id'] ?? 1); $symbol = strtoupper(trim($input['symbol'] ?? 'EURUSD')); $tradeType = strtolower(trim($input['trade_type'] ?? 'buy')); $lotSize = (float)($input['lot_size'] ?? 0.1); $openPrice = (float)($input['open_price'] ?? 0.0); $closePrice = isset($input['close_price']) && $input['close_price'] !== '' ? (float)$input['close_price'] : null; $stopLoss = isset($input['stop_loss']) && $input['stop_loss'] !== '' ? (float)$input['stop_loss'] : null; $takeProfit = isset($input['take_profit']) && $input['take_profit'] !== '' ? (float)$input['take_profit'] : null; $openTime = !empty($input['open_time']) ? date('Y-m-d H:i:s', strtotime($input['open_time'])) : date('Y-m-d H:i:s'); $closeTime = !empty($input['close_time']) ? date('Y-m-d H:i:s', strtotime($input['close_time'])) : null; $status = trim($input['status'] ?? ($closePrice !== null ? 'closed' : 'open')); $strategy = trim($input['strategy'] ?? 'Price Action'); $timeframe = trim($input['timeframe'] ?? 'M15'); $session = trim($input['session'] ?? 'London'); $emotion = trim($input['emotion'] ?? 'Disciplined'); $comm = (float)($input['commission'] ?? 0.0); $swap = (float)($input['swap'] ?? 0.0); $profit = isset($input['profit']) ? (float)$input['profit'] : 0.0; $pips = isset($input['pips']) ? (float)$input['pips'] : 0.0; $rr = isset($input['risk_reward']) ? (float)$input['risk_reward'] : null; // Auto calculate pips if not provided and closed if ($closePrice !== null && empty($input['pips'])) { $pips = calculatePips($symbol, $tradeType, $openPrice, $closePrice); } // Auto calculate profit if not provided and closed if ($closePrice !== null && !isset($input['profit'])) { // Rough standard lot pip value estimation ($10 per lot per pip for EURUSD) $pipVal = 10.0 * $lotSize; $profit = round($pips * $pipVal, 2); } $stmt = $pdo->prepare(" INSERT INTO trades ( account_id, ticket, symbol, trade_type, lot_size, open_price, close_price, stop_loss, take_profit, open_time, close_time, pips, profit, commission, swap, status, strategy, timeframe, session, risk_reward, emotion, entry_notes, exit_notes, lessons, screenshot_entry, screenshot_exit, tags ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ $accountId, trim($input['ticket'] ?? ''), $symbol, $tradeType, $lotSize, $openPrice, $closePrice, $stopLoss, $takeProfit, $openTime, $closeTime, $pips, $profit, $comm, $swap, $status, $strategy, $timeframe, $session, $rr, $emotion, trim($input['entry_notes'] ?? ''), trim($input['exit_notes'] ?? ''), trim($input['lessons'] ?? ''), trim($input['screenshot_entry'] ?? ''), trim($input['screenshot_exit'] ?? ''), trim($input['tags'] ?? '') ]); $tradeId = (int)$pdo->lastInsertId(); sendJson(['success' => true, 'id' => $tradeId, 'message' => 'معامله با موفقیت ثبت شد']); } } if (preg_match('#^/api/trades/(\d+)$#', $uri, $matches)) { $tradeId = (int)$matches[1]; if ($method === 'GET') { $stmt = $pdo->prepare("SELECT * FROM trades WHERE id = ?"); $stmt->execute([$tradeId]); $trade = $stmt->fetch(); if (!$trade) sendJson(['success' => false, 'error' => 'معامله یافت نشد'], 404); sendJson(['success' => true, 'trade' => $trade]); } elseif ($method === 'PUT') { $input = getJsonInput(); $stmtGet = $pdo->prepare("SELECT * FROM trades WHERE id = ?"); $stmtGet->execute([$tradeId]); $curr = $stmtGet->fetch(); if (!$curr) sendJson(['success' => false, 'error' => 'معامله یافت نشد'], 404); $symbol = strtoupper(trim($input['symbol'] ?? $curr['symbol'])); $tradeType = strtolower(trim($input['trade_type'] ?? $curr['trade_type'])); $openPrice = isset($input['open_price']) ? (float)$input['open_price'] : (float)$curr['open_price']; $closePrice = array_key_exists('close_price', $input) ? ($input['close_price'] !== null && $input['close_price'] !== '' ? (float)$input['close_price'] : null) : $curr['close_price']; $pips = isset($input['pips']) ? (float)$input['pips'] : $curr['pips']; $profit = isset($input['profit']) ? (float)$input['profit'] : $curr['profit']; if ($closePrice !== null && (!isset($input['pips']) || $input['pips'] === '')) { $pips = calculatePips($symbol, $tradeType, $openPrice, (float)$closePrice); } $status = $input['status'] ?? ($closePrice !== null ? 'closed' : $curr['status']); $closeTime = array_key_exists('close_time', $input) ? (!empty($input['close_time']) ? date('Y-m-d H:i:s', strtotime($input['close_time'])) : null) : $curr['close_time']; $stmt = $pdo->prepare(" UPDATE trades SET ticket = COALESCE(?, ticket), symbol = ?, trade_type = ?, lot_size = COALESCE(?, lot_size), open_price = ?, close_price = ?, stop_loss = ?, take_profit = ?, open_time = COALESCE(?, open_time), close_time = ?, pips = ?, profit = ?, commission = COALESCE(?, commission), swap = COALESCE(?, swap), status = ?, strategy = COALESCE(?, strategy), timeframe = COALESCE(?, timeframe), session = COALESCE(?, session), risk_reward = ?, emotion = COALESCE(?, emotion), entry_notes = COALESCE(?, entry_notes), exit_notes = COALESCE(?, exit_notes), lessons = COALESCE(?, lessons), screenshot_entry = COALESCE(?, screenshot_entry), screenshot_exit = COALESCE(?, screenshot_exit), tags = COALESCE(?, tags), updated_at = CURRENT_TIMESTAMP WHERE id = ? "); $stmt->execute([ $input['ticket'] ?? null, $symbol, $tradeType, isset($input['lot_size']) ? (float)$input['lot_size'] : null, $openPrice, $closePrice, isset($input['stop_loss']) && $input['stop_loss'] !== '' ? (float)$input['stop_loss'] : null, isset($input['take_profit']) && $input['take_profit'] !== '' ? (float)$input['take_profit'] : null, !empty($input['open_time']) ? date('Y-m-d H:i:s', strtotime($input['open_time'])) : null, $closeTime, $pips, $profit, isset($input['commission']) ? (float)$input['commission'] : null, isset($input['swap']) ? (float)$input['swap'] : null, $status, $input['strategy'] ?? null, $input['timeframe'] ?? null, $input['session'] ?? null, isset($input['risk_reward']) ? (float)$input['risk_reward'] : null, $input['emotion'] ?? null, $input['entry_notes'] ?? null, $input['exit_notes'] ?? null, $input['lessons'] ?? null, $input['screenshot_entry'] ?? null, $input['screenshot_exit'] ?? null, $input['tags'] ?? null, $tradeId ]); sendJson(['success' => true, 'message' => 'معامله با موفقیت ویرایش شد']); } elseif ($method === 'DELETE') { $stmt = $pdo->prepare("DELETE FROM trades WHERE id = ?"); $stmt->execute([$tradeId]); sendJson(['success' => true, 'message' => 'معامله حذف شد']); } } // ------------------------------------------------------------- // 4. MT4 / MT5 STATEMENT IMPORTER (CSV & HTML) // ------------------------------------------------------------- if (preg_match('#^/api/trades/import/?$#', $uri) && $method === 'POST') { $accountId = (int)($_POST['account_id'] ?? 1); if (!isset($_FILES['statement']) || $_FILES['statement']['error'] !== UPLOAD_ERR_OK) { sendJson(['success' => false, 'error' => 'فایل گزارش انتخاب نشده یا آپلود ناموفق بود'], 400); } $fileTmp = $_FILES['statement']['tmp_name']; $fileName = $_FILES['statement']['name']; $content = file_get_contents($fileTmp); $importedCount = 0; // Check if file is CSV if (str_ends_with(strtolower($fileName), '.csv') || strpos($content, ',') !== false && strpos($content, ' $line) { $line = trim($line); if (empty($line)) continue; $row = str_getcsv($line); if ($lineNum === 0 || empty($header)) { $header = array_map('strtolower', array_map('trim', $row)); continue; } if (count($row) < 5) continue; $rowAssoc = []; foreach ($header as $idx => $colName) { $rowAssoc[$colName] = $row[$idx] ?? ''; } $ticket = $rowAssoc['ticket'] ?? $rowAssoc['order'] ?? (string)rand(1000000, 9999999); $symbol = strtoupper($rowAssoc['symbol'] ?? $rowAssoc['item'] ?? 'EURUSD'); $type = strtolower($rowAssoc['type'] ?? $rowAssoc['cmd'] ?? 'buy'); if (!in_array($type, ['buy', 'sell'])) continue; $size = (float)($rowAssoc['size'] ?? $rowAssoc['volume'] ?? $rowAssoc['lots'] ?? 0.1); $openPrice = (float)($rowAssoc['open price'] ?? $rowAssoc['price'] ?? $rowAssoc['open_price'] ?? 0.0); $closePrice = (float)($rowAssoc['close price'] ?? $rowAssoc['close_price'] ?? 0.0); $profit = (float)($rowAssoc['profit'] ?? $rowAssoc['p/l'] ?? 0.0); $comm = (float)($rowAssoc['commission'] ?? $rowAssoc['taxes'] ?? 0.0); $swap = (float)($rowAssoc['swap'] ?? 0.0); $openTime = $rowAssoc['open time'] ?? $rowAssoc['time'] ?? date('Y-m-d H:i:s'); $closeTime = $rowAssoc['close time'] ?? $rowAssoc['time'] ?? date('Y-m-d H:i:s'); $pips = calculatePips($symbol, $type, $openPrice, $closePrice); $stmtIns = $pdo->prepare(" INSERT INTO trades ( account_id, ticket, symbol, trade_type, lot_size, open_price, close_price, open_time, close_time, pips, profit, commission, swap, status, strategy, emotion ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'closed', 'Imported MT4/MT5', 'Disciplined') "); $stmtIns->execute([$accountId, $ticket, $symbol, $type, $size, $openPrice, $closePrice, $openTime, $closeTime, $pips, $profit, $comm, $swap]); $importedCount++; } } else { // HTML Statement Parser (MT4 / MT5 Standard HTML Reports) preg_match_all('#