Files
alpaca-llm-bot-v1/templates/index.html

154 lines
6.4 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>alpaca-llm-bot-v1</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root { --bg:#070b12; --card:#0f172a; --line:#1f2937; --text:#e5e7eb; --muted:#9ca3af; --buy:#22c55e; --sell:#ef4444; --hold:#94a3b8; --acc:#60a5fa; }
* { box-sizing: border-box; }
body{background:radial-gradient(1200px 800px at 20% -10%, #1a2340 0%, var(--bg) 40%);color:var(--text);font-family:Inter,system-ui,sans-serif;margin:0;padding:20px}
h2,h3{margin:8px 0 14px}
.sub{color:var(--muted);font-size:13px;margin-bottom:16px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}
.card{background:linear-gradient(180deg,#101b33 0%,var(--card) 100%);border:1px solid var(--line);border-radius:14px;padding:14px;box-shadow:0 8px 30px rgba(0,0,0,.25)}
.h{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}
.v{font-size:26px;font-weight:700;margin-top:4px}
.layout{display:grid;grid-template-columns:2fr 1fr;gap:12px}
.charts{display:grid;grid-template-columns:1fr;gap:12px}
.chart-wrap{height:270px}
table{width:100%;border-collapse:collapse}
th,td{padding:8px;border-bottom:1px solid var(--line);text-align:left;font-size:13px}
.buy{color:var(--buy)} .sell{color:var(--sell)} .hold{color:var(--hold)}
.pill{display:inline-block;padding:3px 8px;border:1px solid var(--line);border-radius:999px;font-size:12px;color:var(--muted)}
.scroll{max-height:430px;overflow:auto}
</style>
</head>
<body>
<h2>alpaca-llm-bot-v1 · Mission Control</h2>
<div class="sub">Autonomous trading telemetry · updates every 60s · non-blocking UI reads</div>
<div class="grid">
<div class="card"><div class="h">Decisions (24h)</div><div class="v">{{ stats.decisions }}</div></div>
<div class="card"><div class="h">Trades (24h)</div><div class="v">{{ stats.trades }}</div></div>
<div class="card"><div class="h">Executed</div><div class="v">{{ stats.executed }}</div></div>
<div class="card"><div class="h">Failed</div><div class="v">{{ stats.failed }}</div></div>
<div class="card"><div class="h">Curated Insights</div><div class="v">{{ stats.insights }}</div></div>
</div>
<div class="layout" style="margin-top:12px">
<div class="charts">
<div class="card">
<div class="h">Decision Flow (last 72h)</div>
<div class="chart-wrap"><canvas id="decisionChart"></canvas></div>
</div>
<div class="card">
<div class="h">Cumulative Notional Activity</div>
<div class="chart-wrap"><canvas id="activityChart"></canvas></div>
</div>
</div>
<div class="card">
<div class="h">Symbol Leaderboard</div>
<div id="leaderboard" class="scroll" style="margin-top:8px"></div>
</div>
</div>
<div style="margin-top:12px" class="layout">
<div>
<h3>Recent Decisions <span class="pill">latest 120</span></h3>
<div class="card scroll">
<table>
<thead><tr><th>Time</th><th>Symbol</th><th>Action</th><th>Conf</th><th>Status</th><th>Reason</th></tr></thead>
<tbody>
{% for d in decisions %}
<tr>
<td>{{ d.ts }}</td>
<td>{{ d.symbol }}</td>
<td class="{{ d.action }}">{{ d.action }}</td>
<td>{{ '%.2f'|format(d.confidence or 0) }}</td>
<td>{{ d.status }}</td>
<td>{{ d.reason }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div>
<h3>Curated Briefs <span class="pill">latest 30</span></h3>
<div class="card scroll">
{% for i in insights %}
<div style="margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid var(--line)">
<div><b>{{ i.symbol }}</b> · <span class="h">{{ i.ts }}</span></div>
<div style="font-size:13px;line-height:1.45">{{ i.summary }}</div>
</div>
{% endfor %}
</div>
</div>
</div>
<script>
let decisionChart, activityChart;
function renderLeaderboard(rows){
const el = document.getElementById('leaderboard');
if(!rows || !rows.length){ el.innerHTML = '<div class="sub">No data yet.</div>'; return; }
el.innerHTML = rows.map((r,i)=>`<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid #1f2937"><span>#${i+1} <b>${r.symbol}</b></span><span class="sub">exec ${r.executed} / dec ${r.decisions}</span></div>`).join('');
}
function upsertCharts(m){
const d = m.decisionSeries;
const a = m.activitySeries;
if(!decisionChart){
decisionChart = new Chart(document.getElementById('decisionChart'), {
type: 'line',
data: { labels: d.labels, datasets: [
{label:'buy', data:d.buy, borderColor:'#22c55e', tension:.25},
{label:'sell', data:d.sell, borderColor:'#ef4444', tension:.25},
{label:'hold', data:d.hold, borderColor:'#94a3b8', tension:.25},
{label:'executed', data:d.executed, borderColor:'#60a5fa', tension:.25},
]},
options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{labels:{color:'#e5e7eb'}}}, scales:{x:{ticks:{color:'#9ca3af'}},y:{ticks:{color:'#9ca3af'}}} }
});
} else {
decisionChart.data.labels = d.labels;
decisionChart.data.datasets[0].data = d.buy;
decisionChart.data.datasets[1].data = d.sell;
decisionChart.data.datasets[2].data = d.hold;
decisionChart.data.datasets[3].data = d.executed;
decisionChart.update();
}
if(!activityChart){
activityChart = new Chart(document.getElementById('activityChart'), {
type:'bar',
data:{ labels:a.labels, datasets:[{label:'cum notional', data:a.cumulativeNotional, backgroundColor:'#60a5fa66', borderColor:'#60a5fa'}] },
options:{ responsive:true, maintainAspectRatio:false, plugins:{legend:{labels:{color:'#e5e7eb'}}}, scales:{x:{ticks:{color:'#9ca3af'}},y:{ticks:{color:'#9ca3af'}}} }
});
} else {
activityChart.data.labels = a.labels;
activityChart.data.datasets[0].data = a.cumulativeNotional;
activityChart.update();
}
}
async function refreshMetrics(){
try{
const r = await fetch('/api/metrics?hours=72');
const m = await r.json();
if(!m.ok) return;
upsertCharts(m);
renderLeaderboard(m.leaderboard);
}catch(e){ }
}
refreshMetrics();
setInterval(refreshMetrics, 60000);
</script>
</body>
</html>