alphax/static/market.html
2026-05-18 15:40:59 +08:00

78 lines
16 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 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 {'&':'&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,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 %}