feat(macro-vibes): rename BTC Signal → Macro Vibes; add MacroPanel UI

Module rename across page H1, navbar tab, URL (/en/btc → /en/macro),
all metadata/JSON-LD, sitemap, llms.txt, opengraph image, and SystemControl
copy. Old /btc route fully removed.

New components/btc/MacroPanel.tsx polls /api/macro/snapshot and lays out
the 8 indicators in four sections (Valuation / Bottom trigger reference /
Market structure / Sentiment & flows / Positioning) with tone-coloured
values, current-band threshold chips, and a single CoinGlass / source
chart link per card. Composite -100..+100 needle pulses on score change.

Also fixes the pinned bottom-reversal alert on the homepage, which still
linked to the now-404 /en/btc — now routes to /en/macro.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-26 01:05:18 +08:00
parent d72323b1c6
commit f34ae9eb00
33 changed files with 1810 additions and 228 deletions
+23 -9
View File
@@ -7,6 +7,8 @@ import { getPerformance, getTrades, getSignalAccuracy } from '@/lib/api'
import type { BotPerformance, BotTrade } from '@/types'
import type { SignalAccuracy } from '@/lib/api'
import { getCachedViewEnvelope, getOrCreateViewEnvelope } from '@/lib/signedRequest'
import PageHint from '@/components/ui/PageHint'
import InfoTip from '@/components/ui/InfoTip'
type Period = '7d' | '30d' | '90d' | 'All'
@@ -115,12 +117,18 @@ export default function AnalyticsPageClient() {
: totalPnl
const metrics = [
{ k: isZh ? '最大回撤' : 'Max drawdown', v: filteredTrades.length ? maxDrawdown.toFixed(1) + '%' : '—', sub: isZh ? '从高点到低点的最大跌幅' : 'Worst peak-to-trough', down: true },
{ k: isZh ? '总交易数' : 'Total trades', v: String(filteredTrades.length || '—'), sub: isZh ? '已平仓机器人交易' : 'Closed bot executions' },
{ k: isZh ? '平均持仓时间' : 'Avg hold time', v: filteredTrades.length ? fmtHold(Math.round(avgHold)) : '—', sub: isZh ? '按单笔计算' : 'Per trade' },
{ k: isZh ? '平均单笔盈亏' : 'Avg trade P&L', v: fmtMoney(avgTrade), sub: isZh ? '每笔交易均值' : 'Mean per trade', up: avgTrade > 0 },
{ k: isZh ? '最佳单笔' : 'Best trade', v: fmtMoney(bestTrade), sub: isZh ? '单笔最大盈利' : 'Largest single win', up: true },
{ k: isZh ? '最差单笔' : 'Worst trade', v: fmtMoney(worstTrade), sub: isZh ? '单笔最大亏损' : 'Largest single loss', down: true },
{ k: isZh ? '最大回撤' : 'Max drawdown', v: filteredTrades.length ? maxDrawdown.toFixed(1) + '%' : '—', sub: isZh ? '从高点到低点的最大跌幅' : 'Worst peak-to-trough', down: true,
tip: 'Largest equity drop from a previous high. Tells you the worst losing streak you would have lived through.' },
{ k: isZh ? '总交易数' : 'Total trades', v: String(filteredTrades.length || '—'), sub: isZh ? '已平仓机器人交易' : 'Closed bot executions',
tip: 'How many closed positions in this window. Open trades not counted.' },
{ k: isZh ? '平均持仓时间' : 'Avg hold time', v: filteredTrades.length ? fmtHold(Math.round(avgHold)) : '—', sub: isZh ? '单笔计算' : 'Per trade',
tip: 'Entry → exit duration averaged across every closed trade.' },
{ k: isZh ? '平均单笔盈亏' : 'Avg trade P&L', v: fmtMoney(avgTrade), sub: isZh ? '每笔交易均值' : 'Mean per trade', up: avgTrade > 0,
tip: 'Total P&L ÷ number of trades. Positive = the strategy has edge per trade.' },
{ k: isZh ? '最佳单笔' : 'Best trade', v: fmtMoney(bestTrade), sub: isZh ? '单笔最大盈利' : 'Largest single win', up: true,
tip: 'Biggest realized gain on one trade in this window.' },
{ k: isZh ? '最差单笔' : 'Worst trade', v: fmtMoney(worstTrade), sub: isZh ? '单笔最大亏损' : 'Largest single loss', down: true,
tip: 'Biggest realized loss on one trade. Should be bounded by your stop-loss setting.' },
]
return (
@@ -128,7 +136,10 @@ export default function AnalyticsPageClient() {
<div className="page-head">
<div>
<h1 className="page-title">{isZh ? '分析面板' : 'Analytics'}</h1>
<p className="page-sub">{isZh ? `查看 ${period} 窗口内的信号与交易表现。` : `Deep dive into ${period} of signals and trades.`}</p>
<PageHint>
Did the bot actually make money? Win rate, drawdown, average trade,
and AI signal accuracy across the time window you pick on the right.
</PageHint>
</div>
<div className="nav-tabs">
{(['7d', '30d', '90d', 'All'] as const).map(p => (
@@ -154,8 +165,11 @@ export default function AnalyticsPageClient() {
<div className="metric-grid" style={{ marginBottom: 20 }}>
{metrics.map(m => (
<div key={m.k} className="card" style={{ padding: 20 }}>
<div className="tiny" style={{ marginBottom: 8 }}>{m.k}</div>
<div key={m.k} className="card" style={{ padding: 20, overflow: 'visible' }}>
<div className="tiny" style={{ marginBottom: 8, display: 'flex', alignItems: 'center' }}>
{m.k}
{m.tip && <InfoTip text={m.tip} placement="top" width={240} />}
</div>
<div className={`mono ${m.up ? 'delta up' : m.down ? 'delta down' : ''}`} style={{ fontSize: 24, fontWeight: 500, letterSpacing: '-0.01em' }}>{m.v}</div>
<div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4 }}>{m.sub}</div>
</div>