From 0f3ba1382af5cf04de07336c6e0876ee11ef3017 Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Sat, 27 Dec 2025 23:35:15 +0800 Subject: [PATCH] feat: use coinank free API for kline data and show exchange badge - Switch to coinank free/open kline API (no authentication required) - Add exchange badge display with brand colors (Binance/Bybit/OKX/Hyperliquid/Aster) - Auto-convert OKX symbol format (BTCUSDT -> BTC-USDT-SWAP) - Fallback to Binance data for unsupported exchanges (Bitget/Lighter) - Pass exchange prop from trader account to chart component --- api/server.go | 37 +++++++++++++++++++++------- market/data.go | 15 +++++------ web/src/App.tsx | 8 +++--- web/src/components/AdvancedChart.tsx | 36 +++++++++++++++++++++++++++ web/src/components/ChartTabs.tsx | 1 + 5 files changed, 75 insertions(+), 22 deletions(-) diff --git a/api/server.go b/api/server.go index bcdb8d10..bf1752cb 100644 --- a/api/server.go +++ b/api/server.go @@ -13,7 +13,7 @@ import ( "nofx/logger" "nofx/manager" "nofx/market" - "nofx/provider/coinank" + "nofx/provider/coinank/coinank_api" "nofx/provider/coinank/coinank_enum" "nofx/store" "nofx/trader" @@ -2321,11 +2321,8 @@ func (s *Server) handleKlines(c *gin.Context) { c.JSON(http.StatusOK, klines) } -// getKlinesFromCoinank fetches kline data from coinank API for multiple exchanges +// getKlinesFromCoinank fetches kline data from coinank free/open API for multiple exchanges func (s *Server) getKlinesFromCoinank(symbol, interval, exchange string, limit int) ([]market.Kline, error) { - // Import coinank packages - coinankClient := coinank.NewCoinankClient(coinank_enum.MainUrl, "0cccbd7992754b67b1848c6746c0fce0") - // Map exchange string to coinank enum var coinankExchange coinank_enum.Exchange switch strings.ToLower(exchange) { @@ -2397,12 +2394,34 @@ func (s *Server) getKlinesFromCoinank(symbol, interval, exchange string, limit i return nil, fmt.Errorf("unsupported interval for coinank: %s", interval) } - // Call coinank API + // Convert symbol format for different exchanges + // OKX uses "BTC-USDT-SWAP" format instead of "BTCUSDT" + apiSymbol := symbol + if coinankExchange == coinank_enum.Okex { + // Convert BTCUSDT -> BTC-USDT-SWAP + if strings.HasSuffix(symbol, "USDT") { + base := strings.TrimSuffix(symbol, "USDT") + apiSymbol = fmt.Sprintf("%s-USDT-SWAP", base) + } + } + + // Call coinank free/open API (no authentication required) ctx := context.Background() - endTime := time.Now().UnixMilli() - coinankKlines, err := coinankClient.Kline(ctx, symbol, coinankExchange, 0, endTime, limit, coinankInterval) + ts := time.Now().UnixMilli() + // Use "To" side to search backward from current time (get historical klines) + coinankKlines, err := coinank_api.Kline(ctx, apiSymbol, coinankExchange, ts, coinank_enum.To, limit, coinankInterval) if err != nil { - return nil, fmt.Errorf("coinank API error: %w", err) + // Free API doesn't support all exchanges (e.g., OKX, Bitget) + // Fallback to Binance data as reference + if coinankExchange != coinank_enum.Binance { + logger.Warnf("⚠️ CoinAnk free API doesn't support %s, falling back to Binance data", coinankExchange) + coinankKlines, err = coinank_api.Kline(ctx, symbol, coinank_enum.Binance, ts, coinank_enum.To, limit, coinankInterval) + if err != nil { + return nil, fmt.Errorf("coinank API error (fallback): %w", err) + } + } else { + return nil, fmt.Errorf("coinank API error: %w", err) + } } // Convert coinank kline format to market.Kline format diff --git a/market/data.go b/market/data.go index 2c4732be..a92cecee 100644 --- a/market/data.go +++ b/market/data.go @@ -6,7 +6,7 @@ import ( "fmt" "io" "nofx/logger" - "nofx/provider/coinank" + "nofx/provider/coinank/coinank_api" "nofx/provider/coinank/coinank_enum" "math" "strconv" @@ -25,13 +25,9 @@ type FundingRateCache struct { var ( fundingRateMap sync.Map // map[string]*FundingRateCache frCacheTTL = 1 * time.Hour - coinankClient *coinank.CoinankClient // Global CoinAnk client for kline data ) -// Initialize CoinAnk client -func init() { - coinankClient = coinank.NewCoinankClient(coinank_enum.MainUrl, "0cccbd7992754b67b1848c6746c0fce0") -} +// Note: Kline data now uses free/open API (coinank_api.Kline) which doesn't require authentication // getKlinesFromCoinAnk fetches kline data from CoinAnk API (replacement for WSMonitorCli) func getKlinesFromCoinAnk(symbol, interval string, limit int) ([]Kline, error) { @@ -70,10 +66,11 @@ func getKlinesFromCoinAnk(symbol, interval string, limit int) ([]Kline, error) { return nil, fmt.Errorf("unsupported interval: %s", interval) } - // Call CoinAnk API (default to Binance exchange for compatibility) + // Call CoinAnk free/open API (no authentication required) ctx := context.Background() - endTime := time.Now().UnixMilli() - coinankKlines, err := coinankClient.Kline(ctx, symbol, coinank_enum.Binance, 0, endTime, limit, coinankInterval) + ts := time.Now().UnixMilli() + // Use "To" side to search backward from current time (get historical klines) + coinankKlines, err := coinank_api.Kline(ctx, symbol, coinank_enum.Binance, ts, coinank_enum.To, limit, coinankInterval) if err != nil { return nil, fmt.Errorf("CoinAnk API error: %w", err) } diff --git a/web/src/App.tsx b/web/src/App.tsx index 2eb98269..5ae009c6 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -72,15 +72,15 @@ function getExchangeDisplayNameFromList( : typeName } -// Helper function to get exchange type from exchange ID (UUID) - for TradingView charts +// Helper function to get exchange type from exchange ID (UUID) - for kline charts function getExchangeTypeFromList( exchangeId: string | undefined, exchanges: Exchange[] | undefined ): string { - if (!exchangeId) return 'BINANCE' + if (!exchangeId) return 'binance' const exchange = exchanges?.find((e) => e.id === exchangeId) - if (!exchange) return 'BINANCE' // Default to BINANCE for charts - return exchange.exchange_type?.toUpperCase() || 'BINANCE' + if (!exchange) return 'binance' // Default to binance for charts + return exchange.exchange_type?.toLowerCase() || 'binance' } // Helper function to check if exchange is a perp-dex type (wallet-based) diff --git a/web/src/components/AdvancedChart.tsx b/web/src/components/AdvancedChart.tsx index 42bb46a6..73d0fb0a 100644 --- a/web/src/components/AdvancedChart.tsx +++ b/web/src/components/AdvancedChart.tsx @@ -678,6 +678,42 @@ export function AdvancedChart({ {interval} + {/* 交易所标识 */} + + {exchange} + {['bitget', 'lighter'].includes(exchange?.toLowerCase() || '') && ( + * + )} +
diff --git a/web/src/components/ChartTabs.tsx b/web/src/components/ChartTabs.tsx index 7fd59b49..2d9256cd 100644 --- a/web/src/components/ChartTabs.tsx +++ b/web/src/components/ChartTabs.tsx @@ -171,6 +171,7 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: C interval={interval} traderID={traderId} height={550} + exchange={exchangeId || 'binance'} onSymbolChange={setChartSymbol} />