Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a719752031 |
@@ -13,7 +13,6 @@ type IMarket interface {
|
|||||||
// Alpha token methods
|
// Alpha token methods
|
||||||
IsAlphaToken(symbol string) bool
|
IsAlphaToken(symbol string) bool
|
||||||
GetAlphaToken(symbol string) (market.AlphaTokenInfo, bool)
|
GetAlphaToken(symbol string) (market.AlphaTokenInfo, bool)
|
||||||
GetAlphaPrice(symbol string) (float64, bool)
|
|
||||||
|
|
||||||
// Trading pair methods
|
// Trading pair methods
|
||||||
IsSpotPair(symbol string) bool
|
IsSpotPair(symbol string) bool
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -55,21 +54,10 @@ type AlphaTokenInfo struct {
|
|||||||
|
|
||||||
// AlphaTokenResponse represents the API response structure
|
// AlphaTokenResponse represents the API response structure
|
||||||
type AlphaTokenResponse struct {
|
type AlphaTokenResponse struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Message *string `json:"message"`
|
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"`
|
MessageDetail *string `json:"messageDetail"`
|
||||||
Data AlphaTickerData `json:"data"`
|
Data []AlphaTokenInfo `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrice returns the price as float64
|
// GetPrice returns the price as float64
|
||||||
@@ -162,37 +150,3 @@ func (ms *MarketData) IsAlphaToken(symbol string) bool {
|
|||||||
_, exists := ms.GetAlphaToken(symbol)
|
_, exists := ms.GetAlphaToken(symbol)
|
||||||
return exists
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -67,13 +67,9 @@ func collectRichTokenData(token string) buildRichTokenMessageArgs {
|
|||||||
|
|
||||||
if alphaToken, ok := data.Market.GetAlphaToken(token); ok {
|
if alphaToken, ok := data.Market.GetAlphaToken(token); ok {
|
||||||
a.HasAlpha = true
|
a.HasAlpha = true
|
||||||
|
a.AlphaPrice = alphaToken.GetPrice()
|
||||||
a.HasAlpha24h = true
|
a.HasAlpha24h = true
|
||||||
a.Alpha24h = alphaToken.GetPercentChange24h()
|
a.Alpha24h = alphaToken.GetPercentChange24h()
|
||||||
if alphaPrice, ok := data.Market.GetAlphaPrice(token + "USDT"); ok {
|
|
||||||
a.AlphaPrice = alphaPrice
|
|
||||||
} else {
|
|
||||||
a.AlphaPrice = alphaToken.GetPrice()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
futureSymbols := binancex.Token2FutureSymbols(token)
|
futureSymbols := binancex.Token2FutureSymbols(token)
|
||||||
@@ -113,6 +109,7 @@ func OnTokenInfoByToken(context telebot.Context, token string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
msg := view.RenderRichTokenMessage(buildRichTokenMessageInput(args))
|
msg := view.RenderRichTokenMessage(buildRichTokenMessageInput(args))
|
||||||
|
|
||||||
_ = chat.ReplyMessage(context, msg)
|
_ = chat.ReplyMessage(context, msg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,45 @@ import (
|
|||||||
"me.thuanle/bbot/internal/data/market"
|
"me.thuanle/bbot/internal/data/market"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type marketStub struct {
|
||||||
|
spotPairs map[string]bool
|
||||||
|
futuresPairs map[string]bool
|
||||||
|
spotPrices map[string]float64
|
||||||
|
futurePrices map[string]struct {
|
||||||
|
price float64
|
||||||
|
rate float64
|
||||||
|
time int64
|
||||||
|
}
|
||||||
|
alphaTokens map[string]market.AlphaTokenInfo
|
||||||
|
marginRates map[string]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *marketStub) GetFuturePrice(symbol string) (float64, float64, int64, bool) {
|
||||||
|
v, ok := m.futurePrices[symbol]
|
||||||
|
if !ok {
|
||||||
|
return 0, 0, 0, false
|
||||||
|
}
|
||||||
|
return v.price, v.rate, v.time, true
|
||||||
|
}
|
||||||
|
func (m *marketStub) GetAllPremiumIndex() (map[string]market.PremiumIndex, error) { return nil, nil }
|
||||||
|
func (m *marketStub) GetAllFundRate() (map[string]float64, map[string]int64) { return nil, nil }
|
||||||
|
func (m *marketStub) GetSpotPrice(symbol string) (float64, bool) {
|
||||||
|
v, ok := m.spotPrices[symbol]
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
func (m *marketStub) GetMarginInterestRates() map[string]float64 { return m.marginRates }
|
||||||
|
func (m *marketStub) IsAlphaToken(symbol string) bool {
|
||||||
|
_, ok := m.alphaTokens[symbol]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
func (m *marketStub) GetAlphaToken(symbol string) (market.AlphaTokenInfo, bool) {
|
||||||
|
v, ok := m.alphaTokens[symbol]
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
func (m *marketStub) IsSpotPair(symbol string) bool { return m.spotPairs[symbol] }
|
||||||
|
func (m *marketStub) IsFuturesPair(symbol string) bool { return m.futuresPairs[symbol] }
|
||||||
|
func (m *marketStub) RefreshTradingPairCache() error { return nil }
|
||||||
|
|
||||||
func TestBuildRichTokenMessageInput_AllSources(t *testing.T) {
|
func TestBuildRichTokenMessageInput_AllSources(t *testing.T) {
|
||||||
in := buildRichTokenMessageInput(buildRichTokenMessageArgs{
|
in := buildRichTokenMessageInput(buildRichTokenMessageArgs{
|
||||||
Token: "ETH",
|
Token: "ETH",
|
||||||
@@ -46,50 +85,6 @@ func TestBuildRichTokenMessageInput_OnlyAlpha(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type marketStub struct {
|
|
||||||
spotPairs map[string]bool
|
|
||||||
futuresPairs map[string]bool
|
|
||||||
spotPrices map[string]float64
|
|
||||||
futurePrices map[string]struct {
|
|
||||||
price float64
|
|
||||||
rate float64
|
|
||||||
time int64
|
|
||||||
}
|
|
||||||
alphaTokens map[string]market.AlphaTokenInfo
|
|
||||||
alphaPrices map[string]float64
|
|
||||||
marginRates map[string]float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *marketStub) GetFuturePrice(symbol string) (float64, float64, int64, bool) {
|
|
||||||
v, ok := m.futurePrices[symbol]
|
|
||||||
if !ok {
|
|
||||||
return 0, 0, 0, false
|
|
||||||
}
|
|
||||||
return v.price, v.rate, v.time, true
|
|
||||||
}
|
|
||||||
func (m *marketStub) GetAllPremiumIndex() (map[string]market.PremiumIndex, error) { return nil, nil }
|
|
||||||
func (m *marketStub) GetAllFundRate() (map[string]float64, map[string]int64) { return nil, nil }
|
|
||||||
func (m *marketStub) GetSpotPrice(symbol string) (float64, bool) {
|
|
||||||
v, ok := m.spotPrices[symbol]
|
|
||||||
return v, ok
|
|
||||||
}
|
|
||||||
func (m *marketStub) GetMarginInterestRates() map[string]float64 { return m.marginRates }
|
|
||||||
func (m *marketStub) IsAlphaToken(symbol string) bool {
|
|
||||||
_, ok := m.alphaTokens[symbol]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
func (m *marketStub) GetAlphaToken(symbol string) (market.AlphaTokenInfo, bool) {
|
|
||||||
v, ok := m.alphaTokens[symbol]
|
|
||||||
return v, ok
|
|
||||||
}
|
|
||||||
func (m *marketStub) GetAlphaPrice(symbol string) (float64, bool) {
|
|
||||||
v, ok := m.alphaPrices[symbol]
|
|
||||||
return v, ok
|
|
||||||
}
|
|
||||||
func (m *marketStub) IsSpotPair(symbol string) bool { return m.spotPairs[symbol] }
|
|
||||||
func (m *marketStub) IsFuturesPair(symbol string) bool { return m.futuresPairs[symbol] }
|
|
||||||
func (m *marketStub) RefreshTradingPairCache() error { return nil }
|
|
||||||
|
|
||||||
func TestCollectRichTokenData_SpotOnlyReachable(t *testing.T) {
|
func TestCollectRichTokenData_SpotOnlyReachable(t *testing.T) {
|
||||||
orig := data.Market
|
orig := data.Market
|
||||||
defer func() { data.Market = orig }()
|
defer func() { data.Market = orig }()
|
||||||
@@ -199,25 +194,21 @@ func TestCollectRichTokenData_PrefixFutureMapsToSpot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCollectRichTokenData_AlphaUsesSymbolPrice(t *testing.T) {
|
func TestCollectRichTokenData_AlphaIndependent(t *testing.T) {
|
||||||
orig := data.Market
|
orig := data.Market
|
||||||
defer func() { data.Market = orig }()
|
defer func() { data.Market = orig }()
|
||||||
|
|
||||||
data.Market = &marketStub{
|
data.Market = &marketStub{
|
||||||
alphaTokens: map[string]market.AlphaTokenInfo{
|
alphaTokens: map[string]market.AlphaTokenInfo{
|
||||||
"ABC": {Symbol: "ABC", PercentChange24h: "12.4", Price: "0.1"},
|
"ABC": {Symbol: "ABC", PercentChange24h: "12.4", Price: "0.1234"},
|
||||||
},
|
},
|
||||||
alphaPrices: map[string]float64{"ABCUSDT": 0.1234},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := collectRichTokenData("ABC")
|
args := collectRichTokenData("ABC")
|
||||||
if !args.HasAlpha {
|
if !args.HasAlpha {
|
||||||
t.Fatalf("expected alpha to be available: %+v", args)
|
t.Fatalf("expected alpha source available, got %+v", args)
|
||||||
}
|
}
|
||||||
if !args.HasAlpha24h {
|
if args.HasSpot || args.HasFuture {
|
||||||
t.Fatalf("expected alpha 24h to be available: %+v", args)
|
t.Fatalf("expected alpha independent from spot/future, got %+v", args)
|
||||||
}
|
|
||||||
if args.AlphaPrice != 0.1234 {
|
|
||||||
t.Fatalf("expected alpha price from symbol lookup, got %.4f", args.AlphaPrice)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user