diff --git a/api/server.go b/api/server.go index 32d24c51..875aeae0 100644 --- a/api/server.go +++ b/api/server.go @@ -57,7 +57,7 @@ func corsMiddleware() gin.HandlerFunc { // setupRoutes 设置路由 func (s *Server) setupRoutes() { // 健康检查 - s.router.GET("/health", s.handleHealth) + s.router.Any("/health", s.handleHealth) // API路由组 api := s.router.Group("/api") diff --git a/trader/aster_trader.go b/trader/aster_trader.go index 9aaf078c..b821be61 100644 --- a/trader/aster_trader.go +++ b/trader/aster_trader.go @@ -195,11 +195,11 @@ func (t *AsterTrader) formatQuantity(symbol string, quantity float64) (float64, func (t *AsterTrader) formatFloatWithPrecision(value float64, precision int) string { // 使用指定精度格式化 formatted := strconv.FormatFloat(value, 'f', precision, 64) - + // 去除末尾的0和小数点(如果有) formatted = strings.TrimRight(formatted, "0") formatted = strings.TrimRight(formatted, ".") - + return formatted } @@ -522,6 +522,11 @@ func (t *AsterTrader) GetPositions() ([]map[string]interface{}, error) { // OpenLong 开多单 func (t *AsterTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) { + // 开仓前先取消所有挂单,防止残留挂单导致仓位叠加 + if err := t.CancelAllOrders(symbol); err != nil { + log.Printf(" ⚠ 取消挂单失败(继续开仓): %v", err) + } + // 先设置杠杆 if err := t.SetLeverage(symbol, leverage); err != nil { return nil, fmt.Errorf("设置杠杆失败: %w", err) @@ -556,7 +561,7 @@ func (t *AsterTrader) OpenLong(symbol string, quantity float64, leverage int) (m priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision) qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision) - log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)", + log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)", limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision) params := map[string]interface{}{ @@ -584,6 +589,11 @@ func (t *AsterTrader) OpenLong(symbol string, quantity float64, leverage int) (m // OpenShort 开空单 func (t *AsterTrader) OpenShort(symbol string, quantity float64, leverage int) (map[string]interface{}, error) { + // 开仓前先取消所有挂单,防止残留挂单导致仓位叠加 + if err := t.CancelAllOrders(symbol); err != nil { + log.Printf(" ⚠ 取消挂单失败(继续开仓): %v", err) + } + // 先设置杠杆 if err := t.SetLeverage(symbol, leverage); err != nil { return nil, fmt.Errorf("设置杠杆失败: %w", err) @@ -618,7 +628,7 @@ func (t *AsterTrader) OpenShort(symbol string, quantity float64, leverage int) ( priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision) qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision) - log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)", + log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)", limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision) params := map[string]interface{}{ @@ -693,7 +703,7 @@ func (t *AsterTrader) CloseLong(symbol string, quantity float64) (map[string]int priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision) qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision) - log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)", + log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)", limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision) params := map[string]interface{}{ @@ -717,6 +727,12 @@ func (t *AsterTrader) CloseLong(symbol string, quantity float64) (map[string]int } log.Printf("✓ 平多仓成功: %s 数量: %s", symbol, qtyStr) + + // 平仓后取消该币种的所有挂单(止损止盈单) + if err := t.CancelAllOrders(symbol); err != nil { + log.Printf(" ⚠ 取消挂单失败: %v", err) + } + return result, nil } @@ -770,7 +786,7 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision) qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision) - log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)", + log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)", limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision) params := map[string]interface{}{ @@ -794,6 +810,12 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in } log.Printf("✓ 平空仓成功: %s 数量: %s", symbol, qtyStr) + + // 平仓后取消该币种的所有挂单(止损止盈单) + if err := t.CancelAllOrders(symbol); err != nil { + log.Printf(" ⚠ 取消挂单失败: %v", err) + } + return result, nil } diff --git a/web/src/components/EquityChart.tsx b/web/src/components/EquityChart.tsx index 3a90f628..4022c112 100644 --- a/web/src/components/EquityChart.tsx +++ b/web/src/components/EquityChart.tsx @@ -60,7 +60,10 @@ export function EquityChart({ traderId }: EquityChartProps) { ); } - if (!history || history.length === 0) { + // 过滤掉无效数据:total_equity为0或小于1的数据点(API失败导致) + const validHistory = history?.filter(point => point.total_equity > 1) || []; + + if (!validHistory || validHistory.length === 0) { return (

{t('accountEquityCurve', language)}

@@ -76,12 +79,12 @@ export function EquityChart({ traderId }: EquityChartProps) { // 限制显示最近的数据点(性能优化) // 如果数据超过2000个点,只显示最近2000个 const MAX_DISPLAY_POINTS = 2000; - const displayHistory = history.length > MAX_DISPLAY_POINTS - ? history.slice(-MAX_DISPLAY_POINTS) - : history; + const displayHistory = validHistory.length > MAX_DISPLAY_POINTS + ? validHistory.slice(-MAX_DISPLAY_POINTS) + : validHistory; - // 计算初始余额(使用第一个数据点,如果无数据则从account获取,最后才用默认值) - const initialBalance = history[0]?.total_equity + // 计算初始余额(使用第一个有效数据点,如果无数据则从account获取,最后才用默认值) + const initialBalance = validHistory[0]?.total_equity || account?.total_equity || 100; // 默认值改为100,与常见配置一致 @@ -250,12 +253,13 @@ export function EquityChart({ traderId }: EquityChartProps) { }} /> 50 ? false : { fill: '#F0B90B', r: 3 }} activeDot={{ r: 6, fill: '#FCD535', stroke: '#F0B90B', strokeWidth: 2 }} + connectNulls={true} /> @@ -277,12 +281,12 @@ export function EquityChart({ traderId }: EquityChartProps) {
{t('historicalCycles', language)}
-
{history.length} {t('cycles', language)}
+
{validHistory.length} {t('cycles', language)}
{t('displayRange', language)}
- {history.length > MAX_DISPLAY_POINTS + {validHistory.length > MAX_DISPLAY_POINTS ? `${t('recent', language)} ${MAX_DISPLAY_POINTS}` : t('allData', language) }