ftx: add basic exchange order execution limits for lower bound (#725)

* ftx: add basic exchange order execution limits for lower bound

* ftx/binance: conform to standard logger output

* ftx: fix field from SizeIncrement -> MinProvideSize

* limits: fix whoopsie

* limit: add tests

* ftx: fix comment
This commit is contained in:
Ryan O'Hara-Reid
2021-07-29 09:15:02 +10:00
committed by GitHub
parent a6e158ab0c
commit 9ea72f2193
7 changed files with 163 additions and 22 deletions

View File

@@ -321,7 +321,7 @@ func (b *Binance) Run() {
err = b.UpdateOrderExecutionLimits(a[x])
if err != nil {
log.Errorf(log.ExchangeSys,
"Could not set %s exchange exchange limits: %v",
"%s failed to set exchange order execution limits. Err: %v",
b.Name,
err)
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
)
@@ -1378,3 +1379,46 @@ func (f *FTX) SubaccountTransfer(coin currency.Code, source, destination string,
}
return &resp.Data, nil
}
// FetchExchangeLimits fetches spot order execution limits
func (f *FTX) FetchExchangeLimits() ([]order.MinMaxLevel, error) {
data, err := f.GetMarkets()
if err != nil {
return nil, err
}
var limits []order.MinMaxLevel
for x := range data {
if !data[x].Enabled {
continue
}
var cp currency.Pair
var a asset.Item
switch data[x].MarketType {
case "future":
a = asset.Futures
cp, err = currency.NewPairFromString(data[x].Name)
if err != nil {
return nil, err
}
case "spot":
a = asset.Spot
cp, err = currency.NewPairFromStrings(data[x].BaseCurrency, data[x].QuoteCurrency)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unhandled data type %s, cannot process exchange limit",
data[x].MarketType)
}
limits = append(limits, order.MinMaxLevel{
Pair: cp,
Asset: a,
StepPrice: data[x].PriceIncrement,
StepAmount: data[x].SizeIncrement,
MinAmount: data[x].MinProvideSize,
})
}
return limits, nil
}

View File

@@ -1679,3 +1679,29 @@ func TestStakeRequest(t *testing.T) {
t.Error(err)
}
}
func TestUpdateOrderExecutionLimits(t *testing.T) {
err := f.UpdateOrderExecutionLimits("")
if err != nil {
t.Fatal(err)
}
cp := currency.NewPair(currency.BTC, currency.USD)
limit, err := f.GetOrderExecutionLimits(asset.Spot, cp)
if err != nil {
t.Fatal(err)
}
err = limit.Conforms(33000, 0.00001, order.Limit)
if !errors.Is(err, order.ErrAmountBelowMin) {
t.Fatalf("expected error %v but received %v",
order.ErrAmountBelowMin,
err)
}
err = limit.Conforms(33000, 0.0001, order.Limit)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v",
nil,
err)
}
}

View File

