38239-vm/api.php
Flatlogic Bot 752e63d5fc bit
2026-02-07 06:11:37 +00:00

339 lines
13 KiB
PHP

<?php
include_once 'config.php';
$action = $_GET['action'] ?? '';
// Function to fetch prices with caching
function get_real_prices() {
$cache_file = __DIR__ . '/db/price_cache.json';
$cache_time = 2; // Cache for 2 seconds
if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_time)) {
return json_decode(file_get_contents($cache_file), true);
}
// Fetch active coins from DB to only ask for what we need
$stmt = db()->query("SELECT symbol FROM cryptocurrencies WHERE is_active = 1");
$symbols = $stmt->fetchAll(PDO::FETCH_COLUMN);
if (empty($symbols)) return [];
// Binance API - symbols parameter format: ["BTCUSDT","ETHUSDT"]
$symbols_encoded = urlencode(json_encode($symbols));
$url = "https://api.binance.com/api/v3/ticker/24hr?symbols=" . $symbols_encoded;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');
$response = curl_exec($ch);
curl_close($ch);
if (!$response) {
// If Binance fails, try to return expired cache if exists
if (file_exists($cache_file)) return json_decode(file_get_contents($cache_file), true);
return [];
}
$data = json_decode($response, true);
$prices = [];
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']
];
}
}
}
if (!empty($prices)) {
file_put_contents($cache_file, json_encode($prices));
}
return $prices;
}
if ($action === 'market_data') {
$real_prices = get_real_prices();
$stmt = db()->query("SELECT * FROM cryptocurrencies WHERE is_active = 1 ORDER BY id ASC");
$coins = $stmt->fetchAll();
$updated_coins = [];
foreach ($coins as $coin) {
$symbol = $coin['symbol'];
if (isset($real_prices[$symbol])) {
$coin['price'] = (float)$real_prices[$symbol]['price'];
$coin['change'] = (float)$real_prices[$symbol]['change'];
$coin['high'] = (float)$real_prices[$symbol]['high'];
$coin['low'] = (float)$real_prices[$symbol]['low'];
$coin['volume'] = (float)$real_prices[$symbol]['volume'];
// Apply manual price if set
if ($coin['manual_price'] > 0) {
$coin['price'] = (float)$coin['manual_price'];
}
// Periodically update DB (every few seconds to avoid overhead)
// We'll update the database to keep it relatively fresh for order submission
$upd = db()->prepare("UPDATE cryptocurrencies SET current_price = ?, change_24h = ? WHERE id = ?");
$upd->execute([$coin['price'], $coin['change'], $coin['id']]);
} else {
$coin['price'] = (float)$coin['current_price'];
$coin['change'] = (float)$coin['change_24h'];
$coin['high'] = $coin['price'] * 1.02; // Fallback
$coin['low'] = $coin['price'] * 0.98;
$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;
}
// IMPORTANT: Fetch FRESH price for order execution
$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)');
}
// Deduct USDT
$stmt = $db->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
$stmt->execute([$total_cost, $account['id']]);
// Add Asset
$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('资产余额不足');
}
// Deduct Asset
$stmt = $db->prepare("UPDATE assets SET balance = balance - ? WHERE account_id = ? AND currency = ?");
$stmt->execute([$amount, $account['id'], $currency]);
// Add USDT
$total_gain = $amount * $current_price;
$stmt = $db->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
$stmt->execute([$total_gain, $account['id']]);
}
// Record Order as FILLED
$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 per Lot is 100 USDT by default in trade.php
// but we use 'amount' as lots.
$contract_value = 100; // Standard value
$total_value = $amount * $contract_value;
$margin = $total_value / $leverage;
if ($account['balance'] < $margin) {
throw new Exception('保证金不足 (需要 ' . number_format($margin, 2) . ' USDT)');
}
// Deduct Margin
$stmt = $db->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
$stmt->execute([$margin, $account['id']]);
// Create Position
$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]);
// Record Order
$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();
// Calculate PnL for each position
foreach ($positions as &$pos) {
$symbol = $pos['symbol'];
// Use fresh price for PnL calculation
$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;
// PnL Calculation: (PriceDiff / EntryPrice) * Margin * Leverage
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'];
}
// Apply Win/Loss Control (Display purpose)
if ($account['win_loss_control'] == 1 && $pos['pnl'] < 0) {
$pos['pnl'] = abs($pos['pnl']) * 0.2; // Show small profit
} else if ($account['win_loss_control'] == -1 && $pos['pnl'] > 0) {
$pos['pnl'] = -abs($pos['pnl']) * 1.5; // Show big loss
}
}
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'];
}
// Win/Loss Control Logic
if ($account['win_loss_control'] == 1) { // Always Win
if ($pnl < 0) $pnl = abs($pnl) * 0.1; // Force win
} else if ($account['win_loss_control'] == -1) { // Always Loss
if ($pnl > 0) $pnl = -abs($pnl) * 1.2; // Force loss
}
// Return Margin + PnL
$payout = $pos['margin'] + $pnl;
if ($payout < 0) $payout = 0;
$stmt = $db->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
$stmt->execute([$payout, $account['id']]);
// Deactivate Position
$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;
}
?>