diff --git a/pkg/notifier/slacknotifier/slack.go b/pkg/notifier/slacknotifier/slack.go index ba5f9d06c..7d62a7ee8 100644 --- a/pkg/notifier/slacknotifier/slack.go +++ b/pkg/notifier/slacknotifier/slack.go @@ -29,6 +29,8 @@ var groupIdRegExp = regexp.MustCompile(`^$`) var emailRegExp = regexp.MustCompile("`^(?P[a-zA-Z0-9.!#$%&'*+/=?^_ \\x60{|}~-]+)@(?P[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$`gm") +var typeNamePrefixRE = regexp.MustCompile(`^\*?([a-zA-Z0-9_]+\.)?`) + type notifyTask struct { channel string @@ -521,7 +523,7 @@ func diffsToComment(obj any, diffs []dynamic.Diff) (text string) { return text } - text += fmt.Sprintf("%T updated\n", obj) + text += fmt.Sprintf("_%s_ updated\n", objectName(obj)) for _, diff := range diffs { text += fmt.Sprintf("- %s: `%s` transited to `%s`\n", diff.Field, diff.Before, diff.After) @@ -529,3 +531,17 @@ func diffsToComment(obj any, diffs []dynamic.Diff) (text string) { return text } + +func objectName(obj any) string { + type labelInf interface { + Label() string + } + + if ll, ok := obj.(labelInf); ok { + return ll.Label() + } + + typeName := fmt.Sprintf("%T", obj) + typeName = typeNamePrefixRE.ReplaceAllString(typeName, "") + return typeName +} diff --git a/pkg/notifier/slacknotifier/slack_test.go b/pkg/notifier/slacknotifier/slack_test.go new file mode 100644 index 000000000..1528aa26d --- /dev/null +++ b/pkg/notifier/slacknotifier/slack_test.go @@ -0,0 +1,22 @@ +package slacknotifier + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/types" +) + +func Test_objectName(t *testing.T) { + t.Run("deposit", func(t *testing.T) { + var deposit = &types.Deposit{} + assert.Equal(t, "Deposit", objectName(deposit)) + }) + + t.Run("local type", func(t *testing.T) { + type A struct{} + var obj = &A{} + assert.Equal(t, "A", objectName(obj)) + }) +} diff --git a/pkg/strategy/deposit2transfer/strategy.go b/pkg/strategy/deposit2transfer/strategy.go index 3cd9ebd6b..0bb7fd80f 100644 --- a/pkg/strategy/deposit2transfer/strategy.go +++ b/pkg/strategy/deposit2transfer/strategy.go @@ -55,6 +55,9 @@ type Strategy struct { Interval types.Duration `json:"interval"` TransferDelay types.Duration `json:"transferDelay"` + IgnoreDust bool `json:"ignoreDust"` + DustAmounts map[string]fixedpoint.Value `json:"dustAmounts"` + SlackAlert *SlackAlert `json:"slackAlert"` marginTransferService marginTransferService @@ -85,6 +88,15 @@ func (s *Strategy) Defaults() error { s.TransferDelay = types.Duration(3 * time.Second) } + if s.DustAmounts == nil { + s.DustAmounts = map[string]fixedpoint.Value{ + "USDC": fixedpoint.NewFromFloat(1.0), + "USDT": fixedpoint.NewFromFloat(1.0), + "BTC": fixedpoint.NewFromFloat(0.00001), + "ETH": fixedpoint.NewFromFloat(0.00001), + } + } + return nil } @@ -129,6 +141,16 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return nil } +func (s *Strategy) isDust(asset string, amount fixedpoint.Value) bool { + if s.IgnoreDust { + if dustAmount, ok := s.DustAmounts[asset]; ok { + return amount.Compare(dustAmount) <= 0 + } + } + + return false +} + func (s *Strategy) tickWatcher(ctx context.Context, interval time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() @@ -284,6 +306,10 @@ func (s *Strategy) scanDepositHistory(ctx context.Context, asset string, duratio continue } + if s.isDust(asset, deposit.Amount) { + continue + } + // if the deposit record is already in the watch list, update it if _, ok := s.watchingDeposits[deposit.TransactionID]; ok { s.addWatchingDeposit(deposit) diff --git a/pkg/strategy/xmaker/signal_book.go b/pkg/strategy/xmaker/signal_book.go index ecfb36637..c9612af78 100644 --- a/pkg/strategy/xmaker/signal_book.go +++ b/pkg/strategy/xmaker/signal_book.go @@ -28,6 +28,7 @@ type StreamBookSetter interface { type OrderBookBestPriceVolumeSignal struct { RatioThreshold fixedpoint.Value `json:"ratioThreshold"` MinVolume fixedpoint.Value `json:"minVolume"` + MinQuoteVolume fixedpoint.Value `json:"minQuoteVolume"` symbol string book *types.StreamOrderBook diff --git a/pkg/types/deposit.go b/pkg/types/deposit.go index dcd58e9c6..d45a4da24 100644 --- a/pkg/types/deposit.go +++ b/pkg/types/deposit.go @@ -28,6 +28,21 @@ const ( DepositCredited = DepositStatus("credited") ) +func (s DepositStatus) SlackEmoji() string { + switch s { + case DepositPending: + return ":hourglass_not_done:" + case DepositCredited: + return ":dollar_banknote:" + case DepositSuccess: + return ":check_mark_button:" + case DepositCancelled: + return ":stop_button:" + } + + return "" +} + type Deposit struct { GID int64 `json:"gid" db:"gid"` Exchange ExchangeName `json:"exchange" db:"exchange"` @@ -106,7 +121,7 @@ func (d *Deposit) SlackAttachment() slack.Attachment { if len(d.Status) > 0 { fields = append(fields, slack.AttachmentField{ Title: "Status", - Value: string(d.Status), + Value: string(d.Status) + " " + d.Status.SlackEmoji(), Short: false, }) } @@ -138,21 +153,59 @@ func (d *Deposit) SlackAttachment() slack.Attachment { }) return slack.Attachment{ - Color: depositStatusSlackColor(d.Status), - Title: fmt.Sprintf("Deposit %s %s To %s (%s)", d.Amount.String(), d.Asset, d.Address, d.Exchange), - // TitleLink: "", - Pretext: "", - Text: "", + Color: depositStatusSlackColor(d.Status), + Fallback: "", + ID: 0, + Title: fmt.Sprintf("Deposit %s %s To %s (%s)", d.Amount.String(), d.Asset, d.Address, d.Exchange), + TitleLink: getExplorerURL(d.Network, d.TransactionID), + Pretext: "", + Text: "", + ImageURL: "", + ThumbURL: "", + ServiceName: "", + ServiceIcon: "", + FromURL: "", + OriginalURL: "", // ServiceName: "", // ServiceIcon: "", // FromURL: "", // OriginalURL: "", Fields: fields, + Actions: nil, + MarkdownIn: nil, + Blocks: slack.Blocks{}, Footer: fmt.Sprintf("Apply Time: %s", d.Time.Time().Format(time.RFC3339)), FooterIcon: ExchangeFooterIcon(d.Exchange), + Ts: "", } } +func getExplorerURL(network string, txID string) string { + switch strings.ToUpper(network) { + case "BTC": + return getBitcoinNetworkExplorerURL(txID) + case "BSC": + return getBscNetworkExplorerURL(txID) + case "ETH": + return getEthNetworkExplorerURL(txID) + + } + + return "" +} + +func getEthNetworkExplorerURL(txID string) string { + return "https://etherscan.io/tx/" + txID +} + +func getBitcoinNetworkExplorerURL(txID string) string { + return "https://www.blockchain.com/explorer/transactions/btc/" + txID +} + +func getBscNetworkExplorerURL(txID string) string { + return "https://bscscan.com/tx/" + txID +} + func depositStatusSlackColor(status DepositStatus) string { switch status {