|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>CryptoPilot Pro - Trading Pair</title> |
|
|
<link rel="stylesheet" href="style.css"> |
|
|
<script src="https://cdn.tailwindcss.com"> |
|
|
</script> |
|
|
<style> |
|
|
#volume-chart { |
|
|
height: 100px; |
|
|
width: 100%; |
|
|
margin-top: 1rem; |
|
|
} |
|
|
.chart-container { |
|
|
position: relative; |
|
|
height: 400px; |
|
|
} |
|
|
</style> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<custom-navbar></custom-navbar> |
|
|
|
|
|
<main class="container mx-auto px-4 py-8"> |
|
|
<div class="flex items-center mb-8"> |
|
|
<a href="/" class="text-gray-600 hover:text-primary mr-4"> |
|
|
<i data-feather="arrow-left"></i> |
|
|
</a> |
|
|
<h1 id="pair-title" class="text-3xl font-bold text-gray-800">BTC/USDT</h1> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
|
|
<div class="lg:col-span-2 bg-white rounded-xl shadow-lg p-6"> |
|
|
<div class="h-80"> |
|
|
<canvas id="price-chart"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow-lg p-6"> |
|
|
<div class="mb-6"> |
|
|
<h3 class="text-xl font-semibold mb-2">Current Price</h3> |
|
|
<div class="text-3xl font-bold" id="current-price">$0.00</div> |
|
|
<div class="text-sm" id="price-change">0.00% (0.00)</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<h3 class="text-xl font-semibold mb-2">Prediction</h3> |
|
|
<div class="flex items-center mb-2"> |
|
|
<div class="w-2/3"> |
|
|
<div class="flex justify-between mb-1"> |
|
|
<span>Up Probability</span> |
|
|
<span id="up-prob">0%</span> |
|
|
</div> |
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
|
<div id="up-progress" class="bg-green-500 h-2.5 rounded-full" style="width: 0%"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex items-center"> |
|
|
<div class="w-2/3"> |
|
|
<div class="flex justify-between mb-1"> |
|
|
<span>Down Probability</span> |
|
|
<span id="down-prob">0%</span> |
|
|
</div> |
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
|
<div id="down-progress" class="bg-red-500 h-2.5 rounded-full" style="width: 0%"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-blue-50 p-4 rounded-lg"> |
|
|
<h4 class="font-semibold mb-2">Trading Advice</h4> |
|
|
<p id="trading-advice" class="text-blue-800">Loading recommendation...</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="lg:col-span-3 bg-white rounded-xl shadow-lg p-6"> |
|
|
<h3 class="text-xl font-semibold mb-4">Historical Predictions</h3> |
|
|
<div class="overflow-x-auto"> |
|
|
<table class="w-full"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-4 py-2 text-left">Time</th> |
|
|
<th class="px-4 py-2 text-left">Prediction</th> |
|
|
<th class="px-4 py-2 text-left">Actual</th> |
|
|
<th class="px-4 py-2 text-left">Confidence</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody id="predictions-table"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
|
|
|
<custom-footer></custom-footer> |
|
|
<script src="components/navbar.js"></script> |
|
|
<script src="components/footer.js"></script> |
|
|
<script src="script.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> |
|
|
<script> |
|
|
// Binance API endpoints |
|
|
const BINANCE_API = { |
|
|
klines: 'https://api.binance.com/api/v3/klines', |
|
|
ticker: 'https://api.binance.com/api/v3/ticker/24hr' |
|
|
}; |
|
|
document.addEventListener('DOMContentLoaded', async () => { |
|
|
feather.replace(); |
|
|
await loadPairData(); |
|
|
initRealTimeUpdates(); |
|
|
}); |
|
|
|
|
|
async function loadPairData() { |
|
|
const params = new URLSearchParams(window.location.search); |
|
|
const pair = params.get('pair') || 'BTC/USDT'; |
|
|
const symbol = pair.replace('/', ''); |
|
|
document.getElementById('pair-title').textContent = pair; |
|
|
|
|
|
try { |
|
|
// Get latest price and 24h change |
|
|
const [tickerRes, klinesRes] = await Promise.all([ |
|
|
axios.get(`${BINANCE_API.ticker}?symbol=${symbol}`), |
|
|
axios.get(`${BINANCE_API.klines}?symbol=${symbol}&interval=5m&limit=100`) |
|
|
]); |
|
|
|
|
|
const ticker = tickerRes.data; |
|
|
const klines = klinesRes.data; |
|
|
|
|
|
// Update price display |
|
|
const currentPrice = parseFloat(ticker.lastPrice); |
|
|
const priceChange = parseFloat(ticker.priceChangePercent); |
|
|
document.getElementById('current-price').textContent = `${currentPrice.toFixed(2)}`; |
|
|
const changeElement = document.getElementById('price-change'); |
|
|
changeElement.textContent = `${priceChange.toFixed(2)}% (${parseFloat(ticker.priceChange).toFixed(2)})`; |
|
|
changeElement.className = priceChange >= 0 ? 'text-sm text-green-600' : 'text-sm text-red-600'; |
|
|
|
|
|
// Update chart with real data |
|
|
updateChart(klines, pair); |
|
|
|
|
|
// Get prediction from our API |
|
|
const signalRes = await fetch(`/api/signal/${symbol}`); |
|
|
const signal = await signalRes.json(); |
|
|
|
|
|
// Update prediction UI |
|
|
document.getElementById('up-prob').textContent = `${Math.round(signal.prob_up * 100)}%`; |
|
|
document.getElementById('down-prob').textContent = `${Math.round(signal.prob_down * 100)}%`; |
|
|
document.getElementById('up-progress').style.width = `${signal.prob_up * 100}%`; |
|
|
document.getElementById('down-progress').style.width = `${signal.prob_down * 100}%`; |
|
|
document.getElementById('trading-advice').textContent = signal.advice.replace('_', ' ').toUpperCase(); |
|
|
document.getElementById('trading-advice').className = |
|
|
signal.advice === 'long_bias' ? 'text-green-800' : |
|
|
signal.advice === 'short_bias' ? 'text-red-800' : 'text-blue-800'; |
|
|
|
|
|
// Update predictions table |
|
|
updatePredictionsTable(symbol); |
|
|
} catch (error) { |
|
|
console.error('Error loading pair data:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
function initRealTimeUpdates() { |
|
|
// Update price every 5 seconds |
|
|
setInterval(async () => { |
|
|
const pair = new URLSearchParams(window.location.search).get('pair') || 'BTC/USDT'; |
|
|
const symbol = pair.replace('/', ''); |
|
|
|
|
|
try { |
|
|
const res = await axios.get(`${BINANCE_API.ticker}?symbol=${symbol}`); |
|
|
const ticker = res.data; |
|
|
const currentPrice = parseFloat(ticker.lastPrice); |
|
|
document.getElementById('current-price').textContent = `${currentPrice.toFixed(2)}`; |
|
|
} catch (error) { |
|
|
console.error('Error updating price:', error); |
|
|
} |
|
|
}, 5000); |
|
|
} |
|
|
function updateChart(klines, pair) { |
|
|
const ctx = document.getElementById('price-chart').getContext('2d'); |
|
|
|
|
|
// Process klines data |
|
|
const labels = klines.map(k => new Date(k[0]).toLocaleTimeString()); |
|
|
const closes = klines.map(k => parseFloat(k[4])); |
|
|
const volumes = klines.map(k => parseFloat(k[5])); |
|
|
|
|
|
// Create main price chart |
|
|
const priceChart = new Chart(ctx, { |
|
|
type: 'candlestick', |
|
|
data: { |
|
|
labels: labels, |
|
|
datasets: [{ |
|
|
label: `${pair} Price`, |
|
|
data: klines.map(k => ({ |
|
|
x: new Date(k[0]), |
|
|
o: parseFloat(k[1]), |
|
|
h: parseFloat(k[2]), |
|
|
l: parseFloat(k[3]), |
|
|
c: parseFloat(k[4]) |
|
|
})), |
|
|
color: { |
|
|
up: '#10b981', |
|
|
down: '#ef4444', |
|
|
unchanged: '#9ca3af', |
|
|
} |
|
|
}] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
scales: { |
|
|
x: { |
|
|
type: 'time', |
|
|
time: { |
|
|
unit: 'hour' |
|
|
} |
|
|
}, |
|
|
y: { |
|
|
position: 'right', |
|
|
ticks: { |
|
|
callback: function(value) { |
|
|
return ' |
|
|
async function updatePredictionsTable(symbol) { |
|
|
try { |
|
|
// In a real app, this would come from your backend API |
|
|
const predictions = await fetchHistoricalPredictions(symbol); |
|
|
const table = document.getElementById('predictions-table'); |
|
|
|
|
|
table.innerHTML = predictions.map(pred => { |
|
|
const time = new Date(pred.timestamp).toLocaleTimeString(); |
|
|
const isCorrect = pred.prediction === pred.actual; |
|
|
|
|
|
return ` |
|
|
<tr class="border-b"> |
|
|
<td class="px-4 py-3">${time}</td> |
|
|
<td class="px-4 py-3 font-semibold ${pred.prediction === 'UP' ? 'text-green-600' : 'text-red-600'}"> |
|
|
${pred.prediction} |
|
|
</td> |
|
|
<td class="px-4 py-3 font-semibold ${pred.actual === 'UP' ? 'text-green-600' : 'text-red-600'}"> |
|
|
${pred.actual} ${isCorrect ? 'β' : 'β'} |
|
|
</td> |
|
|
<td class="px-4 py-3"> |
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
|
<div class="h-2.5 rounded-full ${isCorrect ? 'bg-green-600' : 'bg-red-600'}" |
|
|
style="width: ${pred.confidence}%"></div> |
|
|
</div> |
|
|
<span class="text-xs">${pred.confidence}%</span> |
|
|
</td> |
|
|
</tr> |
|
|
`; |
|
|
}).join(''); |
|
|
} catch (error) { |
|
|
console.error('Error loading predictions:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
async function fetchHistoricalPredictions(symbol) { |
|
|
// Mock data - replace with actual API call to your backend |
|
|
return [ |
|
|
{ |
|
|
timestamp: Date.now() - 300000, |
|
|
prediction: 'UP', |
|
|
actual: 'UP', |
|
|
confidence: 85 |
|
|
}, |
|
|
{ |
|
|
timestamp: Date.now() - 600000, |
|
|
prediction: 'DOWN', |
|
|
actual: 'UP', |
|
|
confidence: 72 |
|
|
}, |
|
|
{ |
|
|
timestamp: Date.now() - 900000, |
|
|
prediction: 'UP', |
|
|
actual: 'UP', |
|
|
confidence: 91 |
|
|
} |
|
|
]; |
|
|
} |
|
|
</script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> |
|
|
</body> |
|
|
</html> + value.toFixed(2); |
|
|
} |
|
|
} |
|
|
} |
|
|
}, |
|
|
plugins: { |
|
|
tooltip: { |
|
|
callbacks: { |
|
|
label: function(context) { |
|
|
return [ |
|
|
`Open: ${context.raw.o.toFixed(2)}`, |
|
|
`High: ${context.raw.h.toFixed(2)}`, |
|
|
`Low: ${context.raw.l.toFixed(2)}`, |
|
|
`Close: ${context.raw.c.toFixed(2)}` |
|
|
]; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
// Add volume chart below |
|
|
const volumeCtx = document.createElement('canvas'); |
|
|
volumeCtx.id = 'volume-chart'; |
|
|
document.querySelector('#price-chart').parentNode.appendChild(volumeCtx); |
|
|
|
|
|
new Chart(volumeCtx, { |
|
|
type: 'bar', |
|
|
data: { |
|
|
labels: labels, |
|
|
datasets: [{ |
|
|
label: 'Volume', |
|
|
data: volumes, |
|
|
backgroundColor: klines.map((k, i) => |
|
|
parseFloat(k[4]) > parseFloat(k[1]) ? '#10b981' : '#ef4444' |
|
|
) |
|
|
}] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
scales: { |
|
|
x: { |
|
|
display: false |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
function updatePredictionsTable(pair) { |
|
|
const table = document.getElementById('predictions-table'); |
|
|
const predictions = [ |
|
|
{ time: '5m ago', prediction: 'UP', actual: 'UP', confidence: '85%' }, |
|
|
{ time: '10m ago', prediction: 'DOWN', actual: 'UP', confidence: '72%' }, |
|
|
{ time: '15m ago', prediction: 'UP', actual: 'UP', confidence: '91%' } |
|
|
]; |
|
|
|
|
|
table.innerHTML = predictions.map(p => ` |
|
|
<tr class="border-b"> |
|
|
<td class="px-4 py-3">${p.time}</td> |
|
|
<td class="px-4 py-3 font-semibold ${p.prediction === 'UP' ? 'text-green-600' : 'text-red-600'}">${p.prediction}</td> |
|
|
<td class="px-4 py-3 font-semibold ${p.actual === 'UP' ? 'text-green-600' : 'text-red-600'}">${p.actual}</td> |
|
|
<td class="px-4 py-3">${p.confidence}</td> |
|
|
</tr> |
|
|
`).join(''); |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |