alphax/static/market.html
2026-06-07 20:58:35 +08:00

73 lines
14 KiB
HTML
Raw Permalink 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 44px}.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;margin-bottom:16px;flex-wrap:wrap}.page-head h1{font-size:28px;font-weight:950;letter-spacing:0;color:var(--ink)}.page-head p{margin-top:5px;color:var(--stone);font-size:13px;line-height:1.55;max-width:820px}.head-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.select,.btn{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)}#marketUpdatedAt{display:inline-flex;align-items:center;justify-content:center;line-height:1;white-space:nowrap}.btn{cursor:pointer}.hero{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(320px,.75fr);gap:12px;margin-bottom:12px}.decision,.scorecard,.panel{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);min-width:0}.decision{padding:18px}.decision-top{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;margin-bottom:14px}.eyebrow{font-size:11px;color:var(--stone);font-weight:950}.decision h2{margin-top:5px;color:var(--ink);font-size:30px;line-height:1.12;font-weight:950;letter-spacing:0}.decision p{margin-top:8px;color:var(--slate);font-size:13px;line-height:1.65;max-width:760px}.stance{display:inline-flex;align-items:center;height:34px;padding:0 12px;border-radius:999px;border:1px solid var(--hairline-soft);background:var(--surface);font-size:13px;font-weight:950;color:var(--slate);white-space:nowrap}.stance.risk_on{background:var(--green-light);border-color:rgba(0,180,115,.2);color:var(--green)}.stance.selective,.stance.neutral{background:var(--yellow-light);border-color:rgba(252,185,0,.22);color:var(--yellow-dark)}.stance.risk_off{background:var(--red-light);border-color:rgba(229,62,62,.2);color:var(--red)}.evidence{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.ev{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:11px}.ev span{display:block;color:var(--stone);font-size:10px;font-weight:950}.ev b{display:block;margin-top:5px;color:var(--ink);font-size:17px;font-weight:950}.scorecard{padding:14px}.score-row{display:flex;justify-content:space-between;gap:10px;align-items:center;border-bottom:1px solid var(--hairline-soft);padding:10px 0}.score-row:first-child{padding-top:0}.score-row:last-child{border-bottom:0;padding-bottom:0}.score-row span{color:var(--stone);font-size:11px;font-weight:950}.score-row b{color:var(--ink);font-size:14px;font-weight:950;text-align:right}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-bottom:12px}.panel{overflow:hidden}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;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:12px}.metric-list{display:grid;gap:8px}.metric{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:10px}.metric span{color:var(--stone);font-size:11px;font-weight:950}.metric b{color:var(--ink);font-size:13px;font-weight:950;text-align:right}.rank-list{display:grid;gap:8px}.rank{display:grid;grid-template-columns:34px minmax(0,1fr) auto;align-items:center;gap:9px;border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:9px 10px}.rank .idx{width:24px;height:24px;border-radius:999px;background:var(--canvas);border:1px solid var(--hairline-soft);display:grid;place-items:center;color:var(--stone);font-size:11px;font-weight:950}.rank .sym{min-width:0}.rank .sym b{display:block;color:var(--ink);font-size:13px;font-weight:950;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.rank .sym span{display:block;color:var(--stone);font-size:11px;margin-top:2px}.rank .val{font-size:13px;font-weight:950;color:var(--slate)}.rank .val.up{color:var(--green)}.rank .val.down{color:var(--red)}.chips{display:flex;flex-wrap:wrap;gap:8px}.chip{display:inline-flex;align-items:center;padding:7px 10px;border-radius:999px;background:var(--surface);border:1px solid var(--hairline-soft);font-size:12px;font-weight:850;color:var(--slate)}.chip.hot{background:var(--green-light);border-color:rgba(0,180,115,.18);color:var(--green)}.chip.risk{background:var(--red-light);border-color:rgba(229,62,62,.18);color:var(--red)}.chip.blue{background:rgba(66,98,255,.06);border-color:rgba(66,98,255,.16);color:var(--blue)}.note{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:11px;color:var(--slate);font-size:12px;line-height:1.6}.empty,.loading{padding:28px 12px;text-align:center;color:var(--stone);font-size:13px}.raw-list{display:grid;gap:8px}.raw-item{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:10px}.raw-item h3{font-size:12px;color:var(--ink);font-weight:950;line-height:1.45}.raw-item .sub{margin-top:5px;color:var(--stone);font-size:11px;line-height:1.45}.full{grid-column:1/-1}@media(max-width:1100px){.hero,.grid{grid-template-columns:1fr}.evidence{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:620px){.shell{width:min(100% - 24px,1280px)}.page-head h1{font-size:22px}.decision h2{font-size:24px}.evidence{grid-template-columns:1fr}.rank{grid-template-columns:28px minmax(0,1fr) auto}}
</style>
{% endblock %}
{% block content %}
<div class="shell">
<div class="page-head">
<div>
<h1>市场总览</h1>
<p>基于 CEX 行情判断今天的大环境BTC/ETH 方向、山寨市场广度、成交额、资金费率,再结合新闻舆情和 AI 摘要作为辅助证据。</p>
</div>
<div class="head-actions">
<select class="select" id="hoursSel" onchange="reloadAll()">
<option value="24">近 24h</option>
<option value="72">近 3 天</option>
<option value="168">近 7 天</option>
</select>
<span class="select" id="marketUpdatedAt">等待刷新</span>
<button class="btn" onclick="reloadAll()">刷新</button>
</div>
</div>
<section class="hero">
<div class="decision" id="decisionPanel"><div class="loading">加载中...</div></div>
<div class="scorecard" id="scorePanel"><div class="loading">加载中...</div></div>
</section>
<div class="grid">
<section class="panel">
<div class="panel-head"><div class="panel-title">全市场广度</div><div class="panel-note">Binance USDT 山寨市场</div></div>
<div class="panel-body" id="breadthPanel"><div class="loading">加载中...</div></div>
</section>
<section class="panel">
<div class="panel-head"><div class="panel-title">24h 强势榜</div><div class="panel-note">涨幅靠前</div></div>
<div class="panel-body" id="gainersPanel"><div class="loading">加载中...</div></div>
</section>
<section class="panel">
<div class="panel-head"><div class="panel-title">成交额榜</div><div class="panel-note">流动性靠前</div></div>
<div class="panel-body" id="volumePanel"><div class="loading">加载中...</div></div>
</section>
<section class="panel">
<div class="panel-head"><div class="panel-title">合约情绪</div><div class="panel-note">Binance USDT 永续</div></div>
<div class="panel-body" id="fundingPanel"><div class="loading">加载中...</div></div>
</section>
<section class="panel">
<div class="panel-head"><div class="panel-title">AI 舆情</div><div class="panel-note">新闻聚合后的摘要</div></div>
<div class="panel-body" id="aiPanel"><div class="loading">加载中...</div></div>
</section>
<section class="panel full">
<div class="panel-head"><div class="panel-title">重要事件流</div><div class="panel-note">CEX 事件与新闻舆情中的高价值观察</div></div>
<div class="panel-body" id="rawPanel"><div class="loading">加载中...</div></div>
</section>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script>
var API='';function $(id){return document.getElementById(id)}function esc(v){return String(v==null?'':v).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]})}function fmtUsd(v){v=Number(v||0);if(Math.abs(v)>=1e9)return '$'+(v/1e9).toFixed(2)+'B';if(Math.abs(v)>=1e6)return '$'+(v/1e6).toFixed(2)+'M';if(Math.abs(v)>=1e3)return '$'+(v/1e3).toFixed(1)+'K';return '$'+v.toFixed(0)}function fmt(v,d){return Number(v||0).toFixed(d==null?1:d)}function pct(v,d){return fmt(v,d==null?2:d)+'%'}function chip(t,c){return '<span class="chip '+(c||'')+'">'+esc(t)+'</span>'}function compact(t){return esc(String(t||'').replace(/\s+/g,' ').trim())}
function rankList(items,mode){items=(items||[]).slice(0,8);if(!items.length)return '<div class="empty">暂无数据</div>';return '<div class="rank-list">'+items.map(function(x,i){var change=Number(x.change_24h||0),val=mode==='volume'?fmtUsd(x.volume_24h):pct(change,2),cls=mode==='volume'?'':(change>=0?'up':'down');return '<div class="rank"><span class="idx">'+(i+1)+'</span><div class="sym"><b>'+esc(x.symbol||'--')+'</b><span>'+esc(fmtUsd(x.volume_24h||0))+' · '+esc(fmt(x.price||0,6))+'</span></div><div class="val '+cls+'">'+esc(val)+'</div></div>'}).join('')+'</div>'}
function renderDecision(m,news,ai){var st=m.state||{},btc=(m.benchmarks||{})['BTC/USDT']||{},eth=(m.benchmarks||{})['ETH/USDT']||{},delay=m.snapshot_stale?'<div class="note" style="margin-top:10px">市场数据更新有延迟,系统会自动刷新。当前判断请适当降低权重。</div>':'';$('decisionPanel').innerHTML='<div class="decision-top"><div><div class="eyebrow">当前市场判断</div><h2>'+esc(st.label||'暂无全市场数据')+'</h2></div><span class="stance '+esc(st.tone||'neutral')+'">'+esc(st.label||'等待数据')+'</span></div><p>'+esc(st.summary||'全市场行情暂时不可用,请稍后刷新。')+'</p><div class="evidence"><div class="ev"><span>BTC / ETH 24h</span><b>'+pct(btc.change_24h,2)+' / '+pct(eth.change_24h,2)+'</b></div><div class="ev"><span>山寨涨跌比</span><b>'+fmt(m.advance_decline_ratio,2)+'</b></div><div class="ev"><span>强势 / 大跌币</span><b>'+esc((m.hot_count_5pct||0)+' / '+(m.crash_count_5pct||0))+'</b></div></div>'+delay}
function renderScore(m,ai){var f=m.funding||{};$('scorePanel').innerHTML=[['覆盖币种',m.sample_count||0],['上涨 / 下跌',(m.up_count||0)+' / '+(m.down_count||0)],['24h 总成交额',fmtUsd(m.total_quote_volume_24h||0)],['平均涨跌幅',pct(m.avg_change_24h,2)],['平均资金费率',f.sample_count?pct((f.avg_funding_rate||0)*100,4):'暂无'],['AI 状态',(ai&&ai.status)||'暂无']].map(function(x){return '<div class="score-row"><span>'+esc(x[0])+'</span><b>'+esc(x[1])+'</b></div>'}).join('')}
function renderBreadth(m){$('breadthPanel').innerHTML='<div class="metric-list"><div class="metric"><span>山寨覆盖范围</span><b>'+esc(m.sample_count||0)+' 个</b></div><div class="metric"><span>上涨 / 下跌 / 横盘</span><b>'+esc((m.up_count||0)+' / '+(m.down_count||0)+' / '+(m.flat_count||0))+'</b></div><div class="metric"><span>平均 / 中位涨跌</span><b>'+pct(m.avg_change_24h,2)+' / '+pct(m.median_change_24h,2)+'</b></div><div class="metric"><span>25% / 75% 分位</span><b>'+pct(m.p25_change_24h,2)+' / '+pct(m.p75_change_24h,2)+'</b></div></div><div class="note" style="margin-top:10px">'+esc(m.universe||'全市场口径')+'</div>'}
function renderFunding(m){var f=m.funding||{};$('fundingPanel').innerHTML='<div class="metric-list"><div class="metric"><span>样本数</span><b>'+esc(f.sample_count||0)+'</b></div><div class="metric"><span>平均资金费率</span><b>'+esc(f.sample_count?pct((f.avg_funding_rate||0)*100,4):'暂无')+'</b></div><div class="metric"><span>正费率 / 负费率</span><b>'+esc((f.positive_count||0)+' / '+(f.negative_count||0))+'</b></div><div class="metric"><span>极端多头 / 极端空头</span><b>'+esc((f.extreme_positive_count||0)+' / '+(f.extreme_negative_count||0))+'</b></div></div><div class="note" style="margin-top:10px">资金费率用于判断合约拥挤度。极端正费率越多,追高风险越需要被压低。</div>'}
function renderAi(ai,news){var c=(ai&&ai.content)||{},txt=c.summary||c.memo||c.why_now_or_not||'',fg=(news&&news.fear_greed)||{};$('aiPanel').innerHTML='<div class="metric-list"><div class="metric"><span>恐惧贪婪</span><b>'+esc((fg.classification||'--')+' · '+(fg.value||'--'))+'</b></div><div class="metric"><span>AI 生成状态</span><b>'+esc((ai&&ai.status)||'暂无')+'</b></div></div><div class="note" style="margin-top:10px">'+(txt?compact(txt):'暂无 AI 舆情摘要。')+'</div>'}
function renderRaw(news){var items=((news&&news.items)||[]).slice(0,6);if(!items.length){$('rawPanel').innerHTML='<div class="empty">暂无高价值事件。</div>';return}$('rawPanel').innerHTML='<div class="raw-list">'+items.map(function(e){return '<div class="raw-item"><h3>'+compact(e.title||e.event_type||'事件')+'</h3><div class="sub">'+esc([(e.symbol||''),(e.source||''),(e.importance||''),(e.decision||'')].filter(Boolean).join(' · '))+'</div></div>'}).join('')+'</div>'}
function fmtMarketTime(t){if(!t)return '等待刷新';var d=new Date(t);if(isNaN(d.getTime()))return t;return '行情更新 '+(d.getMonth()+1)+'/'+d.getDate()+' '+('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2)+':'+('0'+d.getSeconds()).slice(-2)}
function renderError(message){['decisionPanel','scorePanel','breadthPanel','gainersPanel','volumePanel','fundingPanel','aiPanel','rawPanel'].forEach(function(id){$(id).innerHTML='<div class="empty">'+esc(message||'加载失败')+'</div>'})}
async function reloadAll(){try{var h=$('hoursSel').value,d=await(await fetch(API+'/api/market/overview?hours='+h+'&_ts='+Date.now(),{cache:'no-store'})).json(),box=d.market||{},m=box.crypto_market||{},news=box.newsfeed||{},ai=box.ai_analysis||{};$('marketUpdatedAt').textContent=fmtMarketTime(d.updated_at||m.updated_at);if(box.market_error&&!m.sample_count){renderError('全市场行情获取失败:'+box.market_error);return}renderDecision(m,news,ai);renderScore(m,ai);renderBreadth(m);$('gainersPanel').innerHTML=rankList(m.top_gainers,'change');$('volumePanel').innerHTML=rankList(m.top_volume,'volume');renderFunding(m);renderAi(ai,news);renderRaw(news)}catch(e){renderError('加载失败')}}
reloadAll();
</script>
{% endblock %}