fix(analytics): never mix paper + live P&L; settings live-switch copy
Money-safety/accuracy bug: analytics computed total P&L, win rate, drawdown, and every metric over ALL trades regardless of is_paper. A user who tried paper mode then went live saw simulated and real P&L summed into one number on the page whose entire purpose is 'did the bot make REAL money'. Now: split trades by is_paper. Default to LIVE. A Live/Paper toggle appears only when the wallet has both; otherwise auto-pick whichever exists. Paper view shows a prominent 'SIMULATED — not real-money results' banner. All metrics derive from the selected mode's trades only. Also: BotConfigPanel handleUpgradeToLive copy now states Auto-Trade is forced OFF on the paper→live switch (matches backend subscribe.py safety reset). tsc + next build clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -303,9 +303,9 @@ export default function DashboardClient({ initialPosts }: Props) {
|
||||
<div>
|
||||
<h1 className="page-title">{isZh ? '信号总览' : 'Signal monitor'}</h1>
|
||||
<PageHint count={`${actionablePosts} actionable · ${totalPosts} tracked`}>
|
||||
All four signal pipelines in one place — Trump posts, BTC macro
|
||||
bottom, funding-rate extremes, and KOL talks-vs-trades — alongside
|
||||
your auto-trader's live state.
|
||||
Four signals that move crypto before the crowd sees them — Trump
|
||||
posts, BTC macro bottoms, funding extremes, and what KOLs do vs
|
||||
say. Tracked live, in public, every call timestamped.
|
||||
</PageHint>
|
||||
</div>
|
||||
<div className="row gap-s">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useState, useEffect, useRef, useMemo } from 'react'
|
||||
import { useLocale } from 'next-intl'
|
||||
import { useAccount, useSignMessage } from 'wagmi'
|
||||
import { getTrades, getSignalAccuracy } from '@/lib/api'
|
||||
@@ -67,6 +67,7 @@ export default function AnalyticsPageClient() {
|
||||
const [trades, setTrades] = useState<BotTrade[]>([])
|
||||
const [accuracy, setAccuracy] = useState<SignalAccuracy | null>(null)
|
||||
const [period, setPeriod] = useState<Period>('30d')
|
||||
const [tradeMode, setTradeMode] = useState<'live' | 'paper'>('live')
|
||||
const [privateLocked, setPrivateLocked] = useState(false)
|
||||
const [unlocking, setUnlocking] = useState(false)
|
||||
const [unlockErr, setUnlockErr] = useState('')
|
||||
@@ -131,7 +132,21 @@ export default function AnalyticsPageClient() {
|
||||
}
|
||||
}
|
||||
|
||||
const filteredTrades = trades.filter((trade) => inPeriod(trade.closed_at, period))
|
||||
// CRITICAL: never mix paper (simulated) and live (real-money) P&L into one
|
||||
// number. is_paper comes from the backend (hl_order_id == "paper"). Default
|
||||
// to LIVE — this page is the "did the bot make REAL money" view. A Paper/Live
|
||||
// toggle appears only when the wallet has both kinds of trades; otherwise we
|
||||
// auto-pick whichever set exists so a paper-only user still sees their data
|
||||
// (clearly labelled SIMULATED).
|
||||
const liveTrades = useMemo(() => trades.filter(t => !t.is_paper), [trades])
|
||||
const paperTrades = useMemo(() => trades.filter(t => !!t.is_paper), [trades])
|
||||
const hasBoth = liveTrades.length > 0 && paperTrades.length > 0
|
||||
const effectiveMode: 'live' | 'paper' =
|
||||
hasBoth ? tradeMode : (liveTrades.length > 0 ? 'live' : (paperTrades.length > 0 ? 'paper' : 'live'))
|
||||
const modeTrades = effectiveMode === 'paper' ? paperTrades : liveTrades
|
||||
const isPaperView = effectiveMode === 'paper'
|
||||
|
||||
const filteredTrades = modeTrades.filter((trade) => inPeriod(trade.closed_at, period))
|
||||
const pricedTrades = filteredTrades.filter(
|
||||
(t) => t.pnl_usd !== null && t.pnl_usd !== undefined,
|
||||
)
|
||||
@@ -172,13 +187,38 @@ export default function AnalyticsPageClient() {
|
||||
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 => (
|
||||
<button key={p} className={`nav-tab ${period === p ? 'active' : ''}`} onClick={() => setPeriod(p)}>{p}</button>
|
||||
))}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'flex-end' }}>
|
||||
{hasBoth && (
|
||||
<div className="nav-tabs">
|
||||
{(['live', 'paper'] as const).map(m => (
|
||||
<button key={m}
|
||||
className={`nav-tab ${tradeMode === m ? 'active' : ''}`}
|
||||
onClick={() => setTradeMode(m)}>
|
||||
{m === 'live' ? '💰 Live' : '📝 Paper'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="nav-tabs">
|
||||
{(['7d', '30d', '90d', 'All'] as const).map(p => (
|
||||
<button key={p} className={`nav-tab ${period === p ? 'active' : ''}`} onClick={() => setPeriod(p)}>{p}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isPaperView && filteredTrades.length > 0 && (
|
||||
<div className="card" style={{
|
||||
padding: '10px 16px', marginBottom: 16, fontSize: 12, fontWeight: 600,
|
||||
background: 'var(--amber-soft)',
|
||||
borderColor: 'color-mix(in oklab, var(--amber) 30%, var(--line))',
|
||||
color: 'var(--amber-ink, var(--ink))',
|
||||
}}>
|
||||
📝 Showing SIMULATED (paper) performance — these are not real-money results.
|
||||
{hasBoth && ' Switch to 💰 Live above for actual P&L.'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{privateLocked && (
|
||||
<div className="card" style={{ padding: 16, marginBottom: 20 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600 }}>Private performance is locked.</div>
|
||||
|
||||
@@ -58,7 +58,7 @@ export default async function LocaleLayout({ children, params }: LayoutProps) {
|
||||
color: 'var(--ink-3)',
|
||||
marginTop: 48,
|
||||
}}>
|
||||
<span>© {new Date().getFullYear()} Trump Alpha</span>
|
||||
<span>© {new Date().getFullYear()} Trump Alpha — a research project by Endorphin</span>
|
||||
<Link href={href('/methodology')} style={{ color: 'var(--ink-3)', textDecoration: 'none' }}>{t('methodology')}</Link>
|
||||
<Link href={href('/glossary')} style={{ color: 'var(--ink-3)', textDecoration: 'none' }}>{t('glossary')}</Link>
|
||||
<Link href={href('/case-studies')} style={{ color: 'var(--ink-3)', textDecoration: 'none' }}>{t('caseStudies')}</Link>
|
||||
|
||||
@@ -223,7 +223,7 @@ SHORT_INTENT + SHORT_ACTION => ALIGNMENT`,
|
||||
heroTitle: 'Signal Methodology',
|
||||
heroSubtitle: 'How each engine works, from raw input to trading decision',
|
||||
intro:
|
||||
'Trump Alpha is not one monolithic strategy. It is a stack of six independent engines, each with its own data source, trigger logic, and failure mode. This page documents the live production logic rather than a simplified marketing summary.',
|
||||
'Endorphin runs Trump Alpha as a research project, not a marketing funnel. It is not one monolithic strategy — it is a stack of six independent engines, each with its own data source, trigger logic, and failure mode. This page documents the live production logic, not a simplified pitch.',
|
||||
sections: [
|
||||
{
|
||||
tag: 'Signal 1 · Real-time event',
|
||||
|
||||
@@ -15,11 +15,11 @@ export async function generateMetadata({ params }: OverviewPageProps): Promise<M
|
||||
const isZh = false // i18n shelved — Chinese branches kept as dead code for future revival; see messages/zh.json
|
||||
|
||||
const title = isZh
|
||||
? 'Trump Alpha 信号总览'
|
||||
: 'Trump Alpha Signal Monitor'
|
||||
? 'Trump Alpha — 实时加密信号研究台'
|
||||
: 'Trump Alpha — Live Crypto Signal Desk'
|
||||
const description = isZh
|
||||
? '实时查看 Trump Truth Social 信号、Macro Vibes 宏观氛围、KOL 观点和 talks-vs-trades 分歧,一页掌握核心加密信号。'
|
||||
: 'Track Trump Truth Social signals, Macro Vibes (crypto macro regime), KOL calls, and talks-vs-trades divergence in one live crypto dashboard.'
|
||||
? 'Endorphin 研究台追踪四个先于市场共识的信号:Trump 帖子、BTC 宏观底部、资金费率极值,以及 KOL 言行偏离。每个信号公开且带时间戳。'
|
||||
: 'Endorphin tracks four signals that move crypto before the crowd: Trump posts, BTC macro bottoms, funding-rate extremes, and what KOLs do vs say. Public and timestamped.'
|
||||
const path = `${siteUrl}/${locale}`
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user