diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts index c9dd8d0f..e1562893 100644 --- a/web/src/i18n/translations.ts +++ b/web/src/i18n/translations.ts @@ -1015,6 +1015,11 @@ export const translations = { cancel: 'Cancel', positionClosed: 'Position closed successfully', closeFailed: 'Failed to close position', + closeAll: 'Close All', + confirmCloseAllPositions: + 'Market-close ALL {count} open positions?', + allPositionsClosed: 'All positions closed', + closeAllPartial: '{failed} of {count} positions failed to close', hideAddress: 'Hide address', showFullAddress: 'Show full address', copyAddress: 'Copy address', @@ -2181,6 +2186,10 @@ export const translations = { cancel: '取消', positionClosed: '平仓成功', closeFailed: '平仓失败', + closeAll: '一键全平', + confirmCloseAllPositions: '确定要市价平掉全部 {count} 个持仓吗?', + allPositionsClosed: '全部持仓已平', + closeAllPartial: '{count} 个持仓中有 {failed} 个平仓失败', hideAddress: '隐藏地址', showFullAddress: '显示完整地址', copyAddress: '复制地址', @@ -3293,6 +3302,11 @@ export const translations = { cancel: 'Batal', positionClosed: 'Posisi berhasil ditutup', closeFailed: 'Gagal menutup posisi', + closeAll: 'Tutup Semua', + confirmCloseAllPositions: + 'Tutup SEMUA {count} posisi terbuka dengan harga pasar?', + allPositionsClosed: 'Semua posisi ditutup', + closeAllPartial: '{failed} dari {count} posisi gagal ditutup', hideAddress: 'Sembunyikan alamat', showFullAddress: 'Tampilkan alamat lengkap', copyAddress: 'Salin alamat', diff --git a/web/src/pages/TraderDashboardPage.tsx b/web/src/pages/TraderDashboardPage.tsx index 0d143387..9b94f776 100644 --- a/web/src/pages/TraderDashboardPage.tsx +++ b/web/src/pages/TraderDashboardPage.tsx @@ -137,6 +137,7 @@ export function TraderDashboardPage({ exchanges, }: TraderDashboardPageProps) { const [closingPosition, setClosingPosition] = useState(null) + const [closingAll, setClosingAll] = useState(false) const [selectedChartSymbol, setSelectedChartSymbol] = useState(undefined) const [chartUpdateKey, setChartUpdateKey] = useState(0) const chartSectionRef = useRef(null) @@ -231,6 +232,52 @@ export function TraderDashboardPage({ } } + const handleCloseAllPositions = async () => { + if (!selectedTraderId || !positions || positions.length === 0) return + + const count = String(positions.length) + const confirmed = await confirmToast( + t('traderDashboard.confirmCloseAllPositions', language, { count }), + { + title: t('traderDashboard.confirmClose', language), + okText: t('traderDashboard.confirm', language), + cancelText: t('traderDashboard.cancel', language), + } + ) + if (!confirmed) return + + setClosingAll(true) + let failed = 0 + // Sequential on purpose: parallel closes on the same account can race + // on exchange nonces (Hyperliquid) and rate limits. + for (const pos of positions) { + try { + await api.closePosition( + selectedTraderId, + pos.symbol, + pos.side.toUpperCase() + ) + } catch { + failed++ + } + } + await Promise.all([ + mutate(`positions-${selectedTraderId}`), + mutate(`account-${selectedTraderId}`), + ]) + if (failed === 0) { + notify.success(t('traderDashboard.allPositionsClosed', language)) + } else { + notify.error( + t('traderDashboard.closeAllPartial', language, { + failed: String(failed), + count, + }) + ) + } + setClosingAll(false) + } + // If API failed with error, show empty state (likely backend not running) if (tradersError) { return ( @@ -588,8 +635,24 @@ export function TraderDashboardPage({ {t('currentPositions', language)} {positions && positions.length > 0 && ( -
- {positions.length} {t('active', language)} +
+
+ {positions.length} {t('active', language)} +
+
)}