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:
k
2026-05-30 01:03:15 +08:00
parent e78a61bd6e
commit 594d9817ba
8 changed files with 72 additions and 25 deletions
+3 -3
View File
@@ -303,9 +303,9 @@ export default function DashboardClient({ initialPosts }: Props) {
<div> <div>
<h1 className="page-title">{isZh ? '信号总览' : 'Signal monitor'}</h1> <h1 className="page-title">{isZh ? '信号总览' : 'Signal monitor'}</h1>
<PageHint count={`${actionablePosts} actionable · ${totalPosts} tracked`}> <PageHint count={`${actionablePosts} actionable · ${totalPosts} tracked`}>
All four signal pipelines in one place Trump posts, BTC macro Four signals that move crypto before the crowd sees them Trump
bottom, funding-rate extremes, and KOL talks-vs-trades alongside posts, BTC macro bottoms, funding extremes, and what KOLs do vs
your auto-trader's live state. say. Tracked live, in public, every call timestamped.
</PageHint> </PageHint>
</div> </div>
<div className="row gap-s"> <div className="row gap-s">
+46 -6
View File
@@ -1,6 +1,6 @@
'use client' 'use client'
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef, useMemo } from 'react'
import { useLocale } from 'next-intl' import { useLocale } from 'next-intl'
import { useAccount, useSignMessage } from 'wagmi' import { useAccount, useSignMessage } from 'wagmi'
import { getTrades, getSignalAccuracy } from '@/lib/api' import { getTrades, getSignalAccuracy } from '@/lib/api'
@@ -67,6 +67,7 @@ export default function AnalyticsPageClient() {
const [trades, setTrades] = useState<BotTrade[]>([]) const [trades, setTrades] = useState<BotTrade[]>([])
const [accuracy, setAccuracy] = useState<SignalAccuracy | null>(null) const [accuracy, setAccuracy] = useState<SignalAccuracy | null>(null)
const [period, setPeriod] = useState<Period>('30d') const [period, setPeriod] = useState<Period>('30d')
const [tradeMode, setTradeMode] = useState<'live' | 'paper'>('live')
const [privateLocked, setPrivateLocked] = useState(false) const [privateLocked, setPrivateLocked] = useState(false)
const [unlocking, setUnlocking] = useState(false) const [unlocking, setUnlocking] = useState(false)
const [unlockErr, setUnlockErr] = useState('') 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( const pricedTrades = filteredTrades.filter(
(t) => t.pnl_usd !== null && t.pnl_usd !== undefined, (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. and AI signal accuracy across the time window you pick on the right.
</PageHint> </PageHint>
</div> </div>
<div className="nav-tabs"> <div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'flex-end' }}>
{(['7d', '30d', '90d', 'All'] as const).map(p => ( {hasBoth && (
<button key={p} className={`nav-tab ${period === p ? 'active' : ''}`} onClick={() => setPeriod(p)}>{p}</button> <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>
</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 && ( {privateLocked && (
<div className="card" style={{ padding: 16, marginBottom: 20 }}> <div className="card" style={{ padding: 16, marginBottom: 20 }}>
<div style={{ fontSize: 13, fontWeight: 600 }}>Private performance is locked.</div> <div style={{ fontSize: 13, fontWeight: 600 }}>Private performance is locked.</div>
+1 -1
View File
@@ -58,7 +58,7 @@ export default async function LocaleLayout({ children, params }: LayoutProps) {
color: 'var(--ink-3)', color: 'var(--ink-3)',
marginTop: 48, 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('/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('/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> <Link href={href('/case-studies')} style={{ color: 'var(--ink-3)', textDecoration: 'none' }}>{t('caseStudies')}</Link>
+1 -1
View File
@@ -223,7 +223,7 @@ SHORT_INTENT + SHORT_ACTION => ALIGNMENT`,
heroTitle: 'Signal Methodology', heroTitle: 'Signal Methodology',
heroSubtitle: 'How each engine works, from raw input to trading decision', heroSubtitle: 'How each engine works, from raw input to trading decision',
intro: 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: [ sections: [
{ {
tag: 'Signal 1 · Real-time event', tag: 'Signal 1 · Real-time event',
+4 -4
View File
@@ -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 isZh = false // i18n shelved — Chinese branches kept as dead code for future revival; see messages/zh.json
const title = isZh const title = isZh
? 'Trump Alpha 信号总览' ? 'Trump Alpha — 实时加密信号研究台'
: 'Trump Alpha Signal Monitor' : 'Trump Alpha — Live Crypto Signal Desk'
const description = isZh const description = isZh
? '实时查看 Trump Truth Social 信号、Macro Vibes 宏观氛围、KOL 观点和 talks-vs-trades 分歧,一页掌握核心加密信号。' ? 'Endorphin 研究台追踪四个先于市场共识的信号:Trump 帖子、BTC 宏观底部、资金费率极值,以及 KOL 言行偏离。每个信号公开且带时间戳。'
: 'Track Trump Truth Social signals, Macro Vibes (crypto macro regime), KOL calls, and talks-vs-trades divergence in one live crypto dashboard.' : '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}` const path = `${siteUrl}/${locale}`
return { return {
+14 -7
View File
@@ -4,8 +4,8 @@ import Script from 'next/script'
import './[locale]/globals.css' import './[locale]/globals.css'
const siteTitle = 'Trump Alpha' const siteTitle = 'Trump Alpha'
const siteTagline = 'Four crypto alpha feeds, one dashboard' const siteTagline = 'The crypto signals that move price before the crowd'
const siteDescription = 'Real-time Trump Truth Social signals, BTC macro-bottom confluence, KOL Substack/podcast signals, and talks-vs-trades divergence — all scored by AI in one live dashboard.' const siteDescription = "Endorphin is a crypto research desk tracking four signals that move markets before consensus catches up — Trump's posts, BTC macro bottoms, funding-rate extremes, and what KOLs do versus what they say. Every signal is public and timestamped."
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL const siteUrl = process.env.NEXT_PUBLIC_SITE_URL
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -42,9 +42,9 @@ export const metadata: Metadata = {
'funding rate reversal', 'funding rate reversal',
'Bitcoin cycle bottom', 'Bitcoin cycle bottom',
], ],
authors: [{ name: 'Trump Alpha' }], authors: [{ name: 'Endorphin' }],
creator: 'Trump Alpha', creator: 'Endorphin',
publisher: 'Trump Alpha', publisher: 'Endorphin',
category: 'Finance', category: 'Finance',
robots: { robots: {
index: true, index: true,
@@ -137,6 +137,8 @@ const jsonLd = {
applicationSubCategory: 'Cryptocurrency Trading Tool', applicationSubCategory: 'Cryptocurrency Trading Tool',
operatingSystem: 'Web', operatingSystem: 'Web',
description: siteDescription, description: siteDescription,
publisher: { '@id': `${_base}/#org` },
author: { '@id': `${_base}/#org` },
datePublished: '2025-01-01', datePublished: '2025-01-01',
dateModified: _today, dateModified: _today,
inLanguage: ['en'], inLanguage: ['en'],
@@ -168,10 +170,15 @@ const jsonLd = {
{ {
'@type': 'Organization', '@type': 'Organization',
'@id': `${_base}/#org`, '@id': `${_base}/#org`,
name: 'Trump Alpha', name: 'Endorphin',
alternateName: ['Endorphin Research', 'Endorphin Desk'],
url: _base, url: _base,
logo: `${_base}/icon`, logo: `${_base}/icon`,
description: 'AI-powered crypto intelligence platform tracking Trump posts, on-chain signals, and KOL sentiment.', description: 'Endorphin is a crypto research desk. Trump Alpha is its public signal project — four market-moving signals tracked live and timestamped.',
brand: {
'@type': 'Brand',
name: 'Trump Alpha',
},
contactPoint: { contactPoint: {
'@type': 'ContactPoint', '@type': 'ContactPoint',
email: 'support@bitnews.day', email: 'support@bitnews.day',
+1 -1
View File
@@ -304,7 +304,7 @@ export default function BotConfigPanel() {
if (!address) return if (!address) return
const ok = await confirmSign({ const ok = await confirmSign({
label: 'Switch to live trading', label: 'Switch to live trading',
description: 'Your subscription will change from paper mode to live. No trade opens immediately you still need to add a Hyperliquid API key and enable a signal module before the bot can act.', description: 'Your subscription will change from paper mode to live. For your safety, Auto-Trade is turned OFF on this switch — you must re-enable it explicitly while live. No trade opens immediately; you still need to add a Hyperliquid API key first.',
danger: true, danger: true,
}) })
if (!ok) return if (!ok) return
+2 -2
View File
@@ -1,10 +1,10 @@
# Trump Alpha # Trump Alpha
> AI-powered crypto intelligence platform. Four uncorrelated signal sources: Trump Truth Social sentiment, Macro Vibes (crypto macro regime dashboard), KOL Substack/podcast signals, and talks-vs-trades on-chain divergence. > A crypto research project by Endorphin, a research desk. Tracks four uncorrelated signals that move markets before consensus catches up: Trump Truth Social sentiment, Macro Vibes (crypto macro regime dashboard), KOL Substack/podcast signals, and talks-vs-trades on-chain divergence. Every signal is public and timestamped.
## What this site does ## What this site does
Trump Alpha aggregates four signal engines and one macro context view into one live dashboard. All four dashboards are free to read — no account required. Trump Alpha is Endorphin's public signal desk: four independent signal engines and one macro context view in one live dashboard. All four dashboards are free to read — no account required, every call timestamped.
1. **Trump Truth Social Signal** — Scrapes Trump's Truth Social posts within 15 seconds of publish. AI classifies each post as LONG (bullish crypto), SHORT (bearish), or NOISE (off-topic) with a confidence score 0100. Only posts scoring ≥ 88 confidence trigger the optional auto-trader. The entire pipeline from post to classification runs in under 3 seconds. Optional: auto-execute on Hyperliquid with isolated margin, automatic TP/SL, and six user-defined safety limits (leverage cap, position size, daily budget, active-hours window, confidence floor, stop-loss floor). 1. **Trump Truth Social Signal** — Scrapes Trump's Truth Social posts within 15 seconds of publish. AI classifies each post as LONG (bullish crypto), SHORT (bearish), or NOISE (off-topic) with a confidence score 0100. Only posts scoring ≥ 88 confidence trigger the optional auto-trader. The entire pipeline from post to classification runs in under 3 seconds. Optional: auto-execute on Hyperliquid with isolated margin, automatic TP/SL, and six user-defined safety limits (leverage cap, position size, daily budget, active-hours window, confidence floor, stop-loss floor).