@@ -55,24 +55,27 @@ type LendingInfoData struct {
// MarketData stores market data
type MarketData struct {
Name string `json:"name"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
MarketType string `json:"type"`
Underlying string `json:"underlying"`
Change1h float64 `json:"change1h"`
Change24h float64 `json:"change24h"`
ChangeBod float64 `json:"changeBod"`
QuoteVolume24h float64 `json:"quoteVolume24h"`
Enabled bool `json:"enabled"`
Ask float64 `json:"ask"`
Bid float64 `json:"bid"`
Last float64 `json:"last"`
USDVolume24h float64 `json:"volumeUSD24h"`
MinProvideSize float64 `json:"minProvideSize"`
PriceIncrement float64 `json:"priceIncrement"`
SizeIncrement float64 `json:"sizeIncrement"`
Restricted bool `json:"restricted"`
Name string `json:"name"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
MarketType string `json:"type"`
Underlying string `json:"underlying"`
Change1h float64 `json:"change1h"`
Change24h float64 `json:"change24h"`
ChangeBod float64 `json:"changeBod"`
QuoteVolume24h float64 `json:"quoteVolume24h"`
Enabled bool `json:"enabled"`
Ask float64 `json:"ask"`
Bid float64 `json:"bid"`
Last float64 `json:"last"`
USDVolume24h float64 `json:"volumeUSD24h"`
MinProvideSize float64 `json:"minProvideSize"`
PriceIncrement float64 `json:"priceIncrement"`
SizeIncrement float64 `json:"sizeIncrement"`
Restricted bool `json:"restricted"`
PostOnly bool `json:"postOnly"`
Price float64 `json:"price"`
HighLeverageFeeExempt bool `json:"highLeverageFeeExempt"`
}
// OData stores orderdata in orderbook

View File

@@ -218,11 +218,19 @@ func (f *FTX) Run() {
f.PrintEnabledPairs()
}
err := f.UpdateOrderExecutionLimits("")
if err != nil {
log.Errorf(log.ExchangeSys,
"%s failed to set exchange order execution limits. Err: %v",
f.Name,
err)
}
if !f.GetEnabledFeatures().AutoPairUpdates {
return
}
err := f.UpdateTradablePairs(false)
err = f.UpdateTradablePairs(false)
if err != nil {
log.Errorf(log.ExchangeSys,
"%s failed to update tradable pairs. Err: %s",
@@ -1113,3 +1121,12 @@ func (f *FTX) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, start, e
ret.SortCandlesByTimestamp(false)
return ret, nil
}
// UpdateOrderExecutionLimits sets exchange executions for a required asset type
func (f *FTX) UpdateOrderExecutionLimits(_ asset.Item) error {
limits, err := f.FetchExchangeLimits()
if err != nil {
return fmt.Errorf("cannot update exchange execution limits: %w", err)
}
return f.LoadLimits(limits)
}

View File

@@ -98,6 +98,11 @@ func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error {
}
for x := range levels {
if !levels[x].Asset.IsValid() {
return fmt.Errorf("cannot load levels for '%s': %w",
levels[x].Asset,
asset.ErrNotSupported)
}
m1, ok := e.m[levels[x].Asset]
if !ok {
m1 = make(map[*currency.Item]map[*currency.Item]*Limits)
@@ -116,7 +121,9 @@ func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error {
m2[levels[x].Pair.Quote.Item] = limit
}
if levels[x].MinPrice > levels[x].MaxPrice {
if levels[x].MinPrice > 0 &&
levels[x].MaxPrice > 0 &&
levels[x].MinPrice > levels[x].MaxPrice {
return fmt.Errorf("%w for %s %s supplied min: %f max: %f",
errInvalidPriceLevels,
levels[x].Asset,
@@ -125,7 +132,9 @@ func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error {
levels[x].MaxPrice)
}
if levels[x].MinAmount > levels[x].MaxAmount {
if levels[x].MinAmount > 0 &&
levels[x].MaxAmount > 0 &&
levels[x].MinAmount > levels[x].MaxAmount {
return fmt.Errorf("%w for %s %s supplied min: %f max: %f",
errInvalidAmountLevels,
levels[x].Asset,

View File

@@ -20,6 +20,22 @@ func TestLoadLimits(t *testing.T) {
t.Fatalf("expected error %v but received %v", errCannotLoadLimit, err)
}
invalidAsset := []MinMaxLevel{
{
Pair: btcusd,
MinPrice: 100000,
MaxPrice: 1000000,
MinAmount: 1,
MaxAmount: 10,
},
}
err = e.LoadLimits(invalidAsset)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("expected error %v but received %v",
asset.ErrNotSupported,
err)
}
newLimits := []MinMaxLevel{
{
Pair: btcusd,
@@ -79,6 +95,32 @@ func TestLoadLimits(t *testing.T) {
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
noCompare := []MinMaxLevel{
{
Pair: btcusd,
Asset: asset.Spot,
MinAmount: 10,
},
}
err = e.LoadLimits(noCompare)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
noCompare = []MinMaxLevel{
{
Pair: btcusd,
Asset: asset.Spot,
MinPrice: 10,
},
}
err = e.LoadLimits(noCompare)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v", nil, err)
}
}
func TestGetOrderExecutionLimits(t *testing.T) {