From 78f9c7d569e67978195d44a1a5781948d2e4304e Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 2 Jun 2022 01:27:04 +0800 Subject: [PATCH] improve autoborrow checks --- pkg/bbgo/session.go | 7 ++-- pkg/exchange/max/exchange.go | 65 ++++++++++++++--------------- pkg/strategy/autoborrow/strategy.go | 42 +++++++++++++++---- pkg/strategy/xnav/strategy.go | 2 +- 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index 89443ca2b..7daf377da 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -295,15 +295,16 @@ func (session *ExchangeSession) GetAccount() (a *types.Account) { } // UpdateAccount locks the account mutex and update the account object -func (session *ExchangeSession) UpdateAccount(ctx context.Context) error { +func (session *ExchangeSession) UpdateAccount(ctx context.Context) (*types.Account, error) { account, err := session.Exchange.QueryAccount(ctx) if err != nil { - return err + return nil, err } + session.accountMutex.Lock() session.Account = account session.accountMutex.Unlock() - return nil + return account, nil } // Init initializes the basic data structure and market information by its exchange. diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index b9471b608..d84245c53 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -38,7 +38,6 @@ type Exchange struct { v3margin *v3.MarginService } - func New(key, secret string) *Exchange { baseURL := maxapi.ProductionAPIURL if override := os.Getenv("MAX_API_BASE_URL"); len(override) > 0 { @@ -583,6 +582,38 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) { return a, nil } +func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) { + if err := accountQueryLimiter.Wait(ctx); err != nil { + return nil, err + } + + walletType := maxapi.WalletTypeSpot + if e.MarginSettings.IsMargin { + walletType = maxapi.WalletTypeMargin + } + + req := e.v3order.NewGetWalletAccountsRequest(walletType) + accounts, err := req.Do(ctx) + if err != nil { + return nil, err + } + + var balances = make(types.BalanceMap) + for _, b := range accounts { + cur := toGlobalCurrency(b.Currency) + balances[cur] = types.Balance{ + Currency: cur, + Available: b.Balance, + Locked: b.Locked, + NetAsset: b.Balance.Add(b.Locked), + Borrowed: b.Debt, + Interest: b.Interest, + } + } + + return balances, nil +} + func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []types.Withdraw, err error) { startTime := since limit := 1000 @@ -744,37 +775,6 @@ func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since, return allDeposits, err } -func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) { - if err := accountQueryLimiter.Wait(ctx); err != nil { - return nil, err - } - - walletType := maxapi.WalletTypeSpot - if e.MarginSettings.IsMargin { - walletType = maxapi.WalletTypeMargin - } - - req := e.v3order.NewGetWalletAccountsRequest(walletType) - accounts, err := req.Do(ctx) - if err != nil { - return nil, err - } - - var balances = make(types.BalanceMap) - for _, b := range accounts { - cur := toGlobalCurrency(b.Currency) - balances[cur] = types.Balance{ - Currency: cur, - Available: b.Balance, - Locked: b.Locked, - NetAsset: b.Balance.Add(b.Locked), - // Borrowed: b.Debt - } - } - - return balances, nil -} - func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) (trades []types.Trade, err error) { if err := tradeQueryLimiter.Wait(ctx); err != nil { return nil, err @@ -927,7 +927,6 @@ func (e *Exchange) QueryAveragePrice(ctx context.Context, symbol string) (fixedp Add(fixedpoint.MustNewFromString(ticker.Buy)).Div(Two), nil } - func (e *Exchange) RepayMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error { req := e.v3margin.NewMarginRepayRequest() req.Currency(toLocalCurrency(asset)) diff --git a/pkg/strategy/autoborrow/strategy.go b/pkg/strategy/autoborrow/strategy.go index 675c89f2b..95d273b63 100644 --- a/pkg/strategy/autoborrow/strategy.go +++ b/pkg/strategy/autoborrow/strategy.go @@ -75,12 +75,12 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { func (s *Strategy) tryToRepayAnyDebt(ctx context.Context) { log.Infof("trying to repay any debt...") - if err := s.ExchangeSession.UpdateAccount(ctx); err != nil { + account, err := s.ExchangeSession.UpdateAccount(ctx) + if err != nil { log.WithError(err).Errorf("can not update account") return } - account := s.ExchangeSession.GetAccount() minMarginLevel := s.MinMarginLevel curMarginLevel := account.MarginLevel @@ -115,13 +115,13 @@ func (s *Strategy) checkAndBorrow(ctx context.Context) { return } - if err := s.ExchangeSession.UpdateAccount(ctx); err != nil { + account, err := s.ExchangeSession.UpdateAccount(ctx) + if err != nil { log.WithError(err).Errorf("can not update account") return } minMarginLevel := s.MinMarginLevel - account := s.ExchangeSession.GetAccount() curMarginLevel := account.MarginLevel log.Infof("current account margin level: %s margin ratio: %s, margin tolerance: %s", @@ -137,13 +137,15 @@ func (s *Strategy) checkAndBorrow(ctx context.Context) { return } - balances := s.ExchangeSession.GetAccount().Balances() + balances := account.Balances() if len(balances) == 0 { log.Warn("balance is empty, skip autoborrow") return } for _, marginAsset := range s.Assets { + changed := false + if marginAsset.Low.IsZero() { log.Warnf("margin asset low balance is not set: %+v", marginAsset) continue @@ -176,6 +178,10 @@ func (s *Strategy) checkAndBorrow(ctx context.Context) { } } + if toBorrow.IsZero() { + continue + } + s.Notifiability.Notify(&MarginAction{ Action: "Borrow", Asset: marginAsset.Asset, @@ -184,7 +190,11 @@ func (s *Strategy) checkAndBorrow(ctx context.Context) { MinMarginLevel: minMarginLevel, }) log.Infof("sending borrow request %f %s", toBorrow.Float64(), marginAsset.Asset) - s.marginBorrowRepay.BorrowMarginAsset(ctx, marginAsset.Asset, toBorrow) + if err := s.marginBorrowRepay.BorrowMarginAsset(ctx, marginAsset.Asset, toBorrow); err != nil { + log.WithError(err).Errorf("repay error") + continue + } + changed = true } else { // available balance is less than marginAsset.Low, we should trigger borrow toBorrow := marginAsset.Low @@ -193,6 +203,10 @@ func (s *Strategy) checkAndBorrow(ctx context.Context) { toBorrow = fixedpoint.Min(toBorrow, marginAsset.MaxQuantityPerBorrow) } + if toBorrow.IsZero() { + continue + } + s.Notifiability.Notify(&MarginAction{ Action: "Borrow", Asset: marginAsset.Asset, @@ -202,7 +216,21 @@ func (s *Strategy) checkAndBorrow(ctx context.Context) { }) log.Infof("sending borrow request %f %s", toBorrow.Float64(), marginAsset.Asset) - s.marginBorrowRepay.BorrowMarginAsset(ctx, marginAsset.Asset, toBorrow) + if err := s.marginBorrowRepay.BorrowMarginAsset(ctx, marginAsset.Asset, toBorrow); err != nil { + log.WithError(err).Errorf("borrow error") + continue + } + + changed = true + } + + // if debt is changed, we need to update account + if changed { + account, err = s.ExchangeSession.UpdateAccount(ctx) + if err != nil { + log.WithError(err).Errorf("can not update account") + return + } } } } diff --git a/pkg/strategy/xnav/strategy.go b/pkg/strategy/xnav/strategy.go index 5ac73a050..3464612ad 100644 --- a/pkg/strategy/xnav/strategy.go +++ b/pkg/strategy/xnav/strategy.go @@ -86,7 +86,7 @@ func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string] // iterate the sessions and record them for sessionName, session := range sessions { // update the account balances and the margin information - if err := session.UpdateAccount(ctx); err != nil { + if _, err := session.UpdateAccount(ctx); err != nil { log.WithError(err).Errorf("can not update account") return }