mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
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
This commit is contained in:
+27
-8
@@ -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,13 +2394,35 @@ 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 {
|
||||
// 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
|
||||
klines := make([]market.Kline, len(coinankKlines))
|
||||
|
||||
+6
-9
@@ -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)
|
||||
}
|
||||
|
||||
+4
-4
@@ -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)
|
||||
|
||||
@@ -678,6 +678,42 @@ export function AdvancedChart({
|
||||
<span className="text-xs px-2 py-0.5 rounded" style={{ background: '#2B3139', color: '#848E9C' }}>
|
||||
{interval}
|
||||
</span>
|
||||
{/* 交易所标识 */}
|
||||
<span
|
||||
className="text-xs px-2 py-0.5 rounded font-medium uppercase"
|
||||
style={{
|
||||
background: exchange === 'binance' ? 'rgba(243, 186, 47, 0.15)' :
|
||||
exchange === 'bybit' ? 'rgba(247, 147, 26, 0.15)' :
|
||||
exchange === 'okx' ? 'rgba(0, 180, 255, 0.15)' :
|
||||
exchange === 'bitget' ? 'rgba(0, 212, 170, 0.15)' :
|
||||
exchange === 'hyperliquid' ? 'rgba(80, 227, 194, 0.15)' :
|
||||
exchange === 'aster' ? 'rgba(138, 43, 226, 0.15)' :
|
||||
'rgba(255, 255, 255, 0.1)',
|
||||
color: exchange === 'binance' ? '#F3BA2F' :
|
||||
exchange === 'bybit' ? '#F7931A' :
|
||||
exchange === 'okx' ? '#00B4FF' :
|
||||
exchange === 'bitget' ? '#00D4AA' :
|
||||
exchange === 'hyperliquid' ? '#50E3C2' :
|
||||
exchange === 'aster' ? '#8A2BE2' :
|
||||
'#848E9C',
|
||||
border: `1px solid ${
|
||||
exchange === 'binance' ? 'rgba(243, 186, 47, 0.3)' :
|
||||
exchange === 'bybit' ? 'rgba(247, 147, 26, 0.3)' :
|
||||
exchange === 'okx' ? 'rgba(0, 180, 255, 0.3)' :
|
||||
exchange === 'bitget' ? 'rgba(0, 212, 170, 0.3)' :
|
||||
exchange === 'hyperliquid' ? 'rgba(80, 227, 194, 0.3)' :
|
||||
exchange === 'aster' ? 'rgba(138, 43, 226, 0.3)' :
|
||||
'rgba(255, 255, 255, 0.2)'
|
||||
}`
|
||||
}}
|
||||
title={['bitget', 'lighter'].includes(exchange?.toLowerCase() || '')
|
||||
? 'Data source: Binance (fallback)' : undefined}
|
||||
>
|
||||
{exchange}
|
||||
{['bitget', 'lighter'].includes(exchange?.toLowerCase() || '') && (
|
||||
<span className="ml-1 text-[9px] opacity-60">*</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -171,6 +171,7 @@ export function ChartTabs({ traderId, selectedSymbol, updateKey, exchangeId }: C
|
||||
interval={interval}
|
||||
traderID={traderId}
|
||||
height={550}
|
||||
exchange={exchangeId || 'binance'}
|
||||
onSymbolChange={setChartSymbol}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user