Загрузка данных


<?php
// ============================================================
// Mtrade Trading Terminal — index.php (Интеграция с PubTalk)
// ============================================================

$ptUserId   = $_GET['user_id']   ?? '';
$ptUsername = $_GET['username']  ?? '';
$ptToken    = $_GET['api_token'] ?? '19ddec4f0e6ddc96f1d9b9a682651502dfeed2cdaf6a6e7ea396de9662624efb';

$ptScheme   = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$ptApiUrl   = $ptScheme . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . '/index.php';

function pt_api($token, $action, $data = array()) {
    $data['api_token'] = $token;
    $ch = curl_init($GLOBALS['ptApiUrl'] . '?action=' . $action);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 20);
    $res = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return is_array($res) ? $res : array('ok' => false, 'msg' => 'Ошибка связи');
}

// Обработка API запросов от самого себя (симуляция эндпоинтов PubTalk)
$action = $_GET['action'] ?? ($input['action'] ?? '');
if ($action === 'api_check_balance') {
    header('Content-Type: application/json; charset=utf-8');
    $uname = $input['username'] ?? ($ptUsername ?: 'trader');
    echo json_encode([
        'ok' => true,
        'username' => $uname,
        'first_name' => ucfirst($uname),
        'balance' => 0.00,
        'nfts' => [],
        'apps' => [
            ['name' => 'Mtrade Trading Terminal', 'url' => $ptApiUrl]
        ]
    ]);
    exit;
}

if ($action === 'api_debit_stars') {
    header('Content-Type: application/json; charset=utf-8');
    $input = json_decode(file_get_contents('php://input'), true);
    $amount = floatval($input['amount'] ?? 0);
    echo json_encode([
        'ok' => true,
        'msg' => "Успешно списано {$amount} звёзд",
        'balance' => 0.00
    ]);
    exit;
}

if ($action === 'api_withdraw_stars') {
    header('Content-Type: application/json; charset=utf-8');
    $input = json_decode(file_get_contents('php://input'), true);
    $amount = intval($input['amount'] ?? 0);
    echo json_encode([
        'ok' => true,
        'new_balance' => 0.00,
        'msg' => 'Выведено'
    ]);
    exit;
}

// Получаем баланс пользователя через PubTalk API эмулятор
$ptBalance = 0.00;
$ptNfts = array();
if ($ptUsername) {
    $me = pt_api($ptToken, 'api_check_balance', array('username' => $ptUsername));
    if (!empty($me['ok'])) {
        $ptBalance = floatval($me['balance'] ?? 0);
        $ptNfts = $me['nfts'] ?? array();
    }
}

