mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
add autoborrow strategy
This commit is contained in:
parent
cf055c3f7d
commit
c70317af2b
25
config/autoborrow.yaml
Normal file
25
config/autoborrow.yaml
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
201
pkg/strategy/autoborrow/strategy.go
Normal file
201
pkg/strategy/autoborrow/strategy.go
Normal 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
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user