154 lines
6.4 KiB
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>
|