From ed7df4ddbe8e8a6da830874b6b8ad86b231f4fa6 Mon Sep 17 00:00:00 2001 From: Raphanus Lo Date: Tue, 2 Aug 2022 11:32:26 +0800 Subject: [PATCH] exchange: order fee-amount protection Reduce the order amount to prevent submit rejection because of balance exceeding. submit_amount = original_amount / (1 + fee_rate) Currently supported only by FTX Pro. --- pkg/bbgo/session.go | 17 ++++++++++++++--- pkg/exchange/ftx/exchange.go | 27 +++++++++++++++++++++------ pkg/types/exchange.go | 4 ++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index aa86fe06f..1e59f602e 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -39,9 +39,10 @@ type ExchangeSession struct { SubAccount string `json:"subAccount,omitempty" yaml:"subAccount,omitempty"` // Withdrawal is used for enabling withdrawal functions - Withdrawal bool `json:"withdrawal,omitempty" yaml:"withdrawal,omitempty"` - MakerFeeRate fixedpoint.Value `json:"makerFeeRate" yaml:"makerFeeRate"` - TakerFeeRate fixedpoint.Value `json:"takerFeeRate" yaml:"takerFeeRate"` + Withdrawal bool `json:"withdrawal,omitempty" yaml:"withdrawal,omitempty"` + MakerFeeRate fixedpoint.Value `json:"makerFeeRate" yaml:"makerFeeRate"` + TakerFeeRate fixedpoint.Value `json:"takerFeeRate" yaml:"takerFeeRate"` + ModifyOrderAmountForFee bool `json:"modifyOrderAmountForFee" yaml:"modifyOrderAmountForFee"` PublicOnly bool `json:"publicOnly,omitempty" yaml:"publicOnly"` Margin bool `json:"margin,omitempty" yaml:"margin"` @@ -202,6 +203,16 @@ func (session *ExchangeSession) Init(ctx context.Context, environ *Environment) } } + if session.ModifyOrderAmountForFee { + amountProtectExchange, ok := session.Exchange.(types.ExchangeAmountFeeProtect) + if !ok { + return fmt.Errorf("exchange %s does not support order amount protection", session.ExchangeName.String()) + } + + fees := types.ExchangeFee{MakerFeeRate: session.MakerFeeRate, TakerFeeRate: session.TakerFeeRate} + amountProtectExchange.SetModifyOrderAmountForFee(fees) + } + if session.UseHeikinAshi { session.MarketDataStream = &types.HeikinAshiStream{ StandardStreamEmitter: session.MarketDataStream.(types.StandardStreamEmitter), diff --git a/pkg/exchange/ftx/exchange.go b/pkg/exchange/ftx/exchange.go index 194ff4b52..4f62cc052 100644 --- a/pkg/exchange/ftx/exchange.go +++ b/pkg/exchange/ftx/exchange.go @@ -37,9 +37,10 @@ var marketDataLimiter = rate.NewLimiter(rate.Every(500*time.Millisecond), 2) type Exchange struct { client *ftxapi.RestClient - key, secret string - subAccount string - restEndpoint *url.URL + key, secret string + subAccount string + restEndpoint *url.URL + orderAmountReduceFactor fixedpoint.Value } type MarketTicker struct { @@ -91,8 +92,9 @@ func NewExchange(key, secret string, subAccount string) *Exchange { restEndpoint: u, key: key, // pragma: allowlist nextline secret - secret: secret, - subAccount: subAccount, + secret: secret, + subAccount: subAccount, + orderAmountReduceFactor: fixedpoint.One, } } @@ -221,6 +223,13 @@ func (e *Exchange) DefaultFeeRates() types.ExchangeFee { } } +// SetModifyOrderAmountForFee protects the limit buy orders by reducing amount with taker fee. +// The amount is recalculated before submit: submit_amount = original_amount / (1 + taker_fee_rate) . +// This prevents balance exceeding error while closing position without spot margin enabled. +func (e *Exchange) SetModifyOrderAmountForFee(feeRate types.ExchangeFee) { + e.orderAmountReduceFactor = fixedpoint.One.Add(feeRate.TakerFeeRate) +} + // resolution field in api // window length in seconds. options: 15, 60, 300, 900, 3600, 14400, 86400, or any multiple of 86400 up to 30*86400 var supportedIntervals = map[types.Interval]int{ @@ -406,11 +415,17 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder logrus.WithError(err).Error("type error") } + submitQuantity := so.Quantity + switch orderType { + case ftxapi.OrderTypeLimit, ftxapi.OrderTypeStopLimit: + submitQuantity = so.Quantity.Div(e.orderAmountReduceFactor) + } + req := e.client.NewPlaceOrderRequest() req.Market(toLocalSymbol(TrimUpperString(so.Symbol))) req.OrderType(orderType) req.Side(ftxapi.Side(TrimLowerString(string(so.Side)))) - req.Size(so.Quantity) + req.Size(submitQuantity) switch so.Type { case types.OrderTypeLimit, types.OrderTypeLimitMaker: diff --git a/pkg/types/exchange.go b/pkg/types/exchange.go index e026decb4..9e142b5e5 100644 --- a/pkg/types/exchange.go +++ b/pkg/types/exchange.go @@ -106,6 +106,10 @@ type ExchangeDefaultFeeRates interface { DefaultFeeRates() ExchangeFee } +type ExchangeAmountFeeProtect interface { + SetModifyOrderAmountForFee(ExchangeFee) +} + type ExchangeTradeHistoryService interface { QueryTrades(ctx context.Context, symbol string, options *TradeQueryOptions) ([]Trade, error) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) (orders []Order, err error)