strategy/autoborrow: add margin level alert

This commit is contained in:
c9s 2023-07-14 13:19:46 +08:00
parent c2568ab22c
commit a9d0242a9d
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
4 changed files with 94 additions and 18 deletions

View File

@ -21,10 +21,6 @@ type notifyTask struct {
Opts []slack.MsgOption Opts []slack.MsgOption
} }
type slackAttachmentCreator interface {
SlackAttachment() slack.Attachment
}
type Notifier struct { type Notifier struct {
client *slack.Client client *slack.Client
channel string channel string
@ -59,6 +55,7 @@ func (n *Notifier) worker() {
case task := <-n.taskC: case task := <-n.taskC:
limiter.Wait(ctx) limiter.Wait(ctx)
_, _, err := n.client.PostMessageContext(ctx, task.Channel, task.Opts...) _, _, err := n.client.PostMessageContext(ctx, task.Channel, task.Opts...)
if err != nil { if err != nil {
log.WithError(err). log.WithError(err).
@ -86,7 +83,7 @@ func filterSlackAttachments(args []interface{}) (slackAttachments []slack.Attach
slackAttachments = append(slackAttachments, a) slackAttachments = append(slackAttachments, a)
case slackAttachmentCreator: case types.SlackAttachmentCreator:
if firstAttachmentOffset == -1 { if firstAttachmentOffset == -1 {
firstAttachmentOffset = idx firstAttachmentOffset = idx
} }
@ -132,7 +129,7 @@ func (n *Notifier) NotifyTo(channel string, obj interface{}, args ...interface{}
case slack.Attachment: case slack.Attachment:
opts = append(opts, slack.MsgOptionAttachments(append([]slack.Attachment{a}, slackAttachments...)...)) opts = append(opts, slack.MsgOptionAttachments(append([]slack.Attachment{a}, slackAttachments...)...))
case slackAttachmentCreator: case types.SlackAttachmentCreator:
// convert object to slack attachment (if supported) // convert object to slack attachment (if supported)
opts = append(opts, slack.MsgOptionAttachments(append([]slack.Attachment{a.SlackAttachment()}, slackAttachments...)...)) opts = append(opts, slack.MsgOptionAttachments(append([]slack.Attachment{a.SlackAttachment()}, slackAttachments...)...))

View File

@ -23,8 +23,8 @@ func init() {
bbgo.RegisterStrategy(ID, &Strategy{}) bbgo.RegisterStrategy(ID, &Strategy{})
} }
/** /*
- on: binance - on: binance
autoborrow: autoborrow:
interval: 30m interval: 30m
repayWhenDeposit: true repayWhenDeposit: true
@ -32,15 +32,51 @@ func init() {
# minMarginLevel for triggering auto borrow # minMarginLevel for triggering auto borrow
minMarginLevel: 1.5 minMarginLevel: 1.5
assets: assets:
- asset: ETH - asset: ETH
low: 3.0 low: 3.0
maxQuantityPerBorrow: 1.0 maxQuantityPerBorrow: 1.0
maxTotalBorrow: 10.0 maxTotalBorrow: 10.0
- asset: USDT - asset: USDT
low: 1000.0 low: 1000.0
maxQuantityPerBorrow: 100.0 maxQuantityPerBorrow: 100.0
maxTotalBorrow: 10.0 maxTotalBorrow: 10.0
*/ */
type MarginAlert struct {
CurrentMarginLevel fixedpoint.Value
MinimalMarginLevel fixedpoint.Value
SlackMentions []string
SessionName string
}
func (m *MarginAlert) SlackAttachment() slack.Attachment {
return slack.Attachment{
Color: "red",
Title: fmt.Sprintf("Margin Level Alert: %s session - current margin level %f < required margin level %f",
m.SessionName, m.CurrentMarginLevel.Float64(), m.MinimalMarginLevel.Float64()),
Text: strings.Join(m.SlackMentions, " "),
Fields: []slack.AttachmentField{
{
Title: "Session",
Value: m.SessionName,
Short: true,
},
{
Title: "Current Margin Level",
Value: m.CurrentMarginLevel.String(),
Short: true,
},
{
Title: "Minimal Margin Level",
Value: m.MinimalMarginLevel.String(),
Short: true,
},
},
// Footer: "",
// FooterIcon: "",
}
}
type MarginAsset struct { type MarginAsset struct {
Asset string `json:"asset"` Asset string `json:"asset"`
@ -57,6 +93,8 @@ type Strategy struct {
MaxMarginLevel fixedpoint.Value `json:"maxMarginLevel"` MaxMarginLevel fixedpoint.Value `json:"maxMarginLevel"`
AutoRepayWhenDeposit bool `json:"autoRepayWhenDeposit"` AutoRepayWhenDeposit bool `json:"autoRepayWhenDeposit"`
MarginLevelAlertInterval types.Duration `json:"marginLevelAlertInterval"`
MarginLevelAlertMinMargin fixedpoint.Value `json:"marginLevelAlertMinMargin"`
MarginLevelAlertSlackMentions []string `json:"marginLevelAlertSlackMentions"` MarginLevelAlertSlackMentions []string `json:"marginLevelAlertSlackMentions"`
Assets []MarginAsset `json:"assets"` Assets []MarginAsset `json:"assets"`
@ -510,6 +548,39 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
} }
if !s.MarginLevelAlertMinMargin.IsZero() {
alertInterval := time.Minute * 5
if s.MarginLevelAlertInterval > 0 {
alertInterval = s.MarginLevelAlertInterval.Duration()
}
go s.marginAlertWorker(ctx, alertInterval)
}
go s.run(ctx, s.Interval.Duration()) go s.run(ctx, s.Interval.Duration())
return nil return nil
} }
func (s *Strategy) marginAlertWorker(ctx context.Context, alertInterval time.Duration) {
go func() {
ticker := time.NewTicker(alertInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
account := s.ExchangeSession.GetAccount()
if account.MarginLevel.Compare(s.MarginLevelAlertMinMargin) <= 0 {
bbgo.Notify(&MarginAlert{
CurrentMarginLevel: account.MarginLevel,
MinimalMarginLevel: s.MarginLevelAlertMinMargin,
SlackMentions: s.MarginLevelAlertSlackMentions,
SessionName: s.ExchangeSession.Name,
})
bbgo.Notify(account.Balances().Debts())
}
}
}
}()
}

View File

@ -89,6 +89,7 @@ func ParseSimpleDuration(s string) (*SimpleDuration, error) {
return nil, errors.Wrapf(ErrNotSimpleDuration, "input %q is not a simple duration", s) return nil, errors.Wrapf(ErrNotSimpleDuration, "input %q is not a simple duration", s)
} }
// Duration
type Duration time.Duration type Duration time.Duration
func (d *Duration) Duration() time.Duration { func (d *Duration) Duration() time.Duration {

7
pkg/types/slack.go Normal file
View File

@ -0,0 +1,7 @@
package types
import "github.com/slack-go/slack"
type SlackAttachmentCreator interface {
SlackAttachment() slack.Attachment
}