364 lines
14 KiB
PHP
364 lines
14 KiB
PHP
<?php
|
|
include_once 'config.php';
|
|
|
|
$action = $_GET['action'] ?? '';
|
|
|
|
/**
|
|
* Fetch prices from Binance with caching and high precision
|
|
*/
|
|
function get_real_prices() {
|
|
$cache_file = __DIR__ . '/db/price_cache.json';
|
|
$cache_time = 2; // Cache for 2 seconds
|
|
|
|
// Check cache
|
|
if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_time)) {
|
|
$cache_data = json_decode(file_get_contents($cache_file), true);
|
|
if (!empty($cache_data)) return $cache_data;
|
|
}
|
|
|
|
// Fetch active coins from DB
|
|
try {
|
|
$stmt = db()->query("SELECT symbol FROM cryptocurrencies WHERE is_active = 1");
|
|
$symbols = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
} catch (Exception $e) {
|
|
$symbols = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'DOGEUSDT'];
|
|
}
|
|
|
|
if (empty($symbols)) $symbols = ['BTCUSDT'];
|
|
|
|
// Use Binance 24hr ticker for comprehensive data
|
|
$symbols_json = json_encode($symbols);
|
|
$url = "https://api.binance.com/api/v3/ticker/24hr?symbols=" . urlencode($symbols_json);
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
|
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
|
|
// Disable SSL verification if needed for some environments, but prefer keeping it
|
|
// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
|
|
$response = curl_exec($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
$prices = [];
|
|
|
|
if ($http_code == 200 && $response) {
|
|
$data = json_decode($response, true);
|
|
if (is_array($data)) {
|
|
foreach ($data as $item) {
|
|
if (isset($item['symbol'])) {
|
|
$prices[$item['symbol']] = [
|
|
'price' => $item['lastPrice'],
|
|
'change' => $item['priceChangePercent'],
|
|
'high' => $item['highPrice'],
|
|
'low' => $item['lowPrice'],
|
|
'volume' => $item['quoteVolume'],
|
|
'ts' => time()
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: If 24hr fails, try simpler price-only endpoint
|
|
if (empty($prices)) {
|
|
$url_simple = "https://api.binance.com/api/v3/ticker/price";
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $url_simple);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
|
$resp_simple = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
if ($resp_simple) {
|
|
$data_simple = json_decode($resp_simple, true);
|
|
if (is_array($data_simple)) {
|
|
foreach ($data_simple as $item) {
|
|
if (in_array($item['symbol'], $symbols)) {
|
|
$prices[$item['symbol']] = [
|
|
'price' => $item['price'],
|
|
'change' => '0.00',
|
|
'high' => $item['price'],
|
|
'low' => $item['price'],
|
|
'volume' => '0',
|
|
'ts' => time()
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($prices)) {
|
|
// Only update file if we have new data
|
|
@file_put_contents($cache_file, json_encode($prices));
|
|
} else if (file_exists($cache_file)) {
|
|
// Last resort: return expired cache
|
|
return json_decode(file_get_contents($cache_file), true);
|
|
}
|
|
|
|
return $prices;
|
|
}
|
|
|
|
if ($action === 'market_data') {
|
|
$real_prices = get_real_prices();
|
|
try {
|
|
$stmt = db()->query("SELECT * FROM cryptocurrencies WHERE is_active = 1 ORDER BY id ASC");
|
|
$coins = $stmt->fetchAll();
|
|
} catch (Exception $e) {
|
|
$coins = [];
|
|
}
|
|
|
|
$updated_coins = [];
|
|
foreach ($coins as $coin) {
|
|
$symbol = $coin['symbol'];
|
|
|
|
if (isset($real_prices[$symbol])) {
|
|
$coin['price'] = (string)$real_prices[$symbol]['price']; // Keep as string for precision
|
|
$coin['change'] = (float)$real_prices[$symbol]['change'];
|
|
$coin['high'] = (string)$real_prices[$symbol]['high'];
|
|
$coin['low'] = (string)$real_prices[$symbol]['low'];
|
|
$coin['volume'] = (float)$real_prices[$symbol]['volume'];
|
|
|
|
if ($coin['manual_price'] > 0) {
|
|
$coin['price'] = (string)$coin['manual_price'];
|
|
}
|
|
|
|
// Sync to DB occasionally (logic can be improved, but this is current)
|
|
$upd = db()->prepare("UPDATE cryptocurrencies SET current_price = ?, change_24h = ? WHERE id = ?");
|
|
$upd->execute([$coin['price'], $coin['change'], $coin['id']]);
|
|
} else {
|
|
$coin['price'] = (string)$coin['current_price'];
|
|
$coin['change'] = (float)$coin['change_24h'];
|
|
$coin['high'] = (string)($coin['current_price'] * 1.01);
|
|
$coin['low'] = (string)($coin['current_price'] * 0.99);
|
|
$coin['volume'] = 0;
|
|
}
|
|
$updated_coins[] = $coin;
|
|
}
|
|
|
|
header('Content-Type: application/json');
|
|
echo json_encode($updated_coins);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'submit_order') {
|
|
check_auth();
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$data) {
|
|
echo json_encode(['status' => 'error', 'message' => '无效请求数据']);
|
|
exit;
|
|
}
|
|
|
|
$user_id = $_SESSION['user_id'];
|
|
$account = get_account($user_id);
|
|
|
|
$symbol = $data['symbol'] ?? 'BTCUSDT';
|
|
$side = $data['side'] ?? 'BUY';
|
|
$trade_type = strtoupper($data['trade_type'] ?? 'SPOT');
|
|
$amount = (float)($data['amount'] ?? 0);
|
|
$leverage = (int)($data['leverage'] ?? 1);
|
|
|
|
if ($amount <= 0) {
|
|
echo json_encode(['status' => 'error', 'message' => '请输入有效数量']);
|
|
exit;
|
|
}
|
|
|
|
$real_prices = get_real_prices();
|
|
$stmt = db()->prepare("SELECT * FROM cryptocurrencies WHERE symbol = ?");
|
|
$stmt->execute([$symbol]);
|
|
$coin = $stmt->fetch();
|
|
|
|
if (!$coin) {
|
|
echo json_encode(['status' => 'error', 'message' => '不支持该币种']);
|
|
exit;
|
|
}
|
|
|
|
if ($coin['manual_price'] > 0) {
|
|
$current_price = (float)$coin['manual_price'];
|
|
} elseif (isset($real_prices[$symbol])) {
|
|
$current_price = (float)$real_prices[$symbol]['price'];
|
|
} else {
|
|
$current_price = (float)$coin['current_price'];
|
|
}
|
|
|
|
if ($current_price <= 0) {
|
|
echo json_encode(['status' => 'error', 'message' => '价格获取失败,请重试']);
|
|
exit;
|
|
}
|
|
|
|
try {
|
|
$db = db();
|
|
$db->beginTransaction();
|
|
|
|
if ($trade_type === 'SPOT') {
|
|
if ($side === 'BUY') {
|
|
$total_cost = $amount * $current_price;
|
|
if ($account['balance'] < $total_cost) {
|
|
throw new Exception('余额不足 (需要 ' . number_format($total_cost, 2) . ' USDT)');
|
|
}
|
|
$stmt = $db->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
|
|
$stmt->execute([$total_cost, $account['id']]);
|
|
|
|
$currency = str_replace('USDT', '', $symbol);
|
|
$stmt = $db->prepare("INSERT INTO assets (account_id, currency, balance) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE balance = balance + ?");
|
|
$stmt->execute([$account['id'], $currency, $amount, $amount]);
|
|
|
|
} else { // SELL
|
|
$currency = str_replace('USDT', '', $symbol);
|
|
$stmt = $db->prepare("SELECT balance FROM assets WHERE account_id = ? AND currency = ?");
|
|
$stmt->execute([$account['id'], $currency]);
|
|
$asset = $stmt->fetch();
|
|
|
|
if (!$asset || $asset['balance'] < $amount) {
|
|
throw new Exception('资产余额不足');
|
|
}
|
|
|
|
$stmt = $db->prepare("UPDATE assets SET balance = balance - ? WHERE account_id = ? AND currency = ?");
|
|
$stmt->execute([$amount, $account['id'], $currency]);
|
|
|
|
$total_gain = $amount * $current_price;
|
|
$stmt = $db->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
|
|
$stmt->execute([$total_gain, $account['id']]);
|
|
}
|
|
|
|
$stmt = $db->prepare("INSERT INTO orders (account_id, symbol, trade_type, side, order_type, price, amount, total_usdt, status) VALUES (?, ?, 'SPOT', ?, 'MARKET', ?, ?, ?, 'FILLED')");
|
|
$stmt->execute([$account['id'], $symbol, $side, $current_price, $amount, $amount * $current_price]);
|
|
|
|
} else if ($trade_type === 'CONTRACT') {
|
|
$contract_value = 100;
|
|
$total_value = $amount * $contract_value;
|
|
$margin = $total_value / $leverage;
|
|
|
|
if ($account['balance'] < $margin) {
|
|
throw new Exception('保证金不足 (需要 ' . number_format($margin, 2) . ' USDT)');
|
|
}
|
|
|
|
$stmt = $db->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
|
|
$stmt->execute([$margin, $account['id']]);
|
|
|
|
$stmt = $db->prepare("INSERT INTO positions (account_id, symbol, side, leverage, entry_price, lots, margin) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
$stmt->execute([$account['id'], $symbol, ($side === 'BUY' ? 'LONG' : 'SHORT'), $leverage, $current_price, $amount, $margin]);
|
|
|
|
$stmt = $db->prepare("INSERT INTO orders (account_id, symbol, trade_type, side, order_type, price, amount, leverage, status) VALUES (?, ?, 'CONTRACT', ?, 'MARKET', ?, ?, ?, 'FILLED')");
|
|
$stmt->execute([$account['id'], $symbol, $side, $current_price, $amount, $leverage]);
|
|
}
|
|
|
|
$db->commit();
|
|
echo json_encode(['status' => 'success', 'message' => '交易成功']);
|
|
} catch (Exception $e) {
|
|
$db->rollBack();
|
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
|
}
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'positions') {
|
|
check_auth();
|
|
$user_id = $_SESSION['user_id'];
|
|
$account = get_account($user_id);
|
|
|
|
$stmt = db()->prepare("SELECT * FROM positions WHERE account_id = ? AND is_active = 1");
|
|
$stmt->execute([$account['id']]);
|
|
$positions = $stmt->fetchAll();
|
|
|
|
$real_prices = get_real_prices();
|
|
|
|
foreach ($positions as &$pos) {
|
|
$symbol = $pos['symbol'];
|
|
$stmt = db()->prepare("SELECT manual_price, current_price FROM cryptocurrencies WHERE symbol = ?");
|
|
$stmt->execute([$symbol]);
|
|
$coin = $stmt->fetch();
|
|
|
|
if ($coin && $coin['manual_price'] > 0) {
|
|
$current_price = (float)$coin['manual_price'];
|
|
} elseif (isset($real_prices[$symbol])) {
|
|
$current_price = (float)$real_prices[$symbol]['price'];
|
|
} else {
|
|
$current_price = (float)$pos['entry_price'];
|
|
}
|
|
|
|
$pos['current_price'] = $current_price;
|
|
|
|
if ($pos['side'] === 'LONG') {
|
|
$pos['pnl'] = (($current_price - $pos['entry_price']) / $pos['entry_price']) * $pos['margin'] * $pos['leverage'];
|
|
} else {
|
|
$pos['pnl'] = (($pos['entry_price'] - $current_price) / $pos['entry_price']) * $pos['margin'] * $pos['leverage'];
|
|
}
|
|
|
|
if ($account['win_loss_control'] == 1 && $pos['pnl'] < 0) {
|
|
$pos['pnl'] = abs($pos['pnl']) * 0.2;
|
|
} else if ($account['win_loss_control'] == -1 && $pos['pnl'] > 0) {
|
|
$pos['pnl'] = -abs($pos['pnl']) * 1.5;
|
|
}
|
|
}
|
|
|
|
echo json_encode($positions);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'close_position') {
|
|
check_auth();
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
$pos_id = $data['id'] ?? 0;
|
|
$user_id = $_SESSION['user_id'];
|
|
$account = get_account($user_id);
|
|
|
|
try {
|
|
$db = db();
|
|
$db->beginTransaction();
|
|
|
|
$stmt = $db->prepare("SELECT * FROM positions WHERE id = ? AND account_id = ? AND is_active = 1");
|
|
$stmt->execute([$pos_id, $account['id']]);
|
|
$pos = $stmt->fetch();
|
|
|
|
if (!$pos) throw new Exception('仓位不存在');
|
|
|
|
$symbol = $pos['symbol'];
|
|
$real_prices = get_real_prices();
|
|
|
|
$stmt = db()->prepare("SELECT manual_price, current_price FROM cryptocurrencies WHERE symbol = ?");
|
|
$stmt->execute([$symbol]);
|
|
$coin = $stmt->fetch();
|
|
|
|
if ($coin && $coin['manual_price'] > 0) {
|
|
$current_price = (float)$coin['manual_price'];
|
|
} elseif (isset($real_prices[$symbol])) {
|
|
$current_price = (float)$real_prices[$symbol]['price'];
|
|
} else {
|
|
$current_price = (float)$pos['entry_price'];
|
|
}
|
|
|
|
if ($pos['side'] === 'LONG') {
|
|
$pnl = (($current_price - $pos['entry_price']) / $pos['entry_price']) * $pos['margin'] * $pos['leverage'];
|
|
} else {
|
|
$pnl = (($pos['entry_price'] - $current_price) / $pos['entry_price']) * $pos['margin'] * $pos['leverage'];
|
|
}
|
|
|
|
if ($account['win_loss_control'] == 1) {
|
|
if ($pnl < 0) $pnl = abs($pnl) * 0.1;
|
|
} else if ($account['win_loss_control'] == -1) {
|
|
if ($pnl > 0) $pnl = -abs($pnl) * 1.2;
|
|
}
|
|
|
|
$payout = $pos['margin'] + $pnl;
|
|
if ($payout < 0) $payout = 0;
|
|
|
|
$stmt = $db->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
|
|
$stmt->execute([$payout, $account['id']]);
|
|
|
|
$stmt = $db->prepare("UPDATE positions SET is_active = 0 WHERE id = ?");
|
|
$stmt->execute([$pos_id]);
|
|
|
|
$db->commit();
|
|
echo json_encode(['status' => 'success', 'message' => '平仓成功']);
|
|
} catch (Exception $e) {
|
|
$db->rollBack();
|
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
|
}
|
|
exit;
|
|
}
|
|
?>
|