Files
thuanle 82b0f2f1a8 feat: fetch alpha price by symbol ticker
Keep alpha token existence from list cache and fetch live alpha price via symbol ticker endpoint for richer token response accuracy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 18:22:48 +07:00

199 lines
6.0 KiB
Go

package market
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/rs/zerolog/log"
)
// AlphaTokenInfo represents the response from Binance Alpha token API
type AlphaTokenInfo struct {
TokenID string `json:"tokenId"`
ChainID string `json:"chainId"`
ChainIconURL string `json:"chainIconUrl"`
ChainName string `json:"chainName"`
ContractAddress string `json:"contractAddress"`
Name string `json:"name"`
Symbol string `json:"symbol"`
IconURL string `json:"iconUrl"`
Price string `json:"price"`
PercentChange24h string `json:"percentChange24h"`
Volume24h string `json:"volume24h"`
MarketCap string `json:"marketCap"`
FDV string `json:"fdv"`
Liquidity string `json:"liquidity"`
TotalSupply string `json:"totalSupply"`
CirculatingSupply string `json:"circulatingSupply"`
Holders string `json:"holders"`
Decimals int `json:"decimals"`
ListingCex bool `json:"listingCex"`
HotTag bool `json:"hotTag"`
CexCoinName string `json:"cexCoinName"`
CanTransfer bool `json:"canTransfer"`
Denomination int `json:"denomination"`
Offline bool `json:"offline"`
TradeDecimal int `json:"tradeDecimal"`
AlphaID string `json:"alphaId"`
Offsell bool `json:"offsell"`
PriceHigh24h string `json:"priceHigh24h"`
PriceLow24h string `json:"priceLow24h"`
Count24h string `json:"count24h"`
OnlineTge bool `json:"onlineTge"`
OnlineAirdrop bool `json:"onlineAirdrop"`
Score int `json:"score"`
CexOffDisplay bool `json:"cexOffDisplay"`
StockState bool `json:"stockState"`
ListingTime int64 `json:"listingTime"`
MulPoint int `json:"mulPoint"`
BnExclusiveState bool `json:"bnExclusiveState"`
}
// AlphaTokenResponse represents the API response structure
type AlphaTokenResponse struct {
Code string `json:"code"`
Message *string `json:"message"`
MessageDetail *string `json:"messageDetail"`
Data []AlphaTokenInfo `json:"data"`
}
type AlphaTickerData struct {
Price string `json:"price"`
}
type AlphaTickerResponse struct {
Code string `json:"code"`
Message *string `json:"message"`
MessageDetail *string `json:"messageDetail"`
Data AlphaTickerData `json:"data"`
}
// GetPrice returns the price as float64
func (a *AlphaTokenInfo) GetPrice() float64 {
price, err := strconv.ParseFloat(a.Price, 64)
if err != nil {
log.Error().Err(err).Str("symbol", a.Symbol).Msg("Failed to parse Alpha token price")
return 0
}
return price
}
// GetPercentChange24h returns the 24h percentage change as float64
func (a *AlphaTokenInfo) GetPercentChange24h() float64 {
change, err := strconv.ParseFloat(a.PercentChange24h, 64)
if err != nil {
log.Error().Err(err).Str("symbol", a.Symbol).Msg("Failed to parse Alpha token 24h change")
return 0
}
return change
}
// fetchAlphaTokens fetches Alpha tokens from Binance API
func (ms *MarketData) fetchAlphaTokens() ([]AlphaTokenInfo, error) {
const alphaTokenURL = "https://www.binance.com/bapi/defi/v1/public/wallet-direct/buw/wallet/cex/alpha/all/token/list"
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Get(alphaTokenURL)
if err != nil {
return nil, fmt.Errorf("failed to fetch Alpha tokens: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Alpha token API returned status: %d", resp.StatusCode)
}
var tokenResponse AlphaTokenResponse
if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil {
return nil, fmt.Errorf("failed to decode Alpha token response: %w", err)
}
if tokenResponse.Code != "000000" {
return nil, fmt.Errorf("Alpha token API returned error code: %s", tokenResponse.Code)
}
return tokenResponse.Data, nil
}
// refreshAlphaTokenCache refreshes the Alpha token cache
func (ms *MarketData) refreshAlphaTokenCache() {
ms.alphaCacheMutex.Lock()
defer ms.alphaCacheMutex.Unlock()
log.Info().Msg("Refreshing Alpha token cache")
tokens, err := ms.fetchAlphaTokens()
if err != nil {
log.Error().Err(err).Msg("Failed to refresh Alpha token cache")
return
}
// Clear existing cache
ms.alphaTokens = make(map[string]AlphaTokenInfo)
// Populate cache with new data
for _, token := range tokens {
symbol := token.Symbol // Already uppercase from API
ms.alphaTokens[symbol] = token
}
ms.lastAlphaCacheUpdate = time.Now()
log.Info().Int("count", len(tokens)).Msg("Alpha token cache refreshed successfully")
}
// GetAlphaToken returns Alpha token info by symbol
func (ms *MarketData) GetAlphaToken(symbol string) (AlphaTokenInfo, bool) {
ms.alphaCacheMutex.RLock()
defer ms.alphaCacheMutex.RUnlock()
token, exists := ms.alphaTokens[symbol]
return token, exists
}
// IsAlphaToken checks if a symbol is an Alpha token
func (ms *MarketData) IsAlphaToken(symbol string) bool {
_, exists := ms.GetAlphaToken(symbol)
return exists
}
func (ms *MarketData) GetAlphaPrice(symbol string) (float64, bool) {
const alphaTickerURL = "https://www.binance.com/bapi/defi/v1/public/alpha-trade/ticker"
client := &http.Client{Timeout: 5 * time.Second}
endpoint := alphaTickerURL + "?" + url.Values{"symbol": []string{symbol}}.Encode()
resp, err := client.Get(endpoint)
if err != nil {
log.Error().Err(err).Str("symbol", symbol).Msg("Failed to fetch Alpha ticker")
return 0, false
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return 0, false
}
var tickerResp AlphaTickerResponse
if err := json.NewDecoder(resp.Body).Decode(&tickerResp); err != nil {
return 0, false
}
if tickerResp.Code != "000000" {
return 0, false
}
price, err := strconv.ParseFloat(tickerResp.Data.Price, 64)
if err != nil || price <= 0 {
return 0, false
}
return price, true
}