Files
xau-traderbot/dashboard/static/twa.html
T

410 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>XAUUSD Trader Bot Mini App</title>
<!-- Telegram WebApp JS -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<style>
:root {
--bg-color: #0b0e14;
--card-bg: rgba(22, 28, 38, 0.75);
--border-color: rgba(255, 215, 0, 0.15);
--gold-accent: #f3ba2f;
--gold-gradient: linear-gradient(135deg, #f3ba2f 0%, #ff9900 100%);
--green: #00c853;
--red: #ff1744;
--text-main: #f0f4f8;
--text-sub: #8a99ad;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
-webkit-tap-highlight-color: transparent;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
padding: 12px;
padding-bottom: 80px;
min-height: 100vh;
}
/* Header Card */
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
background: var(--card-bg);
border: 1px solid var(--border-color);
backdrop-filter: blur(12px);
border-radius: 16px;
margin-bottom: 12px;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
}
.brand-icon {
width: 38px;
height: 38px;
background: var(--gold-gradient);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
color: #000;
font-size: 18px;
box-shadow: 0 4px 15px rgba(243, 186, 47, 0.3);
}
.brand-title {
font-size: 16px;
font-weight: 700;
letter-spacing: 0.5px;
}
.status-badge {
padding: 4px 10px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
background: rgba(0, 200, 83, 0.15);
color: var(--green);
border: 1px solid rgba(0, 200, 83, 0.3);
display: flex;
align-items: center;
gap: 6px;
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--green);
box-shadow: 0 0 8px var(--green);
}
/* Account Metric Cards */
.metrics-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 12px;
}
.card {
background: var(--card-bg);
border: 1px solid var(--border-color);
backdrop-filter: blur(12px);
border-radius: 14px;
padding: 14px;
}
.card-label {
font-size: 11px;
color: var(--text-sub);
text-transform: uppercase;
font-weight: 600;
margin-bottom: 4px;
}
.card-val {
font-size: 20px;
font-weight: 700;
font-family: 'JetBrains Mono', monospace;
}
.val-gold { color: var(--gold-accent); }
.val-green { color: var(--green); }
.val-red { color: var(--red); }
/* Section Header */
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding: 0 4px;
}
.section-title {
font-size: 13px;
font-weight: 700;
color: var(--text-sub);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Positions List */
.position-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 12px 14px;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.pos-type {
font-size: 12px;
font-weight: 700;
padding: 3px 8px;
border-radius: 6px;
display: inline-block;
margin-bottom: 4px;
}
.type-buy { background: rgba(0, 200, 83, 0.2); color: var(--green); }
.type-sell { background: rgba(255, 23, 68, 0.2); color: var(--red); }
.pos-detail {
font-size: 11px;
color: var(--text-sub);
}
.btn-close {
background: rgba(255, 23, 68, 0.15);
color: var(--red);
border: 1px solid rgba(255, 23, 68, 0.3);
padding: 6px 12px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
}
.btn-close:active {
transform: scale(0.95);
}
/* Emergency Action Button */
.btn-emergency {
width: 100%;
padding: 14px;
background: rgba(255, 23, 68, 0.2);
border: 1px solid rgba(255, 23, 68, 0.5);
border-radius: 12px;
color: var(--red);
font-weight: 700;
font-size: 14px;
cursor: pointer;
margin-top: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-emergency:active {
transform: scale(0.98);
}
/* Voice AI Floating Button */
.mic-fab {
position: fixed;
bottom: 20px;
right: 20px;
width: 56px;
height: 56px;
border-radius: 50%;
background: var(--gold-gradient);
color: #000;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
box-shadow: 0 6px 20px rgba(243, 186, 47, 0.5);
border: 2px solid #fff;
cursor: pointer;
z-index: 99;
}
</style>
</head>
<body>
<!-- Header Card -->
<div class="app-header">
<div class="brand">
<div class="brand-icon">⚡</div>
<div>
<div class="brand-title">XAUUSD AI Trader Bot</div>
<div style="font-size:10px; color:var(--text-sub)">Live MT5 Remote Command Center</div>
</div>
</div>
<div class="status-badge">
<div class="status-dot"></div>
<span id="bot-status-text">BOT ACTIVE</span>
</div>
</div>
<!-- Account Metrics -->
<div class="metrics-grid">
<div class="card">
<div class="card-label">Balance</div>
<div class="card-val val-gold" id="val-balance">$0.00</div>
</div>
<div class="card">
<div class="card-label">Equity</div>
<div class="card-val" id="val-equity">$0.00</div>
</div>
<div class="card">
<div class="card-label">Today's Net P&L</div>
<div class="card-val" id="val-pnl">$0.00</div>
</div>
<div class="card">
<div class="card-label">XAUUSD Price</div>
<div class="card-val val-gold" id="val-price">Loading...</div>
</div>
</div>
<!-- Positions Section -->
<div class="section-header">
<div class="section-title">Active XAUUSD Positions</div>
</div>
<div id="positions-container">
<div class="card" style="text-align:center; color:var(--text-sub); padding:20px;">
Loading active positions...
</div>
</div>
<!-- Emergency Kill Switch Button -->
<button class="btn-emergency" onclick="emergencyCloseAll()">🛑 Emergency Close All Positions</button>
<!-- Floating Voice AI Trigger -->
<div class="mic-fab" onclick="triggerVoiceCommand()" title="Voice Command">
🎙️
</div>
<script>
const tg = window.Telegram?.WebApp;
if (tg) {
tg.ready();
tg.expand();
}
async function fetchMetrics() {
try {
const res = await fetch('/api/account');
const data = await res.json();
if (data.connected) {
document.getElementById('val-balance').innerText = `$${(data.balance || 0).toLocaleString('en-US', {minimumFractionDigits:2})}`;
document.getElementById('val-equity').innerText = `$${(data.equity || 0).toLocaleString('en-US', {minimumFractionDigits:2})}`;
const pnl = data.net_pnl_today || 0;
const pnlEl = document.getElementById('val-pnl');
pnlEl.innerText = `${pnl >= 0 ? '+' : ''}$${pnl.toFixed(2)}`;
pnlEl.className = `card-val ${pnl >= 0 ? 'val-green' : 'val-red'}`;
}
const posRes = await fetch('/api/positions');
const positions = await posRes.json();
renderPositions(positions);
const priceRes = await fetch('/api/price');
if (priceRes.ok) {
const priceData = await priceRes.json();
document.getElementById('val-price').innerText = `$${priceData.bid?.toFixed(2) || '---'}`;
}
} catch (e) {
console.error("Fetch metrics error:", e);
}
}
function renderPositions(positions) {
const container = document.getElementById('positions-container');
if (!Array.isArray(positions) || positions.length === 0) {
container.innerHTML = `<div class="card" style="text-align:center; color:var(--text-sub); padding:20px;">No Active Open Positions</div>`;
return;
}
container.innerHTML = positions.map(p => `
<div class="position-card">
<div>
<span class="pos-type ${p.type === 'BUY' ? 'type-buy' : 'type-sell'}">${p.type} ${p.volume} Lots</span>
<div style="font-weight:700; font-size:14px;">${p.symbol} @ $${p.open_price?.toFixed(2)}</div>
<div class="pos-detail">SL: $${p.sl?.toFixed(2) || 'None'} | TP: $${p.tp?.toFixed(2) || 'None'}</div>
</div>
<div style="text-align:right;">
<div style="font-weight:700; font-size:15px; margin-bottom:4px;" class="${p.profit >= 0 ? 'val-green' : 'val-red'}">
${p.profit >= 0 ? '+' : ''}$${p.profit?.toFixed(2)}
</div>
<button class="btn-close" onclick="closePosition(${p.ticket})">Close</button>
</div>
</div>
`).join('');
}
async function closePosition(ticket) {
if (tg?.HapticFeedback) tg.HapticFeedback.notificationOccurred('warning');
try {
const res = await fetch(`/api/close_position?ticket=${ticket}`, { method: 'POST' });
const data = await res.json();
if (data.status === "SUCCESS") {
if (tg?.HapticFeedback) tg.HapticFeedback.notificationOccurred('success');
fetchMetrics();
} else {
alert("Close position failed: " + (data.message || data.error));
}
} catch (e) {
alert("Error closing position: " + e.message);
}
}
async function emergencyCloseAll() {
if (!confirm("Are you sure you want to CLOSE ALL open positions immediately?")) return;
if (tg?.HapticFeedback) tg.HapticFeedback.notificationOccurred('error');
try {
const res = await fetch('/api/twa/voice_command', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command: "close all positions" })
});
const data = await res.json();
alert(data.result || "Closed all positions");
fetchMetrics();
} catch (e) {
alert("Error: " + e.message);
}
}
function triggerVoiceCommand() {
if (tg?.HapticFeedback) tg.HapticFeedback.impactOccurred('medium');
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
alert("Voice command: Send a voice note in Telegram chat!");
return;
}
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.onstart = () => alert("🎙️ Listening for XAUUSD Bot command... (e.g. 'Status', 'Close all')");
recognition.onresult = async (event) => {
const transcript = event.results[0][0].transcript;
alert(`Voice Command: "${transcript}"`);
await fetch(`/api/twa/voice_command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command: transcript })
});
fetchMetrics();
};
recognition.start();
}
setInterval(fetchMetrics, 3000);
fetchMetrics();
</script>
</body>
</html>