diff --git a/pkg/strategy/deposit2transfer/strategy.go b/pkg/strategy/deposit2transfer/strategy.go new file mode 100644 index 000000000..c9741889d --- /dev/null +++ b/pkg/strategy/deposit2transfer/strategy.go @@ -0,0 +1,138 @@ +package deposit2transfer + +import ( + "context" + "errors" + "fmt" + + "github.com/sirupsen/logrus" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/exchange/binance" + "github.com/c9s/bbgo/pkg/types" +) + +const ID = "deposit2transfer" + +var log = logrus.WithField("strategy", ID) + +var errNotBinanceExchange = errors.New("not binance exchange, currently only support binance exchange") + +func init() { + // Register the pointer of the strategy struct, + // so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON) + // Note: built-in strategies need to imported manually in the bbgo cmd package. + bbgo.RegisterStrategy(ID, &Strategy{}) +} + +type Strategy struct { + Environment *bbgo.Environment + + Interval types.Interval `json:"interval"` + + SpotSession string `json:"spotSession"` + FuturesSession string `json:"futuresSession"` + + spotSession, futuresSession *bbgo.ExchangeSession + + binanceFutures, binanceSpot *binance.Exchange +} + +func (s *Strategy) ID() string { + return ID +} + +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {} + +func (s *Strategy) Defaults() error { + if s.Interval == "" { + s.Interval = types.Interval1m + } + + return nil +} + +func (s *Strategy) Validate() error { + if len(s.SpotSession) == 0 { + return errors.New("spotSession name is required") + } + + if len(s.FuturesSession) == 0 { + return errors.New("futuresSession name is required") + } + + return nil +} + +func (s *Strategy) InstanceID() string { + return fmt.Sprintf("%s", ID) +} + +func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + var ok bool + s.binanceFutures, ok = session.Exchange.(*binance.Exchange) + if !ok { + return errNotBinanceExchange + } + + s.binanceSpot, ok = session.Exchange.(*binance.Exchange) + if !ok { + return errNotBinanceExchange + } + + // instanceID := s.InstanceID() + /* + if err := s.transferIn(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, fixedpoint.Zero); err != nil { + log.WithError(err).Errorf("futures asset transfer in error") + } + + if err := s.transferOut(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, fixedpoint.Zero); err != nil { + log.WithError(err).Errorf("futures asset transfer out error") + } + + if err := backoff.RetryGeneral(ctx, func() error { + return s.transferIn(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, trade.Quantity) + }); err != nil { + log.WithError(err).Errorf("spot-to-futures transfer in retry failed") + return err + } + + if err := backoff.RetryGeneral(ctx, func() error { + return s.transferOut(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, quantity) + }); err != nil { + log.WithError(err).Errorf("spot-to-futures transfer in retry failed") + return err + } + */ + + if binanceStream, ok := s.futuresSession.UserDataStream.(*binance.Stream); ok { + binanceStream.OnAccountUpdateEvent(func(e *binance.AccountUpdateEvent) { + s.handleAccountUpdate(ctx, e) + }) + } + + return nil +} + +func (s *Strategy) handleAccountUpdate(ctx context.Context, e *binance.AccountUpdateEvent) { + switch e.AccountUpdate.EventReasonType { + case binance.AccountUpdateEventReasonDeposit: + case binance.AccountUpdateEventReasonWithdraw: + case binance.AccountUpdateEventReasonFundingFee: + // EventBase:{ + // Event:ACCOUNT_UPDATE + // Time:1679760000932 + // } + // Transaction:1679760000927 + // AccountUpdate:{ + // EventReasonType:FUNDING_FEE + // Balances:[{ + // Asset:USDT + // WalletBalance:56.64251742 + // CrossWalletBalance:56.64251742 + // BalanceChange:-0.00037648 + // }] + // } + // } + } +} diff --git a/pkg/strategy/deposit2transfer/transfer.go b/pkg/strategy/deposit2transfer/transfer.go new file mode 100644 index 000000000..d7573f6a6 --- /dev/null +++ b/pkg/strategy/deposit2transfer/transfer.go @@ -0,0 +1,104 @@ +package deposit2transfer + +import ( + "context" + "fmt" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +type FuturesTransfer interface { + TransferFuturesAccountAsset(ctx context.Context, asset string, amount fixedpoint.Value, io types.TransferDirection) error + QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) +} + +func (s *Strategy) resetTransfer(ctx context.Context, ex FuturesTransfer, asset string) error { + balances, err := s.futuresSession.Exchange.QueryAccountBalances(ctx) + if err != nil { + return err + } + + b, ok := balances[asset] + if !ok { + return nil + } + + amount := b.MaxWithdrawAmount + if amount.IsZero() { + return nil + } + + log.Infof("transfering out futures account asset %s %s", amount, asset) + + err = ex.TransferFuturesAccountAsset(ctx, asset, amount, types.TransferOut) + if err != nil { + return err + } + return nil +} + +func (s *Strategy) transferOut(ctx context.Context, ex FuturesTransfer, asset string, quantity fixedpoint.Value) error { + balances, err := s.futuresSession.Exchange.QueryAccountBalances(ctx) + if err != nil { + return err + } + + b, ok := balances[asset] + if !ok { + return fmt.Errorf("%s balance not found", asset) + } + + log.Infof("found futures balance: %+v", b) + + // add the previous pending base transfer and the current trade quantity + amount := b.MaxWithdrawAmount + + // try to transfer more if we enough balance + amount = fixedpoint.Min(amount, b.MaxWithdrawAmount) + + // TODO: according to the fee, we might not be able to get enough balance greater than the trade quantity, we can adjust the quantity here + if amount.IsZero() { + return nil + } + + // de-leverage and get the collateral base quantity + collateralBase := s.FuturesPosition.GetBase().Abs().Div(s.Leverage) + _ = collateralBase + + // if s.State.TotalBaseTransfer.Compare(collateralBase) + + log.Infof("transfering out futures account asset %s %s", amount, asset) + if err := ex.TransferFuturesAccountAsset(ctx, asset, amount, types.TransferOut); err != nil { + return err + } + + return nil +} + +func (s *Strategy) transferIn(ctx context.Context, ex FuturesTransfer, asset string, quantity fixedpoint.Value) error { + balances, err := s.spotSession.Exchange.QueryAccountBalances(ctx) + if err != nil { + return err + } + + b, ok := balances[asset] + if !ok { + return fmt.Errorf("%s balance not found", asset) + } + + // TODO: according to the fee, we might not be able to get enough balance greater than the trade quantity, we can adjust the quantity here + if !quantity.IsZero() && b.Available.Compare(quantity) < 0 { + log.Infof("adding to pending base transfer: %s %s", quantity, asset) + return nil + } + + amount := b.Available + + log.Infof("transfering in futures account asset %s %s", amount, asset) + if err := ex.TransferFuturesAccountAsset(ctx, asset, amount, types.TransferIn); err != nil { + return err + } + + return nil +}