78 lines
16 KiB
HTML
78 lines
16 KiB
HTML
{% 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>基于整个加密市场判断今天的大环境: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">链上证据</div><div class="panel-note">只突出高价值事件</div></div>
|
||
<div class="panel-body" id="onchainPanel"><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">链上与事件流中的高价值观察</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 {'&':'&','<':'<','>':'>','"':'"',"'":'''}[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,on,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">市场快照已延迟 '+esc(m.snapshot_age_seconds||'--')+' 秒,请等待定时任务刷新或到调度中心手动触发 market。</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,on,ai){var f=m.funding||{},k=on.kpi||{};$('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):'暂无'],['链上高价值信号',k.event_count||0],['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 renderOnchain(on){var k=on.kpi||{},providers=((on.provider_status||{}).providers)||[],ptxt=providers.slice(0,4).map(function(p){return chip((p.label||p.provider)+' · '+(p.status||'--'),p.provider==='dexscreener'?'blue':'')}).join('');$('onchainPanel').innerHTML='<div class="metric-list"><div class="metric"><span>高价值事件</span><b>'+(k.event_count||0)+'</b></div><div class="metric"><span>正向 / 风险</span><b>'+(k.positive_events||0)+' / '+(k.risk_events||0)+'</b></div><div class="metric"><span>原始流 / 已映射</span><b>'+(k.raw_event_count||0)+' / '+(k.raw_mapped_count||0)+'</b></div></div><div style="margin-top:10px" class="chips">'+(ptxt||chip('暂无 provider 状态'))+'</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(on){var items=(on.raw_events||[]).filter(function(e){return e.priority!=='low'}).slice(0,6);if(!items.length){$('rawPanel').innerHTML='<div class="empty">暂无高价值原始事件。低优先级 DEX 曝光流已隐藏。</div>';return}$('rawPanel').innerHTML='<div class="raw-list">'+items.map(function(e){return '<div class="raw-item"><h3>'+compact(e.event_label||e.title||e.event_type)+'</h3><div class="sub">'+esc((e.chain||'--')+' · '+(e.mapped_symbol||e.token_short||'未映射')+' · '+(e.pipeline_note||''))+'</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','onchainPanel','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||{},on=box.onchain||{},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,on,news,ai);renderScore(m,on,ai);renderBreadth(m);$('gainersPanel').innerHTML=rankList(m.top_gainers,'change');$('volumePanel').innerHTML=rankList(m.top_volume,'volume');renderFunding(m);renderOnchain(on);renderAi(ai,news);renderRaw(on)}catch(e){renderError('加载失败')}}
|
||
reloadAll();
|
||
</script>
|
||
{% endblock %}
|