Merge pull request #860 from COLDTURNIP/feature/exchange_order_amount_protection

exchange: order fee-amount protection
This commit is contained in:
Raphanus Lo 2022-08-09 15:01:53 +08:00 committed by GitHub
commit ed975b7ed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 17 deletions

View File

@ -12,6 +12,13 @@ sessions:
envVarPrefix: binance
heikinAshi: false
# Drift strategy intends to place buy/sell orders as much value mas it could be. To exchanges that requires to
# calculate fees before placing limit orders (e.g. FTX Pro), make sure the fee rate is configured correctly and
# enable modifyOrderAmountForFee to prevent order rejection.
makerFeeRate: 0.0002
takerFeeRate: 0.0007
modifyOrderAmountForFee: false
exchangeStrategies:
- on: binance

View File

@ -12,6 +12,13 @@ sessions:
envVarPrefix: binance
heikinAshi: false
# Drift strategy intends to place buy/sell orders as much value mas it could be. To exchanges that requires to
# calculate fees before placing limit orders (e.g. FTX Pro), make sure the fee rate is configured correctly and
# enable modifyOrderAmountForFee to prevent order rejection.
makerFeeRate: 0.0002
takerFeeRate: 0.0007
modifyOrderAmountForFee: false
exchangeStrategies:
- on: binance

View File

@ -82,16 +82,25 @@ func TestLoadConfig(t *testing.T) {
assert.Equal(t, map[string]interface{}{
"sessions": map[string]interface{}{
"max": map[string]interface{}{
"exchange": "max",
"envVarPrefix": "MAX",
"takerFeeRate": 0.,
"makerFeeRate": 0.,
"exchange": "max",
"envVarPrefix": "MAX",
"takerFeeRate": 0.,
"makerFeeRate": 0.,
"modifyOrderAmountForFee": false,
},
"binance": map[string]interface{}{
"exchange": "binance",
"envVarPrefix": "BINANCE",
"takerFeeRate": 0.,
"makerFeeRate": 0.,
"exchange": "binance",
"envVarPrefix": "BINANCE",
"takerFeeRate": 0.,
"makerFeeRate": 0.,
"modifyOrderAmountForFee": false,
},
"ftx": map[string]interface{}{
"exchange": "ftx",
"envVarPrefix": "FTX",
"takerFeeRate": 0.,
"makerFeeRate": 0.,
"modifyOrderAmountForFee": true,
},
},
"build": map[string]interface{}{

View File

@ -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),

View File

@ -5,11 +5,19 @@ sessions:
envVarPrefix: MAX
takerFeeRate: 0
makerFeeRate: 0
modifyOrderAmountForFee: false
binance:
exchange: binance
envVarPrefix: BINANCE
takerFeeRate: 0
makerFeeRate: 0
modifyOrderAmountForFee: false
ftx:
exchange: ftx
envVarPrefix: FTX
takerFeeRate: 0
makerFeeRate: 0
modifyOrderAmountForFee: true
exchangeStrategies:
- on: ["binance"]

View File

@ -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:

View File

@ -107,6 +107,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)