From caf5d69f2112fa323ef3c0f74dc9636d4e364a66 Mon Sep 17 00:00:00 2001 From: kbearXD Date: Thu, 24 Oct 2024 17:50:01 +0800 Subject: [PATCH] FEATURE: [xalign] add notification when order quote is over alert amount --- config/xalign.yaml | 6 ++ pkg/strategy/xalign/strategy.go | 108 ++++++++++++++++++++++++++++---- 2 files changed, 102 insertions(+), 12 deletions(-) diff --git a/config/xalign.yaml b/config/xalign.yaml index 6f07ba4b81..2fb1e2cbe1 100644 --- a/config/xalign.yaml +++ b/config/xalign.yaml @@ -47,3 +47,9 @@ crossExchangeStrategies: USDT: 100 USDC: 100 TWD: 3000 + maxAmountAlert: + quote: USDT + amount: 200 + slackMentions: + - '<@USER_ID>' + - '' diff --git a/pkg/strategy/xalign/strategy.go b/pkg/strategy/xalign/strategy.go index 4bf62f23c6..d7a69add9c 100644 --- a/pkg/strategy/xalign/strategy.go +++ b/pkg/strategy/xalign/strategy.go @@ -9,6 +9,7 @@ import ( "time" "github.com/sirupsen/logrus" + "github.com/slack-go/slack" "golang.org/x/time/rate" "github.com/c9s/bbgo/pkg/bbgo" @@ -39,6 +40,55 @@ type QuoteCurrencyPreference struct { Sell []string `json:"sell"` } +type MaxAmountAlertConfig struct { + QuoteCurrency string `json:"quoteCurrency"` + Amount fixedpoint.Value `json:"amount"` + SlackMentions []string `json:"slackMentions"` +} + +type MaxAmountAlert struct { + QuoteCurrency string + AlertAmount fixedpoint.Value + SlackMentions []string + + BaseCurrency string + Side types.SideType + Price fixedpoint.Value + Quantity fixedpoint.Value + Amount fixedpoint.Value +} + +func (m *MaxAmountAlert) SlackAttachment() slack.Attachment { + return slack.Attachment{ + Color: "red", + Title: fmt.Sprintf("xalign amount alert - try to align %s with quote %s amount %f > %f", + m.BaseCurrency, m.QuoteCurrency, m.Amount.Float64(), m.AlertAmount.Float64()), + Text: strings.Join(m.SlackMentions, " "), + Fields: []slack.AttachmentField{ + { + Title: "Base Currency", + Value: m.BaseCurrency, + Short: true, + }, + { + Title: "Side", + Value: m.Side.String(), + Short: true, + }, + { + Title: "Price", + Value: m.Price.String(), + Short: true, + }, + { + Title: "Quantity", + Value: m.Quantity.String(), + Short: true, + }, + }, + } +} + type Strategy struct { *bbgo.Environment Interval types.Interval `json:"interval"` @@ -50,6 +100,7 @@ type Strategy struct { BalanceToleranceRange fixedpoint.Value `json:"balanceToleranceRange"` Duration types.Duration `json:"for"` MaxAmounts map[string]fixedpoint.Value `json:"maxAmounts"` + MaxAmountAlert *MaxAmountAlertConfig `json:"maxAmountAlert"` SlackNotify bool `json:"slackNotify"` SlackNotifyMentions []string `json:"slackNotifyMentions"` @@ -84,7 +135,6 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { } func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) { - } func (s *Strategy) Defaults() error { @@ -191,20 +241,20 @@ func (s *Strategy) detectActiveDeposit( func (s *Strategy) selectSessionForCurrency( ctx context.Context, sessions map[string]*bbgo.ExchangeSession, currency string, changeQuantity fixedpoint.Value, ) (*bbgo.ExchangeSession, *types.SubmitOrder) { + var taker = s.UseTakerOrder + var side types.SideType + var quoteCurrencies []string + if changeQuantity.Sign() > 0 { + quoteCurrencies = s.PreferredQuoteCurrencies.Buy + side = types.SideTypeBuy + } else { + quoteCurrencies = s.PreferredQuoteCurrencies.Sell + side = types.SideTypeSell + } + for _, sessionName := range s.PreferredSessions { session := sessions[sessionName] - var taker = s.UseTakerOrder - var side types.SideType - var quoteCurrencies []string - if changeQuantity.Sign() > 0 { - quoteCurrencies = s.PreferredQuoteCurrencies.Buy - side = types.SideTypeBuy - } else { - quoteCurrencies = s.PreferredQuoteCurrencies.Sell - side = types.SideTypeSell - } - for _, fromQuoteCurrency := range quoteCurrencies { // skip the same currency, because there is no such USDT/USDT market if currency == fromQuoteCurrency { @@ -405,6 +455,16 @@ func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, se } s.priceResolver = pricesolver.NewSimplePriceResolver(markets) + for _, session := range s.sessions { + // init the price + marketPrices := session.LastPrices() + for market, price := range marketPrices { + s.priceResolver.Update(market, price) + } + + // bind on trade to update price + session.UserDataStream.OnTradeUpdate(s.priceResolver.UpdateFromTrade) + } bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() @@ -528,6 +588,30 @@ func (s *Strategy) align(ctx context.Context, sessions map[string]*bbgo.Exchange } } + if price, ok := s.priceResolver.ResolvePrice(currency, s.MaxAmountAlert.QuoteCurrency); ok { + quantity := q.Abs() + amount := price.Mul(quantity) + if amount.Compare(s.MaxAmountAlert.Amount) > 0 { + alert := &MaxAmountAlert{ + QuoteCurrency: s.MaxAmountAlert.QuoteCurrency, + AlertAmount: s.MaxAmountAlert.Amount, + SlackMentions: s.MaxAmountAlert.SlackMentions, + BaseCurrency: currency, + Price: price, + Quantity: quantity, + Amount: amount, + } + + if q.Sign() > 0 { + alert.Side = types.SideTypeBuy + } else { + alert.Side = types.SideTypeSell + } + + bbgo.Notify(alert) + } + } + selectedSession, submitOrder := s.selectSessionForCurrency(ctx, sessions, currency, q) if selectedSession != nil && submitOrder != nil { log.Infof("placing %s order on %s: %+v", submitOrder.Symbol, selectedSession.Name, submitOrder)