From 9ea72f219396d889dfbae85281ad0a6219b58591 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 29 Jul 2021 09:15:02 +1000 Subject: [PATCH] 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 --- exchanges/binance/binance_wrapper.go | 2 +- exchanges/ftx/ftx.go | 44 ++++++++++++++++++++++++++++ exchanges/ftx/ftx_test.go | 26 ++++++++++++++++ exchanges/ftx/ftx_types.go | 39 ++++++++++++------------ exchanges/ftx/ftx_wrapper.go | 19 +++++++++++- exchanges/order/limits.go | 13 ++++++-- exchanges/order/limits_test.go | 42 ++++++++++++++++++++++++++ 7 files changed, 163 insertions(+), 22 deletions(-) diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 889d6784..171ba89c 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -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) } diff --git a/exchanges/ftx/ftx.go b/exchanges/ftx/ftx.go index 06ab7c88..9a7b73d4 100644 --- a/exchanges/ftx/ftx.go +++ b/exchanges/ftx/ftx.go @@ -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 +} diff --git a/exchanges/ftx/ftx_test.go b/exchanges/ftx/ftx_test.go index 948172c2..21d76636 100644 --- a/exchanges/ftx/ftx_test.go +++ b/exchanges/ftx/ftx_test.go @@ -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) + } +} diff --git a/exchanges/ftx/ftx_types.go b/exchanges/ftx/ftx_types.go index 98c8ec0d..a78c2a3e 100644 --- a/exchanges/ftx/ftx_types.go +++ b/exchanges/ftx/ftx_types.go @@ -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 diff --git a/exchanges/ftx/ftx_wrapper.go b/exchanges/ftx/ftx_wrapper.go index 1addac7e..55f36694 100644 --- a/exchanges/ftx/ftx_wrapper.go +++ b/exchanges/ftx/ftx_wrapper.go @@ -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) +} diff --git a/exchanges/order/limits.go b/exchanges/order/limits.go index 2691dbaa..0eb8d985 100644 --- a/exchanges/order/limits.go +++ b/exchanges/order/limits.go @@ -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, diff --git a/exchanges/order/limits_test.go b/exchanges/order/limits_test.go index 8f6a8313..096fb87c 100644 --- a/exchanges/order/limits_test.go +++ b/exchanges/order/limits_test.go @@ -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) {