add autoborrow strategy

This commit is contained in:
c9s 2022-04-23 15:00:04 +08:00
parent cf055c3f7d
commit c70317af2b
5 changed files with 236 additions and 1 deletions

25
config/autoborrow.yaml Normal file
View File

@ -0,0 +1,25 @@
---
exchangeStrategies:
- on: binance
autoborrow:
interval: 30m
autoRepayWhenDeposit: true
# minMarginRatio for triggering auto borrow
# we trigger auto borrow only when the margin ratio is above the number
minMarginRatio: 1.5
# maxMarginRatio for stop auto-repay
# if the margin ratio is high enough, we don't have the urge to repay
maxMarginRatio: 10.0
assets:
- asset: ETH
low: 3.0
maxQuantityPerBorrow: 1.0
maxTotalBorrow: 10.0
- asset: USDT
low: 1000.0
maxQuantityPerBorrow: 100.0
maxTotalBorrow: 10.0
minMarginRatio: 1.3

View File

@ -282,6 +282,13 @@ func NewExchangeSession(name string, exchange types.Exchange) *ExchangeSession {
return session
}
func (session *ExchangeSession) GetAccount() (a *types.Account) {
session.accountMutex.Lock()
a = session.Account
session.accountMutex.Unlock()
return a
}
// UpdateAccount locks the account mutex and update the account object
func (session *ExchangeSession) UpdateAccount(ctx context.Context) error {
account, err := session.Exchange.QueryAccount(ctx)

View File

@ -249,6 +249,7 @@ func (e *Exchange) RepayMarginAsset(ctx context.Context, asset string, amount fi
req.IsolatedSymbol(e.IsolatedMarginSymbol)
}
log.Infof("repaying margin asset %s amount %f", asset, amount.Float64())
resp, err := req.Do(ctx)
log.Debugf("margin repayed %f %s, transaction id = %d", amount.Float64(), asset, resp.TranID)
return err
@ -262,6 +263,7 @@ func (e *Exchange) BorrowMarginAsset(ctx context.Context, asset string, amount f
req.IsolatedSymbol(e.IsolatedMarginSymbol)
}
log.Infof("borrowing margin asset %s amount %f", asset, amount.Float64())
resp, err := req.Do(ctx)
log.Debugf("margin borrowed %f %s, transaction id = %d", amount.Float64(), asset, resp.TranID)
return err

View File

@ -0,0 +1,201 @@
package autoborrow
import (
"context"
"fmt"
"time"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/exchange/binance"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
const ID = "autoborrow"
var log = logrus.WithField("strategy", ID)
func init() {
bbgo.RegisterStrategy(ID, &Strategy{})
}
/**
- on: binance
autoborrow:
interval: 30m
repayWhenDeposit: true
# minMarginRatio for triggering auto borrow
minMarginRatio: 1.5
assets:
- asset: ETH
low: 3.0
maxQuantityPerBorrow: 1.0
maxTotalBorrow: 10.0
- asset: USDT
low: 1000.0
maxQuantityPerBorrow: 100.0
maxTotalBorrow: 10.0
*/
type MarginAsset struct {
Asset string `json:"asset"`
Low fixedpoint.Value `json:"low"`
MaxTotalBorrow fixedpoint.Value `json:"maxTotalBorrow"`
MaxQuantityPerBorrow fixedpoint.Value `json:"maxQuantityPerBorrow"`
MinQuantityPerBorrow fixedpoint.Value `json:"minQuantityPerBorrow"`
}
type Strategy struct {
Interval types.Interval `json:"interval"`
MinMarginRatio fixedpoint.Value `json:"minMarginRatio"`
MaxMarginRatio fixedpoint.Value `json:"maxMarginRatio"`
AutoRepayWhenDeposit bool `json:"autoRepayWhenDeposit"`
Assets []MarginAsset `json:"assets"`
ExchangeSession *bbgo.ExchangeSession
marginBorrowRepay types.MarginBorrowRepay
}
func (s *Strategy) ID() string {
return ID
}
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
// session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
}
func (s *Strategy) checkAndBorrow(ctx context.Context) {
if s.MinMarginRatio.IsZero() {
return
}
// if margin ratio is too low, do not borrow
if s.ExchangeSession.Account.MarginRatio.Compare(s.MinMarginRatio) < 0 {
return
}
balances := s.ExchangeSession.GetAccount().Balances()
for _, marginAsset := range s.Assets {
if marginAsset.Low.IsZero() {
log.Warnf("margin asset low balance is not set: %+v", marginAsset)
continue
}
b, ok := balances[marginAsset.Asset]
if ok {
toBorrow := marginAsset.Low - b.Total()
if toBorrow.Sign() < 0 {
log.Debugf("no need to borrow asset %+v", marginAsset)
continue
}
if !marginAsset.MaxQuantityPerBorrow.IsZero() {
toBorrow = fixedpoint.Min(toBorrow, marginAsset.MaxQuantityPerBorrow)
}
if !marginAsset.MaxTotalBorrow.IsZero() {
toBorrow = fixedpoint.Min(toBorrow.Add(b.Borrowed), marginAsset.MaxTotalBorrow)
}
s.marginBorrowRepay.BorrowMarginAsset(ctx, marginAsset.Asset, toBorrow)
} else {
// available balance is less than marginAsset.Low, we should trigger borrow
toBorrow := marginAsset.Low
if !marginAsset.MaxQuantityPerBorrow.IsZero() {
toBorrow = fixedpoint.Min(toBorrow, marginAsset.MaxQuantityPerBorrow)
}
s.marginBorrowRepay.BorrowMarginAsset(ctx, marginAsset.Asset, toBorrow)
}
}
}
func (s *Strategy) run(ctx context.Context, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}
func (s *Strategy) handleBalanceUpdate(balances types.BalanceMap) {
if s.MinMarginRatio.IsZero() {
return
}
if s.ExchangeSession.GetAccount().MarginRatio.Compare(s.MinMarginRatio) > 0 {
return
}
for _, b := range balances {
if b.Available.IsZero() && b.Borrowed.IsZero() {
continue
}
}
}
func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateEvent) {
if s.MinMarginRatio.IsZero() {
return
}
if s.ExchangeSession.Account.MarginRatio.Compare(s.MinMarginRatio) > 0 {
return
}
delta := fixedpoint.MustNewFromString(event.Delta)
// ignore outflow
if delta.Sign() < 0 {
return
}
if b, ok := s.ExchangeSession.Account.Balance(event.Asset); ok {
if b.Available.IsZero() || b.Borrowed.IsZero() {
return
}
if err := s.marginBorrowRepay.RepayMarginAsset(context.Background(), event.Asset, b.Available); err != nil {
log.WithError(err).Errorf("margin repay error")
}
}
}
// This strategy simply spent all available quote currency to buy the symbol whenever kline gets closed
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
if s.MinMarginRatio.IsZero() {
log.Warnf("minMarginRatio is 0, you should configure this minimal margin ratio for controlling the liquidation risk")
}
marginBorrowRepay, ok := session.Exchange.(types.MarginBorrowRepay)
if !ok {
return fmt.Errorf("exchange %s does not implement types.MarginBorrowRepay", session.ExchangeName)
}
s.marginBorrowRepay = marginBorrowRepay
if s.AutoRepayWhenDeposit {
binanceStream, ok := session.UserDataStream.(*binance.Stream)
if ok {
binanceStream.OnBalanceUpdateEvent(s.handleBinanceBalanceUpdateEvent)
} else {
session.UserDataStream.OnBalanceUpdate(s.handleBalanceUpdate)
}
}
go s.run(ctx, s.Interval.Duration())
return nil
}

View File

@ -49,12 +49,12 @@ type MarginExchange interface {
UseMargin()
UseIsolatedMargin(symbol string)
GetMarginSettings() MarginSettings
// QueryMarginAccount(ctx context.Context) (*binance.MarginAccount, error)
}
type MarginBorrowRepay interface {
RepayMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error
BorrowMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error
QueryMarginAssetMaxBorrowable(ctx context.Context, asset string) (amount fixedpoint.Value, err error)
}
type MarginSettings struct {