625 lines
29 KiB
PHP
625 lines
29 KiB
PHP
<?php
|
|
/**
|
|
* Forex Trading Journal REST API
|
|
* Clean JSON API for accounts, trades, analytics, chart uploads, MT4/MT5 statement import, and exports.
|
|
*/
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Access-Control-Allow-Origin: *');
|
|
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
|
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(200);
|
|
exit;
|
|
}
|
|
|
|
require_once __DIR__ . '/db.php';
|
|
require_once __DIR__ . '/analytics.php';
|
|
|
|
$pdo = Database::getConnection();
|
|
$analyticsEngine = new AnalyticsEngine($pdo);
|
|
|
|
// Parse request path
|
|
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
|
|
// Helper JSON response
|
|
function sendJson(array $data, int $statusCode = 200): void {
|
|
http_response_code($statusCode);
|
|
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
|
exit;
|
|
}
|
|
|
|
// Helper to get raw JSON payload
|
|
function getJsonInput(): array {
|
|
$raw = file_get_contents('php://input');
|
|
$decoded = json_decode($raw, true);
|
|
return is_array($decoded) ? $decoded : [];
|
|
}
|
|
|
|
// Helper Pip Calculator
|
|
function calculatePips(string $symbol, string $type, float $openPrice, float $closePrice): float {
|
|
$sym = strtoupper(trim($symbol));
|
|
$type = strtolower(trim($type));
|
|
$diff = ($type === 'buy') ? ($closePrice - $openPrice) : ($openPrice - $closePrice);
|
|
|
|
if (strpos($sym, 'JPY') !== false) {
|
|
return round($diff * 100, 1);
|
|
} elseif (strpos($sym, 'XAU') !== false || strpos($sym, 'GOLD') !== false) {
|
|
return round($diff * 10, 1); // 10 pips per $1.00 move
|
|
} elseif (strpos($sym, 'BTC') !== false || strpos($sym, 'ETH') !== false || strpos($sym, 'US30') !== false || strpos($sym, 'NAS') !== false) {
|
|
return round($diff, 1); // 1 point
|
|
} else {
|
|
return round($diff * 10000, 1); // Standard forex 4/5 digits
|
|
}
|
|
}
|
|
|
|
try {
|
|
// -------------------------------------------------------------
|
|
// 1. ACCOUNTS ENDPOINTS
|
|
// -------------------------------------------------------------
|
|
if (preg_match('#^/api/accounts/?$#', $uri)) {
|
|
if ($method === 'GET') {
|
|
$stmt = $pdo->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, '<html') === false) {
|
|
$lines = explode("\n", $content);
|
|
$header = [];
|
|
foreach ($lines as $lineNum => $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('#<tr[^>]*>(.*?)</tr>#is', $content, $trMatches);
|
|
foreach ($trMatches[1] as $trHtml) {
|
|
preg_match_all('#<td[^>]*>(.*?)</td>#is', $trHtml, $tdMatches);
|
|
$tds = array_map(fn($t) => trim(strip_tags($t)), $tdMatches[1] ?? []);
|
|
if (count($tds) >= 10 && (strtolower($tds[2] ?? '') === 'buy' || strtolower($tds[2] ?? '') === 'sell')) {
|
|
$ticket = $tds[0];
|
|
$openTime = date('Y-m-d H:i:s', strtotime($tds[1]));
|
|
$type = strtolower($tds[2]);
|
|
$size = (float)$tds[3];
|
|
$symbol = strtoupper($tds[4]);
|
|
$openPrice = (float)$tds[5];
|
|
$sl = (float)($tds[6] ?? 0);
|
|
$tp = (float)($tds[7] ?? 0);
|
|
$closeTime = isset($tds[8]) ? date('Y-m-d H:i:s', strtotime($tds[8])) : $openTime;
|
|
$closePrice = (float)($tds[9] ?? $openPrice);
|
|
$comm = (float)($tds[10] ?? 0);
|
|
$swap = (float)($tds[11] ?? 0);
|
|
$profit = (float)($tds[12] ?? 0);
|
|
|
|
$pips = calculatePips($symbol, $type, $openPrice, $closePrice);
|
|
|
|
$stmtIns = $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, emotion
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'closed', 'MT4/MT5 Statement', 'Disciplined')
|
|
");
|
|
$stmtIns->execute([$accountId, $ticket, $symbol, $type, $size, $openPrice, $closePrice, $sl, $tp, $openTime, $closeTime, $pips, $profit, $comm, $swap]);
|
|
$importedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
sendJson(['success' => true, 'imported_count' => $importedCount, 'message' => "تعداد $importedCount معامله با موفقیت ایمپورت شد"]);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// 5. EXPORT TRADES (CSV)
|
|
// -------------------------------------------------------------
|
|
if (preg_match('#^/api/trades/export/?$#', $uri) && $method === 'GET') {
|
|
$accountId = (int)($_GET['account_id'] ?? 1);
|
|
$stmt = $pdo->prepare("SELECT * FROM trades WHERE account_id = ? ORDER BY open_time ASC");
|
|
$stmt->execute([$accountId]);
|
|
$trades = $stmt->fetchAll();
|
|
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
header('Content-Disposition: attachment; filename="forex_trades_' . date('Ymd_His') . '.csv"');
|
|
|
|
$out = fopen('php://output', 'w');
|
|
// UTF-8 BOM for Excel
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, ['ID', 'Ticket', 'Symbol', 'Type', 'Volume', 'Open Price', 'Close Price', 'S/L', 'T/P', 'Open Time', 'Close Time', 'Pips', 'Profit ($)', 'Commission', 'Swap', 'Status', 'Strategy', 'Timeframe', 'Session', 'Emotion', 'Notes']);
|
|
|
|
foreach ($trades as $t) {
|
|
fputcsv($out, [
|
|
$t['id'],
|
|
$t['ticket'],
|
|
$t['symbol'],
|
|
$t['trade_type'],
|
|
$t['lot_size'],
|
|
$t['open_price'],
|
|
$t['close_price'],
|
|
$t['stop_loss'],
|
|
$t['take_profit'],
|
|
$t['open_time'],
|
|
$t['close_time'],
|
|
$t['pips'],
|
|
$t['profit'],
|
|
$t['commission'],
|
|
$t['swap'],
|
|
$t['status'],
|
|
$t['strategy'],
|
|
$t['timeframe'],
|
|
$t['session'],
|
|
$t['emotion'],
|
|
$t['entry_notes']
|
|
]);
|
|
}
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// 6. SCREENSHOT IMAGE UPLOAD
|
|
// -------------------------------------------------------------
|
|
if (preg_match('#^/api/upload/?$#', $uri) && $method === 'POST') {
|
|
if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
|
|
sendJson(['success' => false, 'error' => 'تصویری ارسال نشد یا خطایی رخ داد'], 400);
|
|
}
|
|
|
|
$file = $_FILES['image'];
|
|
$allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
|
|
if (!in_array($file['type'], $allowed)) {
|
|
sendJson(['success' => false, 'error' => 'فرمت تصویر نامعتبر است (تنها JPG, PNG, WEBP مجاز است)'], 400);
|
|
}
|
|
|
|
$uploadDir = __DIR__ . '/uploads';
|
|
if (!is_dir($uploadDir)) mkdir($uploadDir, 0777, true);
|
|
|
|
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
|
$newFilename = 'chart_' . uniqid() . '.' . $ext;
|
|
$dest = $uploadDir . '/' . $newFilename;
|
|
|
|
if (move_uploaded_file($file['tmp_name'], $dest)) {
|
|
sendJson(['success' => true, 'url' => '/uploads/' . $newFilename]);
|
|
} else {
|
|
sendJson(['success' => false, 'error' => 'ذخیره تصویر با خطا مواجه شد'], 500);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// 7. STRATEGIES & DAILY LOGS
|
|
// -------------------------------------------------------------
|
|
if (preg_match('#^/api/strategies/?$#', $uri)) {
|
|
if ($method === 'GET') {
|
|
$stmt = $pdo->query("SELECT * FROM strategies ORDER BY name ASC");
|
|
sendJson(['success' => true, 'strategies' => $stmt->fetchAll()]);
|
|
} elseif ($method === 'POST') {
|
|
$input = getJsonInput();
|
|
$name = trim($input['name'] ?? '');
|
|
if (empty($name)) sendJson(['success' => false, 'error' => 'نام استراتژی الزامی است'], 400);
|
|
$stmt = $pdo->prepare("INSERT OR IGNORE INTO strategies (name, description, color) VALUES (?, ?, ?)");
|
|
$stmt->execute([$name, $input['description'] ?? '', $input['color'] ?? '#3b82f6']);
|
|
sendJson(['success' => true, 'message' => 'استراتژی ثبت شد']);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// 8. RESET & SEED SAMPLE DATA
|
|
// -------------------------------------------------------------
|
|
if (preg_match('#^/api/reset_sample/?$#', $uri) && $method === 'POST') {
|
|
$accountId = (int)($_GET['account_id'] ?? 1);
|
|
$pdo->exec("DELETE FROM trades WHERE account_id = $accountId");
|
|
Database::generateSampleTrades($accountId, 10000.0);
|
|
sendJson(['success' => true, 'message' => 'دادههای نمونه مایافایکسبوک با موفقیت بازیابی شدند']);
|
|
}
|
|
|
|
sendJson(['success' => false, 'error' => 'مسیر مورد نظر یافت نشد (API Not Found)'], 404);
|
|
|
|
} catch (Throwable $e) {
|
|
sendJson(['success' => false, 'error' => 'Server Error: ' . $e->getMessage()], 500);
|
|
}
|