2026-213
This commit is contained in:
parent
d6a987a3fc
commit
1fcaec3d09
@ -20,7 +20,7 @@ $pdo = db();
|
||||
// --- Settlement Logic for Due Orders ---
|
||||
try {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
// Fetch orders that are pending and due for settlement, and join with users to get control settings.
|
||||
// Fetch orders that are pending and due for settlement
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT o.*, u.win_loss_control FROM option_orders o " .
|
||||
"JOIN users u ON o.user_id = u.id " .
|
||||
@ -33,44 +33,38 @@ try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
foreach ($due_orders as $order) {
|
||||
$result = 'loss'; // Default to loss
|
||||
$result = 'loss';
|
||||
|
||||
// Determine final control setting (user's setting is the only one we consider as per request)
|
||||
$final_control = $order['win_loss_control'];
|
||||
|
||||
if ($final_control == 'win') {
|
||||
$result = 'win';
|
||||
} elseif ($final_control == 'loss') {
|
||||
$result = 'loss';
|
||||
} else { // No control set, default to random
|
||||
$result = (rand(0, 100) < 51) ? 'loss' : 'win'; // 51% chance to lose to have a house edge
|
||||
} else {
|
||||
$result = (rand(0, 100) < 51) ? 'loss' : 'win';
|
||||
}
|
||||
|
||||
$profit = 0;
|
||||
// Calculate profit and update user balance
|
||||
if ($result === 'win') {
|
||||
$profit = $order['amount'] * $order['profit_rate'];
|
||||
$total_return = $order['amount'] + $profit;
|
||||
|
||||
// Credit the user's balance with the principal + profit
|
||||
$bal_stmt = $pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?");
|
||||
$bal_stmt->execute([$total_return, $order['user_id']]);
|
||||
} else {
|
||||
// Loss means the wagered amount is lost. No balance update needed as it was deducted at placement.
|
||||
$profit = -$order['amount'];
|
||||
}
|
||||
|
||||
// Fake a closing price that realistically matches the outcome
|
||||
$price_variation = (float)($order['open_price'] * 0.0001 * rand(1, 5));
|
||||
$price_variation = (float)($order['opening_price'] * 0.0001 * rand(1, 5));
|
||||
if (($order['direction'] === 'up' && $result === 'win') || ($order['direction'] === 'down' && $result === 'loss')) {
|
||||
$closing_price = $order['open_price'] + $price_variation;
|
||||
$closing_price = $order['opening_price'] + $price_variation;
|
||||
} else {
|
||||
$closing_price = $order['open_price'] - $price_variation;
|
||||
$closing_price = $order['opening_price'] - $price_variation;
|
||||
}
|
||||
|
||||
// Update the order to settled status
|
||||
$update_stmt = $pdo->prepare(
|
||||
"UPDATE option_orders SET status = 'completed', result = ?, profit = ?, close_price = ? WHERE id = ?"
|
||||
"UPDATE option_orders SET status = 'completed', result = ?, profit = ?, closing_price = ? WHERE id = ?"
|
||||
);
|
||||
$update_stmt->execute([$result, $profit, $closing_price, $order['id']]);
|
||||
}
|
||||
@ -81,13 +75,12 @@ try {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
error_log("Option settlement failed: " . $e->getMessage());
|
||||
// Don't exit, still try to fetch orders for the user
|
||||
}
|
||||
|
||||
// --- Fetch and Return Orders for Frontend ---
|
||||
$stmt = $pdo->prepare("SELECT * FROM option_orders WHERE user_id = ? AND status = ? ORDER BY start_time DESC LIMIT 50");
|
||||
$stmt = $pdo->prepare("SELECT * FROM option_orders WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 50");
|
||||
$stmt->execute([$user_id, $status_filter]);
|
||||
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode($orders);
|
||||
?>
|
||||
?>
|
||||
@ -17,6 +17,7 @@ $amount = (float)($data['amount'] ?? 0);
|
||||
$direction = $data['direction'] ?? null;
|
||||
$duration = (int)($data['duration'] ?? 0);
|
||||
$rate = (float)($data['rate'] ?? 0); // Profit rate in percentage
|
||||
$frontend_price = isset($data['price']) ? (float)$data['price'] : null;
|
||||
|
||||
if (empty($pair) || empty($direction) || $amount <= 0 || $duration <= 0 || $rate <= 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid order parameters.']);
|
||||
@ -61,9 +62,15 @@ function get_current_price($symbol) {
|
||||
}
|
||||
|
||||
$open_price = get_current_price($pair);
|
||||
|
||||
// Fallback to frontend price if server fetch fails
|
||||
if ($open_price === null) {
|
||||
echo json_encode(['success' => false, 'error' => 'Could not fetch current price for the pair. Please try again.']);
|
||||
exit;
|
||||
if ($frontend_price !== null && $frontend_price > 0) {
|
||||
$open_price = $frontend_price;
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Could not fetch current price for the pair. Please try again.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Database Transaction ---
|
||||
@ -90,10 +97,10 @@ try {
|
||||
// 3. Insert the new option order
|
||||
$start_time = date('Y-m-d H:i:s');
|
||||
$settle_at = date('Y-m-d H:i:s', time() + $duration);
|
||||
$profit_rate_decimal = $rate / 100; // Store as decimal for calculation
|
||||
$profit_rate_decimal = $rate / 100;
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO option_orders (user_id, pair, amount, direction, duration, profit_rate, open_price, status, start_time, settle_at) " .
|
||||
"INSERT INTO option_orders (user_id, symbol, amount, direction, duration, profit_rate, opening_price, status, created_at, settle_at) " .
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)"
|
||||
);
|
||||
$stmt->execute([$user_id, $pair, $amount, $direction, $duration, $profit_rate_decimal, $open_price, $start_time, $settle_at]);
|
||||
@ -115,6 +122,6 @@ try {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
error_log("Option order failed: " . $e->getMessage());
|
||||
echo json_encode(['success' => false, 'error' => 'An internal error occurred. Please try again later.']);
|
||||
echo json_encode(['success' => false, 'error' => 'An internal error occurred.']);
|
||||
}
|
||||
?>
|
||||
BIN
assets/pasted-20260213-151532-3946676c.png
Normal file
BIN
assets/pasted-20260213-151532-3946676c.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
506
futures.php
506
futures.php
@ -1,153 +1,132 @@
|
||||
<?php
|
||||
include 'header.php';
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/i18n.php';
|
||||
|
||||
$user_id = $_SESSION['user_id'] ?? null;
|
||||
$user_assets = [];
|
||||
if ($user_id) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT currency, balance FROM assets WHERE user_id = ?");
|
||||
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$usdt_balance = $stmt->fetchColumn();
|
||||
$user_assets['USDT'] = (float)($usdt_balance ?: 0);
|
||||
|
||||
$stmt = $db->prepare("SELECT symbol, amount FROM user_assets WHERE user_id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$assets_data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
if ($assets_data) {
|
||||
$user_assets = $assets_data;
|
||||
foreach ($assets_data as $symbol => $amount) {
|
||||
$user_assets[$symbol] = (float)$amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
/* New Robust Layout from spot.php */
|
||||
html, body {
|
||||
/* UNLOCKED for scrolling */
|
||||
}
|
||||
.trading-page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh; /* Ensure it takes at least full height */
|
||||
}
|
||||
.trading-page-wrapper .navbar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.trading-container {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
min-height: 800px; /* Give a minimum height for content */
|
||||
background: #0b0e11;
|
||||
}
|
||||
.trading-page-wrapper { display: flex; flex-direction: column; min-height: 100vh; background: #0b0e11; }
|
||||
.trading-container { flex-grow: 1; display: flex; min-height: 800px; max-width: 1920px; margin: 0 auto; width: 100%; }
|
||||
.left-col { width: 280px; flex-shrink: 0; background: #161a1e; border-right: 1px solid #2b3139; display: flex; flex-direction: column; }
|
||||
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
||||
.right-col { width: 300px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
|
||||
|
||||
/* Columns */
|
||||
.left-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
||||
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; min-height: 0; }
|
||||
.right-col { width: 320px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
|
||||
.chart-header { height: 55px; padding: 0 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; flex-shrink: 0; }
|
||||
.chart-box { height: 450px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.bottom-content-wrapper { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
|
||||
/* Center Column Content Layout */
|
||||
.chart-header { flex-shrink: 0; height: 50px; padding: 8px 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; }
|
||||
.chart-box { height: 350px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
|
||||
.bottom-content-wrapper { flex-grow: 1; overflow-y: auto; min-height: 0; }
|
||||
|
||||
.order-box { padding: 12px 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
||||
.records-container { display: flex; flex-direction: column; background: #161a1e; flex-grow: 1; min-height: 400px; }
|
||||
.record-tabs { display: flex; padding: 0 20px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
|
||||
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 14px; font-weight: 600; color: #848e9c; cursor: pointer; border-bottom: 2px solid transparent; }
|
||||
.record-tab.active { color: white; border-bottom-color: var(--primary-color); }
|
||||
#records-list { padding: 10px 0; flex-grow: 1; overflow-y: auto; }
|
||||
.order-box { padding: 20px; background: #161a1e; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.records-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.record-tabs { display: flex; padding: 0 20px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; position: relative; }
|
||||
.record-tab.active { color: white; }
|
||||
.record-tab.active::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background: var(--primary-color); }
|
||||
|
||||
.right-col { display: flex; flex-direction: column;}
|
||||
.ob-panel, .trades-panel { flex-grow: 1; min-height: 0; display: flex; flex-direction: column;}
|
||||
.ob-content { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
||||
#asks-list { flex: 1 1 50%; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-end; }
|
||||
#bids-list { flex: 1 1 50%; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-start; }
|
||||
#trades-content { flex: 1; overflow-y: auto; min-height: 0; }
|
||||
|
||||
.left-col { overflow-y: auto; }
|
||||
#pairs-list { flex: 1; overflow-y: auto; }
|
||||
.ob-header, .trades-header { padding: 10px 15px; font-size: 12px; color: #848e9c; display: flex; justify-content: space-between; border-bottom: 1px solid #2b3139; background: #161a1e; flex-shrink: 0;}
|
||||
.ob-row, .trade-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; height: 20px; align-items: center; flex-shrink: 0; }
|
||||
.ob-bar { position: absolute; right: 0; top: 0; bottom: 0; opacity: 0.1; z-index: 0; transition: width 0.1s linear; }
|
||||
#mid-price { padding: 8px 15px; font-size: 16px; font-weight: 800; text-align: center; border-top: 1px solid #2b3139; border-bottom: 1px solid #2b3139; background: #161a1e; flex-shrink: 0;}
|
||||
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
|
||||
.stats-item { display: flex; flex-direction: column; justify-content: center; }
|
||||
.stats-label { font-size: 10px; color: #848e9c; margin-bottom: 1px; }
|
||||
.stats-value { font-size: 12px; font-weight: 600; color: white; }
|
||||
#websocket-status { display: none; /* Hiding this element as requested */ padding: 8px 15px; font-size: 12px; color: #fff; text-align: center; position: absolute; top: 0; left: 0; right: 0; z-index: 1000; }
|
||||
#records-list-container { height: 350px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
#records-list-container::-webkit-scrollbar { width: 4px; }
|
||||
#records-list-container::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
table { width: 100%; font-size: 12px; border-collapse: collapse; }
|
||||
th { color: #848e9c; font-weight: 500; text-align: left; padding: 10px 15px; border-bottom: 1px solid #2b3139; background: #161a1e; position: sticky; top: 0; z-index: 10; }
|
||||
td { padding: 10px 15px; border-bottom: 1px solid rgba(255,255,255,0.02); color: #eaecef; height: 38px; }
|
||||
|
||||
.category-tabs { display: flex; padding: 15px 15px 5px; gap: 8px; }
|
||||
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; transition: all 0.2s; border: 1px solid transparent; }
|
||||
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; border: 1px solid transparent; }
|
||||
.category-tab.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
|
||||
.search-box { padding: 5px 15px 10px; position: relative; }
|
||||
.search-box i { position: absolute; left: 25px; top: 50%; transform: translateY(-50%); color: #848e9c; font-size: 12px; }
|
||||
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 4px; padding: 8px 10px 8px 30px; color: white; font-size: 13px; outline: none; }
|
||||
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; transition: background 0.2s; border-bottom: 1px solid rgba(255,255,255,0.02); }
|
||||
.pair-item.active { background: #1e2329; }
|
||||
.input-group { background: #2b3139; border-radius: 4px; padding: 6px 12px; display: flex; align-items: center; margin-bottom: 6px; border: 1px solid transparent; }
|
||||
.input-group:focus-within { border-color: var(--primary-color); }
|
||||
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 13px; width: 100%; }
|
||||
.input-group .label { color: #848e9c; font-size: 11px; margin-right: 10px; }
|
||||
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 11px; }
|
||||
.slider-container { margin: 8px 0 15px; padding: 0 5px; }
|
||||
input[type=range] { -webkit-appearance: none; width: 100%; height: 3px; background: #2b3139; border-radius: 2px; outline: none; }
|
||||
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; background: var(--primary-color); border-radius: 50%; cursor: pointer; }
|
||||
.trade-btn { width: 100%; padding: 10px; border-radius: 4px; font-weight: bold; font-size: 14px; border: none; cursor: pointer; margin-top: 5px; }
|
||||
.right-col-tabs { display: flex; border-bottom: 1px solid #2b3139; flex-shrink: 0;}
|
||||
.right-col-tab { flex: 1; text-align: center; padding: 10px 0; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; background: #161a1e; }
|
||||
.right-col-tab.active { color: white; background: #1e2329;}
|
||||
|
||||
.search-box { padding: 10px 15px 15px; position: relative; }
|
||||
.search-box i { position: absolute; left: 25px; top: 22px; color: #848e9c; font-size: 12px; }
|
||||
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 6px; padding: 8px 10px 8px 35px; color: white; font-size: 13px; outline: none; transition: 0.2s; }
|
||||
.search-box input:focus { border-color: var(--primary-color); }
|
||||
|
||||
/* Futures specific styles */
|
||||
.futures-config { display: flex; gap: 10px; margin-bottom: 10px; }
|
||||
.config-btn { flex: 1; background: #2b3139; color: white; padding: 6px; border-radius: 4px; border: 1px solid transparent; cursor: pointer; font-size: 12px; text-align: center; font-weight: 600; }
|
||||
.trade-actions { display: flex; gap: 12px; margin-top: 8px; }
|
||||
.btn-long { background: #0ecb81; color: white; }
|
||||
.btn-short { background: #f6465d; color: white; }
|
||||
#pairs-list { height: 684px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
#pairs-list::-webkit-scrollbar { width: 4px; }
|
||||
#pairs-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
|
||||
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.02); height: 38px; }
|
||||
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
|
||||
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
|
||||
|
||||
.futures-config { display: flex; gap: 10px; margin-bottom: 12px; }
|
||||
.config-btn { flex: 1; background: #2b3139; color: white; padding: 8px; border-radius: 4px; text-align: center; font-size: 12px; cursor: pointer; border: 1px solid #3c424a; }
|
||||
|
||||
.input-group { background: #2b3139; border-radius: 4px; padding: 8px 12px; display: flex; align-items: center; margin-bottom: 10px; border: 1px solid #3c424a; }
|
||||
.input-group:focus-within { border-color: var(--primary-color); }
|
||||
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 14px; width: 100%; }
|
||||
.input-group .label { color: #848e9c; font-size: 12px; margin-right: 10px; min-width: 40px; }
|
||||
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 12px; }
|
||||
.slider-container { margin: 15px 0 20px; }
|
||||
input[type=range] { width: 100%; height: 4px; background: #2b3139; border-radius: 2px; outline: none; }
|
||||
|
||||
.trade-btn { width: 100%; padding: 12px; border-radius: 4px; font-weight: bold; font-size: 15px; border: none; cursor: pointer; color: white; transition: 0.2s; }
|
||||
.btn-buy { background: #0ecb81; }
|
||||
.btn-sell { background: #f6465d; }
|
||||
.trade-btn:hover { filter: brightness(1.1); }
|
||||
|
||||
.right-col-tabs { display: flex; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.right-col-tab { flex: 1; text-align: center; padding: 12px 0; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; }
|
||||
.right-col-tab.active { color: white; background: #1e2329; }
|
||||
.ob-header { display: flex; justify-content: space-between; padding: 8px 15px; font-size: 11px; color: #848e9c; border-bottom: 1px solid #2b3139; }
|
||||
.ob-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; }
|
||||
#ob-panel-content { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
#ob-panel-content::-webkit-scrollbar { width: 4px; }
|
||||
#ob-panel-content::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
</style>
|
||||
|
||||
<div class="trading-page-wrapper">
|
||||
<?php // The real header is included in the file, this is a placeholder for the structure. ?>
|
||||
<div class="trading-container flex-column flex-lg-row">
|
||||
<div class="left-col d-none d-lg-flex flex-column">
|
||||
<div class="trading-container">
|
||||
<!-- Left Column -->
|
||||
<div class="left-col d-none d-lg-flex">
|
||||
<div class="category-tabs">
|
||||
<div class="category-tab" onclick="location.href='options.php'"><?php echo __('nav_options'); ?></div>
|
||||
<div class="category-tab" onclick="location.href='spot.php'"><?php echo __('nav_spot'); ?></div>
|
||||
<div class="category-tab active" onclick="location.href='futures.php'"><?php echo __('nav_futures'); ?></div>
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_contract'); ?>">
|
||||
</div>
|
||||
<div id="pairs-list" class="flex-grow-1" style="overflow-y: auto;"></div>
|
||||
<div class="search-box"><i class="fas fa-search"></i><input type="text" id="pair-search" placeholder="<?php echo __('search_contract'); ?>"></div>
|
||||
<div id="pairs-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Center Column -->
|
||||
<div class="center-col">
|
||||
<div id="websocket-status"></div>
|
||||
<div class="chart-header">
|
||||
<div style="display: flex; align-items: center; gap: 10px; padding-right: 15px; border-right: 1px solid #2b3139;">
|
||||
<img id="curr-icon" src="" class="coin-icon" style="margin:0; width:28px; height:28px;">
|
||||
<span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/--</span>
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<img id="curr-icon" src="" class="coin-icon">
|
||||
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div id="curr-price" style="font-size: 18px; font-weight: 800; color: #0ecb81; line-height: 1.2;">--</div>
|
||||
<div id="curr-change" style="font-size: 11px; font-weight: 600;">--</div>
|
||||
<div id="curr-price" style="font-size: 20px; font-weight: 800; color: #0ecb81;">--</div>
|
||||
<div id="curr-change" style="font-size: 12px; font-weight: 600;">--</div>
|
||||
</div>
|
||||
<div class="stats-item"><span class="stats-label"><?php echo __('24h_high'); ?></span><span class="stats-value" id="h-high">--</span></div>
|
||||
<div class="stats-item"><span class="stats-label"><?php echo __('24h_low'); ?></span><span class="stats-value" id="h-low">--</span></div>
|
||||
<div class="stats-item"><span class="stats-label"><?php echo __('24h_vol'); ?></span><span class="stats-value" id="h-vol">--</span></div>
|
||||
</div>
|
||||
|
||||
<div class="chart-box" id="tv_chart_container"></div>
|
||||
|
||||
<div class="bottom-content-wrapper">
|
||||
<div class="order-box">
|
||||
<div class="order-box">
|
||||
<div class="futures-config">
|
||||
<div class="config-btn" id="margin-mode"><?php echo __('cross'); ?></div>
|
||||
<div class="config-btn"><?php echo __('cross'); ?></div>
|
||||
<div class="config-btn" id="leverage-val">20x</div>
|
||||
</div>
|
||||
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="number" id="futures-price" placeholder="<?php echo __('market'); ?>" disabled><span class="unit">USDT</span></div>
|
||||
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="futures-amount" placeholder="0.00"><span class="unit coin-name">--</span></div>
|
||||
<div class="slider-container"><input type="range" id="futures-slider" min="0" max="100" value="0" step="1"></div>
|
||||
<div style="font-size: 11px; color: #848e9c; margin-bottom: 8px;"><span><?php echo __('available'); ?>: </span><span id="futures-avail" style="color:white; font-weight:600">0.00 USDT</span></div>
|
||||
<div class="trade-actions">
|
||||
<button id="btn-buy-long" class="trade-btn btn-long"><?php echo __('open_long'); ?></button>
|
||||
<button id="btn-sell-short" class="trade-btn btn-short"><?php echo __('open_short'); ?></button>
|
||||
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="text" placeholder="<?php echo __('market'); ?>" disabled><span class="unit">USDT</span></div>
|
||||
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="futures-amount" step="0.0001" placeholder="0.00"><span class="unit coin-name">--</span></div>
|
||||
<div class="slider-container"><input type="range" id="futures-slider" min="0" max="100" value="0"></div>
|
||||
<div style="font-size: 12px; color: #848e9c; margin-bottom: 12px;"><?php echo __('available'); ?>: <span id="avail-usdt" style="color:white; font-weight:600">0.00</span> USDT</div>
|
||||
<div style="display: flex; gap: 15px;">
|
||||
<button id="btn-buy" class="trade-btn btn-buy"><?php echo __('open_long'); ?></button>
|
||||
<button id="btn-sell" class="trade-btn btn-sell"><?php echo __('open_short'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="records-container">
|
||||
@ -155,279 +134,168 @@ if ($user_id) {
|
||||
<div class="record-tab active" data-status="positions"><?php echo __('current_positions'); ?></div>
|
||||
<div class="record-tab" data-status="history"><?php echo __('history_orders'); ?></div>
|
||||
</div>
|
||||
<div id="records-list"></div>
|
||||
<div id="records-list-container">
|
||||
<table style="width:100%;" id="records-table"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-col d-none d-lg-flex">
|
||||
<!-- Right Column -->
|
||||
<div class="right-col d-none d-xl-flex">
|
||||
<div class="right-col-tabs">
|
||||
<div class="right-col-tab active" data-tab="ob"><?php echo __('order_book'); ?></div>
|
||||
<div class="right-col-tab" data-tab="trades"><?php echo __('trades'); ?></div>
|
||||
</div>
|
||||
|
||||
<div id="ob-panel" class="ob-panel">
|
||||
<div class="ob-header">
|
||||
<span><?php echo __('price'); ?>(USDT)</span>
|
||||
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
|
||||
<span><?php echo __('total'); ?></span>
|
||||
</div>
|
||||
<div class="ob-content">
|
||||
<div id="ob-panel" style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
|
||||
<div class="ob-header"><span><?php echo __('price'); ?></span><span><?php echo __('amount'); ?></span></div>
|
||||
<div id="ob-panel-content">
|
||||
<div id="asks-list"></div>
|
||||
<div id="mid-price">--</div>
|
||||
<div id="mid-price" style="padding:10px 15px; text-align:center; font-weight:800; font-size:16px; border-top:1px solid #2b3139; border-bottom:1px solid #2b3139;">--</div>
|
||||
<div id="bids-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="trades-panel" class="trades-panel" style="display: none;">
|
||||
<div class="trades-header">
|
||||
<span><?php echo __('price'); ?>(USDT)</span>
|
||||
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
|
||||
<span><?php echo __('time'); ?></span>
|
||||
</div>
|
||||
<div id="trades-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
<script>
|
||||
const userAssets = <?php echo json_encode($user_assets); ?>;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// --- Globals ---
|
||||
let currentPair = 'BTCUSDT';
|
||||
let currentPrice = 0;
|
||||
let ws;
|
||||
const marketData = {};
|
||||
const pairs = [
|
||||
'BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT',
|
||||
'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT',
|
||||
'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'
|
||||
];
|
||||
|
||||
// --- DOM Elements ---
|
||||
let currentPair = 'BTCUSDT', currentPrice = 0, ws, leverage = 20;
|
||||
const marketData = {}, pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'];
|
||||
const dom = {
|
||||
websocketStatus: document.getElementById('websocket-status'),
|
||||
currIcon: document.getElementById('curr-icon'),
|
||||
currPair: document.getElementById('curr-pair'),
|
||||
currPrice: document.getElementById('curr-price'),
|
||||
currChange: document.getElementById('curr-change'),
|
||||
hHigh: document.getElementById('h-high'),
|
||||
hLow: document.getElementById('h-low'),
|
||||
hVol: document.getElementById('h-vol'),
|
||||
tvChartContainer: document.getElementById('tv_chart_container'),
|
||||
tvContainer: document.getElementById('tv_chart_container'),
|
||||
pairsList: document.getElementById('pairs-list'),
|
||||
pairSearch: document.getElementById('pair-search'),
|
||||
asksList: document.getElementById('asks-list'),
|
||||
bidsList: document.getElementById('bids-list'),
|
||||
midPrice: document.getElementById('mid-price'),
|
||||
tradesContent: document.getElementById('trades-content'),
|
||||
coinNameSpans: document.querySelectorAll('.coin-name'),
|
||||
futuresPriceInput: document.getElementById('futures-price'),
|
||||
availFuturesSpan: document.getElementById('futures-avail')
|
||||
availUsdt: document.getElementById('avail-usdt'),
|
||||
coinNames: document.querySelectorAll('.coin-name'),
|
||||
amountInput: document.getElementById('futures-amount'),
|
||||
slider: document.getElementById('futures-slider'),
|
||||
recordsTable: document.getElementById('records-table')
|
||||
};
|
||||
|
||||
// --- Balance Management ---
|
||||
function updateAvailableBalances() {
|
||||
const usdtBalance = parseFloat(userAssets.USDT || 0).toFixed(2);
|
||||
if (dom.availFuturesSpan) {
|
||||
dom.availFuturesSpan.innerText = `${usdtBalance} USDT`;
|
||||
}
|
||||
function updateBalances() {
|
||||
dom.availUsdt.innerText = parseFloat(userAssets.USDT || 0).toFixed(2);
|
||||
dom.coinNames.forEach(el => el.innerText = currentPair.replace('USDT', ''));
|
||||
}
|
||||
|
||||
// --- TradingView Chart ---
|
||||
function initChartWidget(symbol) {
|
||||
if (dom.tvChartContainer.firstChild) {
|
||||
dom.tvChartContainer.innerHTML = '';
|
||||
}
|
||||
const widget = new TradingView.widget({
|
||||
"autosize": true,
|
||||
"symbol": `BINANCE:${symbol}USDT.PERP`,
|
||||
"interval": "15",
|
||||
"timezone": "Etc/UTC",
|
||||
"theme": "dark",
|
||||
"style": "1",
|
||||
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>",
|
||||
"toolbar_bg": "#f1f3f6",
|
||||
"enable_publishing": false,
|
||||
"withdateranges": true,
|
||||
"hide_side_toolbar": true,
|
||||
"allow_symbol_change": false,
|
||||
"container_id": "tv_chart_container",
|
||||
"studies": [
|
||||
"Volume@tv-basicstudies"
|
||||
],
|
||||
"overrides": {
|
||||
"paneProperties.background": "#161a1e",
|
||||
"paneProperties.vertGridProperties.color": "rgba(255, 255, 255, 0.05)",
|
||||
"paneProperties.horzGridProperties.color": "rgba(255, 255, 255, 0.05)",
|
||||
"scalesProperties.textColor" : "#848e9c",
|
||||
}
|
||||
function initChart(symbol) {
|
||||
dom.tvContainer.innerHTML = '';
|
||||
new TradingView.widget({
|
||||
"autosize": true, "symbol": `BINANCE:${symbol}`, "interval": "15", "theme": "dark", "style": "1",
|
||||
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>", "container_id": "tv_chart_container",
|
||||
"hide_side_toolbar": true, "allow_symbol_change": false, "studies": ["Volume@tv-basicstudies"],
|
||||
"overrides": { "paneProperties.background": "#161a1e" }
|
||||
});
|
||||
}
|
||||
|
||||
// --- WebSocket Connection ---
|
||||
function connectWebSocket() {
|
||||
function connectWS() {
|
||||
if (ws) ws.close();
|
||||
const streams = pairs.map(p => `${p.toLowerCase()}@ticker`).concat([`${currentPair.toLowerCase()}@depth20@100ms`, `${currentPair.toLowerCase()}@trade`]);
|
||||
const streams = pairs.map(p => `${p.toLowerCase()}@ticker`).concat([`${currentPair.toLowerCase()}@depth20@100ms`]);
|
||||
ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${streams.join('/')}`);
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const { stream, data } = JSON.parse(event.data);
|
||||
ws.onmessage = (e) => {
|
||||
const { stream, data } = JSON.parse(e.data);
|
||||
if (!data) return;
|
||||
const type = stream.split('@')[1];
|
||||
|
||||
if (type === 'ticker') {
|
||||
if (stream.endsWith('@ticker')) {
|
||||
marketData[data.s] = data;
|
||||
if (data.s === currentPair) {
|
||||
currentPrice = parseFloat(data.c);
|
||||
updatePriceUI(data);
|
||||
dom.currPrice.innerText = currentPrice.toLocaleString('en-US', {minimumFractionDigits:2});
|
||||
dom.currChange.innerText = (data.P >= 0 ? '+' : '') + parseFloat(data.P).toFixed(2) + '%';
|
||||
dom.currPrice.style.color = dom.currChange.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
dom.midPrice.innerText = currentPrice.toFixed(2);
|
||||
}
|
||||
if (dom.pairsList.offsetParent) renderPairs();
|
||||
} else if (type.startsWith('depth')) {
|
||||
renderOrderBook(data.bids, data.asks);
|
||||
} else if (type === 'trade') {
|
||||
renderTrades(data);
|
||||
} else if (stream.endsWith('@depth20@100ms')) {
|
||||
dom.asksList.innerHTML = data.asks.slice(0, 20).reverse().map(a => `<div class="ob-row" onclick="document.getElementById('futures-amount').value=${parseFloat(a[1]).toFixed(4)};"><span style="color:#f6465d">${parseFloat(a[0]).toFixed(2)}</span><span>${parseFloat(a[1]).toFixed(4)}</span></div>`).join('');
|
||||
dom.bidsList.innerHTML = data.bids.slice(0, 20).map(b => `<div class="ob-row" onclick="document.getElementById('futures-amount').value=${parseFloat(b[1]).toFixed(4)};"><span style="color:#0ecb81">${parseFloat(b[0]).toFixed(2)}</span><span>${parseFloat(b[1]).toFixed(4)}</span></div>`).join('');
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => console.error('WebSocket Error:', error);
|
||||
ws.onclose = () => setTimeout(connectWebSocket, 5000);
|
||||
}
|
||||
|
||||
// --- UI Rendering ---
|
||||
function updatePriceUI(d) {
|
||||
if (!d) return;
|
||||
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
||||
|
||||
document.title = `${priceStr} | ${d.s.replace('USDT', '/USDT')}`;
|
||||
dom.currPrice.innerText = priceStr;
|
||||
dom.currPrice.style.color = color;
|
||||
dom.currChange.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
|
||||
dom.currChange.style.color = color;
|
||||
dom.hHigh.innerText = parseFloat(d.h).toLocaleString('en-US', {minimumFractionDigits: 2});
|
||||
dom.hLow.innerText = parseFloat(d.l).toLocaleString('en-US', {minimumFractionDigits: 2});
|
||||
dom.hVol.innerText = `${(parseFloat(d.v) / 1000).toFixed(1)}K`;
|
||||
dom.midPrice.innerText = priceStr;
|
||||
dom.midPrice.style.color = color;
|
||||
ws.onclose = () => setTimeout(connectWS, 5000);
|
||||
}
|
||||
|
||||
function renderPairs() {
|
||||
const query = dom.pairSearch.value.toUpperCase();
|
||||
const filteredPairs = pairs.filter(p => p.includes(query));
|
||||
|
||||
dom.pairsList.innerHTML = filteredPairs.map(p => {
|
||||
const q = document.getElementById('pair-search').value.toUpperCase();
|
||||
dom.pairsList.innerHTML = pairs.filter(p => p.includes(q)).map(p => {
|
||||
const d = marketData[p] || {};
|
||||
const icon = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
|
||||
const price = d.c ? parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
|
||||
const change = d.P ? `${parseFloat(d.P) >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%` : '--';
|
||||
const color = (d.P && parseFloat(d.P) >= 0) ? '#0ecb81' : '#f6465d';
|
||||
|
||||
return `
|
||||
<div class="pair-item ${p === currentPair ? 'active' : ''}" data-pair="${p}">
|
||||
<img src="${icon}" class="coin-icon" onerror="this.style.display='none'">
|
||||
<div style="flex:1"><div style="font-weight:600; font-size:13px">${p.replace('USDT', '')}<span style="color:#848e9c">/USDT</span></div></div>
|
||||
<div style="text-align:right">
|
||||
<div style="font-size:13px; font-weight:600">${price}</div>
|
||||
<div style="font-size:11px; color:${color}">${change}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
return `<div class="pair-item ${p === currentPair ? 'active' : ''}" onclick="switchPair('${p}')">
|
||||
<img src="https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png" class="coin-icon" onerror="this.style.opacity=0">
|
||||
<div style="flex:1; color:white; font-weight:600">${p.replace('USDT','/USDT')}</div>
|
||||
<div style="text-align:right"><div style="color:white; font-weight:600">${d.c ? parseFloat(d.c).toFixed(2) : '--'}</div><div style="color:${d.P>=0?'#0ecb81':'#f6465d'}; font-size:11px">${d.P?(d.P>=0?'+':'')+parseFloat(d.P).toFixed(2)+'%':'--'}</div></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderOrderBook(bids, asks) {
|
||||
const renderList = (element, data, isAsks) => {
|
||||
if (!element) return;
|
||||
const maxRows = Math.floor(element.offsetHeight / 20);
|
||||
if (maxRows <= 0) return;
|
||||
window.switchPair = (p) => {
|
||||
currentPair = p;
|
||||
dom.currPair.innerText = p.replace('USDT', '/USDT') + ' Perp';
|
||||
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
|
||||
initChart(p);
|
||||
updateBalances();
|
||||
connectWS();
|
||||
};
|
||||
|
||||
const relevantData = isAsks ? data.slice(0, maxRows).reverse() : data.slice(0, maxRows);
|
||||
const maxVol = Math.max(...relevantData.map(([, q]) => parseFloat(q))) || 1;
|
||||
async function placeOrder(side) {
|
||||
const amount = parseFloat(dom.amountInput.value);
|
||||
if (!amount || amount <= 0) { alert("Amount > 0"); return; }
|
||||
const price = currentPrice;
|
||||
const total = amount * price;
|
||||
|
||||
element.innerHTML = relevantData.map(([price, quantity]) => {
|
||||
const p = parseFloat(price), q = parseFloat(quantity);
|
||||
const barWidth = (q / maxVol) * 100;
|
||||
const color = isAsks ? '#f6465d' : '#0ecb81';
|
||||
const bgColor = isAsks ? 'rgba(246, 70, 93, 0.2)' : 'rgba(14, 203, 129, 0.2)';
|
||||
return `
|
||||
<div class="ob-row" onclick="setPrice(${p})">
|
||||
<div class="ob-bar" style="width:${barWidth}%; background-color:${bgColor};"></div>
|
||||
<span style="color:${color}; z-index:1;">${p.toFixed(2)}</span>
|
||||
<span style="z-index:1;">${q.toFixed(4)}</span>
|
||||
<span style="z-index:1;">${(p * q).toFixed(2)}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
};
|
||||
renderList(dom.asksList, asks, true);
|
||||
renderList(dom.bidsList, bids, false);
|
||||
try {
|
||||
const r = await fetch('api/place_order.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
symbol: currentPair, type: 'futures', side: side, order_type: 'market',
|
||||
price: price, amount: amount, total: total, leverage: leverage
|
||||
})
|
||||
});
|
||||
const res = await r.json();
|
||||
if (res.success) { alert("<?php echo __('order_placed'); ?>"); fetchOrders(); }
|
||||
else { alert(res.error || "Error"); }
|
||||
} catch(e) { alert("Network Error"); }
|
||||
}
|
||||
|
||||
function renderTrades(trade) {
|
||||
if (!dom.tradesContent) return;
|
||||
const color = trade.m ? '#f6465d' : '#0ecb81';
|
||||
const time = new Date(trade.T).toLocaleTimeString('en-GB');
|
||||
const row = `
|
||||
<div class="trade-row">
|
||||
<span style="color:${color}">${parseFloat(trade.p).toFixed(2)}</span>
|
||||
<span>${parseFloat(trade.q).toFixed(4)}</span>
|
||||
<span>${time}</span>
|
||||
</div>`;
|
||||
dom.tradesContent.insertAdjacentHTML('afterbegin', row);
|
||||
if (dom.tradesContent.children.length > 50) {
|
||||
dom.tradesContent.removeChild(dom.tradesContent.lastChild);
|
||||
}
|
||||
document.getElementById('btn-buy').onclick = () => placeOrder('buy');
|
||||
document.getElementById('btn-sell').onclick = () => placeOrder('sell');
|
||||
|
||||
let activeTab = 'positions';
|
||||
async function fetchOrders() {
|
||||
try {
|
||||
const r = await fetch(`api/get_orders.php?type=futures&status=${activeTab}`);
|
||||
const res = await r.json();
|
||||
if (res.success) renderOrders(res.data);
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function switchPair(pair) {
|
||||
currentPair = pair;
|
||||
const coinName = pair.replace('USDT', '');
|
||||
|
||||
dom.currPair.innerText = `${coinName}/USDT Perpetual`;
|
||||
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${coinName.toLowerCase()}@2x.png`;
|
||||
dom.coinNameSpans.forEach(el => el.innerText = coinName);
|
||||
|
||||
updatePriceUI(marketData[pair]);
|
||||
initChartWidget(pair);
|
||||
renderPairs();
|
||||
|
||||
dom.asksList.innerHTML = '';
|
||||
dom.bidsList.innerHTML = '';
|
||||
dom.tradesContent.innerHTML = '';
|
||||
connectWebSocket();
|
||||
}
|
||||
|
||||
window.setPrice = (price) => {
|
||||
if(dom.futuresPriceInput) dom.futuresPriceInput.value = price;
|
||||
}
|
||||
|
||||
// --- Event Listeners ---
|
||||
dom.pairsList.addEventListener('click', (e) => {
|
||||
const pairItem = e.target.closest('.pair-item');
|
||||
if (pairItem && pairItem.dataset.pair) {
|
||||
switchPair(pairItem.dataset.pair);
|
||||
}
|
||||
});
|
||||
|
||||
dom.pairSearch.addEventListener('input', renderPairs);
|
||||
|
||||
document.querySelectorAll('.right-col-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelector('.right-col-tab.active').classList.remove('active');
|
||||
tab.classList.add('active');
|
||||
const isOb = tab.dataset.tab === 'ob';
|
||||
document.getElementById('ob-panel').style.display = isOb ? 'flex' : 'none';
|
||||
document.getElementById('trades-panel').style.display = isOb ? 'none' : 'flex';
|
||||
function renderOrders(orders) {
|
||||
if (!orders || orders.length === 0) { dom.recordsTable.innerHTML = '<tr><td colspan="6" style="text-align:center; padding:30px; color:#848e9c"><?php echo __('no_records'); ?></td></tr>'; return; }
|
||||
let h = `<thead><tr><th><?php echo __('pair'); ?></th><th><?php echo __('direction'); ?></th><th><?php echo __('price'); ?></th><th><?php echo __('amount'); ?></th><th><?php echo __('leverage'); ?></th><th><?php echo __('time'); ?></th></tr></thead><tbody>`;
|
||||
orders.forEach(o => {
|
||||
h += `<tr><td>${o.symbol}</td><td style="color:${o.side==='buy'?'#0ecb81':'#f6465d'}">${o.side==='buy'?'<?php echo __('buy_long'); ?>':'<?php echo __('sell_short'); ?>'}</td><td>${parseFloat(o.price).toFixed(2)}</td><td>${parseFloat(o.amount).toFixed(4)}</td><td>${o.leverage}x</td><td style="color:#848e9c">${o.created_at}</td></tr>`;
|
||||
});
|
||||
dom.recordsTable.innerHTML = h + '</tbody>';
|
||||
}
|
||||
|
||||
document.querySelectorAll('.record-tab').forEach(t => t.onclick = () => {
|
||||
if (!t.dataset.status) return;
|
||||
document.querySelector('.record-tabs .active').classList.remove('active');
|
||||
t.classList.add('active');
|
||||
activeTab = t.dataset.status;
|
||||
fetchOrders();
|
||||
});
|
||||
|
||||
// --- Initial Load ---
|
||||
updateAvailableBalances();
|
||||
switchPair('BTCUSDT');
|
||||
fetchOrders();
|
||||
setInterval(fetchOrders, 5000);
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
|
||||
|
||||
<?php include 'footer.php'; ?>
|
||||
@ -145,6 +145,10 @@ $translations = [
|
||||
'order_date' => 'Order Date',
|
||||
'options_order_executing' => 'Order is executing, settlement upon countdown completion.',
|
||||
'options_order_settled' => 'Order settled.',
|
||||
'win' => 'Win',
|
||||
'loss' => 'Loss',
|
||||
'profit_amount' => 'Profit Amount',
|
||||
'loss_amount' => 'Loss Amount',
|
||||
|
||||
'kyc_status' => 'KYC Verification',
|
||||
'kyc_none' => 'Unverified',
|
||||
@ -170,6 +174,7 @@ $translations = [
|
||||
'orders' => 'Orders',
|
||||
'order_placed' => 'Order Placed',
|
||||
'min_amount' => 'Min Amount',
|
||||
'trades' => 'Trades',
|
||||
|
||||
'home_slide1_title' => 'NovaEx Global Launch',
|
||||
'home_slide1_desc' => 'Experience the next generation of digital asset trading with ultra-low latency and bank-grade security.',
|
||||
@ -359,6 +364,10 @@ $translations = [
|
||||
'order_date' => '下单日期',
|
||||
'options_order_executing' => '订单执行中,倒计时结束后结算。',
|
||||
'options_order_settled' => '订单已结算。',
|
||||
'win' => '盈利',
|
||||
'loss' => '亏损',
|
||||
'profit_amount' => '盈利金额',
|
||||
'loss_amount' => '亏损金额',
|
||||
|
||||
'kyc_status' => '实名认证',
|
||||
'kyc_none' => '未认证',
|
||||
@ -384,15 +393,16 @@ $translations = [
|
||||
'orders' => '订单',
|
||||
'order_placed' => '下单成功',
|
||||
'min_amount' => '最小金额',
|
||||
'trades' => '最新成交',
|
||||
|
||||
'home_slide1_title' => 'NovaEx 全球发布',
|
||||
'home_slide1_desc' => '体验超低延迟和银行级安全的新一代数字资产交易。',
|
||||
'home_slide2_title' => '100倍杠杆合约交易',
|
||||
'home_slide2_desc' => '通过我们的专业永续合约最大限度地提高您的资金效率。',
|
||||
'home_slide3_title' => '安全数字资产质押',
|
||||
'home_slide3_desc' => 'Earn passive income on your idle assets with our high-yield staking pools.',
|
||||
'home_slide3_desc' => '通过我们的高收益质押池为您的闲置资产赚取被动收入。',
|
||||
'home_download_title' => '随时随地,随心交易',
|
||||
'home_download_desc' => 'Stay connected to the markets with the NovaEx mobile app. Experience professional trading features in the palm of your hand.',
|
||||
'home_download_desc' => '通过 NovaEx 移动应用随时随地关注市场。在掌中体验专业交易功能。',
|
||||
'fast_secure' => '快速且安全',
|
||||
'fast_secure_desc' => '为您的所有数据提供军用级加密。',
|
||||
'real_time' => '实时行情',
|
||||
@ -454,4 +464,4 @@ $lang = $_SESSION['lang'];
|
||||
function __($key, $default = '') {
|
||||
global $translations, $lang;
|
||||
return $translations[$lang][$key] ?? ($translations['en'][$key] ?? ($default ?: $key));
|
||||
}
|
||||
}
|
||||
715
options.php
715
options.php
@ -1,200 +1,258 @@
|
||||
<?php
|
||||
if (session_status() === PHP_SESSION_NONE) { session_start(); }
|
||||
if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }
|
||||
include 'header.php';
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/i18n.php';
|
||||
$user_id = $_SESSION['user_id'] ?? null;
|
||||
if (!$user_id) { header('Location: login.php'); exit; }
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$user_assets = [];
|
||||
if ($user_id) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT currency, balance FROM assets WHERE user_id = ?");
|
||||
// $db is already defined in header.php
|
||||
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$usdt_balance = $stmt->fetchColumn();
|
||||
$user_assets['USDT'] = (float)($usdt_balance ?: 0);
|
||||
|
||||
$stmt = $db->prepare("SELECT symbol, amount FROM user_assets WHERE user_id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$assets_data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
if ($assets_data) {
|
||||
$user_assets = $assets_data;
|
||||
foreach ($assets_data as $symbol => $amount) {
|
||||
$user_assets[$symbol] = (float)$amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
/* UNIFIED 3-column layout CSS */
|
||||
html, body {
|
||||
/* UNLOCKED for scrolling */
|
||||
}
|
||||
.trading-page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh; /* Ensure it takes at least full height */
|
||||
background: #0b0e11;
|
||||
}
|
||||
.trading-page-wrapper .navbar { flex-shrink: 0; }
|
||||
.trading-container { flex-grow: 1; display: flex; min-height: 800px; }
|
||||
.trading-page-wrapper { display: flex; flex-direction: column; min-height: 100vh; background: #0b0e11; }
|
||||
.trading-container { flex-grow: 1; display: flex; min-height: 800px; max-width: 1920px; margin: 0 auto; width: 100%; }
|
||||
.left-col { width: 280px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
||||
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
||||
.right-col { width: 300px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
|
||||
|
||||
.left-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
||||
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; }
|
||||
.right-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-left: 1px solid #2b3139; }
|
||||
.chart-header { flex-shrink: 0; height: 55px; padding: 0 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 25px; }
|
||||
.chart-box { height: 450px; flex-shrink: 0; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
||||
.bottom-content-wrapper { flex-grow: 1; background: #161a1e; display: flex; flex-direction: column; }
|
||||
|
||||
/* Center Column Content */
|
||||
.chart-header { flex-shrink: 0; height: 50px; padding: 8px 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; }
|
||||
.chart-box { height: 380px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
|
||||
.bottom-content-wrapper { flex-grow: 1; background: #161a1e; overflow-y: auto; display: flex; flex-direction: column; }
|
||||
.col-header { padding: 15px; font-weight: 600; border-bottom: 1px solid #2b3139; font-size: 14px; color: #eaecef; height: 50px; display: flex; align-items: center; }
|
||||
|
||||
#pairs-list { height: 756px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
#pairs-list::-webkit-scrollbar { width: 4px; }
|
||||
#pairs-list::-webkit-scrollbar-track { background: transparent; }
|
||||
#pairs-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
|
||||
/* Left & Right Column Content */
|
||||
.col-header { flex-shrink: 0; padding: 15px; font-weight: 600; border-bottom: 1px solid #2b3139; font-size: 14px; }
|
||||
#pairs-list, #order-book-container { flex: 1; overflow-y: auto; }
|
||||
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
|
||||
.stats-item { display: flex; flex-direction: column; justify-content: center; }
|
||||
.stats-label { font-size: 10px; color: #848e9c; }
|
||||
.stats-value { font-size: 12px; font-weight: 600; color: white; }
|
||||
.category-tabs { display: flex; padding: 15px 15px 5px; gap: 8px; }
|
||||
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; border: 1px solid transparent; }
|
||||
.category-tab.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
|
||||
.search-box { padding: 5px 15px 10px; position: relative; }
|
||||
.search-box i { position: absolute; left: 25px; top: 50%; transform: translateY(-50%); color: #848e9c; font-size: 12px; }
|
||||
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 4px; padding: 8px 10px 8px 30px; color: white; font-size: 13px; outline: none; }
|
||||
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.02); }
|
||||
.pair-item.active { background: #1e2329; }
|
||||
.coin-icon { width: 24px; height: 24px; margin-right: 10px; border-radius: 50%; }
|
||||
.stats-item { display: flex; flex-direction: column; }
|
||||
.stats-label { font-size: 11px; color: #848e9c; }
|
||||
.stats-value { font-size: 13px; font-weight: 600; color: white; }
|
||||
|
||||
.category-tabs { display: flex; padding: 15px 15px 10px; gap: 8px; }
|
||||
.category-tab { flex: 1; text-align: center; padding: 8px 0; background: #2b3139; border-radius: 6px; font-size: 12px; color: #848e9c; cursor: pointer; transition: 0.2s; border: 1px solid transparent; }
|
||||
.category-tab.active { background: rgba(0, 102, 255, 0.1); color: var(--primary-color); border-color: var(--primary-color); font-weight: bold; }
|
||||
|
||||
.search-box { padding: 0 15px 15px; position: relative; }
|
||||
.search-box i { position: absolute; left: 25px; top: 12px; color: #848e9c; font-size: 12px; }
|
||||
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 6px; padding: 8px 10px 8px 35px; color: white; font-size: 13px; outline: none; transition: 0.2s; }
|
||||
.search-box input:focus { border-color: var(--primary-color); }
|
||||
|
||||
.pair-item { display: flex; align-items: center; padding: 12px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.03); height: 42px; }
|
||||
.pair-item:hover { background: #1e2329; }
|
||||
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
|
||||
|
||||
/* Order Panel - New Layout */
|
||||
.order-panel { padding: 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
||||
.panel-row { margin-bottom: 15px; }
|
||||
.panel-label { color: #848e9c; font-size: 13px; margin-bottom: 8px; font-weight: 500; display: block; }
|
||||
|
||||
.duration-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
|
||||
.time-btn { background: #2b3139; border: 1px solid #3c424a; color: #eaecef; padding: 8px 4px; border-radius: 6px; font-size: 13px; cursor: pointer; text-align: center; font-weight: 600; transition: all 0.2s; }
|
||||
.time-btn.active { background: var(--primary-color); border-color: var(--primary-color); color: white; transform: translateY(-1px); }
|
||||
.time-btn .rate { font-size: 10px; font-weight: 400; color: #0ecb81; margin-top: 1px; }
|
||||
.time-btn.active .rate { color: #fff; opacity: 0.9; }
|
||||
|
||||
.input-wrapper { position: relative; }
|
||||
.amount-input-group { background: #2b3139; border-radius: 6px; padding: 10px 12px; display: flex; align-items: center; border: 1px solid #3c424a; transition: 0.2s; }
|
||||
.amount-input-group:focus-within { border-color: var(--primary-color); }
|
||||
.amount-input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 16px; font-weight: 600; width: 100%; }
|
||||
.amount-input-group .unit { color: #848e9c; font-weight: 600; font-size: 14px; }
|
||||
|
||||
.balance-info-row { display: flex; justify-content: space-between; margin-top: 8px; font-size: 12px; }
|
||||
.balance-info-row .label { color: #848e9c; }
|
||||
.balance-info-row .value { color: #eaecef; font-weight: 600; }
|
||||
.balance-info-row .profit-val { color: #0ecb81; font-weight: 700; }
|
||||
|
||||
.action-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 15px; }
|
||||
.trade-btn { padding: 12px; border-radius: 8px; font-weight: 800; font-size: 15px; border: none; cursor: pointer; transition: 0.2s; display: flex; align-items: center; justify-content: center; gap: 8px; color: white; }
|
||||
.btn-up { background: #0ecb81; }
|
||||
.btn-down { background: #f6465d; }
|
||||
.trade-btn:hover { filter: brightness(1.1); }
|
||||
|
||||
/* Records */
|
||||
.records-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.record-tabs { display: flex; padding: 0 20px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; position: relative; }
|
||||
.record-tab.active { color: white; }
|
||||
.record-tab.active::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background: var(--primary-color); }
|
||||
.table-responsive { height: 350px; overflow-y: auto; padding: 0; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
.table-responsive::-webkit-scrollbar { width: 4px; }
|
||||
.table-responsive::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
|
||||
table { width: 100%; font-size: 12px; border-collapse: collapse; }
|
||||
th { color: #848e9c; font-weight: 500; text-align: left; padding: 10px 15px; border-bottom: 1px solid #2b3139; background: #161a1e; position: sticky; top: 0; z-index: 10; }
|
||||
td { padding: 10px 15px; border-bottom: 1px solid rgba(255,255,255,0.02); color: #eaecef; height: 38px; }
|
||||
|
||||
/* Order Book */
|
||||
.order-book-header { display: flex; justify-content: space-between; align-items: center; padding: 0 15px; height: 35px; border-bottom: 1px solid #2b3139; }
|
||||
.order-book-header span { color: #848e9c; font-size: 11px; }
|
||||
.book-row { display: flex; justify-content: space-between; font-size: 12px; padding: 3px 15px; position: relative; }
|
||||
.book-row > div { width: 33.33%; text-align: right; z-index: 2; }
|
||||
.book-row > div:first-child { text-align: left; }
|
||||
.book-row .price.up { color: #0ecb81; }
|
||||
.book-row .price.down { color: #f6465d; }
|
||||
.depth-bar { position: absolute; top: 0; bottom: 0; z-index: 1; opacity: 0.15; }
|
||||
.depth-bar.ask { right: 0; background: #f6465d; }
|
||||
.depth-bar.bid { right: 0; background: #0ecb81; }
|
||||
.ob-header { display: flex; justify-content: space-between; padding: 8px 15px; background: #161a1e; font-size: 11px; color: #848e9c; border-bottom: 1px solid #2b3139; }
|
||||
.ob-row { display: flex; justify-content: space-between; padding: 4px 15px; font-size: 12px; }
|
||||
.ob-row .price.up { color: #0ecb81; }
|
||||
.ob-row .price.down { color: #f6465d; }
|
||||
#order-book-list { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
#order-book-list::-webkit-scrollbar { width: 4px; }
|
||||
#order-book-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
|
||||
/* Options-Specific Panel */
|
||||
.order-panel { padding: 20px 25px; background: #1e2329; border-bottom: 1px solid #2b3139; }
|
||||
.panel-label { color: #848e9c; font-size: 12px; margin-bottom: 10px; font-weight: 500; }
|
||||
.duration-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
|
||||
.time-btn { background: #2b3139; border: 1px solid #2b3139; color: #eaecef; padding: 8px 5px; border-radius: 8px; font-size: 13px; cursor: pointer; text-align: center; font-weight: 600; transition: all 0.2s ease-in-out; }
|
||||
.time-btn:hover { background: #3c424a; border-color: #4a5057; }
|
||||
.time-btn.active { background: linear-gradient(45deg, rgba(0, 82, 255, 0.2), rgba(0, 122, 255, 0.2)); border-color: var(--primary-color); color: #fff; transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0, 102, 255, 0.2); }
|
||||
.time-btn .rate { font-size: 10px; font-weight: normal; color: #0ecb81; margin-top: 3px; }
|
||||
.input-group { background: #2b3139; border-radius: 8px; padding: 8px 15px; display: flex; align-items: center; border: 1px solid #2b3139; transition: all 0.2s ease-in-out; }
|
||||
.input-group:focus-within { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15); }
|
||||
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 16px; font-weight: 600; width: 100%; }
|
||||
.input-group .unit { color: #848e9c; margin-left: 10px; font-weight: 500; font-size: 14px; }
|
||||
.balance-info { display: flex; justify-content: space-between; font-size: 12px; color: #848e9c; margin-top: 8px; padding: 0 5px; }
|
||||
.balance-info .label {color: #848e9c;}
|
||||
.balance-info .value {color: white; font-weight: 600;}
|
||||
.trade-btn { width: 100%; padding: 15px; border-radius: 8px; font-weight: bold; font-size: 16px; border: none; cursor: pointer; transition: all 0.2s ease-in-out; display: flex; align-items: center; justify-content: center; gap: 10px; }
|
||||
.trade-btn:hover { transform: translateY(-2px); filter: brightness(1.1); }
|
||||
.btn-up { background: linear-gradient(45deg, #0b9e6f, #14d291); box-shadow: 0 4px 20px rgba(14, 203, 129, 0.25); color: white; }
|
||||
.btn-down { background: linear-gradient(45deg, #e04156, #f65368); box-shadow: 0 4px 20px rgba(246, 70, 93, 0.25); color: white; }
|
||||
/* Countdown Modal Styles */
|
||||
.countdown-modal {
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.85); display: none; align-items: center; justify-content: center; z-index: 10000;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.modal-content-card {
|
||||
background: #161a1e; width: 360px; border-radius: 20px; padding: 30px; position: relative; color: white;
|
||||
box-shadow: 0 20px 50px rgba(0,0,0,0.5); border: 1px solid #2b3139;
|
||||
}
|
||||
.modal-close {
|
||||
position: absolute; top: 15px; right: 15px; width: 32px; height: 32px;
|
||||
background: #2b3139; border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
||||
cursor: pointer; color: #848e9c; transition: 0.2s;
|
||||
}
|
||||
.modal-close:hover { background: #3c424a; color: white; }
|
||||
.countdown-circle-wrap { display: flex; flex-direction: column; align-items: center; margin: 25px 0; }
|
||||
.circle-progress { position: relative; width: 140px; height: 140px; }
|
||||
.circle-progress svg { transform: rotate(-90deg); width: 140px; height: 140px; }
|
||||
.circle-bg { fill: none; stroke: #2b3139; stroke-width: 10; }
|
||||
.circle-bar {
|
||||
fill: none; stroke: #0ecb81; stroke-width: 10; stroke-linecap: round;
|
||||
stroke-dasharray: 408; stroke-dashoffset: 408; transition: stroke-dashoffset 0.1s linear;
|
||||
}
|
||||
.countdown-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 32px; font-weight: 800; }
|
||||
.modal-info-row { display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 14px; }
|
||||
.modal-info-label { color: #848e9c; }
|
||||
.modal-info-value { font-weight: 600; color: #eaecef; }
|
||||
.modal-info-value.up { color: #0ecb81; }
|
||||
.modal-info-value.down { color: #f6465d; }
|
||||
.modal-footer-text { text-align: center; color: #848e9c; font-size: 12px; margin-top: 20px; border-top: 1px solid #2b3139; padding-top: 15px; }
|
||||
|
||||
/* Records (Order History) */
|
||||
.records-container { flex-grow: 1; display: flex; flex-direction: column; min-height: 400px; }
|
||||
.record-tabs { display: flex; padding: 0 20px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
|
||||
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 14px; font-weight: 600; color: #848e9c; cursor: pointer; border-bottom: 2px solid transparent; }
|
||||
.record-tab.active { color: white; border-bottom-color: var(--primary-color); }
|
||||
.records-table-container { flex-grow: 1; overflow-y: auto; padding: 0 20px; }
|
||||
#records-list { width: 100%; font-size: 12px; }
|
||||
#records-list th, #records-list td { padding: 10px 8px; border-bottom: 1px solid #2b3139; text-align: left; }
|
||||
#records-list th { color: #848e9c; font-weight: 500; }
|
||||
.status-win { color: #0ecb81; font-weight: 600; }
|
||||
.status-loss { color: #f6465d; font-weight: 600; }
|
||||
.status-pending { color: #ffc107; font-weight: 600; }
|
||||
|
||||
/* Countdown Modal */
|
||||
.option-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 20, 30, 0.4); z-index: 3000; display: none; align-items: center; justify-content: center; backdrop-filter: blur(10px); }
|
||||
.option-modal-content { background: #1e2329; width: 420px; border-radius: 20px; border: 1px solid #2b3139; text-align: center; animation: modal-fade-in 0.3s; }
|
||||
@keyframes modal-fade-in { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
|
||||
.modal-header { padding: 20px; border-bottom: 1px solid #2b3139; font-size: 18px; font-weight: 600; color: white; }
|
||||
.modal-body { padding: 25px 30px; }
|
||||
.circular-timer { position: relative; width: 140px; height: 140px; margin: 0 auto 25px auto; }
|
||||
.circular-timer-svg { transform: rotate(-90deg); }
|
||||
.circular-timer-path { stroke-width: 10; fill: none; }
|
||||
.timer-path-bg { stroke: #2b3139; }
|
||||
.timer-path-progress { stroke: var(--primary-color); stroke-linecap: round; transition: stroke-dashoffset 1s linear; }
|
||||
.timer-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
||||
.timer-countdown { font-size: 36px; font-weight: 700; color: white; }
|
||||
.timer-label { font-size: 12px; color: #848e9c; }
|
||||
.modal-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px 25px; text-align: left; }
|
||||
.info-item .label { font-size: 12px; color: #848e9c; margin-bottom: 5px; }
|
||||
.info-item .value { font-size: 15px; font-weight: 600; color: white; }
|
||||
.modal-footer { padding: 20px; background: #161a1e; border-radius: 0 0 20px 20px; font-size: 13px; color: #848e9c; border-top: 1px solid #2b3139; }
|
||||
#result-overlay {
|
||||
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: #161a1e; border-radius: 20px; display: none; flex-direction: column; align-items: center; justify-content: center; z-index: 10;
|
||||
}
|
||||
.result-icon { font-size: 60px; margin-bottom: 20px; }
|
||||
.result-title { font-size: 24px; font-weight: 800; margin-bottom: 10px; }
|
||||
.result-amount { font-size: 32px; font-weight: 800; margin-bottom: 30px; }
|
||||
.result-btn { background: var(--primary-color); color: white; padding: 12px 40px; border-radius: 10px; font-weight: 700; cursor: pointer; border: none; }
|
||||
</style>
|
||||
|
||||
<div class="trading-page-wrapper">
|
||||
<?php // Header is included via main layout ?>
|
||||
<div class="trading-container">
|
||||
<!-- Left Column: Pair Selection -->
|
||||
<div class="left-col d-none d-lg-flex flex-column">
|
||||
<!-- Left: Pair Selection -->
|
||||
<div class="left-col d-none d-lg-flex">
|
||||
<div class="category-tabs">
|
||||
<div class="category-tab active" onclick="location.href='options.php'"><?php echo __('nav_options'); ?></div>
|
||||
<div class="category-tab" onclick="location.href='spot.php'"><?php echo __('nav_spot'); ?></div>
|
||||
<div class="category-tab" onclick="location.href='futures.php'"><?php echo __('nav_futures'); ?></div>
|
||||
</div>
|
||||
<div class="search-box"><i class="fas fa-search"></i><input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>"></div>
|
||||
<div id="pairs-list" class="flex-grow-1"></div>
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
|
||||
</div>
|
||||
<div id="pairs-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Center Column: Chart & Order Panel -->
|
||||
<!-- Center: Chart & Order -->
|
||||
<div class="center-col">
|
||||
<div class="chart-header">
|
||||
<div style="display: flex; align-items: center; gap: 10px;"><img id="curr-icon" src="" class="coin-icon"><span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/--</span></div>
|
||||
<div class="stats-item"><div id="curr-price" style="font-size: 18px; font-weight: 800; color: #0ecb81; line-height: 1.2;">--</div><div id="curr-change" style="font-size: 11px; font-weight: 600;">--</div></div>
|
||||
<div class="stats-item"><span class="stats-label"><?php echo __('24h_high'); ?></span><span class="stats-value" id="h-high">--</span></div>
|
||||
<div class="stats-item"><span class="stats-label"><?php echo __('24h_low'); ?></span><span class="stats-value" id="h-low">--</span></div>
|
||||
<div class="d-flex align-items-center" style="gap:12px;">
|
||||
<img id="curr-icon" src="" class="coin-icon">
|
||||
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div id="curr-price" style="font-size: 20px; font-weight: 800; color: #0ecb81; line-height: 1;">--</div>
|
||||
<div id="curr-change" style="font-size: 12px; font-weight: 600; margin-top:2px;">--</div>
|
||||
</div>
|
||||
<div class="stats-item d-none d-md-flex"><span class="stats-label"><?php echo __('24h_high'); ?></span><span class="stats-value" id="h-high">--</span></div>
|
||||
<div class="stats-item d-none d-md-flex"><span class="stats-label"><?php echo __('24h_low'); ?></span><span class="stats-value" id="h-low">--</span></div>
|
||||
</div>
|
||||
|
||||
<div class="chart-box" id="tv_chart_container"></div>
|
||||
|
||||
<div class="bottom-content-wrapper">
|
||||
<div class="order-panel">
|
||||
<div style="display: grid; grid-template-columns: 2.5fr 1.5fr 2fr; gap: 20px; align-items: flex-end;">
|
||||
<div>
|
||||
<div class="panel-label"><?php echo __('settlement_time'); ?> / <?php echo __('profit'); ?></div>
|
||||
<div class="duration-grid">
|
||||
<div class="time-btn active" data-duration="60" data-rate="8" data-min="100">60s<div class="rate">+8%</div></div>
|
||||
<div class="time-btn" data-duration="90" data-rate="12" data-min="5000">90s<div class="rate">+12%</div></div>
|
||||
<div class="time-btn" data-duration="120" data-rate="15" data-min="30000">120s<div class="rate">+15%</div></div>
|
||||
<div class="time-btn" data-duration="180" data-rate="20" data-min="100000">180s<div class="rate">+20%</div></div>
|
||||
<div class="time-btn" data-duration="300" data-rate="32" data-min="300000">300s<div class="rate">+32%</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label"><?php echo __('buy_amount'); ?></div>
|
||||
<div class="input-group"><input type="number" id="option-amount" placeholder="<?php echo __('min_amount_is_pl'); ?> 100"><span class="unit">USDT</span></div>
|
||||
<div class="balance-info">
|
||||
<div><span class="label"><?php echo __('available'); ?>: </span><b id="usdt-balance" class="value">0.00</b></div>
|
||||
<div><span class="label"><?php echo __('profit'); ?>: </span><b id="potential-profit" class="value" style="color: #0ecb81;">+0.00</b></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:15px;">
|
||||
<button class="trade-btn btn-up" id="btn-buy-up"><i class="fas fa-arrow-up fa-lg"></i> <?php echo __('buy_up'); ?></button>
|
||||
<button class="trade-btn btn-down" id="btn-buy-down"><i class="fas fa-arrow-down fa-lg"></i> <?php echo __('buy_down'); ?></button>
|
||||
<!-- Row 1: Duration -->
|
||||
<div class="panel-row">
|
||||
<label class="panel-label"><?php echo __('settlement_time'); ?> / <?php echo __('profit'); ?></label>
|
||||
<div class="duration-grid">
|
||||
<div class="time-btn active" data-duration="60" data-rate="8" data-min="100">60s<div class="rate">+8%</div></div>
|
||||
<div class="time-btn" data-duration="90" data-rate="12" data-min="5000">90s<div class="rate">+12%</div></div>
|
||||
<div class="time-btn" data-duration="120" data-rate="15" data-min="30000">120s<div class="rate">+15%</div></div>
|
||||
<div class="time-btn" data-duration="180" data-rate="20" data-min="100000">180s<div class="rate">+20%</div></div>
|
||||
<div class="time-btn" data-duration="300" data-rate="32" data-min="300000">300s<div class="rate">+32%</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: Amount -->
|
||||
<div class="panel-row" style="margin-bottom: 10px;">
|
||||
<label class="panel-label"><?php echo __('buy_amount'); ?></label>
|
||||
<div class="amount-input-group">
|
||||
<input type="number" id="option-amount" value="100" placeholder="<?php echo __('min_amount'); ?> 100">
|
||||
<span class="unit">USDT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2.5: Balance & Profit -->
|
||||
<div class="balance-info-row">
|
||||
<div>
|
||||
<span class="label"><?php echo __('available'); ?>: </span>
|
||||
<span class="value"><span id="usdt-balance">0.00</span> USDT</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label"><?php echo __('expected_profit'); ?>: </span>
|
||||
<span class="profit-val" id="potential-profit">+0.00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 3: Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="trade-btn btn-up" id="btn-buy-up">
|
||||
<i class="fas fa-arrow-up"></i> <?php echo __('buy_up'); ?>
|
||||
</button>
|
||||
<button class="trade-btn btn-down" id="btn-buy-down">
|
||||
<i class="fas fa-arrow-down"></i> <?php echo __('buy_down'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="records-container">
|
||||
<div class="record-tabs">
|
||||
<div class="record-tab active" data-status="pending"><?php echo __('in_progress'); ?></div>
|
||||
<div class="record-tab" data-status="completed"><?php echo __('settled'); ?></div>
|
||||
</div>
|
||||
<div class="records-table-container">
|
||||
<div class="table-responsive">
|
||||
<table id="records-list"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Order Book -->
|
||||
<div class="right-col d-none d-lg-flex flex-column">
|
||||
<!-- Right: Order Book -->
|
||||
<div class="right-col d-none d-xl-flex">
|
||||
<div class="col-header"><?php echo __('order_book'); ?></div>
|
||||
<div class="order-book-header">
|
||||
<span><?php echo __('price'); ?> (USDT)</span>
|
||||
<span id="ob-amount-label"><?php echo __('amount'); ?> (BTC)</span>
|
||||
<span><?php echo __('total'); ?></span>
|
||||
<div class="ob-header">
|
||||
<span><?php echo __('price'); ?>(USDT)</span>
|
||||
<span><?php echo __('amount'); ?></span>
|
||||
</div>
|
||||
<div id="order-book-container">
|
||||
<div id="order-book-list">
|
||||
<div id="asks-list"></div>
|
||||
<div id="current-price-display" style="padding: 8px 15px; text-align: center; font-weight: bold; font-size: 16px; border-top: 1px solid #2b3139; border-bottom: 1px solid #2b3139;"></div>
|
||||
<div id="price-mid" style="padding:10px 15px; font-weight:800; text-align:center; font-size:16px; border-top:1px solid #2b3139; border-bottom:1px solid #2b3139;">--</div>
|
||||
<div id="bids-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -202,30 +260,56 @@ if ($user_id) {
|
||||
</div>
|
||||
|
||||
<!-- Countdown Modal -->
|
||||
<div class="option-modal-overlay" id="option-modal">
|
||||
<div class="option-modal-content">
|
||||
<div class="modal-header" id="modal-header"></div>
|
||||
<div class="modal-body">
|
||||
<div class="circular-timer">
|
||||
<svg class="circular-timer-svg" viewBox="0 0 36 36">
|
||||
<path class="circular-timer-path timer-path-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
<path class="circular-timer-path timer-path-progress" id="timer-progress" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
<div id="countdown-modal" class="countdown-modal">
|
||||
<div class="modal-content-card">
|
||||
<div class="modal-close" onclick="closeCountdownModal()"><i class="fas fa-times"></i></div>
|
||||
<div style="font-size: 20px; font-weight: 800; text-align: left;" id="modal-symbol">--</div>
|
||||
|
||||
<div class="countdown-circle-wrap">
|
||||
<div class="circle-progress">
|
||||
<svg>
|
||||
<circle class="circle-bg" cx="70" cy="70" r="65"></circle>
|
||||
<circle id="circle-bar" class="circle-bar" cx="70" cy="70" r="65"></circle>
|
||||
</svg>
|
||||
<div class="timer-text">
|
||||
<div class="timer-countdown" id="timer-countdown">--</div>
|
||||
<div class="timer-label"><?php echo __('seconds_remaining'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-info-grid">
|
||||
<div class="info-item"><div class="label"><?php echo __('order_price'); ?></div><div class="value" id="modal-open-price"></div></div>
|
||||
<div class="info-item"><div class="label"><?php echo __('buy_amount'); ?></div><div class="value" id="modal-amount"></div></div>
|
||||
<div class="info-item"><div class="label"><?php echo __('current_price'); ?></div><div class="value" id="modal-current-price"></div></div>
|
||||
<div class="info-item"><div class="label"><?php echo __('profit_and_loss'); ?></div><div class="value" id="modal-pnl"></div></div>
|
||||
<div class="info-item"><div class="label"><?php echo __('order_time'); ?></div><div class="value" id="modal-start-time"></div></div>
|
||||
<div class="info-item"><div class="label"><?php echo __('settlement_time'); ?></div><div class="value" id="modal-end-time"></div></div>
|
||||
<div class="countdown-text" id="countdown-timer">00:00</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-footer" class="modal-footer"></div>
|
||||
|
||||
<div class="modal-info-row">
|
||||
<span class="modal-info-label"><?php echo __('current_price'); ?></span>
|
||||
<span class="modal-info-value" id="modal-curr-price">--</span>
|
||||
</div>
|
||||
<div class="modal-info-row">
|
||||
<span class="modal-info-label"><?php echo __('period'); ?></span>
|
||||
<span class="modal-info-value" id="modal-period">--</span>
|
||||
</div>
|
||||
<div class="modal-info-row">
|
||||
<span class="modal-info-label"><?php echo __('direction'); ?></span>
|
||||
<span class="modal-info-value" id="modal-direction">--</span>
|
||||
</div>
|
||||
<div class="modal-info-row">
|
||||
<span class="modal-info-label"><?php echo __('amount'); ?></span>
|
||||
<span class="modal-info-value" id="modal-amount">--</span>
|
||||
</div>
|
||||
<div class="modal-info-row">
|
||||
<span class="modal-info-label"><?php echo __('price'); ?></span>
|
||||
<span class="modal-info-value" id="modal-price">--</span>
|
||||
</div>
|
||||
<div class="modal-info-row">
|
||||
<span class="modal-info-label"><?php echo __('expected_profit'); ?></span>
|
||||
<span class="modal-info-value up" id="modal-profit">--</span>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer-text">
|
||||
<?php echo __('final_price_notice'); ?>
|
||||
</div>
|
||||
|
||||
<div id="result-overlay">
|
||||
<div class="result-icon" id="result-icon"></div>
|
||||
<div class="result-title" id="result-title"></div>
|
||||
<div class="result-amount" id="result-amount"></div>
|
||||
<button class="result-btn" onclick="closeCountdownModal()"><?php echo __('confirm'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -234,17 +318,13 @@ if ($user_id) {
|
||||
const userAssets = <?php echo json_encode($user_assets); ?>;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// --- Globals ---
|
||||
let currentPair = 'BTCUSDT', currentPrice = 0, ws, chartWidget;
|
||||
let selectedDuration = 60, selectedRate = 8, minAmount = 100;
|
||||
const marketData = {};
|
||||
const pairs = [
|
||||
'BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT',
|
||||
'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT',
|
||||
'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'
|
||||
];
|
||||
let countdownInterval, fetchOrdersInterval;
|
||||
const pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'];
|
||||
let fetchOrdersInterval;
|
||||
let activeOrdersTab = 'pending';
|
||||
let countdownInterval;
|
||||
|
||||
const dom = {
|
||||
currIcon: document.getElementById('curr-icon'),
|
||||
@ -253,52 +333,52 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
currChange: document.getElementById('curr-change'),
|
||||
hHigh: document.getElementById('h-high'),
|
||||
hLow: document.getElementById('h-low'),
|
||||
tvChartContainer: document.getElementById('tv_chart_container'),
|
||||
pairsList: document.getElementById('pairs-list'),
|
||||
pairSearch: document.getElementById('pair-search'),
|
||||
asksList: document.getElementById('asks-list'),
|
||||
bidsList: document.getElementById('bids-list'),
|
||||
priceDisplay: document.getElementById('current-price-display'),
|
||||
obAmountLabel: document.getElementById('ob-amount-label'),
|
||||
midPrice: document.getElementById('price-mid'),
|
||||
optionAmountInput: document.getElementById('option-amount'),
|
||||
potentialProfit: document.getElementById('potential-profit'),
|
||||
usdtBalance: document.getElementById('usdt-balance'),
|
||||
recordsList: document.getElementById('records-list'),
|
||||
modal: document.getElementById('option-modal'),
|
||||
modalHeader: document.getElementById('modal-header'),
|
||||
modalOpenPrice: document.getElementById('modal-open-price'),
|
||||
|
||||
// Modal DOM
|
||||
modal: document.getElementById('countdown-modal'),
|
||||
modalSymbol: document.getElementById('modal-symbol'),
|
||||
modalTimer: document.getElementById('countdown-timer'),
|
||||
modalCircle: document.getElementById('circle-bar'),
|
||||
modalCurrPrice: document.getElementById('modal-curr-price'),
|
||||
modalPeriod: document.getElementById('modal-period'),
|
||||
modalDirection: document.getElementById('modal-direction'),
|
||||
modalAmount: document.getElementById('modal-amount'),
|
||||
modalCurrentPrice: document.getElementById('modal-current-price'),
|
||||
modalPnl: document.getElementById('modal-pnl'),
|
||||
modalStartTime: document.getElementById('modal-start-time'),
|
||||
modalEndTime: document.getElementById('modal-end-time'),
|
||||
modalFooter: document.getElementById('modal-footer'),
|
||||
timerCountdown: document.getElementById('timer-countdown'),
|
||||
timerProgress: document.getElementById('timer-progress'),
|
||||
modalPrice: document.getElementById('modal-price'),
|
||||
modalProfit: document.getElementById('modal-profit'),
|
||||
resultOverlay: document.getElementById('result-overlay'),
|
||||
resultIcon: document.getElementById('result-icon'),
|
||||
resultTitle: document.getElementById('result-title'),
|
||||
resultAmount: document.getElementById('result-amount')
|
||||
};
|
||||
|
||||
function updateAvailableBalance() {
|
||||
const usdtBalance = parseFloat(userAssets.USDT || 0).toFixed(2);
|
||||
dom.usdtBalance.innerText = usdtBalance;
|
||||
dom.usdtBalance.innerText = parseFloat(userAssets.USDT || 0).toFixed(2);
|
||||
}
|
||||
|
||||
function initChartWidget(symbol) {
|
||||
if (chartWidget) { chartWidget = null; }
|
||||
if (dom.tvChartContainer.firstChild) dom.tvChartContainer.innerHTML = '';
|
||||
document.getElementById('tv_chart_container').innerHTML = '';
|
||||
chartWidget = new TradingView.widget({
|
||||
"autosize": true, "symbol": `BINANCE:${symbol}`,
|
||||
"interval": "1", "timezone": "Etc/UTC", "theme": "dark", "style": "3",
|
||||
"interval": "1", "timezone": "Etc/UTC", "theme": "dark", "style": "1",
|
||||
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>",
|
||||
"toolbar_bg": "#161a1e", "enable_publishing": false, "hide_side_toolbar": true, "allow_symbol_change": false,
|
||||
"container_id": "tv_chart_container",
|
||||
"studies": [],
|
||||
"studies": ["Volume@tv-basicstudies"],
|
||||
"overrides": {
|
||||
"paneProperties.background": "#161a1e",
|
||||
"paneProperties.vertGridProperties.color": "rgba(255, 255, 255, 0.0)",
|
||||
"paneProperties.horzGridProperties.color": "rgba(255, 255, 255, 0.0)",
|
||||
"paneProperties.vertGridProperties.color": "rgba(255, 255, 255, 0.03)",
|
||||
"paneProperties.horzGridProperties.color": "rgba(255, 255, 255, 0.03)",
|
||||
"scalesProperties.textColor" : "#848e9c",
|
||||
"mainSeriesProperties.style": 3,
|
||||
"mainSeriesProperties.lineStyle.color": "#007aff",
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -306,81 +386,70 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function connectWebSocket() {
|
||||
if (ws) ws.close();
|
||||
const tickerStreams = pairs.map(p => `${p.toLowerCase()}@ticker`).join('/');
|
||||
const depthStream = `${currentPair.toLowerCase()}@depth5@100ms`;
|
||||
const depthStream = `${currentPair.toLowerCase()}@depth20@100ms`;
|
||||
ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${tickerStreams}/${depthStream}`);
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const { stream, data } = JSON.parse(event.data);
|
||||
if (!data) return;
|
||||
|
||||
if (stream.endsWith('@ticker')) {
|
||||
marketData[data.s] = data;
|
||||
if (data.s === currentPair) {
|
||||
currentPrice = parseFloat(data.c);
|
||||
updatePriceUI(data);
|
||||
dom.priceDisplay.innerText = currentPrice.toLocaleString('en-US', {minimumFractionDigits: 4, maximumFractionDigits: 4});
|
||||
dom.priceDisplay.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
dom.midPrice.innerText = currentPrice.toFixed(2);
|
||||
dom.midPrice.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
|
||||
if (dom.modal.style.display === 'flex') {
|
||||
dom.modalCurrPrice.innerText = currentPrice.toFixed(4);
|
||||
dom.modalCurrPrice.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
}
|
||||
}
|
||||
if (dom.pairsList.offsetParent) renderPairs();
|
||||
} else if (stream.endsWith('@depth5@100ms')) {
|
||||
} else if (stream.endsWith('@depth20@100ms')) {
|
||||
renderOrderBook(data.bids, data.asks);
|
||||
}
|
||||
};
|
||||
ws.onerror = (error) => console.error("WebSocket Error:", error);
|
||||
ws.onclose = () => setTimeout(connectWebSocket, 5000);
|
||||
}
|
||||
|
||||
function renderOrderBook(bids, asks) {
|
||||
const formatRow = (row, type) => {
|
||||
const price = parseFloat(row[0]);
|
||||
const amount = parseFloat(row[1]);
|
||||
return `<div class="book-row">
|
||||
<div class="price ${type}">${price.toFixed(4)}</div>
|
||||
<div>${amount.toFixed(4)}</div>
|
||||
<div>${(price * amount).toFixed(2)}</div>
|
||||
</div>`;
|
||||
};
|
||||
dom.asksList.innerHTML = asks.slice(0, 8).reverse().map(ask => formatRow(ask, 'down')).join('');
|
||||
dom.bidsList.innerHTML = bids.slice(0, 8).map(bid => formatRow(bid, 'up')).join('');
|
||||
const formatRow = (row, type) => `<div class="ob-row"><div class="price ${type}">${parseFloat(row[0]).toFixed(2)}</div><div>${parseFloat(row[1]).toFixed(3)}</div></div>`;
|
||||
dom.asksList.innerHTML = asks.slice(0, 20).reverse().map(a => formatRow(a, 'down')).join('');
|
||||
dom.bidsList.innerHTML = bids.slice(0, 20).map(b => formatRow(b, 'up')).join('');
|
||||
}
|
||||
|
||||
function updatePriceUI(d) {
|
||||
if (!d) return;
|
||||
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 4, maximumFractionDigits: 4});
|
||||
document.title = `${priceStr} | ${d.s.replace('USDT', '/USDT')}`;
|
||||
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2});
|
||||
dom.currPrice.innerText = priceStr;
|
||||
dom.currPrice.style.color = color;
|
||||
dom.currChange.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
|
||||
dom.currChange.style.color = color;
|
||||
dom.hHigh.innerText = parseFloat(d.h).toLocaleString('en-US', {minimumFractionDigits: 4});
|
||||
dom.hLow.innerText = parseFloat(d.l).toLocaleString('en-US', {minimumFractionDigits: 4});
|
||||
dom.hHigh.innerText = parseFloat(d.h).toFixed(2);
|
||||
dom.hLow.innerText = parseFloat(d.l).toFixed(2);
|
||||
document.title = `${priceStr} | ${currentPair}`;
|
||||
}
|
||||
|
||||
function renderPairs() {
|
||||
const query = dom.pairSearch.value.toUpperCase();
|
||||
const filteredPairs = pairs.filter(p => p.includes(query));
|
||||
dom.pairsList.innerHTML = filteredPairs.map(p => {
|
||||
dom.pairsList.innerHTML = pairs.filter(p => p.includes(query)).map(p => {
|
||||
const d = marketData[p] || {};
|
||||
const icon = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
|
||||
const price = d.c ? parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 4}) : '--';
|
||||
const price = d.c ? parseFloat(d.c).toFixed(2) : '--';
|
||||
const change = d.P ? `${parseFloat(d.P) >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%` : '--';
|
||||
const color = (d.P && parseFloat(d.P) >= 0) ? '#0ecb81' : '#f6465d';
|
||||
return `<div class="pair-item ${p === currentPair ? 'active' : ''}" data-pair="${p}">
|
||||
<img src="${icon}" class="coin-icon" onerror="this.style.display='none'">
|
||||
<div style="flex:1"><div style="font-weight:600; font-size:13px">${p.replace('USDT','/USDT')}</div></div>
|
||||
<div style="text-align:right"><div style="font-size:13px; font-weight:600">${price}</div><div style="font-size:11px; color:${color}">${change}</div></div>
|
||||
<img src="https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png" class="coin-icon" onerror="this.style.opacity=0">
|
||||
<div style="flex:1"><div style="font-weight:600; color:white;">${p.replace('USDT','/USDT')}</div></div>
|
||||
<div style="text-align:right"><div style="font-weight:600; color:white;">${price}</div><div style="font-size:11px; color:${color}">${change}</div></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function switchPair(pair) {
|
||||
currentPair = pair;
|
||||
const coinName = pair.replace('USDT', '');
|
||||
dom.obAmountLabel.textContent = `<?php echo __('amount'); ?> (${coinName})`;
|
||||
dom.currPair.innerText = `${coinName}/USDT`;
|
||||
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${coinName.toLowerCase()}@2x.png`;
|
||||
updatePriceUI(marketData[pair]);
|
||||
dom.currPair.innerText = `${pair.replace('USDT', '/USDT')}`;
|
||||
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${pair.replace('USDT','').toLowerCase()}@2x.png`;
|
||||
initChartWidget(pair);
|
||||
renderPairs();
|
||||
connectWebSocket();
|
||||
@ -388,77 +457,159 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
function updatePotentialProfit() {
|
||||
const amount = parseFloat(dom.optionAmountInput.value) || 0;
|
||||
const profit = amount * (selectedRate / 100);
|
||||
dom.potentialProfit.innerText = `+${profit.toFixed(2)}`;
|
||||
dom.potentialProfit.innerText = `+${(amount * (selectedRate / 100)).toFixed(2)}`;
|
||||
}
|
||||
|
||||
function handleDurationChange(btn) {
|
||||
window.closeCountdownModal = () => {
|
||||
dom.modal.style.display = 'none';
|
||||
dom.resultOverlay.style.display = 'none';
|
||||
if (countdownInterval) clearInterval(countdownInterval);
|
||||
};
|
||||
|
||||
function startCountdown(duration, orderId) {
|
||||
let timeLeft = duration;
|
||||
const total = duration;
|
||||
dom.modalTimer.innerText = `00:${timeLeft < 10 ? '0' + timeLeft : timeLeft}`;
|
||||
dom.modalCircle.style.strokeDashoffset = 0;
|
||||
|
||||
if (countdownInterval) clearInterval(countdownInterval);
|
||||
|
||||
countdownInterval = setInterval(async () => {
|
||||
timeLeft--;
|
||||
if (timeLeft <= 0) {
|
||||
clearInterval(countdownInterval);
|
||||
dom.modalTimer.innerText = "00:00";
|
||||
dom.modalCircle.style.strokeDashoffset = 408;
|
||||
|
||||
// Show final result
|
||||
try {
|
||||
const r = await fetch(`api/get_option_orders.php?status=completed`);
|
||||
const orders = await r.json();
|
||||
const order = orders.find(o => o.id == orderId);
|
||||
if (order) {
|
||||
const profit = parseFloat(order.profit);
|
||||
dom.resultIcon.innerHTML = profit > 0 ? '<i class="fas fa-trophy" style="color:#0ecb81"></i>' : '<i class="fas fa-times-circle" style="color:#f6465d"></i>';
|
||||
dom.resultTitle.innerText = profit > 0 ? '<?php echo __('win'); ?>' : '<?php echo __('loss'); ?>';
|
||||
dom.resultTitle.style.color = profit > 0 ? '#0ecb81' : '#f6465d';
|
||||
dom.resultAmount.innerText = (profit > 0 ? '+' : '') + profit.toFixed(2) + ' USDT';
|
||||
dom.resultAmount.style.color = profit > 0 ? '#0ecb81' : '#f6465d';
|
||||
dom.resultOverlay.style.display = 'flex';
|
||||
} else {
|
||||
// Fallback if order not found immediately
|
||||
setTimeout(() => startCountdown(0, orderId), 2000);
|
||||
}
|
||||
} catch(e) {}
|
||||
return;
|
||||
}
|
||||
|
||||
const minutes = Math.floor(timeLeft / 60);
|
||||
const seconds = timeLeft % 60;
|
||||
dom.modalTimer.innerText = `${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`;
|
||||
|
||||
const offset = (408 * (total - timeLeft) / total);
|
||||
dom.modalCircle.style.strokeDashoffset = offset;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function placeOrder(direction) {
|
||||
const amount = parseFloat(dom.optionAmountInput.value);
|
||||
if (isNaN(amount) || amount < minAmount) { alert("<?php echo __('min_amount'); ?>: " + minAmount + " USDT"); return; }
|
||||
|
||||
const btn = direction === 'up' ? document.getElementById('btn-buy-up') : document.getElementById('btn-buy-down');
|
||||
const originalText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
|
||||
try {
|
||||
const response = await fetch('api/place_option_order.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
pair: currentPair, amount: amount, direction: direction,
|
||||
duration: selectedDuration, rate: selectedRate, price: currentPrice
|
||||
})
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// Show Modal
|
||||
dom.modalSymbol.innerText = currentPair.replace('USDT', '');
|
||||
dom.modalPeriod.innerText = selectedDuration + 's';
|
||||
dom.modalDirection.innerText = direction === 'up' ? '<?php echo __('buy_up'); ?>' : '<?php echo __('buy_down'); ?>';
|
||||
dom.modalDirection.className = 'modal-info-value ' + (direction === 'up' ? 'up' : 'down');
|
||||
dom.modalAmount.innerText = amount + ' USDT';
|
||||
dom.modalPrice.innerText = currentPrice.toFixed(4);
|
||||
dom.modalProfit.innerText = '+' + (amount * (selectedRate / 100)).toFixed(2) + ' USDT';
|
||||
dom.modalProfit.className = 'modal-info-value up';
|
||||
dom.modal.style.display = 'flex';
|
||||
dom.resultOverlay.style.display = 'none';
|
||||
|
||||
startCountdown(selectedDuration, result.order.id);
|
||||
fetchOrders();
|
||||
} else {
|
||||
alert(result.error || "<?php echo __('error_placing_order'); ?>");
|
||||
}
|
||||
} catch(e) { alert("<?php echo __('network_error'); ?>"); }
|
||||
finally { btn.disabled = false; btn.innerHTML = originalText; }
|
||||
}
|
||||
|
||||
async function fetchOrders() {
|
||||
try {
|
||||
const r = await fetch(`api/get_option_orders.php?status=${activeOrdersTab}`);
|
||||
const orders = await r.json();
|
||||
renderOrders(orders);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function renderOrders(orders) {
|
||||
if (!orders || orders.length === 0) {
|
||||
dom.recordsList.innerHTML = '<tr><td colspan="8" style="text-align:center; padding:40px; color:#848e9c"><?php echo __('no_records'); ?></td></tr>';
|
||||
return;
|
||||
}
|
||||
let html = `<thead><tr><th><?php echo __('pair'); ?></th><th><?php echo __('direction'); ?></th><th><?php echo __('amount'); ?></th><th><?php echo __('opening_price'); ?></th><th><?php echo __('closing_price'); ?></th><th><?php echo __('profit'); ?></th><th><?php echo __('status'); ?></th><th><?php echo __('time'); ?></th></tr></thead><tbody>`;
|
||||
orders.forEach(o => {
|
||||
const color = o.direction === 'up' ? '#0ecb81' : '#f6465d';
|
||||
const statusColor = o.status === 'completed' ? (parseFloat(o.profit) > 0 ? '#0ecb81' : '#f6465d') : '#ffc107';
|
||||
html += `<tr>
|
||||
<td style="font-weight:600">${o.symbol}</td>
|
||||
<td style="color:${color}">${o.direction === 'up' ? '<?php echo __('buy_up'); ?>' : '<?php echo __('buy_down'); ?>'}</td>
|
||||
<td>${parseFloat(o.amount).toFixed(2)}</td>
|
||||
<td>${parseFloat(o.opening_price).toFixed(4)}</td>
|
||||
<td>${o.closing_price ? parseFloat(o.closing_price).toFixed(4) : '--'}</td>
|
||||
<td style="color:${statusColor}">${o.status === 'completed' ? (parseFloat(o.profit) > 0 ? '+' : '') + parseFloat(o.profit).toFixed(2) : '--'}</td>
|
||||
<td style="color:${statusColor}">${o.status === 'completed' ? (parseFloat(o.profit) > 0 ? '<?php echo __('win'); ?>' : '<?php echo __('loss'); ?>') : '<?php echo __('in_progress'); ?>'}</td>
|
||||
<td style="color:#848e9c">${o.created_at}</td>
|
||||
</tr>`;
|
||||
});
|
||||
dom.recordsList.innerHTML = html + '</tbody>';
|
||||
}
|
||||
|
||||
dom.pairsList.addEventListener('click', e => { const item = e.target.closest('.pair-item'); if (item) switchPair(item.dataset.pair); });
|
||||
dom.pairSearch.addEventListener('input', renderPairs);
|
||||
document.querySelectorAll('.time-btn').forEach(btn => btn.addEventListener('click', () => {
|
||||
document.querySelector('.time-btn.active').classList.remove('active');
|
||||
btn.classList.add('active');
|
||||
selectedDuration = parseInt(btn.dataset.duration);
|
||||
selectedRate = parseInt(btn.dataset.rate);
|
||||
minAmount = parseInt(btn.dataset.min);
|
||||
dom.optionAmountInput.placeholder = `<?php echo __('min_amount_is_pl'); ?> ${minAmount}`;
|
||||
dom.optionAmountInput.placeholder = "<?php echo __('min_amount'); ?> " + minAmount;
|
||||
updatePotentialProfit();
|
||||
}
|
||||
|
||||
async function placeOrder(direction) {
|
||||
const amount = parseFloat(dom.optionAmountInput.value);
|
||||
if (isNaN(amount) || amount < minAmount) {
|
||||
alert(`<?php echo __('min_amount_is'); ?> ${minAmount} USDT`);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('api/place_option_order.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
pair: currentPair, amount: amount, direction: direction,
|
||||
duration: selectedDuration, rate: selectedRate
|
||||
})
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.order) {
|
||||
showCountdownModal(result.order);
|
||||
fetchOrders(); // Refresh orders list
|
||||
} else {
|
||||
alert(`<?php echo __('order_failed'); ?>: ${result.error || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchOrders() {
|
||||
const response = await fetch(`api/get_option_orders.php?status=${activeOrdersTab}`);
|
||||
const orders = await response.json();
|
||||
renderOrders(orders);
|
||||
}
|
||||
|
||||
function renderOrders(orders) { /* Unchanged */ }
|
||||
function showCountdownModal(order) { /* Unchanged */ }
|
||||
|
||||
// --- Event Listeners ---
|
||||
dom.pairsList.addEventListener('click', e => { e.target.closest('.pair-item')?.dataset.pair && switchPair(e.target.closest('.pair-item').dataset.pair); });
|
||||
dom.pairSearch.addEventListener('input', renderPairs);
|
||||
document.querySelectorAll('.time-btn').forEach(btn => btn.addEventListener('click', () => handleDurationChange(btn)));
|
||||
}));
|
||||
dom.optionAmountInput.addEventListener('input', updatePotentialProfit);
|
||||
document.getElementById('btn-buy-up').addEventListener('click', () => placeOrder('up'));
|
||||
document.getElementById('btn-buy-down').addEventListener('click', () => placeOrder('down'));
|
||||
document.querySelectorAll('.record-tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
document.querySelector('.record-tab.active').classList.remove('active');
|
||||
this.classList.add('active');
|
||||
activeOrdersTab = this.dataset.status;
|
||||
fetchOrders();
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('.record-tab').forEach(tab => tab.addEventListener('click', () => {
|
||||
document.querySelector('.record-tab.active').classList.remove('active');
|
||||
tab.classList.add('active');
|
||||
activeOrdersTab = tab.dataset.status;
|
||||
fetchOrders();
|
||||
}));
|
||||
|
||||
// --- Initial Load ---
|
||||
updateAvailableBalance();
|
||||
switchPair('BTCUSDT');
|
||||
updatePotentialProfit();
|
||||
fetchOrders(); // Initial fetch
|
||||
fetchOrdersInterval = setInterval(fetchOrders, 5000); // Poll for updates
|
||||
fetchOrders();
|
||||
setInterval(fetchOrders, 5000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include 'footer.php'; ?>
|
||||
<?php include 'footer.php'; ?>
|
||||
553
spot.php
553
spot.php
@ -1,477 +1,316 @@
|
||||
<?php
|
||||
include 'header.php';
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/i18n.php';
|
||||
|
||||
$user_id = $_SESSION['user_id'] ?? null;
|
||||
$user_assets = [];
|
||||
if ($user_id) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT currency, balance FROM assets WHERE user_id = ?");
|
||||
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$usdt_balance = $stmt->fetchColumn();
|
||||
$user_assets['USDT'] = (float)($usdt_balance ?: 0);
|
||||
|
||||
$stmt = $db->prepare("SELECT symbol, amount FROM user_assets WHERE user_id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$assets_data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
if ($assets_data) {
|
||||
$user_assets = $assets_data;
|
||||
foreach ($assets_data as $symbol => $amount) {
|
||||
$user_assets[$symbol] = (float)$amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
/* New Robust Layout */
|
||||
html, body {
|
||||
/* height: 100%; */
|
||||
/* overflow: hidden; */ /* UNLOCKED for scrolling */
|
||||
}
|
||||
.trading-page-wrapper {
|
||||
/* height: 100vh; */ /* UNLOCKED for scrolling */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh; /* Ensure it takes at least full height */
|
||||
}
|
||||
.trading-page-wrapper .navbar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.trading-container {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
min-height: 800px; /* Give a minimum height for content */
|
||||
background: #0b0e11;
|
||||
}
|
||||
.trading-page-wrapper { display: flex; flex-direction: column; min-height: 100vh; background: #0b0e11; }
|
||||
.trading-container { flex-grow: 1; display: flex; min-height: 800px; max-width: 1920px; margin: 0 auto; width: 100%; }
|
||||
.left-col { width: 280px; flex-shrink: 0; background: #161a1e; border-right: 1px solid #2b3139; display: flex; flex-direction: column; }
|
||||
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
||||
.right-col { width: 300px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
|
||||
|
||||
/* Columns */
|
||||
.left-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
||||
.center-col { flex: 1; min-width: 600px; display: flex; flex-direction: column; border-right: 1px solid #2b3139; min-height: 0; }
|
||||
.right-col { width: 320px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
|
||||
.chart-header { height: 55px; padding: 0 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; flex-shrink: 0; }
|
||||
.chart-box { height: 450px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.bottom-content-wrapper { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
|
||||
/* Center Column Content Layout */
|
||||
.chart-header { flex-shrink: 0; height: 50px; padding: 8px 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; gap: 20px; }
|
||||
.chart-box { height: 350px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
|
||||
.bottom-content-wrapper { flex-grow: 1; overflow-y: auto; min-height: 0; }
|
||||
|
||||
.order-box { padding: 12px 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
||||
.records-container { display: flex; flex-direction: column; background: #161a1e; flex-grow: 1; min-height: 400px; }
|
||||
.record-tabs { display: flex; padding: 0 20px; flex-shrink: 0; border-bottom: 1px solid #2b3139; }
|
||||
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 14px; font-weight: 600; color: #848e9c; cursor: pointer; border-bottom: 2px solid transparent; }
|
||||
.record-tab.active { color: white; border-bottom-color: var(--primary-color); }
|
||||
#records-list { padding: 10px 0; flex-grow: 1; overflow-y: auto; }
|
||||
.order-box { padding: 20px; background: #161a1e; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.records-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.record-tabs { display: flex; padding: 0 20px; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.record-tab { padding: 12px 0; margin-right: 25px; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; position: relative; }
|
||||
.record-tab.active { color: white; }
|
||||
.record-tab.active::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background: var(--primary-color); }
|
||||
|
||||
.right-col { display: flex; flex-direction: column;}
|
||||
.ob-panel, .trades-panel { flex-grow: 1; min-height: 0; display: flex; flex-direction: column;}
|
||||
.ob-content { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
||||
#asks-list { flex: 1 1 50%; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-end; }
|
||||
#bids-list { flex: 1 1 50%; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-start; }
|
||||
#trades-content { flex: 1; overflow-y: auto; min-height: 0; }
|
||||
|
||||
.left-col { overflow-y: auto; }
|
||||
#pairs-list { flex: 1; overflow-y: auto; }
|
||||
.ob-header, .trades-header { padding: 10px 15px; font-size: 12px; color: #848e9c; display: flex; justify-content: space-between; border-bottom: 1px solid #2b3139; background: #161a1e; flex-shrink: 0;}
|
||||
.ob-row, .trade-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; height: 20px; align-items: center; flex-shrink: 0; }
|
||||
.ob-bar { position: absolute; right: 0; top: 0; bottom: 0; opacity: 0.1; z-index: 0; transition: width 0.1s linear; }
|
||||
#mid-price { padding: 8px 15px; font-size: 16px; font-weight: 800; text-align: center; border-top: 1px solid #2b3139; border-bottom: 1px solid #2b3139; background: #161a1e; flex-shrink: 0;}
|
||||
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
|
||||
.stats-item { display: flex; flex-direction: column; justify-content: center; }
|
||||
.stats-label { font-size: 10px; color: #848e9c; margin-bottom: 1px; }
|
||||
.stats-value { font-size: 12px; font-weight: 600; color: white; }
|
||||
#websocket-status { display: none; /* Hiding this element as requested */ padding: 8px 15px; font-size: 12px; color: #fff; text-align: center; position: absolute; top: 0; left: 0; right: 0; z-index: 1000; }
|
||||
#records-list-container { height: 350px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
#records-list-container::-webkit-scrollbar { width: 4px; }
|
||||
#records-list-container::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
table { width: 100%; font-size: 12px; border-collapse: collapse; }
|
||||
th { color: #848e9c; font-weight: 500; text-align: left; padding: 10px 15px; border-bottom: 1px solid #2b3139; background: #161a1e; position: sticky; top: 0; z-index: 10; }
|
||||
td { padding: 10px 15px; border-bottom: 1px solid rgba(255,255,255,0.02); color: #eaecef; height: 38px; }
|
||||
|
||||
.category-tabs { display: flex; padding: 15px 15px 5px; gap: 8px; }
|
||||
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; transition: all 0.2s; border: 1px solid transparent; }
|
||||
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 12px; color: #848e9c; cursor: pointer; border: 1px solid transparent; }
|
||||
.category-tab.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
|
||||
.search-box { padding: 5px 15px 10px; position: relative; }
|
||||
.search-box i { position: absolute; left: 25px; top: 50%; transform: translateY(-50%); color: #848e9c; font-size: 12px; }
|
||||
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 4px; padding: 8px 10px 8px 30px; color: white; font-size: 13px; outline: none; }
|
||||
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; transition: background 0.2s; border-bottom: 1px solid rgba(255,255,255,0.02); }
|
||||
.pair-item.active { background: #1e2329; }
|
||||
.order-type-tabs { display: flex; gap: 20px; margin-bottom: 10px; }
|
||||
.order-type-tab { font-size: 13px; color: #848e9c; cursor: pointer; padding-bottom: 4px; border-bottom: 2px solid transparent; }
|
||||
.order-type-tab.active { color: white; border-bottom-color: var(--primary-color); font-weight: bold; }
|
||||
.input-group { background: #2b3139; border-radius: 4px; padding: 6px 12px; display: flex; align-items: center; margin-bottom: 6px; border: 1px solid transparent; }
|
||||
|
||||
.search-box { padding: 10px 15px 15px; position: relative; }
|
||||
.search-box i { position: absolute; left: 25px; top: 22px; color: #848e9c; font-size: 12px; }
|
||||
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 6px; padding: 8px 10px 8px 35px; color: white; font-size: 13px; outline: none; transition: 0.2s; }
|
||||
.search-box input:focus { border-color: var(--primary-color); }
|
||||
|
||||
#pairs-list { height: 684px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
#pairs-list::-webkit-scrollbar { width: 4px; }
|
||||
#pairs-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
|
||||
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.02); height: 38px; }
|
||||
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
|
||||
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
|
||||
|
||||
.input-group { background: #2b3139; border-radius: 4px; padding: 8px 12px; display: flex; align-items: center; margin-bottom: 10px; border: 1px solid #3c424a; }
|
||||
.input-group:focus-within { border-color: var(--primary-color); }
|
||||
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 13px; width: 100%; }
|
||||
.input-group .label { color: #848e9c; font-size: 11px; margin-right: 10px; }
|
||||
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 11px; }
|
||||
.slider-container { margin: 8px 0 15px; padding: 0 5px; }
|
||||
input[type=range] { -webkit-appearance: none; width: 100%; height: 3px; background: #2b3139; border-radius: 2px; outline: none; }
|
||||
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; background: var(--primary-color); border-radius: 50%; cursor: pointer; }
|
||||
.trade-btn { width: 100%; padding: 10px; border-radius: 4px; font-weight: bold; font-size: 14px; border: none; cursor: pointer; margin-top: 5px; }
|
||||
.btn-buy { background: #0ecb81; color: white; }
|
||||
.btn-sell { background: #f6465d; color: white; }
|
||||
.right-col-tabs { display: flex; border-bottom: 1px solid #2b3139; flex-shrink: 0;}
|
||||
.right-col-tab { flex: 1; text-align: center; padding: 10px 0; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; background: #161a1e; }
|
||||
.right-col-tab.active { color: white; background: #1e2329;}
|
||||
.input-group input { background: transparent; border: none; color: white; flex: 1; outline: none; font-size: 14px; width: 100%; }
|
||||
.input-group .label { color: #848e9c; font-size: 12px; margin-right: 10px; min-width: 40px; }
|
||||
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 12px; }
|
||||
.slider-container { margin: 15px 0 20px; }
|
||||
input[type=range] { width: 100%; height: 4px; background: #2b3139; border-radius: 2px; outline: none; }
|
||||
|
||||
.trade-btn { width: 100%; padding: 12px; border-radius: 4px; font-weight: bold; font-size: 15px; border: none; cursor: pointer; color: white; transition: 0.2s; }
|
||||
.btn-buy { background: #0ecb81; }
|
||||
.btn-sell { background: #f6465d; }
|
||||
.btn-buy:hover, .btn-sell:hover { filter: brightness(1.1); }
|
||||
|
||||
.right-col-tabs { display: flex; border-bottom: 1px solid #2b3139; flex-shrink: 0; }
|
||||
.right-col-tab { flex: 1; text-align: center; padding: 12px 0; font-size: 13px; font-weight: 600; color: #848e9c; cursor: pointer; }
|
||||
.right-col-tab.active { color: white; background: #1e2329; }
|
||||
.ob-header { display: flex; justify-content: space-between; padding: 8px 15px; font-size: 11px; color: #848e9c; border-bottom: 1px solid #2b3139; }
|
||||
.ob-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; }
|
||||
#ob-panel-content { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
|
||||
#ob-panel-content::-webkit-scrollbar { width: 4px; }
|
||||
#ob-panel-content::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
|
||||
</style>
|
||||
|
||||
<div class="trading-page-wrapper">
|
||||
<?php // The real header is included in the file, this is a placeholder for the structure. We will inject the actual header.php content. ?>
|
||||
<div class="trading-container">
|
||||
<div class="left-col d-none d-lg-flex flex-column">
|
||||
<!-- Left Column -->
|
||||
<div class="left-col d-none d-lg-flex">
|
||||
<div class="category-tabs">
|
||||
<div class="category-tab" onclick="location.href='options.php'"><?php echo __('nav_options'); ?></div>
|
||||
<div class="category-tab active" onclick="location.href='spot.php'"><?php echo __('nav_spot'); ?></div>
|
||||
<div class="category-tab" onclick="location.href='futures.php'"><?php echo __('nav_futures'); ?></div>
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
|
||||
</div>
|
||||
<div id="pairs-list" class="flex-grow-1" style="overflow-y: auto;"></div>
|
||||
<div class="search-box"><i class="fas fa-search"></i><input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>"></div>
|
||||
<div id="pairs-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Center Column -->
|
||||
<div class="center-col">
|
||||
<div id="websocket-status"></div>
|
||||
<div class="chart-header">
|
||||
<div style="display: flex; align-items: center; gap: 10px; padding-right: 15px; border-right: 1px solid #2b3139;">
|
||||
<img id="curr-icon" src="" class="coin-icon" style="margin:0; width:28px; height:28px;">
|
||||
<span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/--</span>
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<img id="curr-icon" src="" class="coin-icon">
|
||||
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div id="curr-price" style="font-size: 18px; font-weight: 800; color: #0ecb81; line-height: 1.2;">--</div>
|
||||
<div id="curr-change" style="font-size: 11px; font-weight: 600;">--</div>
|
||||
<div id="curr-price" style="font-size: 20px; font-weight: 800; color: #0ecb81;">--</div>
|
||||
<div id="curr-change" style="font-size: 12px; font-weight: 600;">--</div>
|
||||
</div>
|
||||
<div class="stats-item"><span class="stats-label"><?php echo __('24h_high'); ?></span><span class="stats-value" id="h-high">--</span></div>
|
||||
<div class="stats-item"><span class="stats-label"><?php echo __('24h_low'); ?></span><span class="stats-value" id="h-low">--</span></div>
|
||||
<div class="stats-item"><span class="stats-label"><?php echo __('24h_vol'); ?></span><span class="stats-value" id="h-vol">--</span></div>
|
||||
</div>
|
||||
|
||||
<div class="chart-box" id="tv_chart_container"></div>
|
||||
|
||||
<div class="bottom-content-wrapper">
|
||||
<div class="order-box">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 25px;">
|
||||
<div>
|
||||
<div class="order-type-tabs" data-side="buy">
|
||||
<div class="order-type-tab active" data-type="limit"><?php echo __('limit'); ?></div>
|
||||
<div class="order-type-tab" data-type="market"><?php echo __('market'); ?></div>
|
||||
</div>
|
||||
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="number" id="buy-price" placeholder="0.00"><span class="unit">USDT</span></div>
|
||||
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="buy-amount" placeholder="0.00"><span class="unit coin-name">--</span></div>
|
||||
<div class="slider-container"><input type="range" id="buy-slider" min="0" max="100" value="0" step="1"></div>
|
||||
<div style="font-size:11px; color:#848e9c; margin-bottom:8px; display:flex; justify-content:space-between"><span><?php echo __('available'); ?>:</span><span id="avail-usdt" style="font-weight:bold; color:white;">0.00 USDT</span></div>
|
||||
<div style="display: flex; gap: 20px; margin-bottom: 15px;">
|
||||
<div class="record-tab active"><?php echo __('market'); ?></div>
|
||||
</div>
|
||||
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="text" placeholder="<?php echo __('market'); ?>" disabled><span class="unit">USDT</span></div>
|
||||
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="spot-amount" step="0.0001" placeholder="0.00"><span class="unit coin-name">--</span></div>
|
||||
<div class="slider-container"><input type="range" id="spot-slider" min="0" max="100" value="0"></div>
|
||||
<div style="display: flex; gap: 15px;">
|
||||
<div style="flex: 1;">
|
||||
<div style="font-size: 12px; color: #848e9c; margin-bottom: 8px;"><?php echo __('available'); ?>: <span id="avail-usdt" style="color:white; font-weight:600">0.00</span> USDT</div>
|
||||
<button id="btn-buy" class="trade-btn btn-buy"><?php echo __('buy'); ?> <span class="coin-name">--</span></button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="order-type-tabs" data-side="sell">
|
||||
<div class="order-type-tab active" data-type="limit"><?php echo __('limit'); ?></div>
|
||||
<div class="order-type-tab" data-type="market"><?php echo __('market'); ?></div>
|
||||
</div>
|
||||
<div class="input-group"><span class="label"><?php echo __('price'); ?></span><input type="number" id="sell-price" placeholder="0.00"><span class="unit">USDT</span></div>
|
||||
<div class="input-group"><span class="label"><?php echo __('amount'); ?></span><input type="number" id="sell-amount" placeholder="0.00"><span class="unit coin-name">--</span></div>
|
||||
<div class="slider-container"><input type="range" id="sell-slider" min="0" max="100" value="0" step="1"></div>
|
||||
<div style="font-size:11px; color:#848e9c; margin-bottom:8px; display:flex; justify-content:space-between"><span><?php echo __('available'); ?>:</span><span id="avail-coin" style="font-weight:bold; color:white;">0.00 --</span></div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-size: 12px; color: #848e9c; margin-bottom: 8px;"><?php echo __('available'); ?>: <span id="avail-coin" style="color:white; font-weight:600">0.00</span> <span class="coin-name">--</span></div>
|
||||
<button id="btn-sell" class="trade-btn btn-sell"><?php echo __('sell'); ?> <span class="coin-name">--</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="records-container">
|
||||
<div class="record-tabs">
|
||||
<div class="record-tab active" data-status="open"><?php echo __('current_orders'); ?></div>
|
||||
<div class="record-tab active" data-status="open"><?php echo __('open_orders'); ?></div>
|
||||
<div class="record-tab" data-status="history"><?php echo __('history_orders'); ?></div>
|
||||
</div>
|
||||
<div id="records-list"></div>
|
||||
<div id="records-list-container">
|
||||
<table style="width:100%;" id="records-table"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-col d-none d-lg-flex">
|
||||
<!-- Right Column -->
|
||||
<div class="right-col d-none d-xl-flex">
|
||||
<div class="right-col-tabs">
|
||||
<div class="right-col-tab active" data-tab="ob"><?php echo __('order_book'); ?></div>
|
||||
<div class="right-col-tab" data-tab="trades"><?php echo __('trades'); ?></div>
|
||||
</div>
|
||||
|
||||
<div id="ob-panel" class="ob-panel">
|
||||
<div class="ob-header">
|
||||
<span><?php echo __('price'); ?>(USDT)</span>
|
||||
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
|
||||
<span><?php echo __('total'); ?></span>
|
||||
</div>
|
||||
<div class="ob-content">
|
||||
<div id="ob-panel" style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
|
||||
<div class="ob-header"><span><?php echo __('price'); ?></span><span><?php echo __('amount'); ?></span></div>
|
||||
<div id="ob-panel-content">
|
||||
<div id="asks-list"></div>
|
||||
<div id="mid-price">--</div>
|
||||
<div id="mid-price" style="padding:10px 15px; text-align:center; font-weight:800; font-size:16px; border-top:1px solid #2b3139; border-bottom:1px solid #2b3139;">--</div>
|
||||
<div id="bids-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="trades-panel" class="trades-panel" style="display: none;">
|
||||
<div class="trades-header">
|
||||
<span><?php echo __('price'); ?>(USDT)</span>
|
||||
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
|
||||
<span><?php echo __('time'); ?></span>
|
||||
</div>
|
||||
<div id="trades-content"></div>
|
||||
<div id="trades-panel" style="display:none; overflow-y:auto; flex:1;">
|
||||
<div id="trades-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
<script>
|
||||
const userAssets = <?php echo json_encode($user_assets); ?>;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// --- Globals ---
|
||||
let currentPair = 'BTCUSDT';
|
||||
let currentPrice = 0;
|
||||
let ws;
|
||||
const marketData = {};
|
||||
const pairs = [
|
||||
'BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT',
|
||||
'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT',
|
||||
'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'
|
||||
];
|
||||
|
||||
// --- DOM Elements ---
|
||||
let currentPair = 'BTCUSDT', currentPrice = 0, ws, chartWidget;
|
||||
const marketData = {}, pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'];
|
||||
const dom = {
|
||||
websocketStatus: document.getElementById('websocket-status'),
|
||||
currIcon: document.getElementById('curr-icon'),
|
||||
currPair: document.getElementById('curr-pair'),
|
||||
currPrice: document.getElementById('curr-price'),
|
||||
currChange: document.getElementById('curr-change'),
|
||||
hHigh: document.getElementById('h-high'),
|
||||
hLow: document.getElementById('h-low'),
|
||||
hVol: document.getElementById('h-vol'),
|
||||
tvChartContainer: document.getElementById('tv_chart_container'),
|
||||
tvContainer: document.getElementById('tv_chart_container'),
|
||||
pairsList: document.getElementById('pairs-list'),
|
||||
pairSearch: document.getElementById('pair-search'),
|
||||
asksList: document.getElementById('asks-list'),
|
||||
bidsList: document.getElementById('bids-list'),
|
||||
midPrice: document.getElementById('mid-price'),
|
||||
tradesContent: document.getElementById('trades-content'),
|
||||
coinNameSpans: document.querySelectorAll('.coin-name'),
|
||||
buyPriceInput: document.getElementById('buy-price'),
|
||||
sellPriceInput: document.getElementById('sell-price'),
|
||||
availUsdtSpan: document.getElementById('avail-usdt'),
|
||||
availCoinSpan: document.getElementById('avail-coin')
|
||||
availUsdt: document.getElementById('avail-usdt'),
|
||||
availCoin: document.getElementById('avail-coin'),
|
||||
coinNames: document.querySelectorAll('.coin-name'),
|
||||
amountInput: document.getElementById('spot-amount'),
|
||||
slider: document.getElementById('spot-slider'),
|
||||
recordsTable: document.getElementById('records-table')
|
||||
};
|
||||
|
||||
// --- Balance Management ---
|
||||
function updateAvailableBalances() {
|
||||
const baseCurrency = currentPair.replace('USDT', '');
|
||||
const usdtBalance = parseFloat(userAssets.USDT || 0).toFixed(2);
|
||||
const coinBalance = parseFloat(userAssets[baseCurrency] || 0).toFixed(6);
|
||||
|
||||
if (dom.availUsdtSpan) {
|
||||
dom.availUsdtSpan.innerText = `${usdtBalance} USDT`;
|
||||
}
|
||||
if (dom.availCoinSpan) {
|
||||
dom.availCoinSpan.innerText = `${coinBalance} ${baseCurrency}`;
|
||||
}
|
||||
function updateBalances() {
|
||||
const coin = currentPair.replace('USDT', '');
|
||||
dom.availUsdt.innerText = parseFloat(userAssets.USDT || 0).toFixed(2);
|
||||
dom.availCoin.innerText = parseFloat(userAssets[coin] || 0).toFixed(4);
|
||||
dom.coinNames.forEach(el => el.innerText = coin);
|
||||
}
|
||||
|
||||
// --- TradingView Chart ---
|
||||
function initChartWidget(symbol) {
|
||||
if (dom.tvChartContainer.firstChild) {
|
||||
dom.tvChartContainer.innerHTML = '';
|
||||
}
|
||||
const widget = new TradingView.widget({
|
||||
"autosize": true,
|
||||
"symbol": `BINANCE:${symbol}`,
|
||||
"interval": "15",
|
||||
"timezone": "Etc/UTC",
|
||||
"theme": "dark",
|
||||
"style": "1",
|
||||
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>",
|
||||
"toolbar_bg": "#f1f3f6",
|
||||
"enable_publishing": false,
|
||||
"withdateranges": true,
|
||||
"hide_side_toolbar": true,
|
||||
"allow_symbol_change": false,
|
||||
"container_id": "tv_chart_container",
|
||||
"studies": [
|
||||
"Volume@tv-basicstudies"
|
||||
],
|
||||
"overrides": {
|
||||
"paneProperties.background": "#161a1e",
|
||||
"paneProperties.vertGridProperties.color": "rgba(255, 255, 255, 0.05)",
|
||||
"paneProperties.horzGridProperties.color": "rgba(255, 255, 255, 0.05)",
|
||||
"scalesProperties.textColor" : "#848e9c",
|
||||
}
|
||||
function initChart(symbol) {
|
||||
dom.tvContainer.innerHTML = '';
|
||||
new TradingView.widget({
|
||||
"autosize": true, "symbol": `BINANCE:${symbol}`, "interval": "15", "theme": "dark", "style": "1",
|
||||
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>", "container_id": "tv_chart_container",
|
||||
"hide_side_toolbar": true, "allow_symbol_change": false, "studies": ["Volume@tv-basicstudies"],
|
||||
"overrides": { "paneProperties.background": "#161a1e" }
|
||||
});
|
||||
}
|
||||
|
||||
// --- WebSocket Connection ---
|
||||
function connectWebSocket() {
|
||||
function connectWS() {
|
||||
if (ws) ws.close();
|
||||
|
||||
// setWebsocketStatus('<?php echo __('websocket_connecting'); ?>', '#ffc107'); // Hidden
|
||||
|
||||
const streams = pairs.map(p => `${p.toLowerCase()}@ticker`).concat([`${currentPair.toLowerCase()}@depth20@100ms`, `${currentPair.toLowerCase()}@trade`]);
|
||||
const streams = pairs.map(p => `${p.toLowerCase()}@ticker`).concat([`${currentPair.toLowerCase()}@depth20@100ms`]);
|
||||
ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${streams.join('/')}`);
|
||||
|
||||
ws.onopen = () => {
|
||||
// setWebsocketStatus('<?php echo __('websocket_connected'); ?>', '#0ecb81', 2000); // Hidden
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const { stream, data } = JSON.parse(event.data);
|
||||
ws.onmessage = (e) => {
|
||||
const { stream, data } = JSON.parse(e.data);
|
||||
if (!data) return;
|
||||
|
||||
const type = stream.split('@')[1];
|
||||
|
||||
if (type === 'ticker') {
|
||||
marketData[data.s] = data;
|
||||
if (data.s === currentPair) {
|
||||
currentPrice = parseFloat(data.c);
|
||||
updatePriceUI(data);
|
||||
dom.currPrice.innerText = currentPrice.toLocaleString('en-US', {minimumFractionDigits:2});
|
||||
dom.currChange.innerText = (data.P >= 0 ? '+' : '') + parseFloat(data.P).toFixed(2) + '%';
|
||||
dom.currPrice.style.color = dom.currChange.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
dom.midPrice.innerText = currentPrice.toFixed(2);
|
||||
}
|
||||
if (dom.pairsList.offsetParent) renderPairs();
|
||||
} else if (type.startsWith('depth')) {
|
||||
renderOrderBook(data.bids, data.asks);
|
||||
} else if (type === 'trade') {
|
||||
renderTrades(data);
|
||||
const row = (p, q, c) => `<div class="ob-row" onclick="document.getElementById('spot-amount').value=${parseFloat(q).toFixed(4)};"><span style="color:${c}">${parseFloat(p).toFixed(2)}</span><span>${parseFloat(q).toFixed(4)}</span></div>`;
|
||||
dom.asksList.innerHTML = data.asks.slice(0, 20).reverse().map(a => row(a[0], a[1], '#f6465d')).join('');
|
||||
dom.bidsList.innerHTML = data.bids.slice(0, 20).map(b => row(b[0], b[1], '#0ecb81')).join('');
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket Error:', error);
|
||||
// setWebsocketStatus('<?php echo __('websocket_error'); ?>', '#f6465d'); // Hidden
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
// setWebsocketStatus('<?php echo __('websocket_disconnected'); ?>', '#f6465d', 5000); // Hidden
|
||||
setTimeout(connectWebSocket, 5000);
|
||||
};
|
||||
}
|
||||
|
||||
function setWebsocketStatus(message, color, hideAfter = 0) {
|
||||
// This function is kept for logic continuity but does nothing visually.
|
||||
return;
|
||||
}
|
||||
|
||||
// --- UI Rendering ---
|
||||
function updatePriceUI(d) {
|
||||
if (!d) return;
|
||||
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
||||
|
||||
document.title = `${priceStr} | ${d.s.replace('USDT', '/USDT')}`;
|
||||
dom.currPrice.innerText = priceStr;
|
||||
dom.currPrice.style.color = color;
|
||||
dom.currChange.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
|
||||
dom.currChange.style.color = color;
|
||||
dom.hHigh.innerText = parseFloat(d.h).toLocaleString('en-US', {minimumFractionDigits: 2});
|
||||
dom.hLow.innerText = parseFloat(d.l).toLocaleString('en-US', {minimumFractionDigits: 2});
|
||||
dom.hVol.innerText = `${(parseFloat(d.v) / 1000).toFixed(1)}K`;
|
||||
dom.midPrice.innerText = priceStr;
|
||||
dom.midPrice.style.color = color;
|
||||
ws.onclose = () => setTimeout(connectWS, 5000);
|
||||
}
|
||||
|
||||
function renderPairs() {
|
||||
const query = dom.pairSearch.value.toUpperCase();
|
||||
const filteredPairs = pairs.filter(p => p.includes(query));
|
||||
|
||||
dom.pairsList.innerHTML = filteredPairs.map(p => {
|
||||
const q = document.getElementById('pair-search').value.toUpperCase();
|
||||
dom.pairsList.innerHTML = pairs.filter(p => p.includes(q)).map(p => {
|
||||
const d = marketData[p] || {};
|
||||
const icon = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
|
||||
const price = d.c ? parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
|
||||
const change = d.P ? `${parseFloat(d.P) >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%` : '--';
|
||||
const color = (d.P && parseFloat(d.P) >= 0) ? '#0ecb81' : '#f6465d';
|
||||
|
||||
return `
|
||||
<div class="pair-item ${p === currentPair ? 'active' : ''}" data-pair="${p}">
|
||||
<img src="${icon}" class="coin-icon" onerror="this.style.display='none'">
|
||||
<div style="flex:1"><div style="font-weight:600; font-size:13px">${p.replace('USDT', '/USDT')}</div></div>
|
||||
<div style="text-align:right">
|
||||
<div style="font-size:13px; font-weight:600">${price}</div>
|
||||
<div style="font-size:11px; color:${color}">${change}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
return `<div class="pair-item ${p === currentPair ? 'active' : ''}" onclick="switchPair('${p}')">
|
||||
<img src="https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png" class="coin-icon" onerror="this.style.opacity=0">
|
||||
<div style="flex:1; color:white; font-weight:600">${p.replace('USDT','/USDT')}</div>
|
||||
<div style="text-align:right"><div style="color:white; font-weight:600">${d.c ? parseFloat(d.c).toFixed(2) : '--'}</div><div style="color:${d.P>=0?'#0ecb81':'#f6465d'}; font-size:11px">${d.P?(d.P>=0?'+':'')+parseFloat(d.P).toFixed(2)+'%':'--'}</div></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderOrderBook(bids, asks) {
|
||||
const renderList = (element, data, isAsks) => {
|
||||
if (!element) return;
|
||||
const maxRows = Math.floor(element.offsetHeight / 20);
|
||||
if (maxRows <= 0) return;
|
||||
window.switchPair = (p) => {
|
||||
currentPair = p;
|
||||
dom.currPair.innerText = p.replace('USDT', '/USDT');
|
||||
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
|
||||
initChart(p);
|
||||
updateBalances();
|
||||
connectWS();
|
||||
};
|
||||
|
||||
const relevantData = isAsks ? data.slice(0, maxRows).reverse() : data.slice(0, maxRows);
|
||||
const maxVol = Math.max(...relevantData.map(([, q]) => parseFloat(q))) || 1;
|
||||
dom.slider.oninput = () => {
|
||||
const percent = dom.slider.value / 100;
|
||||
const coin = currentPair.replace('USDT', '');
|
||||
// Simplified slider logic
|
||||
};
|
||||
|
||||
element.innerHTML = relevantData.map(([price, quantity]) => {
|
||||
const p = parseFloat(price), q = parseFloat(quantity);
|
||||
const barWidth = (q / maxVol) * 100;
|
||||
const color = isAsks ? '#f6465d' : '#0ecb81';
|
||||
const bgColor = isAsks ? 'rgba(246, 70, 93, 0.2)' : 'rgba(14, 203, 129, 0.2)';
|
||||
return `
|
||||
<div class="ob-row" onclick="setPrice(${p})">
|
||||
<div class="ob-bar" style="width:${barWidth}%; background-color:${bgColor};"></div>
|
||||
<span style="color:${color}; z-index:1;">${p.toFixed(2)}</span>
|
||||
<span style="z-index:1;">${q.toFixed(4)}</span>
|
||||
<span style="z-index:1;">${(p * q).toFixed(2)}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
};
|
||||
renderList(dom.asksList, asks, true);
|
||||
renderList(dom.bidsList, bids, false);
|
||||
async function placeOrder(side) {
|
||||
const amount = parseFloat(dom.amountInput.value);
|
||||
if (!amount || amount <= 0) { alert("<?php echo __('amount'); ?> > 0"); return; }
|
||||
const price = currentPrice;
|
||||
const total = amount * price;
|
||||
|
||||
try {
|
||||
const r = await fetch('api/place_order.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
symbol: currentPair, type: 'spot', side: side, order_type: 'market',
|
||||
price: price, amount: amount, total: total
|
||||
})
|
||||
});
|
||||
const res = await r.json();
|
||||
if (res.success) { alert("<?php echo __('order_placed'); ?>"); fetchOrders(); }
|
||||
else { alert(res.error || "Error"); }
|
||||
} catch(e) { alert("Network Error"); }
|
||||
}
|
||||
|
||||
function renderTrades(trade) {
|
||||
if (!dom.tradesContent) return;
|
||||
const color = trade.m ? '#f6465d' : '#0ecb81'; // m: is maker (seller is maker)
|
||||
const time = new Date(trade.T).toLocaleTimeString('en-GB');
|
||||
const row = `
|
||||
<div class="trade-row">
|
||||
<span style="color:${color}">${parseFloat(trade.p).toFixed(2)}</span>
|
||||
<span>${parseFloat(trade.q).toFixed(4)}</span>
|
||||
<span>${time}</span>
|
||||
</div>`;
|
||||
dom.tradesContent.insertAdjacentHTML('afterbegin', row);
|
||||
if (dom.tradesContent.children.length > 50) {
|
||||
dom.tradesContent.removeChild(dom.tradesContent.lastChild);
|
||||
}
|
||||
document.getElementById('btn-buy').onclick = () => placeOrder('buy');
|
||||
document.getElementById('btn-sell').onclick = () => placeOrder('sell');
|
||||
|
||||
let activeTab = 'open';
|
||||
async function fetchOrders() {
|
||||
try {
|
||||
const r = await fetch(`api/get_orders.php?type=spot&status=${activeTab}`);
|
||||
const res = await r.json();
|
||||
if (res.success) renderOrders(res.data);
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function switchPair(pair) {
|
||||
currentPair = pair;
|
||||
const coinName = pair.replace('USDT', '');
|
||||
|
||||
dom.currPair.innerText = `${coinName}/USDT`;
|
||||
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${coinName.toLowerCase()}@2x.png`;
|
||||
dom.coinNameSpans.forEach(el => el.innerText = coinName);
|
||||
|
||||
updatePriceUI(marketData[pair]);
|
||||
updateAvailableBalances();
|
||||
initChartWidget(pair);
|
||||
renderPairs();
|
||||
|
||||
// Clear old data and resubscribe
|
||||
dom.asksList.innerHTML = '';
|
||||
dom.bidsList.innerHTML = '';
|
||||
dom.tradesContent.innerHTML = '';
|
||||
connectWebSocket();
|
||||
}
|
||||
|
||||
window.setPrice = (price) => {
|
||||
dom.buyPriceInput.value = price;
|
||||
dom.sellPriceInput.value = price;
|
||||
}
|
||||
|
||||
// --- Event Listeners ---
|
||||
dom.pairsList.addEventListener('click', (e) => {
|
||||
const pairItem = e.target.closest('.pair-item');
|
||||
if (pairItem && pairItem.dataset.pair) {
|
||||
switchPair(pairItem.dataset.pair);
|
||||
}
|
||||
});
|
||||
|
||||
dom.pairSearch.addEventListener('input', renderPairs);
|
||||
|
||||
document.querySelectorAll('.right-col-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelector('.right-col-tab.active').classList.remove('active');
|
||||
tab.classList.add('active');
|
||||
const isOb = tab.dataset.tab === 'ob';
|
||||
document.getElementById('ob-panel').style.display = isOb ? 'flex' : 'none';
|
||||
document.getElementById('trades-panel').style.display = isOb ? 'none' : 'flex';
|
||||
function renderOrders(orders) {
|
||||
if (!orders || orders.length === 0) { dom.recordsTable.innerHTML = '<tr><td colspan="6" style="text-align:center; padding:30px; color:#848e9c"><?php echo __('no_records'); ?></td></tr>'; return; }
|
||||
let h = `<thead><tr><th><?php echo __('pair'); ?></th><th><?php echo __('direction'); ?></th><th><?php echo __('price'); ?></th><th><?php echo __('amount'); ?></th><th><?php echo __('status'); ?></th><th><?php echo __('time'); ?></th></tr></thead><tbody>`;
|
||||
orders.forEach(o => {
|
||||
h += `<tr><td>${o.symbol}</td><td style="color:${o.side==='buy'?'#0ecb81':'#f6465d'}">${o.side==='buy'?'<?php echo __('buy'); ?>':'<?php echo __('sell'); ?>'}</td><td>${parseFloat(o.price).toFixed(2)}</td><td>${parseFloat(o.amount).toFixed(4)}</td><td>${o.status}</td><td style="color:#848e9c">${o.created_at}</td></tr>`;
|
||||
});
|
||||
dom.recordsTable.innerHTML = h + '</tbody>';
|
||||
}
|
||||
|
||||
document.querySelectorAll('.record-tab').forEach(t => t.onclick = () => {
|
||||
if (!t.dataset.status) return;
|
||||
document.querySelector('.record-tabs .active').classList.remove('active');
|
||||
t.classList.add('active');
|
||||
activeTab = t.dataset.status;
|
||||
fetchOrders();
|
||||
});
|
||||
|
||||
// --- Initial Load ---
|
||||
switchPair('BTCUSDT');
|
||||
fetchOrders();
|
||||
setInterval(fetchOrders, 5000);
|
||||
});
|
||||
</script>
|
||||
<!-- This script is for the TradingView widget -->
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/external-embedding/embed-widget-advanced-chart.js"></script>
|
||||
|
||||
|
||||
<?php include 'footer.php'; ?>
|
||||
Loading…
x
Reference in New Issue
Block a user