mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-13 15:57:01 +08:00
fix: reconcile local positions against the live exchange book — stop lost PnL
Root cause of the dashboard under-reporting: any missed or unmatched fill (position flip, sync gap, liquidation) left a 'zombie' OPEN row. Every later close on that symbol landed as a partial close against the zombie, so the row never reached CLOSED and its realized PnL never entered the closed-trade statistics — Edge Profile, realized P/L, win rate, and the AI's own track-record context all silently under-reported. Fix: after each Hyperliquid order sync, reconcile local OPEN rows against the exchange's live book (core perps + xyz dex), scoped by exchange account so rows left by prior autopilot incarnations (each relaunch mints a fresh trader_id sharing one exchange) are healed too. Rows the exchange no longer holds are closed with their accumulated PnL; oversized rows are trimmed to the live quantity. Live run confirmed: 9 zombie rows closed across incarnations, book now matches the exchange exactly. Also removes the header language switcher (desktop + mobile) and the login/setup LanguageSwitcher — the product UI is English-only. New store methods GetOpenPositionsByExchange + ReconcileOpenPositionsWithLive with cross-incarnation test coverage.
This commit is contained in:
@@ -6,7 +6,6 @@ import { useAuth } from '../../contexts/AuthContext'
|
||||
import { useLanguage } from '../../contexts/LanguageContext'
|
||||
import { t } from '../../i18n/translations'
|
||||
import { DeepVoidBackground } from '../common/DeepVoidBackground'
|
||||
import { LanguageSwitcher } from '../common/LanguageSwitcher'
|
||||
|
||||
export function LoginPage() {
|
||||
const { language } = useLanguage()
|
||||
@@ -64,7 +63,6 @@ export function LoginPage() {
|
||||
|
||||
return (
|
||||
<DeepVoidBackground disableAnimation>
|
||||
<LanguageSwitcher />
|
||||
|
||||
{/* Self-contained centering grid — works regardless of parent flex setup */}
|
||||
<main className="flex-1 grid lg:grid-cols-2">
|
||||
|
||||
@@ -30,8 +30,7 @@ export default function HeaderBar({
|
||||
isLoggedIn = false,
|
||||
isHomePage = false,
|
||||
currentPage,
|
||||
language = 'zh' as Language,
|
||||
onLanguageChange,
|
||||
language = 'en' as Language,
|
||||
user,
|
||||
onLogout,
|
||||
onPageChange,
|
||||
@@ -40,12 +39,10 @@ export default function HeaderBar({
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
const [languageDropdownOpen, setLanguageDropdownOpen] = useState(false)
|
||||
const [userDropdownOpen, setUserDropdownOpen] = useState(false)
|
||||
const [userMode, setUserModeState] = useState<UserMode>(
|
||||
() => getUserMode() ?? 'advanced'
|
||||
)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const userDropdownRef = useRef<HTMLDivElement>(null)
|
||||
const resolvedCurrentPage =
|
||||
currentPage ?? getCurrentPageForPath(location.pathname)
|
||||
@@ -63,12 +60,6 @@ export default function HeaderBar({
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setLanguageDropdownOpen(false)
|
||||
}
|
||||
if (
|
||||
userDropdownRef.current &&
|
||||
!userDropdownRef.current.contains(event.target as Node)
|
||||
@@ -357,56 +348,7 @@ export default function HeaderBar({
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Language Toggle - Always at the rightmost */}
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setLanguageDropdownOpen(!languageDropdownOpen)}
|
||||
className="flex items-center gap-2 px-3 py-2 rounded transition-colors text-nofx-text-muted hover:bg-white/5"
|
||||
>
|
||||
<span className="text-lg">
|
||||
{language === 'zh' ? '🇨🇳' : language === 'id' ? '🇮🇩' : '🇺🇸'}
|
||||
</span>
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{languageDropdownOpen && (
|
||||
<div className="absolute right-0 top-full mt-2 w-32 rounded-lg shadow-lg overflow-hidden z-50 bg-nofx-bg-lighter border border-nofx-gold/20">
|
||||
<button
|
||||
onClick={() => {
|
||||
onLanguageChange?.('zh')
|
||||
setLanguageDropdownOpen(false)
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors text-nofx-text-muted hover:text-nofx-text
|
||||
${language === 'zh' ? 'bg-nofx-gold/10' : 'hover:bg-[rgba(26,24,19,0.06)]'}`}
|
||||
>
|
||||
<span className="text-base">🇨🇳</span>
|
||||
<span className="text-sm">Chinese</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onLanguageChange?.('en')
|
||||
setLanguageDropdownOpen(false)
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors text-nofx-text-muted hover:text-nofx-text
|
||||
${language === 'en' ? 'bg-nofx-gold/10' : 'hover:bg-[rgba(26,24,19,0.06)]'}`}
|
||||
>
|
||||
<span className="text-base">🇺🇸</span>
|
||||
<span className="text-sm">English</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onLanguageChange?.('id')
|
||||
setLanguageDropdownOpen(false)
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors text-nofx-text-muted hover:text-nofx-text
|
||||
${language === 'id' ? 'bg-nofx-gold/10' : 'hover:bg-[rgba(26,24,19,0.06)]'}`}
|
||||
>
|
||||
<span className="text-base">🇮🇩</span>
|
||||
<span className="text-sm">Bahasa</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Language switcher removed — the product UI is English-only. */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -619,28 +561,8 @@ export default function HeaderBar({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Account / Lang */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Lang Switcher */}
|
||||
<div className="flex bg-zinc-900 rounded-lg p-1 border border-zinc-800">
|
||||
{['zh', 'en', 'id'].map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
onClick={() => {
|
||||
onLanguageChange?.(lang as Language)
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
className={`flex-1 py-3 text-sm font-bold rounded-md transition-colors ${
|
||||
language === lang
|
||||
? 'bg-zinc-800 text-white shadow-sm'
|
||||
: 'text-zinc-500'
|
||||
}`}
|
||||
>
|
||||
{lang === 'zh' ? 'CN' : lang === 'id' ? 'ID' : 'EN'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Account (language switcher removed — English-only UI) */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{/* Auth Actions */}
|
||||
{isLoggedIn && user ? (
|
||||
<button
|
||||
|
||||
@@ -5,7 +5,6 @@ import { invalidateSystemConfig } from '../../lib/config'
|
||||
import { OnboardingModeSelector } from '../auth/OnboardingModeSelector'
|
||||
import type { UserMode } from '../../lib/onboarding'
|
||||
import { useLanguage } from '../../contexts/LanguageContext'
|
||||
import { LanguageSwitcher } from '../common/LanguageSwitcher'
|
||||
|
||||
const labels = {
|
||||
zh: {
|
||||
@@ -127,7 +126,6 @@ export function SetupPage() {
|
||||
{/* Blur overlay */}
|
||||
<div className="absolute inset-0 backdrop-blur-md bg-nofx-bg/60" />
|
||||
|
||||
<LanguageSwitcher />
|
||||
|
||||
{/* Modal card */}
|
||||
<div className="relative z-10 flex min-h-screen items-center justify-center px-4 py-16">
|
||||
|
||||
Reference in New Issue
Block a user