alphax/static/market.html
2026-05-16 14:52:10 +08:00

166 lines
15 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 Crypto — 市场总览{% endblock %}
{% block nav_links %}
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
<a class="sidebar-link active" href="/market"><svg class="link-icon"><use href="#svg-target"/></svg>市场总览</a>
<a class="sidebar-link" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
<a class="sidebar-link" href="/onchain"><svg class="link-icon"><use href="#svg-onchain"/></svg>链上异动</a>
<a class="sidebar-link" href="/subscription"><svg class="link-icon"><use href="#svg-subscribe"/></svg>订阅</a>
<a class="sidebar-link" href="/referral"><svg class="link-icon"><use href="#svg-referral"/></svg>推荐</a>
<div class="sidebar-section-label admin-link" style="display:none">研发</div>
<a class="sidebar-link admin-link" href="/pipeline" style="display:none"><svg class="link-icon"><use href="#svg-pipeline"/></svg>链路日志</a>
<a class="sidebar-link admin-link" href="/cron" style="display:none"><svg class="link-icon"><use href="#svg-cron"/></svg>调度中心</a>
<a class="sidebar-link admin-link" href="/llm-insights" style="display:none"><svg class="link-icon"><use href="#svg-ai"/></svg>AI 记录</a>
<a class="sidebar-link admin-link" href="/strategy" style="display:none"><svg class="link-icon"><use href="#svg-target"/></svg>策略</a>
<a class="sidebar-link admin-link" href="/iteration" style="display:none"><svg class="link-icon"><use href="#svg-iterate"/></svg>迭代</a>
<a class="sidebar-link admin-link" href="/admin.html" style="display:none"><svg class="link-icon"><use href="#svg-admin"/></svg>管理</a>
{% 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:-.8px;color:var(--ink)}.page-head p{margin-top:5px;color:var(--stone);font-size:13px;line-height:1.55;max-width:880px}.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)}.btn{cursor:pointer}.hint{padding:10px 12px;border:1px solid rgba(66,98,255,.14);background:rgba(66,98,255,.045);border-radius:var(--radius-md);color:var(--slate);font-size:12px;line-height:1.55;margin-bottom:14px}.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:13px;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:22px;line-height:1;font-weight:950;letter-spacing:-.5px}.kpi b.green{color:var(--green)}.kpi b.red{color:var(--red)}.kpi b.blue{color:var(--blue)}.kpi b.yellow{color:var(--yellow-dark)}.grid{display:grid;grid-template-columns:1.1fr .9fr;gap:12px;margin-bottom:14px}.panel{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);overflow:hidden;min-width:0}.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}.mini-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.mini{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:10px;min-width:0}.mini span{display:block;color:var(--stone);font-size:10px;font-weight:900}.mini b{display:block;margin-top:4px;color:var(--ink);font-size:15px;font-weight:950;line-height:1.3}.line{display:flex;justify-content:space-between;gap:10px;align-items:center;border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:10px 11px;margin-bottom:8px}.line .lbl{font-size:12px;font-weight:900;color:var(--ink)}.line .val{font-size:12px;color:var(--slate);font-weight:850;text-align:right}.chips{display:flex;flex-wrap:wrap;gap:8px}.chip{display:inline-flex;align-items:center;gap:6px;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)}.raw-list{display:grid;gap:8px}.raw-item{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:11px}.raw-item h3{font-size:12px;font-weight:950;color:var(--ink);line-height:1.45}.raw-item .sub{margin-top:6px;color:var(--stone);font-size:11px;line-height:1.45}.raw-tags{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}.tag{display:inline-flex;padding:3px 7px;border-radius:999px;background:var(--canvas);border:1px solid var(--hairline-soft);font-size:10px;font-weight:900;color:var(--blue)}.tag.risk{color:var(--red)}.tag.hot{color:var(--green)}.empty,.loading{padding:34px 16px;text-align:center;color:var(--stone);font-size:13px}.soft-note{padding:10px 12px;border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);color:var(--slate);font-size:12px;line-height:1.5}.compact-note{color:var(--stone);font-size:12px;line-height:1.5}.status-pill{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px;border-radius:999px;border:1px solid var(--hairline-soft);background:var(--surface);font-size:12px;font-weight:900;color:var(--slate)}.status-pill.ok{background:var(--green-light);border-color:rgba(0,180,115,.18);color:var(--green)}.status-pill.warn{background:var(--yellow-light);border-color:rgba(252,185,0,.22);color:var(--yellow-dark)}.status-pill.bad{background:var(--red-light);border-color:rgba(229,62,62,.18);color:var(--red)}@media(max-width:1080px){.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.grid{grid-template-columns:1fr}}@media(max-width:620px){.shell{width:min(100% - 24px,1280px)}.page-head h1{font-size:22px}.mini-grid{grid-template-columns:1fr}}
</style>
{% endblock %}
{% block content %}
<div class="shell">
<div class="page-head">
<div>
<h1>市场总览</h1>
<p>这里不是单币推荐页而是帮你快速判断当前环境场内有没有动能链上有没有真异动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>
<button class="btn" onclick="reloadAll()">刷新</button>
</div>
</div>
<div class="hint">只保留高信号信息。空的 AI / 舆情 / 链上统计不占主位,避免页面看起来热闹但没有结论。</div>
<div class="kpis" id="kpis"><div class="loading">加载中...</div></div>
<div class="grid">
<section class="panel">
<div class="panel-head"><div class="panel-title">市场结论</div><div class="panel-note">场内 + 链上 + AI 的最短摘要</div></div>
<div class="panel-body" id="summaryPanel"><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="sectorPanel"><div class="loading">加载中...</div></div>
</section>
</div>
<div class="grid">
<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">原始链上流</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 fmtNum(v,d){v=Number(v||0);return v.toFixed(d==null?1:d)}
function chip(text, cls){return '<span class="chip '+(cls||'')+'">'+esc(text)+'</span>'}
function compact(text){return esc(String(text||'').replace(/\s+/g,' ').trim())}
function loadKpis(stats, onchain, news, ai){
var fg=(news&&news.fear_greed)||{};
var market=(stats&&stats.market_context_overview)||{};
var onchainKpi=(onchain&&onchain.kpi)||{};
var aiStatus=(ai&&ai.status)||'--';
$('kpis').innerHTML=[
['情绪指数', fg.value!=null?fg.value:'--', 'blue'],
['场内热点', market.hot_sector_count||0, 'green'],
['链上原始流', onchainKpi.raw_event_count||0, 'blue'],
['AI 状态', aiStatus, 'yellow']
].map(function(x){return '<div class="kpi"><span>'+x[0]+'</span><b class="'+x[2]+'">'+x[1]+'</b></div>'}).join('');
}
function renderSummary(stats, onchain, news, ai){
var market=(stats&&stats.market_context_overview)||{};
var fg=(news&&news.fear_greed)||{};
var aiContent=(ai&&ai.content)||{};
var summary=(aiContent.summary||aiContent.memo||aiContent.why_now_or_not||'').trim();
var mood = '观望';
var moodClass = 'warn';
var heat = Number(market.avg_turnover_acceleration_1h||0);
if (heat >= 1.2) { mood='偏机会'; moodClass='ok'; }
else if (heat <= 0.7) { mood='偏谨慎'; moodClass='bad'; }
var aiStatus = (ai&&ai.status) || '--';
var aiText = summary || '暂无 AI 舆情摘要';
$('summaryPanel').innerHTML =
'<div class="line"><span class="lbl">市场温度</span><span class="val"><span class="status-pill '+moodClass+'">'+mood+'</span></span></div>'+
'<div class="line"><span class="lbl">动能</span><span class="val">'+fmtNum(market.avg_turnover_acceleration_1h||0,1)+'x / '+fmtNum(market.avg_turnover_acceleration_4h||0,1)+'x</span></div>'+
'<div class="line"><span class="lbl">成交额</span><span class="val">'+fmtUsd(market.avg_volume_24h||0)+'</span></div>'+
'<div class="line"><span class="lbl">情绪指数</span><span class="val">'+esc((fg.classification||'--')+' · '+(fg.value!=null?fg.value:'--'))+'</span></div>'+
'<div class="line"><span class="lbl">AI 舆情</span><span class="val">'+esc(aiStatus)+'</span></div>'+
'<div class="soft-note">'+compact(aiText)+'</div>';
}
function renderSectors(stats){
var sectors=((stats&&stats.market_context_overview||{}).top_hot_sectors)||[];
if (!sectors.length) { $('sectorPanel').innerHTML = '<div class="empty">暂无热点板块</div>'; return; }
$('sectorPanel').innerHTML = '<div class="chips">'+sectors.map(function(sec){return chip((sec.sector||'--')+' · '+(sec.count||0),'hot')}).join('')+'</div>';
}
function renderOnchain(d){
var k=(d&&d.kpi)||{};
var hot=(d.hot_tokens||[]).slice(0,4);
var risk=(d.risk_tokens||[]).slice(0,4);
var lines = [];
lines.push(['原始事件', k.raw_event_count||0]);
lines.push(['已映射', k.raw_mapped_count||0]);
lines.push(['覆盖币种', k.token_count||0]);
lines.push(['正向异动', k.positive_events||0]);
lines.push(['风险事件', k.risk_events||0]);
lines.push(['DEX 成交', fmtUsd(k.dex_volume_usd||0)]);
var body = '<div class="mini-grid">'+lines.map(function(x){return '<div class="mini"><span>'+x[0]+'</span><b>'+x[1]+'</b></div>'}).join('')+'</div>';
var hotHtml = hot.length ? '<div style="margin-top:10px"><div class="panel-note" style="margin-bottom:8px">链上热度</div><div class="chips">'+hot.map(function(t){return chip((t.symbol||'--')+' · '+fmtUsd(t.dex_volume_usd),'hot')}).join('')+'</div></div>' : '';
var riskHtml = risk.length ? '<div style="margin-top:10px"><div class="panel-note" style="margin-bottom:8px">风险异动</div><div class="chips">'+risk.map(function(t){return chip((t.symbol||'--')+' · '+Number(t.risk_score||0).toFixed(0),'risk')}).join('')+'</div></div>' : '';
$('onchainPanel').innerHTML = body + hotHtml + riskHtml;
}
function renderRaw(d){
var items=(d&&d.raw_events)||[];
if (!items.length) { $('rawPanel').innerHTML = '<div class="empty">暂无原始链上流</div>'; return; }
$('rawPanel').innerHTML = '<div class="raw-list">'+items.slice(0,6).map(function(e){
var mapped = e.mapping_status === 'mapped';
return '<div class="raw-item"><h3>'+compact(e.title||e.event_label||e.event_type||'链上原始事件')+' <span class="status-pill '+(mapped?'ok':'warn')+'" style="margin-left:6px;height:22px">'+(mapped?'已映射':'未映射')+'</span></h3><div class="sub">'+esc((e.chain||'--')+' · '+(e.mapped_symbol||'未映射')+' · '+fmtUsd(e.total_amount||e.amount||0)+' · '+(e.detected_at||'--'))+'</div><div class="raw-tags">'+[(e.source||''),(e.event_type||''),(e.url?'来源':'')].filter(Boolean).map(function(t){return '<span class="tag">'+esc(t)+'</span>'}).join('')+'</div></div>';
}).join('')+'</div>';
}
function renderAiStatus(ai){
var content=(ai&&ai.content)||{};
var summary=(content.summary||content.memo||content.why_now_or_not||'').trim();
if (!summary) return '';
return '<div class="soft-note" style="margin-top:10px">AI 结论:'+compact(summary)+'</div>';
}
async function reloadAll(){
try{
var hours=$('hoursSel').value;
var resp=await fetch(API+'/api/market/overview?hours='+hours);
var d=await resp.json();
var market=(d.market||{}).stats||{};
var onchain=(d.market||{}).onchain||{};
var news=(d.market||{}).newsfeed||{};
var ai=(d.market||{}).ai_analysis||{};
loadKpis(market,onchain,news,ai);
renderSummary(market,onchain,news,ai);
renderSectors(market);
renderOnchain(onchain);
renderRaw(onchain);
var aiTail = renderAiStatus(ai);
if (aiTail) $('summaryPanel').innerHTML += aiTail;
}catch(e){
$('kpis').innerHTML='<div class="empty">市场总览加载失败</div>';
$('summaryPanel').innerHTML='';
$('sectorPanel').innerHTML='';
$('onchainPanel').innerHTML='';
$('rawPanel').innerHTML='';
}
}
reloadAll();
</script>
{% endblock %}