From 88de029e142b2e92eb5a59ffe769f1636147efe5 Mon Sep 17 00:00:00 2001 From: thuanle Date: Sun, 26 Apr 2026 17:19:59 +0700 Subject: [PATCH] feat: unify token message composition across sources Remove alpha-first early return and build one rich message from all available sources (spot/future/alpha) with conditional rows. Co-Authored-By: Claude Opus 4.7 --- internal/services/tele/commands/token.go | 119 ++++++++++++++---- internal/services/tele/commands/token_test.go | 42 +++++++ 2 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 internal/services/tele/commands/token_test.go diff --git a/internal/services/tele/commands/token.go b/internal/services/tele/commands/token.go index 0c93d14..01ce105 100644 --- a/internal/services/tele/commands/token.go +++ b/internal/services/tele/commands/token.go @@ -11,6 +11,45 @@ import ( "me.thuanle/bbot/internal/services/tele/view" ) +type buildRichTokenMessageArgs struct { + Token string + + HasSpot bool + SpotPrice float64 + + HasFuture bool + FuturePrice float64 + FundingRate float64 + FundingTimeMs int64 + + HasAlpha bool + AlphaPrice float64 + + HasAlpha24h bool + Alpha24h float64 + + HasMarginAPR bool + MarginAPRPercent float64 +} + +func buildRichTokenMessageInput(a buildRichTokenMessageArgs) view.RichTokenMessageInput { + return view.RichTokenMessageInput{ + Token: a.Token, + HasSpot: a.HasSpot, + SpotPrice: a.SpotPrice, + HasFuture: a.HasFuture, + FuturePrice: a.FuturePrice, + FundingRate: a.FundingRate, + FundingTimeMs: a.FundingTimeMs, + HasAlpha: a.HasAlpha, + AlphaPrice: a.AlphaPrice, + HasAlpha24h: a.HasAlpha24h, + Alpha24hChange: a.Alpha24h, + HasMarginAPR: a.HasMarginAPR, + MarginAPRPercent: a.MarginAPRPercent, + } +} + func showStickerMode(context telebot.Context, token string) { token = strings.ToUpper(token) stickerIdx, ok := tele.Token2StickerIdxMap[token] @@ -25,37 +64,67 @@ func showStickerMode(context telebot.Context, token string) { func OnTokenInfoByToken(context telebot.Context, token string) error { token = strings.ToUpper(token) - - // Check if it's an Alpha token first - if data.Market.IsAlphaToken(token) { - showStickerMode(context, token) - - if alphaToken, exists := data.Market.GetAlphaToken(token); exists { - sp := alphaToken.GetPrice() - change24h := alphaToken.GetPercentChange24h() - _ = chat.ReplyMessage(context, view.RenderOnAlphaPriceMessage(token, sp, change24h)) - } - return nil - } - - // Regular token handling - symbols := binancex.Token2Symbols(token) - if len(symbols) == 0 { - return nil - } - showStickerMode(context, token) - fp, fundRate, fundTime, ok := data.Market.GetFuturePrice(symbols[0]) + var ( + hasAlpha, hasAlpha24h bool + alphaPrice, alpha24h float64 + hasFuture, hasSpot bool + futurePrice float64 + fundingRate float64 + fundingTime int64 + spotPrice float64 + ) + + if alphaToken, ok := data.Market.GetAlphaToken(token); ok { + hasAlpha = true + alphaPrice = alphaToken.GetPrice() + hasAlpha24h = true + alpha24h = alphaToken.GetPercentChange24h() + } + + symbols := binancex.Token2Symbols(token) + if len(symbols) > 0 { + fp, fr, ft, ok := data.Market.GetFuturePrice(symbols[0]) + if ok { + hasFuture = true + futurePrice = fp + fundingRate = fr + fundingTime = ft + + sSymbol := binancex.Future2SpotSymbol(symbols[0]) + if sp, ok := data.Market.GetSpotPrice(sSymbol); ok { + hasSpot = true + spotPrice = sp + } + } + } + marginRates := data.Market.GetMarginInterestRates() - tokenInterestRate := marginRates[token] - if !ok { + marginAPR := marginRates[token] * 365 * 100 + hasMarginAPR := marginRates[token] != 0 + + if !hasSpot && !hasFuture && !hasAlpha { return nil } - sSymbol := binancex.Future2SpotSymbol(symbols[0]) - sp, _ := data.Market.GetSpotPrice(sSymbol) - _ = chat.ReplyMessage(context, view.RenderOnPriceMessage(symbols[0], sp, fp, fundRate, fundTime, tokenInterestRate)) + msg := view.RenderRichTokenMessage(buildRichTokenMessageInput(buildRichTokenMessageArgs{ + Token: token, + HasSpot: hasSpot, + SpotPrice: spotPrice, + HasFuture: hasFuture, + FuturePrice: futurePrice, + FundingRate: fundingRate, + FundingTimeMs: fundingTime, + HasAlpha: hasAlpha, + AlphaPrice: alphaPrice, + HasAlpha24h: hasAlpha24h, + Alpha24h: alpha24h, + HasMarginAPR: hasMarginAPR, + MarginAPRPercent: marginAPR, + })) + + _ = chat.ReplyMessage(context, msg) return nil } diff --git a/internal/services/tele/commands/token_test.go b/internal/services/tele/commands/token_test.go new file mode 100644 index 0000000..21bbb99 --- /dev/null +++ b/internal/services/tele/commands/token_test.go @@ -0,0 +1,42 @@ +package commands + +import "testing" + +func TestBuildRichTokenMessageInput_AllSources(t *testing.T) { + in := buildRichTokenMessageInput(buildRichTokenMessageArgs{ + Token: "ETH", + SpotPrice: 3245, + HasSpot: true, + FuturePrice: 3251, + FundingRate: 0.000123, + FundingTimeMs: 1740000000000, + HasFuture: true, + AlphaPrice: 3248, + HasAlpha: true, + Alpha24h: -3.21, + HasAlpha24h: true, + MarginAPRPercent: 7.665, + HasMarginAPR: true, + }) + + if !in.HasSpot || !in.HasFuture || !in.HasAlpha || !in.HasAlpha24h || !in.HasMarginAPR { + t.Fatalf("expected all source flags true: %+v", in) + } +} + +func TestBuildRichTokenMessageInput_OnlyAlpha(t *testing.T) { + in := buildRichTokenMessageInput(buildRichTokenMessageArgs{ + Token: "ABC", + HasAlpha: true, + AlphaPrice: 0.1234, + HasAlpha24h: true, + Alpha24h: 12.4, + }) + + if !in.HasAlpha || !in.HasAlpha24h { + t.Fatalf("expected alpha flags true: %+v", in) + } + if in.HasSpot || in.HasFuture { + t.Fatalf("did not expect spot/future flags true: %+v", in) + } +}