From e5f69bfea6beb6a9e2c3465e53385a76f958ff4e Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Sun, 18 Jan 2026 18:19:15 +0800 Subject: [PATCH] fix(grid): improve GridRiskPanel layout and fix liquidation data - Make panel collapsible with summary badges when collapsed - Use compact 2-column grid layout for detailed info - Fix auth token key (token -> auth_token) - Only calculate liquidation distance when position exists --- trader/auto_trader_grid.go | 6 +- web/src/components/strategy/GridRiskPanel.tsx | 489 ++++++++---------- 2 files changed, 229 insertions(+), 266 deletions(-) diff --git a/trader/auto_trader_grid.go b/trader/auto_trader_grid.go index c4b80fe0..86a6bfe5 100644 --- a/trader/auto_trader_grid.go +++ b/trader/auto_trader_grid.go @@ -1443,11 +1443,11 @@ func (at *AutoTrader) GetGridRiskInfo() *GridRiskInfo { recommendedLeverage = min(leverage, 2) } - // Calculate liquidation distance - liquidationDistance := 100.0 / float64(leverage) * 0.9 // ~90% of theoretical max - + // Calculate liquidation distance and price only when there's a position + var liquidationDistance float64 var liquidationPrice float64 if currentPositionSize != 0 && currentPrice > 0 { + liquidationDistance = 100.0 / float64(leverage) * 0.9 // ~90% of theoretical max if currentPositionSize > 0 { // Long position: liquidation below entry liquidationPrice = currentPrice * (1 - liquidationDistance/100) diff --git a/web/src/components/strategy/GridRiskPanel.tsx b/web/src/components/strategy/GridRiskPanel.tsx index 6a07f664..75c5b73b 100644 --- a/web/src/components/strategy/GridRiskPanel.tsx +++ b/web/src/components/strategy/GridRiskPanel.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react' -import { Shield, TrendingUp, AlertTriangle, Activity, Box } from 'lucide-react' +import { Shield, TrendingUp, AlertTriangle, Activity, Box, ChevronDown, ChevronUp } from 'lucide-react' import type { GridRiskInfo } from '../../types' interface GridRiskPanelProps { @@ -16,46 +16,48 @@ export function GridRiskPanel({ const [riskInfo, setRiskInfo] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + const [expanded, setExpanded] = useState(false) const t = (key: string) => { const translations: Record> = { // Section titles - leverageInfo: { zh: '杠杆信息', en: 'Leverage Info' }, - positionInfo: { zh: '仓位信息', en: 'Position Info' }, - liquidationInfo: { zh: '清算信息', en: 'Liquidation Info' }, - marketState: { zh: '市场状态', en: 'Market State' }, - boxState: { zh: '盒子状态', en: 'Box State' }, + gridRisk: { zh: '网格风控', en: 'Grid Risk' }, + leverageInfo: { zh: '杠杆', en: 'Leverage' }, + positionInfo: { zh: '仓位', en: 'Position' }, + liquidationInfo: { zh: '清算', en: 'Liquidation' }, + marketState: { zh: '市场', en: 'Market' }, + boxState: { zh: '箱体', en: 'Box' }, // Leverage - currentLeverage: { zh: '当前杠杆', en: 'Current Leverage' }, - effectiveLeverage: { zh: '有效杠杆', en: 'Effective Leverage' }, - recommendedLeverage: { zh: '建议杠杆', en: 'Recommended Leverage' }, + currentLeverage: { zh: '当前', en: 'Current' }, + effectiveLeverage: { zh: '有效', en: 'Effective' }, + recommendedLeverage: { zh: '建议', en: 'Recommend' }, // Position - currentPosition: { zh: '当前仓位', en: 'Current Position' }, - maxPosition: { zh: '最大仓位', en: 'Max Position' }, - positionPercent: { zh: '仓位占比', en: 'Position %' }, + currentPosition: { zh: '当前', en: 'Current' }, + maxPosition: { zh: '最大', en: 'Max' }, + positionPercent: { zh: '占比', en: 'Usage' }, // Liquidation - liquidationPrice: { zh: '清算价格', en: 'Liquidation Price' }, - liquidationDistance: { zh: '清算距离', en: 'Liquidation Distance' }, + liquidationPrice: { zh: '清算价', en: 'Liq Price' }, + liquidationDistance: { zh: '距离', en: 'Distance' }, // Market - regimeLevel: { zh: '波动级别', en: 'Regime Level' }, - currentPrice: { zh: '当前价格', en: 'Current Price' }, - breakoutLevel: { zh: '突破级别', en: 'Breakout Level' }, - breakoutDirection: { zh: '突破方向', en: 'Breakout Direction' }, + regimeLevel: { zh: '波动', en: 'Regime' }, + currentPrice: { zh: '价格', en: 'Price' }, + breakoutLevel: { zh: '突破', en: 'Breakout' }, + breakoutDirection: { zh: '方向', en: 'Direction' }, // Box - shortBox: { zh: '短期盒子', en: 'Short Box' }, - midBox: { zh: '中期盒子', en: 'Mid Box' }, - longBox: { zh: '长期盒子', en: 'Long Box' }, + shortBox: { zh: '短期', en: 'Short' }, + midBox: { zh: '中期', en: 'Mid' }, + longBox: { zh: '长期', en: 'Long' }, // Regime levels - narrow: { zh: '窄幅震荡', en: 'Narrow' }, - standard: { zh: '标准震荡', en: 'Standard' }, - wide: { zh: '宽幅震荡', en: 'Wide' }, - volatile: { zh: '剧烈震荡', en: 'Volatile' }, + narrow: { zh: '窄幅', en: 'Narrow' }, + standard: { zh: '标准', en: 'Standard' }, + wide: { zh: '宽幅', en: 'Wide' }, + volatile: { zh: '剧烈', en: 'Volatile' }, trending: { zh: '趋势', en: 'Trending' }, // Breakout levels @@ -65,8 +67,8 @@ export function GridRiskPanel({ long: { zh: '长期', en: 'Long' }, // Directions - up: { zh: '向上', en: 'Up' }, - down: { zh: '向下', en: 'Down' }, + up: { zh: '↑', en: '↑' }, + down: { zh: '↓', en: '↓' }, // Status loading: { zh: '加载中...', en: 'Loading...' }, @@ -78,7 +80,7 @@ export function GridRiskPanel({ const fetchRiskInfo = useCallback(async () => { try { - const token = localStorage.getItem('token') + const token = localStorage.getItem('auth_token') const response = await fetch(`/api/traders/${traderId}/grid-risk`, { headers: { Authorization: `Bearer ${token}`, @@ -107,40 +109,29 @@ export function GridRiskPanel({ const getRegimeColor = (regime: string) => { switch (regime) { - case 'narrow': - return '#0ECB81' // Green - safe - case 'standard': - return '#F0B90B' // Yellow - normal - case 'wide': - return '#F7931A' // Orange - caution - case 'volatile': - return '#F6465D' // Red - danger - case 'trending': - return '#8B5CF6' // Purple - trending - default: - return '#848E9C' // Gray + case 'narrow': return '#0ECB81' + case 'standard': return '#F0B90B' + case 'wide': return '#F7931A' + case 'volatile': return '#F6465D' + case 'trending': return '#8B5CF6' + default: return '#848E9C' } } const getBreakoutColor = (level: string) => { switch (level) { - case 'none': - return '#0ECB81' // Green - safe - case 'short': - return '#F0B90B' // Yellow - minor - case 'mid': - return '#F7931A' // Orange - warning - case 'long': - return '#F6465D' // Red - critical - default: - return '#848E9C' + case 'none': return '#0ECB81' + case 'short': return '#F0B90B' + case 'mid': return '#F7931A' + case 'long': return '#F6465D' + default: return '#848E9C' } } const getPositionColor = (percent: number) => { - if (percent < 50) return '#0ECB81' // Green - if (percent < 80) return '#F0B90B' // Yellow - return '#F6465D' // Red + if (percent < 50) return '#0ECB81' + if (percent < 80) return '#F0B90B' + return '#F6465D' } const formatPrice = (price: number) => { @@ -151,20 +142,17 @@ export function GridRiskPanel({ } const formatUSD = (value: number) => { - return `$${value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` + return `$${value.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}` } - const sectionStyle = { + const cardStyle = { background: '#0B0E11', border: '1px solid #2B3139', } - const labelStyle = { color: '#848E9C' } - const valueStyle = { color: '#EAECEF' } - if (loading) { return ( -
+
{t('loading')}
) @@ -172,7 +160,7 @@ export function GridRiskPanel({ if (error) { return ( -
+
{t('error')}: {error}
) @@ -180,230 +168,205 @@ export function GridRiskPanel({ if (!riskInfo) { return ( -
+
{t('noData')}
) } return ( -
- {/* Leverage Info */} -
-
- - - {t('leverageInfo')} - -
-
-
-
- {t('currentLeverage')} -
-
- {riskInfo.current_leverage}x -
-
-
-
- {t('effectiveLeverage')} -
-
- {riskInfo.effective_leverage.toFixed(2)}x -
-
-
-
- {t('recommendedLeverage')} -
-
riskInfo.recommended_leverage - ? '#F6465D' - : '#0ECB81', - }} - > - {riskInfo.recommended_leverage}x -
-
-
-
- - {/* Position Info */} -
-
- - - {t('positionInfo')} - -
-
-
-
- {t('currentPosition')} -
-
- {formatUSD(riskInfo.current_position)} -
-
-
-
- {t('maxPosition')} -
-
- {formatUSD(riskInfo.max_position)} -
-
-
-
- {t('positionPercent')} -
-
- {riskInfo.position_percent.toFixed(1)}% -
-
-
- {/* Position Progress Bar */} -
-
-
-
- - {/* Liquidation Info */} -
-
- - - {t('liquidationInfo')} - -
-
-
-
- {t('liquidationPrice')} -
-
- {riskInfo.liquidation_price > 0 ? formatPrice(riskInfo.liquidation_price) : '-'} -
-
-
-
- {t('liquidationDistance')} -
-
- {riskInfo.liquidation_distance.toFixed(1)}% -
-
-
-
- - {/* Market State */} -
-
+
+ {/* Collapsible Header */} +
setExpanded(!expanded)} + > +
- {t('marketState')} + {t('gridRisk')}
-
-
-
- {t('regimeLevel')} -
-
+ {/* Summary badges when collapsed */} +
+ {t(riskInfo.regime_level || 'standard')} -
-
-
-
- {t('currentPrice')} -
-
- {formatPrice(riskInfo.current_price)} -
-
-
-
-
-
- {t('breakoutLevel')} -
-
+ + {riskInfo.effective_leverage.toFixed(1)}x + + - {t(riskInfo.breakout_level || 'none')} -
-
-
-
- {t('breakoutDirection')} -
-
- {riskInfo.breakout_direction ? t(riskInfo.breakout_direction) : '-'} -
+ {riskInfo.position_percent.toFixed(0)}% +
+ {expanded ? ( + + ) : ( + + )}
- {/* Box State */} -
-
- - - {t('boxState')} - -
-
- {/* Short Box */} -
- - {t('shortBox')} - - - {formatPrice(riskInfo.short_box_lower)} - {formatPrice(riskInfo.short_box_upper)} - + {/* Expanded Content */} + {expanded && ( +
+ {/* Row 1: Leverage & Position */} +
+ {/* Leverage */} +
+
+ + {t('leverageInfo')} +
+
+
+
{t('currentLeverage')}
+
{riskInfo.current_leverage}x
+
+
+
{t('effectiveLeverage')}
+
{riskInfo.effective_leverage.toFixed(2)}x
+
+
+
{t('recommendedLeverage')}
+
riskInfo.recommended_leverage ? '#F6465D' : '#0ECB81' }} + > + {riskInfo.recommended_leverage}x +
+
+
+
+ + {/* Position */} +
+
+ + {t('positionInfo')} +
+
+
+
{t('currentPosition')}
+
{formatUSD(riskInfo.current_position)}
+
+
+
{t('maxPosition')}
+
{formatUSD(riskInfo.max_position)}
+
+
+
{t('positionPercent')}
+
+ {riskInfo.position_percent.toFixed(1)}% +
+
+
+ {/* Mini progress bar */} +
+
+
+
- {/* Mid Box */} -
- - {t('midBox')} - - - {formatPrice(riskInfo.mid_box_lower)} - {formatPrice(riskInfo.mid_box_upper)} - + + {/* Row 2: Market State & Liquidation */} +
+ {/* Market State */} +
+
+ + {t('marketState')} +
+
+
+
{t('regimeLevel')}
+
+ {t(riskInfo.regime_level || 'standard')} +
+
+
+
{t('currentPrice')}
+
{formatPrice(riskInfo.current_price)}
+
+
+
{t('breakoutLevel')}
+
+ {t(riskInfo.breakout_level || 'none')} +
+
+
+
{t('breakoutDirection')}
+
+ {riskInfo.breakout_direction ? t(riskInfo.breakout_direction) : '-'} +
+
+
+
+ + {/* Liquidation */} +
+
+ + {t('liquidationInfo')} +
+
+
+
{t('liquidationPrice')}
+
+ {riskInfo.liquidation_price > 0 ? formatPrice(riskInfo.liquidation_price) : '-'} +
+
+
+
{t('liquidationDistance')}
+
+ {riskInfo.liquidation_distance > 0 ? `${riskInfo.liquidation_distance.toFixed(1)}%` : '-'} +
+
+
+
- {/* Long Box */} -
- - {t('longBox')} - - - {formatPrice(riskInfo.long_box_lower)} - {formatPrice(riskInfo.long_box_upper)} - + + {/* Row 3: Box State */} +
+
+ + {t('boxState')} +
+
+
+ {t('shortBox')} + + {formatPrice(riskInfo.short_box_lower)} - {formatPrice(riskInfo.short_box_upper)} + +
+
+ {t('midBox')} + + {formatPrice(riskInfo.mid_box_lower)} - {formatPrice(riskInfo.mid_box_upper)} + +
+
+ {t('longBox')} + + {formatPrice(riskInfo.long_box_lower)} - {formatPrice(riskInfo.long_box_upper)} + +
+
-
+ )}
) }