alphax/static/live_trading.html
2026-05-24 10:34:00 +08:00

139 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}AlphaX Agent — 实盘控制台{% endblock %}
{% block extra_head_css %}
<style>
.shell{width:min(100% - 40px,1280px);margin:0 auto;padding:24px 0 48px}.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-bottom:16px}.page-head h1{font-size:28px;font-weight:950;color:var(--ink);letter-spacing:-.7px}.page-head p{margin-top:5px;color:var(--stone);font-size:13px;line-height:1.55;max-width:880px}.actions{display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap}.btn,.select,.input{height:38px;border:1px solid var(--hairline-strong);background:var(--canvas);border-radius:var(--radius-md);padding:0 12px;font-size:13px;font-weight:850;color:var(--ink);outline:none}.btn{cursor:pointer}.btn.primary{background:var(--primary);color:var(--on-primary);border-color:var(--primary)}.btn.danger{background:var(--red-light);color:var(--red);border-color:rgba(229,62,62,.22)}.btn:disabled{opacity:.55;cursor:default}.layout{display:grid;grid-template-columns:360px minmax(0,1fr);gap:14px;align-items:start}.panel{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);overflow:hidden;margin-bottom:14px}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 14px;border-bottom:1px solid var(--hairline-soft)}.panel-title{font-size:14px;font-weight:950;color:var(--ink)}.panel-note{font-size:11px;color:var(--stone);font-weight:850}.panel-body{padding:14px}.account-list{display:grid;gap:8px}.account{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:12px;cursor:pointer;transition:.16s ease}.account:hover,.account.active{border-color:rgba(66,98,255,.35);box-shadow:0 12px 28px rgba(15,23,42,.08);background:var(--canvas)}.account-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.account b{font-size:13px;color:var(--ink);font-weight:950}.account p{margin-top:6px;color:var(--stone);font-size:11px;line-height:1.45}.meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:9px}.badge{display:inline-flex;align-items:center;height:24px;border-radius:999px;padding:0 8px;font-size:11px;font-weight:900;border:1px solid var(--hairline-soft);background:var(--surface);color:var(--slate);white-space:nowrap}.badge.green{background:var(--green-light);border-color:rgba(0,180,115,.18);color:var(--green)}.badge.red{background:var(--red-light);border-color:rgba(229,62,62,.18);color:var(--red)}.badge.blue{background:rgba(66,98,255,.08);border-color:rgba(66,98,255,.18);color:var(--blue)}.badge.warn{background:rgba(245,158,11,.12);border-color:rgba(245,158,11,.22);color:#a05a00}.kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:14px}.kpi{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);padding:14px;min-width:0}.kpi span{display:block;color:var(--stone);font-size:11px;font-weight:900}.kpi b{display:block;margin-top:7px;color:var(--ink);font-size:23px;line-height:1;font-weight:950;letter-spacing:-.5px}.kpi small{display:block;margin-top:7px;color:var(--stone);font-size:11px;font-weight:800}.kpi b.green{color:var(--green)}.kpi b.red{color:var(--red)}.kpi b.blue{color:var(--blue)}.tabs{display:flex;gap:8px;margin:0 0 14px;padding:6px;border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);overflow:auto}.tab{height:36px;border:0;background:transparent;border-radius:10px;padding:0 12px;font-size:12px;font-weight:950;color:var(--stone);cursor:pointer;white-space:nowrap}.tab.active{background:var(--canvas);color:var(--ink);box-shadow:0 10px 24px rgba(15,23,42,.08)}.tab-panel{display:none}.tab-panel.active{display:block}.form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.field{display:flex;flex-direction:column;gap:6px;min-width:0}.field.wide{grid-column:1/-1}.field span{font-size:11px;color:var(--stone);font-weight:900}.field small{margin-top:-2px;color:var(--stone);font-size:10px;line-height:1.35}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.info{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:12px}.info span{display:block;font-size:11px;font-weight:900;color:var(--stone)}.info b{display:block;margin-top:6px;font-size:14px;color:var(--ink);font-weight:950;word-break:break-word}.table-wrap{overflow:auto;border:1px solid var(--hairline-soft);border-radius:var(--radius-md)}table{width:100%;border-collapse:collapse;background:var(--canvas)}th,td{padding:11px 12px;border-bottom:1px solid var(--hairline-soft);text-align:left;font-size:12px;white-space:nowrap}th{color:var(--stone);font-weight:950;background:var(--surface)}td{color:var(--slate);font-weight:800}tr:last-child td{border-bottom:0}.empty,.loading{text-align:center;padding:30px 14px;color:var(--stone);font-size:13px}.note{border:1px solid rgba(66,98,255,.14);background:rgba(66,98,255,.045);border-radius:var(--radius-md);padding:11px 12px;color:var(--slate);font-size:12px;line-height:1.55;margin-bottom:14px}.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:850}.maint{border-top:1px dashed var(--hairline-strong);margin-top:14px;padding-top:14px}@media(max-width:1100px){.layout{grid-template-columns:1fr}.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:700px){.shell{width:min(100% - 24px,1280px)}.page-head h1{font-size:22px}.kpis,.grid,.form{grid-template-columns:1fr}}
</style>
{% endblock %}
{% block content %}
<div class="shell">
<div class="page-head">
<div>
<h1>实盘控制台</h1>
<p>以交易账号为核心管理 API 环境变量、资金余额、仓位、订单和账号级风控。币种限制留空时表示不限制,策略同步到实盘前会按当前账号配置逐一校验。</p>
</div>
<div class="actions">
<button class="btn" onclick="resetForm()">新增账号</button>
<button class="btn primary" onclick="loadAll()">刷新</button>
</div>
</div>
<div class="layout">
<aside>
<section class="panel">
<div class="panel-head"><div><div class="panel-title">交易账号</div><div class="panel-note">选择一个账号查看资金与订单</div></div></div>
<div class="panel-body"><div class="account-list" id="accounts"><div class="loading">加载中...</div></div></div>
</section>
</aside>
<main>
<div class="kpis" id="kpis"><div class="kpi"><span>账号</span><b>加载中</b></div></div>
<div class="tabs">
<button class="tab active" onclick="showTab('overview',this)">账户总览</button>
<button class="tab" onclick="showTab('orders',this)">订单</button>
<button class="tab" onclick="showTab('config',this)">配置与风控</button>
<button class="tab" onclick="showTab('maintenance',this)">维护</button>
</div>
<section class="tab-panel active" id="overviewPane">
<section class="panel">
<div class="panel-head"><div><div class="panel-title">资金与持仓</div><div class="panel-note" id="exchangeCacheNote">默认显示本地缓存,手动刷新才请求交易所</div></div><button class="btn" id="refreshExchangeBtn" onclick="refreshExchangeData()">刷新交易所数据</button></div>
<div class="panel-body">
<div class="grid" id="accountInfo"></div>
<div style="height:12px"></div>
<div class="table-wrap" id="positions"></div>
</div>
</section>
</section>
<section class="tab-panel" id="ordersPane">
<section class="panel">
<div class="panel-head"><div><div class="panel-title">当前挂单</div><div class="panel-note">交易所未完成订单</div></div></div>
<div class="panel-body"><div class="table-wrap" id="openOrders"></div></div>
</section>
<section class="panel">
<div class="panel-head"><div><div class="panel-title">订单历史</div><div class="panel-note">最近交易所订单与系统记录</div></div></div>
<div class="panel-body"><div class="table-wrap" id="orderHistory"></div></div>
</section>
</section>
<section class="tab-panel" id="configPane">
<section class="panel">
<div class="panel-head">
<div><div class="panel-title">账号配置</div><div class="panel-note">API Key、杠杆、保证金和币种范围按账号独立保存</div></div>
<div class="actions">
<button class="btn danger" onclick="deleteAccount()">删除账号</button>
<button class="btn primary" id="saveAccountBtn" onclick="saveAccount()">保存账号配置</button>
</div>
</div>
<div class="panel-body">
<div class="note">API Key 和 Secret 只保存环境变量名。允许交易币种留空时,表示该账号允许全部币种;填写后只允许列表内币种。</div>
<div class="form">
<label class="field"><span>账号编码</span><input class="input" id="accountCode" value="binance_um_futures" placeholder="例如 binance_um_futures"></label>
<label class="field"><span>账号状态</span><select class="select" id="accountStatus"><option value="enabled">启用</option><option value="disabled">禁用</option></select></label>
<label class="field"><span>交易所</span><select class="select" id="accountExchange"><option value="binance">Binance</option></select></label>
<label class="field"><span>账户类型</span><select class="select" id="accountMarket"><option value="um_futures">U 本位合约</option><option value="spot">现货</option></select></label>
<label class="field wide"><span>API Key 环境变量名</span><input class="input" id="apiKeyEnv" value="ALPHAX_BINANCE_DEMO_API_KEY" placeholder="例如 ALPHAX_BINANCE_API_KEY"><small>页面只保存变量名,不保存真实 Key。</small></label>
<label class="field wide"><span>API Secret 环境变量名</span><input class="input" id="apiSecretEnv" value="ALPHAX_BINANCE_DEMO_API_SECRET" placeholder="例如 ALPHAX_BINANCE_API_SECRET"><small>Secret 从后端环境变量读取,不在页面展示明文。</small></label>
<label class="field"><span>每单保证金上限</span><input class="input" id="maxOrderMargin" value="10" placeholder="USDT"><small>单笔最多占用多少保证金。</small></label>
<label class="field"><span>单币杠杆上限</span><input class="input" id="maxSymbolLeverage" value="1" placeholder="倍数"><small>单个币种允许的最大杠杆。</small></label>
<label class="field"><span>持仓累计杠杆上限</span><input class="input" id="maxCumulativeLeverage" value="1" placeholder="倍数"><small>账户整体风险敞口上限。</small></label>
<label class="field"><span>允许交易币种</span><input class="input" id="allowedSymbols" value="" placeholder="留空=全部,或 BTC/USDT,DOGE/USDT"><small>空值表示全部币种可以交易。</small></label>
</div>
</div>
</section>
</section>
<section class="tab-panel" id="maintenancePane">
<section class="panel">
<div class="panel-head"><div><div class="panel-title">接口验收</div><div class="panel-note">仅用于接入或排障时手动运行</div></div></div>
<div class="panel-body">
<div class="note">这不是日常看板。只在新增账号、换 Key、换 endpoint 或排查交易所接口时运行,会真实调用当前账号配置的 API。</div>
<div class="actions">
<label class="field"><span>验收币种</span><input class="input" id="smokeSymbol" value="BTC/USDT" placeholder="BTC/USDT" style="width:140px"></label>
<label class="field"><span>测试保证金</span><input class="input" id="smokeMargin" value="10" placeholder="USDT" style="width:140px"></label>
<button class="btn danger" id="smokeBtn" onclick="runSmoke()">开始验收</button>
</div>
<div class="maint" id="events"></div>
</div>
</section>
</section>
</main>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script>
var state={summary:{},accounts:[],events:[],overview:null,selectedId:0};
function $(id){return document.getElementById(id)}
function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]})}
function fmt(n,d){n=Number(n||0);return n.toLocaleString(undefined,{maximumFractionDigits:d==null?4:d})}
function time(s){return esc(String(s||'').replace('T',' ').slice(0,19))}
function badge(v){var cls=v==='enabled'||v==='ok'||v==='closed'?'green':(v==='disabled'||v==='error'?'red':(v==='open'||v==='exchange_api'?'blue':'warn'));return '<span class="badge '+cls+'">'+esc(v||'--')+'</span>'}
function sideText(v){v=String(v||'').toLowerCase();if(v==='long'||v==='buy')return '多';if(v==='short'||v==='sell')return '空';return v||'--'}
function selectedAccountObj(){return (state.accounts||[]).find(function(x){return Number(x.id)===Number(state.selectedId)})||{}}
function showTab(id,btn){document.querySelectorAll('.tab').forEach(function(x){x.classList.remove('active')});document.querySelectorAll('.tab-panel').forEach(function(x){x.classList.remove('active')});btn.classList.add('active');$(id+'Pane').classList.add('active')}
function card(k,v,cls,s){return '<div class="kpi"><span>'+esc(k)+'</span><b class="'+(cls||'')+'">'+esc(v)+'</b><small>'+esc(s||'')+'</small></div>'}
function renderAccounts(){var rows=state.accounts||[];if(!rows.length){$('accounts').innerHTML='<div class="empty">暂无账号配置</div>';return}$('accounts').innerHTML=rows.map(function(x){var r=x.risk_config||{},allowed=r.allowed_symbols||[];return '<div class="account '+(Number(x.id)===Number(state.selectedId)?'active':'')+'" onclick="selectAccount('+esc(x.id)+')"><div class="account-head"><b>'+esc(x.account_code)+'</b>'+badge(x.status)+'</div><p>'+esc(x.exchange)+' · '+esc(x.market_type)+' · '+esc(x.api_key_env||'--')+'</p><div class="meta"><span class="badge">保证金 '+fmt(r.max_order_margin_usdt||0,2)+'U</span><span class="badge">杠杆 '+fmt(r.max_symbol_leverage||1,2)+'x</span><span class="badge '+(allowed.length?'blue':'green')+'">'+(allowed.length?'限制 '+allowed.length+' 个币':'全部币种')+'</span></div></div>'}).join('')}
function renderKpis(){var a=selectedAccountObj(),o=state.overview||{},risk=o.risk||{},b=(o.balance||{}).usdt||{},pos=o.positions||[];$('kpis').innerHTML=[card('当前账号',a.account_code||'未选择','blue',a.exchange?esc(a.exchange)+' · '+esc(a.market_type):'--'),card('USDT 总额',fmt(b.total,2),'','可用 '+fmt(b.free,2)+' / 占用 '+fmt(b.used,2)),card('持仓数量',pos.length,'','当前未平仓合约'),card('币种权限',risk.symbol_policy==='all'?'全部币种':'白名单 '+(risk.allowed_symbols||[]).length+' 个',risk.symbol_policy==='all'?'green':'blue','留空即不限制')].join('')}
function fillAccountForm(id){var x=(state.accounts||[]).find(function(a){return Number(a.id)===Number(id)})||{},r=x.risk_config||{};$('accountCode').value=x.account_code||'';$('accountStatus').value=x.status||'disabled';$('accountExchange').value=x.exchange||'binance';$('accountMarket').value=x.market_type||'um_futures';$('apiKeyEnv').value=x.api_key_env||'ALPHAX_BINANCE_API_KEY';$('apiSecretEnv').value=x.api_secret_env||'ALPHAX_BINANCE_API_SECRET';$('maxOrderMargin').value=r.max_order_margin_usdt||10;$('maxSymbolLeverage').value=r.max_symbol_leverage||1;$('maxCumulativeLeverage').value=r.max_cumulative_leverage||1;$('allowedSymbols').value=(r.allowed_symbols||[]).join(',');if($('saveAccountBtn'))$('saveAccountBtn').textContent=Number(id)>0?'保存修改':'新增账号'}
function resetForm(){state.selectedId=0;state.overview=null;renderAccounts();fillAccountForm(0);renderKpis();renderOverview();document.querySelectorAll('.tab').forEach(function(x){x.classList.remove('active')});document.querySelectorAll('.tab-panel').forEach(function(x){x.classList.remove('active')});document.querySelector('.tab[onclick*="config"]').classList.add('active');$('configPane').classList.add('active')}
function info(k,v){return '<div class="info"><span>'+esc(k)+'</span><b>'+esc(v)+'</b></div>'}
function renderOverview(){var a=selectedAccountObj(),o=state.overview||{},risk=o.risk||{},errors=o.errors||[],cache=o.exchange_cache||{};if($('exchangeCacheNote'))$('exchangeCacheNote').textContent=cache.loaded?(cache.cached?'交易所数据来自缓存':'交易所数据已刷新'):(cache.reason||'默认显示本地缓存,手动刷新才请求交易所');$('accountInfo').innerHTML=[info('账号状态',a.status||'--'),info('API Key 变量',a.api_key_env||'--'),info('每单保证金上限',fmt(risk.max_order_margin_usdt,2)+' USDT'),info('单币杠杆上限',fmt(risk.max_symbol_leverage,2)+'x'),info('累计杠杆上限',fmt(risk.max_cumulative_leverage,2)+'x'),info('允许交易币种',risk.symbol_policy==='all'?'全部币种':(risk.allowed_symbols||[]).join(', '))].join('')+(errors.length?'<div class="note" style="grid-column:1/-1">账户数据读取异常:'+esc(errors[0])+'</div>':'')+(!cache.loaded&&a.status==='enabled'?'<div class="note" style="grid-column:1/-1">为避免页面打开被 Binance API 拖慢,余额、持仓和订单默认不自动刷新。点击“刷新交易所数据”后读取。</div>':'');renderPositions();renderOpenOrders();renderOrderHistory();renderEvents()}
function table(headers,rows,empty){if(!rows.length)return '<div class="empty">'+esc(empty||'暂无数据')+'</div>';return '<table><thead><tr>'+headers.map(function(h){return '<th>'+esc(h)+'</th>'}).join('')+'</tr></thead><tbody>'+rows.join('')+'</tbody></table>'}
function renderPositions(){var rows=(state.overview?.positions||[]).map(function(x){var lev=Number(x.leverage||0);return '<tr><td>'+esc(x.symbol)+'</td><td>'+badge(x.side_label||sideText(x.side))+'</td><td>'+fmt(x.contracts,6)+'</td><td>'+fmt(x.position_value_usdt,2)+' U</td><td>'+fmt(x.entry_price,6)+'</td><td>'+fmt(x.mark_price,6)+'</td><td>'+fmt(x.unrealized_pnl,4)+'</td><td>'+fmt(lev,2)+'x</td></tr>'});$('positions').innerHTML=table(['币种','方向','数量','仓位价值','开仓价','标记价','未实现盈亏','杠杆'],rows,'当前账号暂无持仓')}
function renderOpenOrders(){var rows=(state.overview?.open_orders||[]).map(function(x){return '<tr><td>'+time(x.timestamp)+'</td><td>'+esc(x.symbol)+'</td><td>'+esc(x.type)+'</td><td>'+badge(sideText(x.side))+'</td><td>'+fmt(x.price,8)+'</td><td>'+fmt(x.amount,6)+'</td><td>'+badge(x.status)+'</td></tr>'});$('openOrders').innerHTML=table(['时间','币种','类型','方向','价格','数量','状态'],rows,'当前账号暂无挂单')}
function renderOrderHistory(){var orders=(state.overview?.order_history||[]).map(function(x){return '<tr><td>'+time(x.timestamp)+'</td><td>'+esc(x.symbol)+'</td><td>'+esc(x.type)+'</td><td>'+badge(sideText(x.side))+'</td><td>'+fmt(x.average||x.price,8)+'</td><td>'+fmt(x.filled||x.amount,6)+'</td><td>'+badge(x.status)+'</td></tr>'});$('orderHistory').innerHTML=table(['时间','币种','类型','方向','均价/价格','成交/数量','状态'],orders,'当前账号暂无订单历史')}
function renderEvents(){var rows=(state.events||[]).slice(0,20).map(function(e){return '<tr><td>'+time(e.event_time)+'</td><td>'+esc(e.event_type)+'</td><td>'+badge(e.status)+'</td><td>'+esc(e.message||'--')+'</td></tr>'});$('events').innerHTML=table(['时间','事件','状态','说明'],rows,'暂无维护日志')}
function renderAll(){if(!state.selectedId && state.accounts[0])state.selectedId=state.accounts[0].id;renderAccounts();fillAccountForm(state.selectedId);renderKpis();renderOverview()}
async function selectAccount(id){state.selectedId=Number(id);renderAccounts();fillAccountForm(id);await loadOverview()}
async function saveAccount(){var allowed=String($('allowedSymbols').value||'').split(',').map(function(x){return x.trim().toUpperCase()}).filter(Boolean);var lev=Number($('maxSymbolLeverage').value||1),margin=Number($('maxOrderMargin').value||10);var body={account_code:$('accountCode').value,exchange:$('accountExchange').value,market_type:$('accountMarket').value,status:$('accountStatus').value,api_key_env:$('apiKeyEnv').value,api_secret_env:$('apiSecretEnv').value,testnet:true,permissions:{read:true,trade:true},risk_config:{sandbox_mode:'demo',max_order_margin_usdt:margin,max_order_notional_usdt:margin*Math.max(1,lev),max_symbol_leverage:lev,max_cumulative_leverage:Number($('maxCumulativeLeverage').value||1),allowed_symbols:allowed}};var editing=Number(state.selectedId)>0,url=editing?('/api/live-trading/accounts/'+state.selectedId):'/api/live-trading/accounts',method=editing?'PUT':'POST';var resp=await fetch(url,{method:method,headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});var saved=await resp.json().catch(function(){return {}});if(!resp.ok){alert((editing?'修改':'新增')+'账号失败:'+(saved.detail||'unknown_error'));return}state.selectedId=saved.id||state.selectedId;await loadAll()}
async function deleteAccount(){if(!state.selectedId){alert('请先选择要删除的账号');return}var acct=selectedAccountObj();if(!confirm('确认删除账号配置:'+(acct.account_code||state.selectedId)+'\\n历史订单和调用日志会保留。'))return;var resp=await fetch('/api/live-trading/accounts/'+state.selectedId,{method:'DELETE'});var d=await resp.json().catch(function(){return {}});if(!resp.ok){alert('删除失败:'+(d.detail||'unknown_error'));return}state.selectedId=0;await loadAll()}
async function runSmoke(){if(!state.selectedId){alert('请先选择账号');return}var acct=selectedAccountObj(),risk=acct.risk_config||{},lev=Number(risk.max_symbol_leverage||1),margin=Number($('smokeMargin').value||risk.max_order_margin_usdt||10),notional=margin*Math.max(1,lev);var btn=$('smokeBtn');btn.disabled=true;btn.textContent='验收中...';try{var resp=await fetch('/api/live-trading/smoke/binance',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({account_id:state.selectedId,symbol:$('smokeSymbol').value,notional_usdt:notional,leverage:lev})});var d=await resp.json();if(!resp.ok||d.detail){alert('接口验收失败:'+(d.detail||JSON.stringify(d).slice(0,220)))}await loadAll()}catch(e){alert('接口验收请求失败:'+e.message)}finally{btn.disabled=false;btn.textContent='开始验收'}}
async function loadOverview(refresh){if(!state.selectedId){state.overview=null;renderAll();return}state.overview=await (await fetch('/api/live-trading/accounts/'+state.selectedId+'/overview?refresh='+(refresh?1:0)+'&_ts='+Date.now(),{cache:'no-store'})).json();renderAll()}
async function refreshExchangeData(){if(!state.selectedId){alert('请先选择账号');return}var btn=$('refreshExchangeBtn');if(btn){btn.disabled=true;btn.textContent='刷新中...'}try{await loadOverview(true)}finally{if(btn){btn.disabled=false;btn.textContent='刷新交易所数据'}}}
async function loadAll(){try{var s=await (await fetch('/api/live-trading/summary?_ts='+Date.now(),{cache:'no-store'})).json();var a=await (await fetch('/api/live-trading/accounts?_ts='+Date.now(),{cache:'no-store'})).json();var e=await (await fetch('/api/live-trading/events?limit=100&_ts='+Date.now(),{cache:'no-store'})).json();state.summary=s;state.accounts=a.items||[];state.events=e.items||[];if(!state.selectedId&&state.accounts[0])state.selectedId=state.accounts[0].id;await loadOverview()}catch(e){$('kpis').innerHTML='<div class="kpi"><span>状态</span><b>加载失败</b><small>'+esc(e.message)+'</small></div>'}}
loadAll();
</script>
{% endblock %}