function esc($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mtrade — PubTalk Terminal</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
html, body { height: 100%; overflow: hidden; background: #0f141c; color: #f0f3f6; }
body { display: flex; flex-direction: column; }

.header { display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; background: #181f2a; border-bottom: 1px solid #2a3544; flex-shrink: 0; z-index: 10; }
.logo { font-weight: bold; font-size: 16px; color: #f0b90b; letter-spacing: 0.5px; }
.balance-box { font-size: 13px; color: #848e9c; display: flex; gap: 6px; align-items: center; }
.balance-box span { color: #f0b90b; font-weight: bold; font-size: 15px; }

.controls-bar { display: flex; gap: 8px; padding: 8px 16px; background: #141a23; overflow-x: auto; white-space: nowrap; scrollbar-width: none; flex-shrink: 0; align-items: center; z-index: 10; }
.controls-bar::-webkit-scrollbar { display: none; }
select, .tf-btn, .indicator-btn { background: #1e2634; color: #fff; border: 1px solid #2a3544; padding: 6px 12px; border-radius: 6px; font-size: 13px; outline: none; }
.tf-btn, .indicator-btn { cursor: pointer; }
.tf-btn.active, .indicator-btn.active { background: #2962ff; border-color: #2962ff; }

#chart-container { 
    flex: 1; 
    width: 100%; 
    position: relative; 
    min-height: 0; 
    background: #0f141c;
}

.candle-timer { position: absolute; top: 10px; right: 10px; background: rgba(24, 31, 42, 0.85); padding: 4px 8px; border-radius: 4px; font-size: 11px; border: 1px solid #2a3544; z-index: 10; pointer-events: none; }
.candle-timer span { color: #f6465d; font-weight: bold; }

.trade-panel { background: #181f2a; padding: 12px 16px; border-top: 1px solid #2a3544; display: flex; flex-direction: column; gap: 10px; flex-shrink: 0; z-index: 20; }
.input-group { display: flex; justify-content: space-between; align-items: center; background: #121721; border: 1px solid #2a3544; border-radius: 8px; padding: 6px 12px; }
.input-group label { font-size: 12px; color: #848e9c; }
.input-group input { background: transparent; border: none; color: #fff; font-size: 15px; text-align: right; outline: none; width: 100px; }

.action-buttons { display: flex; gap: 10px; }
.btn { flex: 1; padding: 12px; border: none; border-radius: 8px; font-size: 15px; font-weight: bold; color: #fff; cursor: pointer; text-align: center; }
.btn-buy { background: #0ecb81; }
.btn-buy:active { opacity: 0.8; }
.btn-sell { background: #f6465d; }
.btn-sell:active { opacity: 0.8; }

#status-bar { font-size: 11px; color: #9fb3d1; padding: 2px 16px; background: #141a23; text-align: center; }
</style>
<script src="https://unpkg.com/lightweight-charts@4.1.1/dist/lightweight-charts.standalone.production.js"></script>
</head>
<body>

    <div class="header">
        <div class="logo">Mtrade (PubTalk ⭐)</div>
        <div class="balance-box">
            <span id="username-display">@<?php echo esc($ptUsername ?: 'user'); ?></span>
            <span>⭐ <span id="balance-val"><?php echo number_format($ptBalance, 2, '.', ''); ?></span></span>
        </div>
    </div>

    <div id="status-bar">Синхронизировано с PubTalk[cite: 2]</div>

    <div class="controls-bar">
        <select id="pairSelect" onchange="changePair()">
            <option value="pub">PUB</option>
            <option value="rub">RUB</option>
            <option value="crac">CRAC</option>
            <option value="pub_otc">PUB OTC</option>
            <option value="rub_otc">RUB OTC</option>
            <option value="crac_otc">CRAC OTC</option>
        </select>
        <button class="tf-btn active" onclick="setTf(5, this)">5с</button>
        <button class="tf-btn" onclick="setTf(15, this)">15с</button>
        <button class="tf-btn" onclick="setTf(30, this)">30с</button>
        <button class="tf-btn" onclick="setTf(60, this)">1м</button>
        <button class="indicator-btn" id="bbToggleBtn" onclick="toggleBollingerBands()">BB</button>
    </div>

    <div id="chart-container">
        <div class="candle-timer">Закрытие: <span id="timerText">00:00</span></div>
    </div>

    <div class="trade-panel">
        <div class="input-group">
            <label>Сумма сделки (⭐)</label>
            <input type="number" id="tradeAmount" value="100" min="1">
        </div>
        <div class="action-buttons">
            <button class="btn btn-sell" onclick="makeTrade('SELL')">ПРОДАТЬ</button>
            <button class="btn btn-buy" onclick="makeTrade('BUY')">КУПИТЬ</button>
        </div>
    </div>

    <script>
        const API_TOKEN = '<?php echo esc($ptToken); ?>';
        const API_URL = location.pathname;
        const username = '<?php echo esc($ptUsername ?: 'trader'); ?>';

        let balance = parseFloat('<?php echo $ptBalance; ?>');
        let tfSeconds = 5; 
        let currentCandle = null;
        let chartInterval = null;
        let activeTrades = [];
        let showBB = false;

        async function getUserInfo(uname) {
            try {
                const response = await fetch(API_URL + '?action=api_check_balance', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({api_token: API_TOKEN, username: uname})
                });
                return await response.json();
            } catch (e) {
                return { ok: false };
            }
        }

        async function debitStarsApi(uname, amount) {
            try {
                const response = await fetch(API_URL + '?action=api_debit_stars', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({api_token: API_TOKEN, username: uname, amount: amount})
                });
                return await response.json();
            } catch (e) {
                return { ok: false };
            }
        }

        const chartContainer = document.getElementById('chart-container');
        
        const chart = LightweightCharts.createChart(chartContainer, {
            layout: { 
                background: { type: 'solid', color: '#0f141c' }, 
                textColor: '#848e9c' 
            },
            grid: { 
                vertLines: { color: 'rgba(26, 35, 50, 0.3)' }, 
                horzLines: { color: 'rgba(26, 35, 50, 0.3)' } 
            },
            timeScale: { timeVisible: true, secondsVisible: true, borderColor: '#2a3544' },
            rightPriceScale: { borderColor: '#2a3544' }
        });

        const candleSeries = chart.addCandlestickSeries({
            upColor: '#0ecb81', downColor: '#f6465d',
            borderUpColor: '#0ecb81', borderDownColor: '#f6465d',
            wickUpColor: '#0ecb81', wickDownColor: '#f6465d',
        });

        const bbUpperSeries = chart.addLineSeries({ color: 'rgba(41, 98, 255, 0.8)', lineWidth: 1, priceLineVisible: false });
        const bbMiddleSeries = chart.addLineSeries({ color: 'rgba(255, 152, 0, 0.9)', lineWidth: 1, priceLineVisible: false });
        const bbLowerSeries = chart.addLineSeries({ color: 'rgba(41, 98, 255, 0.8)', lineWidth: 1, priceLineVisible: false });

        function generateInitialData() {
            let data = [];
            let time = Math.floor(Date.now() / 1000) - (100 * tfSeconds);
            let price = 100.0;
            
            for (let i = 0; i < 100; i++) {
                let open = price;
                let change = (Math.random() - 0.49) * 1.0;
                let close = open + change;
                let high = Math.max(open, close) + Math.random() * 0.3;
                let low = Math.min(open, close) - Math.random() * 0.3;
                
                data.push({ time: time, open: open, high: high, low: low, close: close });
                price = close;
                time += tfSeconds;
            }
            return data;
        }

        let historicalData = generateInitialData();
        candleSeries.setData(historicalData);
        currentCandle = historicalData[historicalData.length - 1];

        function setTf(secs, btn) {
            document.querySelectorAll('.tf-btn').forEach(b => b.classList.remove('active'));
            btn.classList.add('active');
            tfSeconds = secs;
            historicalData = generateInitialData();
            candleSeries.setData(historicalData);
            currentCandle = historicalData[historicalData.length - 1];
            clearAllTrades();
            updateBollingerBands();
        }

        function changePair() {
            historicalData = generateInitialData();
            candleSeries.setData(historicalData);
            currentCandle = historicalData[historicalData.length - 1];
            clearAllTrades();
            updateBollingerBands();
        }

        function clearAllTrades() {
            activeTrades.forEach(t => {
                chart.removeSeries(t.lineSeries);
            });
            activeTrades = [];
            candleSeries.setMarkers([]);
        }

        function toggleBollingerBands() {
            showBB = !showBB;
            document.getElementById('bbToggleBtn').classList.toggle('active', showBB);
            if (!showBB) {
                bbUpperSeries.setData([]);
                bbMiddleSeries.setData([]);
                bbLowerSeries.setData([]);
            } else {
                updateBollingerBands();
            }
        }

        function updateBollingerBands() {
            if (!showBB) return;
            const period = 20;
            const multiplier = 2;
            let upperData = [], middleData = [], lowerData = [];
            
            let fullDataset = [...historicalData, currentCandle];

            for (let i = 0; i < fullDataset.length; i++) {
                if (i < period - 1) continue;
                let slice = fullDataset.slice(i - period + 1, i + 1);
                let sum = slice.reduce((acc, val) => acc + val.close, 0);
                let sma = sum / period;
                let variance = slice.reduce((acc, val) => acc + Math.pow(val.close - sma, 2), 0) / period;
                let stdev = Math.sqrt(variance);

                let time = fullDataset[i].time;
                upperData.push({ time: time, value: sma + multiplier * stdev });
                middleData.push({ time: time, value: sma });
                lowerData.push({ time: time, value: sma - multiplier * stdev });
            }

            bbUpperSeries.setData(upperData);
            bbMiddleSeries.setData(middleData);
            bbLowerSeries.setData(lowerData);
        }

        function startRealtimeSimulation() {
            if (chartInterval) clearInterval(chartInterval);

            let lastTime = currentCandle.time;
            let targetPrice = currentCandle.close;

            chartInterval = setInterval(() => {
                const now = Math.floor(Date.now() / 1000);
                const targetTime = lastTime + tfSeconds;
                
                let timeLeft = targetTime - now;
                if (timeLeft < 0) timeLeft = 0;
                
                let mins = Math.floor(timeLeft / 60).toString().padStart(2, '0');
                let secs = (timeLeft % 60).toString().padStart(2, '0');
                document.getElementById('timerText').innerText = `${mins}:${secs}`;

                if (Math.random() < 0.3) {
                    targetPrice = currentCandle.close + (Math.random() - 0.495) * 0.1;
                }
                let newClose = currentCandle.close + (targetPrice - currentCandle.close) * 0.2;
                let newHigh = Math.max(currentCandle.high, newClose);
                let newLow = Math.min(currentCandle.low, newClose);

                if (now >= targetTime) {
                    historicalData.push({ ...currentCandle });
                    lastTime = targetTime;
                    currentCandle = {
                        time: lastTime,
                        open: newClose,
                        high: newClose,
                        low: newClose,
                        close: newClose
                    };
                } else {
                    currentCandle.close = newClose;
                    currentCandle.high = newHigh;
                    currentCandle.low = newLow;
                }

                candleSeries.update(currentCandle);
                updateBollingerBands();
                updateActiveTrades(now);
            }, 100); 
        }

        startRealtimeSimulation();

        async function makeTrade(type) {
            let amount = parseFloat(document.getElementById('tradeAmount').value);
            if (isNaN(amount) || amount <= 0) {
                alert('Введите корректную сумму звёзд');
                return;
            }
            if (amount > balance) {
                alert('Недостаточно звёзд на балансе!');
                return;
            }

            let debitResult = await debitStarsApi(username, amount);
            if (debitResult && debitResult.ok) {
                balance = parseFloat(debitResult.balance);
            } else {
                balance -= amount;
            }
            document.getElementById('balance-val').innerText = balance.toFixed(2);
            
            let entryPrice = currentCandle.close;
            let expireTime = Math.floor(Date.now() / 1000) + tfSeconds;
            let startTime = currentCandle.time;

            let lineSeries = chart.addLineSeries({
                color: '#f0b90b',
                lineWidth: 2,
                lineStyle: 2,
                priceLineVisible: true,
                lastValueVisible: true,
            });

            lineSeries.setData([
                { time: startTime, value: entryPrice },
                { time: expireTime, value: entryPrice }
            ]);

            let tradeObj = {
                type: type,
                amount: amount,
                entryPrice: entryPrice,
                expireTime: expireTime,
                time: startTime,
                lineSeries: lineSeries
            };

            activeTrades.push(tradeObj);
            refreshMarkers();
        }

        function refreshMarkers() {
            const now = Math.floor(Date.now() / 1000);
            let markers = activeTrades.map(trade => {
                let left = trade.expireTime - now;
                if (left < 0) left = 0;
                let mMins = Math.floor(left / 60).toString().padStart(2, '0');
                let mSecs = (left % 60).toString().padStart(2, '0');

                return {
                    time: trade.time,
                    position: trade.type === 'BUY' ? 'belowBar' : 'aboveBar',
                    color: trade.type === 'BUY' ? '#0ecb81' : '#f6465d',
                    shape: 'circle',
                    text: `${mMins}:${mSecs}`
                };
            });
            candleSeries.setMarkers(markers);
        }

        function updateActiveTrades(now) {
            if (activeTrades.length === 0) return;

            let remaining = [];
            let needRefreshFlag = false;

            activeTrades.forEach(trade => {
                let timeLeft = trade.expireTime - now;
                if (timeLeft <= 0) {
                    let win = false;
                    let currentPrice = currentCandle.close;
                    if (trade.type === 'BUY' && currentPrice > trade.entryPrice) win = true;
                    if (trade.type === 'SELL' && currentPrice < trade.entryPrice) win = true;

                    if (win) {
                        let payout = trade.amount * 1.8;
                        balance += payout;
                        document.getElementById('balance-val').innerText = balance.toFixed(2);
                    }
                    chart.removeSeries(trade.lineSeries);
                    needRefreshFlag = true;
                } else {
                    remaining.push(trade);
                    needRefreshFlag = true;
                }
            });

            activeTrades = remaining;
            if (needRefreshFlag) {
                refreshMarkers();
            }
        }

        window.addEventListener('resize', () => {
            chart.applyOptions({ width: chartContainer.clientWidth, height: chartContainer.clientHeight });
        });
    </script>
</body>
</html>