Загрузка данных
<?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>