Merge pull request #1222 from c9s/c9s/fix-xfunding

This commit is contained in:
c9s 2023-07-06 16:15:55 +08:00 committed by GitHub
commit 63d6c88594
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 100 additions and 33 deletions

View File

@ -347,6 +347,7 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
bbgo.Notify("Spot Position", s.SpotPosition)
bbgo.Notify("Futures Position", s.FuturesPosition)
bbgo.Notify("Neutral Position", s.NeutralPosition)
bbgo.Notify("State", s.State.PositionState)
// sync funding fee txns
if !s.ProfitStats.LastFundingFeeTime.IsZero() {
@ -356,6 +357,20 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
// TEST CODE:
// s.syncFundingFeeRecords(ctx, time.Now().Add(-3*24*time.Hour))
switch s.State.PositionState {
case PositionOpening:
// transfer all base assets from the spot account into the spot account
if err := s.transferIn(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, fixedpoint.Zero); err != nil {
log.WithError(err).Errorf("futures asset transfer in error")
}
case PositionClosing, PositionClosed:
// transfer all base assets from the futures account back to the spot account
if err := s.transferOut(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, fixedpoint.Zero); err != nil {
log.WithError(err).Errorf("futures asset transfer out error")
}
}
s.spotOrderExecutor = s.allocateOrderExecutor(ctx, s.spotSession, instanceID, s.SpotPosition)
s.spotOrderExecutor.TradeCollector().OnTrade(func(trade types.Trade, profit fixedpoint.Value, netProfit fixedpoint.Value) {
// we act differently on the spot account
@ -405,8 +420,11 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
switch s.getPositionState() {
case PositionClosing:
// de-leverage and get the collateral base quantity for transfer
quantity := trade.Quantity.Div(s.Leverage)
if err := backoff.RetryGeneral(ctx, func() error {
return s.transferOut(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, trade.Quantity)
return s.transferOut(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, quantity)
}); err != nil {
log.WithError(err).Errorf("spot-to-futures transfer in retry failed")
return
@ -635,13 +653,39 @@ func (s *Strategy) reduceFuturesPosition(ctx context.Context) {
return
}
spotBase := s.SpotPosition.GetBase()
if !s.spotMarket.IsDustQuantity(spotBase, s.SpotPosition.AverageCost) {
if balance, ok := s.futuresSession.Account.Balance(s.futuresMarket.BaseCurrency); ok && balance.Available.Sign() > 0 {
if err := backoff.RetryGeneral(ctx, func() error {
return s.transferOut(ctx, s.binanceSpot, s.spotMarket.BaseCurrency, balance.Available)
}); err != nil {
log.WithError(err).Errorf("spot-to-futures transfer in retry failed")
}
}
}
if futuresBase.Compare(fixedpoint.Zero) < 0 {
orderPrice := ticker.Buy
orderQuantity := futuresBase.Abs()
orderQuantity = fixedpoint.Max(orderQuantity, s.minQuantity)
orderQuantity = s.futuresMarket.AdjustQuantityByMinNotional(orderQuantity, orderPrice)
if s.futuresMarket.IsDustQuantity(orderQuantity, orderPrice) {
log.Infof("skip futures order with dust quantity %s, market = %+v", orderQuantity.String(), s.futuresMarket)
submitOrder := types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeLimitMaker,
Price: orderPrice,
Market: s.futuresMarket,
// quantity: Cannot be sent with closePosition=true(Close-All)
// reduceOnly: Cannot be sent with closePosition=true
ClosePosition: true,
}
if _, err := s.futuresOrderExecutor.SubmitOrders(ctx, submitOrder); err != nil {
log.WithError(err).Errorf("can not submit futures order with close position: %+v", submitOrder)
}
return
}
@ -685,7 +729,7 @@ func (s *Strategy) syncFuturesPosition(ctx context.Context) {
return
}
log.Infof("position comparision: %s (spot) <=> %s (futures)", spotBase.String(), futuresBase.String())
log.Infof("syncFuturesPosition: position comparision: %s (spot) <=> %s (futures)", spotBase.String(), futuresBase.String())
if futuresBase.Sign() > 0 {
// unexpected error
@ -722,7 +766,8 @@ func (s *Strategy) syncFuturesPosition(ctx context.Context) {
}
// if - futures position < max futures position, increase it
if futuresBase.Neg().Compare(maxFuturesBasePosition) >= 0 {
// posDiff := futuresBase.Abs().Sub(maxFuturesBasePosition)
if futuresBase.Abs().Compare(maxFuturesBasePosition) >= 0 {
s.setPositionState(PositionReady)
bbgo.Notify("Position Ready")
@ -745,12 +790,16 @@ func (s *Strategy) syncFuturesPosition(ctx context.Context) {
log.Infof("position diff quantity: %s", diffQuantity.String())
orderQuantity := fixedpoint.Max(diffQuantity, s.minQuantity)
orderQuantity := diffQuantity
orderQuantity = fixedpoint.Max(diffQuantity, s.minQuantity)
orderQuantity = s.futuresMarket.AdjustQuantityByMinNotional(orderQuantity, orderPrice)
if s.futuresMarket.IsDustQuantity(orderQuantity, orderPrice) {
log.Warnf("unexpected dust quantity, skip futures order with dust quantity %s, market = %+v", orderQuantity.String(), s.futuresMarket)
return
}
/*
if s.futuresMarket.IsDustQuantity(orderQuantity, orderPrice) {
log.Warnf("unexpected dust quantity, skip futures order with dust quantity %s, market = %+v", orderQuantity.String(), s.futuresMarket)
return
}
*/
submitOrder := types.SubmitOrder{
Symbol: s.Symbol,
@ -792,7 +841,7 @@ func (s *Strategy) syncSpotPosition(ctx context.Context) {
return
}
log.Infof("spot/futures positions: %s (spot) <=> %s (futures)", spotBase.String(), futuresBase.String())
log.Infof("syncSpotPosition: spot/futures positions: %s (spot) <=> %s (futures)", spotBase.String(), futuresBase.String())
if futuresBase.Sign() > 0 {
// unexpected error
@ -800,7 +849,7 @@ func (s *Strategy) syncSpotPosition(ctx context.Context) {
return
}
_ = s.futuresOrderExecutor.GracefulCancel(ctx)
_ = s.spotOrderExecutor.GracefulCancel(ctx)
ticker, err := s.spotSession.Exchange.QueryTicker(ctx, s.Symbol)
if err != nil {
@ -831,27 +880,34 @@ func (s *Strategy) syncSpotPosition(ctx context.Context) {
orderPrice := ticker.Sell
orderQuantity := diffQuantity
if b, ok := s.spotSession.Account.Balance(s.spotMarket.BaseCurrency); ok {
orderQuantity = fixedpoint.Min(b.Available, orderQuantity)
}
// avoid increase the order size
if s.spotMarket.IsDustQuantity(orderQuantity, orderPrice) {
log.Infof("skip futures order with dust quantity %s, market = %+v", orderQuantity.String(), s.spotMarket)
b, ok := s.spotSession.Account.Balance(s.spotMarket.BaseCurrency)
if !ok {
log.Warnf("%s balance not found, can not sync spot position", s.spotMarket.BaseCurrency)
return
}
createdOrders, err := s.spotOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
log.Infof("spot balance: %+v", b)
orderQuantity = fixedpoint.Min(b.Available, orderQuantity)
// avoid increase the order size
if s.spotMarket.IsDustQuantity(orderQuantity, orderPrice) {
log.Infof("skip spot order with dust quantity %s, market=%+v balance=%+v", orderQuantity.String(), s.spotMarket, b)
return
}
submitOrder := types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeLimitMaker,
Quantity: orderQuantity,
Price: orderPrice,
Market: s.futuresMarket,
})
Market: s.spotMarket,
}
createdOrders, err := s.spotOrderExecutor.SubmitOrders(ctx, submitOrder)
if err != nil {
log.WithError(err).Errorf("can not submit spot order")
log.WithError(err).Errorf("can not submit spot order: %+v", submitOrder)
return
}
@ -1092,6 +1148,13 @@ func (s *Strategy) checkAndRestorePositionRisks(ctx context.Context) error {
return err
}
log.Infof("fetched futures position risks: %+v", positionRisks)
if len(positionRisks) == 0 {
s.FuturesPosition.Reset()
return nil
}
for _, positionRisk := range positionRisks {
if positionRisk.Symbol != s.Symbol {
continue

View File

@ -41,31 +41,33 @@ func (s *Strategy) resetTransfer(ctx context.Context, ex FuturesTransfer, asset
return nil
}
func (s *Strategy) transferOut(ctx context.Context, ex FuturesTransfer, asset string, tradeQuantity fixedpoint.Value) error {
func (s *Strategy) transferOut(ctx context.Context, ex FuturesTransfer, asset string, quantity fixedpoint.Value) error {
// if transfer done
if s.State.TotalBaseTransfer.IsZero() {
return nil
}
// de-leverage and get the collateral base quantity for transfer
quantity := tradeQuantity.Div(s.Leverage)
balances, err := s.futuresSession.Exchange.QueryAccountBalances(ctx)
if err != nil {
log.Infof("adding to pending base transfer: %s %s + %s", quantity.String(), asset, s.State.PendingBaseTransfer.String())
log.Infof("balance query error, adding to pending base transfer: %s %s + %s", quantity.String(), asset, s.State.PendingBaseTransfer.String())
s.State.PendingBaseTransfer = s.State.PendingBaseTransfer.Add(quantity)
return err
}
b, ok := balances[asset]
if !ok {
log.Infof("adding to pending base transfer: %s %s + %s", quantity.String(), asset, s.State.PendingBaseTransfer.String())
log.Infof("balance not found, adding to pending base transfer: %s %s + %s", quantity.String(), asset, s.State.PendingBaseTransfer.String())
s.State.PendingBaseTransfer = s.State.PendingBaseTransfer.Add(quantity)
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 := s.State.PendingBaseTransfer.Add(quantity)
amount := b.MaxWithdrawAmount
if !quantity.IsZero() {
amount = s.State.PendingBaseTransfer.Add(quantity)
}
// try to transfer more if we enough balance
amount = fixedpoint.Min(amount, b.MaxWithdrawAmount)
@ -75,7 +77,7 @@ func (s *Strategy) transferOut(ctx context.Context, ex FuturesTransfer, asset st
// 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() {
log.Infof("adding to pending base transfer: %s %s + %s ", quantity.String(), asset, s.State.PendingBaseTransfer.String())
log.Infof("zero amount, adding to pending base transfer: %s %s + %s ", quantity.String(), asset, s.State.PendingBaseTransfer.String())
s.State.PendingBaseTransfer = s.State.PendingBaseTransfer.Add(quantity)
return nil
}
@ -111,17 +113,19 @@ func (s *Strategy) transferIn(ctx context.Context, ex FuturesTransfer, asset str
}
// 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 b.Available.Compare(quantity) < 0 {
if !quantity.IsZero() && b.Available.Compare(quantity) < 0 {
log.Infof("adding to pending base transfer: %s %s", quantity, asset)
s.State.PendingBaseTransfer = s.State.PendingBaseTransfer.Add(quantity)
return nil
}
amount := s.State.PendingBaseTransfer.Add(quantity)
amount := b.Available
if !quantity.IsZero() {
amount = s.State.PendingBaseTransfer.Add(quantity)
}
pos := s.SpotPosition.GetBase().Abs()
rest := pos.Sub(s.State.TotalBaseTransfer)
if rest.Sign() < 0 {
return nil
}