feat(gold): show buy and sell prices in portfolio

This commit is contained in:
2026-07-03 13:34:34 +07:00
parent 2fc50f53ce
commit e1e4c594db
2 changed files with 39 additions and 4 deletions
+4 -3
View File
@@ -184,11 +184,12 @@ func (s *state) handleStats(ctx context.Context, b *bot.Bot, update *models.Upda
// Fetch under a reply-reserved sub-context; reply on the original ctx.
fetchCtx, cancel := chathelper.FetchContext(ctx)
defer cancel()
if buyPrice, _, err := s.prices.FetchLuongPrices(fetchCtx); err == nil {
if buyPrice, sellPrice, err := s.prices.FetchLuongPrices(fetchCtx); err == nil {
goldValue := p.Luong * buyPrice
totalValue += goldValue
lines = append(lines, "Price: "+FormatVND(buyPrice)+"/luong")
lines = append(lines, "Gold value: "+FormatVND(goldValue))
lines = append(lines, "SJC buy (you sell): "+FormatVND(buyPrice)+"/luong")
lines = append(lines, "SJC sell (you buy): "+FormatVND(sellPrice)+"/luong")
lines = append(lines, "Gold value (at SJC buy): "+FormatVND(goldValue))
lines = append(lines, "Total value: "+FormatVND(totalValue))
lines = append(lines, "Invested: "+FormatVND(p.Meta.Invested))
lines = append(lines, "P&L: "+FormatPnL(totalValue, p.Meta.Invested))
+35 -1
View File
@@ -159,7 +159,14 @@ func TestStatsWithAndWithoutPrice(t *testing.T) {
t.Fatalf("stats: %v", err)
}
text := rb.LastSent().Text()
for _, want := range []string{"Gold Account Summary", "Gold: 1 luong", "Price: 2.000.000 VND/luong", "P&L:"} {
for _, want := range []string{
"Gold Account Summary",
"Gold: 1 luong",
"SJC buy (you sell): 2.000.000 VND/luong",
"SJC sell (you buy): 2.000.000 VND/luong",
"Gold value (at SJC buy): 2.000.000 VND",
"P&L:",
} {
if !strings.Contains(text, want) {
t.Fatalf("stats missing %q in %q", want, text)
}
@@ -172,6 +179,33 @@ func TestStatsWithAndWithoutPrice(t *testing.T) {
rb.AssertSentText(t, "Price: no price")
}
func TestHandleStatsShowsBuyAndSellPrices(t *testing.T) {
ctx := context.Background()
s := &state{
store: newGoldStore(),
prices: spreadPriceFetcher{buy: 90_000_000, sell: 91_000_000},
nowFn: func() time.Time { return time.UnixMilli(123) },
}
rb := testutil.NewRecordingBot(t)
_ = s.handleTopup(ctx, rb.Bot, testutil.NewPrivateMessage(7, "/gold_topup 500000000"))
_ = s.handleBuy(ctx, rb.Bot, testutil.NewPrivateMessage(7, "/gold_buy 1"))
rb.Reset()
if err := s.handleStats(ctx, rb.Bot, testutil.NewPrivateMessage(7, "/gold_portfolio")); err != nil {
t.Fatalf("stats: %v", err)
}
text := rb.LastSent().Text()
for _, want := range []string{
"SJC buy (you sell): 90.000.000 VND/luong",
"SJC sell (you buy): 91.000.000 VND/luong",
"Gold value (at SJC buy): 90.000.000 VND",
"Total value: 499.000.000 VND",
} {
if !strings.Contains(text, want) {
t.Fatalf("stats missing %q in %q", want, text)
}
}
}
// spreadPriceFetcher returns different buy/sell prices so handler tests can
// verify that buys use the sell price and sells use the buy price.
type spreadPriceFetcher struct {