diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index cd74498b..36a80400 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -14,6 +14,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -88,6 +89,9 @@ const ( bitfinexFuturesPairs = "conf/pub:list:pair:futures" // TODO: Implement bitfinexSecuritiesPairs = "conf/pub:list:pair:securities" // TODO: Implement + bitfinexInfoPairs = "conf/pub:info:pair" + bitfinexInfoFuturePairs = "conf/pub:info:pair:futures" + // Bitfinex platform status values // When the platform is marked in maintenance mode bots should stop trading // activity. Cancelling orders will be possible. @@ -470,10 +474,9 @@ func (b *Bitfinex) GetPairs(ctx context.Context, a asset.Item) ([]string, error) } } -// GetSiteListConfigData returns site configuration data by pub:{Action}:{Object}:{Detail} +// GetSiteListConfigData returns site configuration data by pub:list:{Object}:{Detail} // string sets. // NOTE: See https://docs.bitfinex.com/reference/rest-public-conf -// ALSO: This only accesses the lists not the maps. func (b *Bitfinex) GetSiteListConfigData(ctx context.Context, set string) ([]string, error) { if set == "" { return nil, errSetCannotBeEmpty @@ -491,6 +494,72 @@ func (b *Bitfinex) GetSiteListConfigData(ctx context.Context, set string) ([]str return resp[0], nil } +// GetSiteInfoConfigData returns site configuration data by pub:info:{AssetType} as a map +// path should be bitfinexInfoPairs or bitfinexInfoPairsFuture??? +// NOTE: See https://docs.bitfinex.com/reference/rest-public-conf +func (b *Bitfinex) GetSiteInfoConfigData(ctx context.Context, assetType asset.Item) ([]order.MinMaxLevel, error) { + var path string + switch assetType { + case asset.Spot: + path = bitfinexInfoPairs + case asset.Futures: + path = bitfinexInfoFuturePairs + default: + return nil, fmt.Errorf("invalid asset type for GetSiteInfoConfigData: %s", assetType) + } + url := bitfinexAPIVersion2 + path + var resp [][][]any + + err := b.SendHTTPRequest(ctx, exchange.RestSpot, url, &resp, status) + if err != nil { + return nil, err + } + if len(resp) != 1 { + return nil, errors.New("response did not contain only one item") + } + data := resp[0] + pairs := make([]order.MinMaxLevel, 0, len(data)) + for i := range data { + if len(data[i]) != 2 { + return nil, errors.New("response contained a tuple without exactly 2 items") + } + pairSymbol, ok := data[i][0].(string) + if !ok { + return nil, fmt.Errorf("could not convert first item in SiteInfoConfigData to string: Type is %T", data[i][0]) + } + if strings.Contains(pairSymbol, "TEST") { + continue + } + // SIC: Array type really is any. It contains nils and strings + info, ok := data[i][1].([]any) + if !ok { + return nil, fmt.Errorf("could not convert second item in SiteInfoConfigData to []any; Type is %T", data[i][1]) + } + if len(info) < 5 { + return nil, errors.New("response contained order info with less than 5 elements") + } + minOrder, err := convert.FloatFromString(info[3]) + if err != nil { + return nil, fmt.Errorf("could not convert MinOrderAmount: %s", err) + } + maxOrder, err := convert.FloatFromString(info[4]) + if err != nil { + return nil, fmt.Errorf("could not convert MaxOrderAmount: %s", err) + } + pair, err := currency.NewPairFromString(pairSymbol) + if err != nil { + return nil, err + } + pairs = append(pairs, order.MinMaxLevel{ + Asset: assetType, + Pair: pair, + MinimumBaseAmount: minOrder, + MaximumBaseAmount: maxOrder, + }) + } + return pairs, nil +} + // GetDerivativeStatusInfo gets status data for the queried derivative func (b *Bitfinex) GetDerivativeStatusInfo(ctx context.Context, keys, startTime, endTime string, sort, limit int64) ([]DerivativeDataResponse, error) { params := url.Values{} diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 37d6966c..29c7855f 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -158,6 +158,39 @@ func TestGetPairs(t *testing.T) { } } +func TestUpdateTradablePairs(t *testing.T) { + t.Parallel() + if err := b.UpdateTradablePairs(context.Background(), true); err != nil { + t.Error("Bitfinex UpdateTradablePairs() error", err) + } +} + +func TestUpdateOrderExecutionLimits(t *testing.T) { + t.Parallel() + tests := map[asset.Item][]currency.Pair{ + asset.Spot: { + currency.NewPair(currency.ETH, currency.UST), + currency.NewPair(currency.BTC, currency.UST), + }, + } + for assetItem, pairs := range tests { + if err := b.UpdateOrderExecutionLimits(context.Background(), assetItem); err != nil { + t.Errorf("Error fetching %s pairs for test: %v", assetItem, err) + continue + } + for _, pair := range pairs { + limits, err := b.GetOrderExecutionLimits(assetItem, pair) + if err != nil { + t.Errorf("GetOrderExecutionLimits() error during TestExecutionLimits; Asset: %s Pair: %s Err: %v", assetItem, pair, err) + continue + } + if limits.MinimumBaseAmount == 0 { + t.Errorf("UpdateOrderExecutionLimits empty minimum base amount; Pair: %s Expected Limit: %v", pair, limits.MinimumBaseAmount) + } + } + } +} + func TestAppendOptionalDelimiter(t *testing.T) { t.Parallel() curr1, err := currency.NewPairFromString("BTCUSD") @@ -1773,6 +1806,19 @@ func TestGetSiteListConfigData(t *testing.T) { } } +func TestGetSiteInfoConfigData(t *testing.T) { + t.Parallel() + for _, assetType := range []asset.Item{asset.Spot, asset.Futures} { + pairs, err := b.GetSiteInfoConfigData(context.Background(), assetType) + if !errors.Is(err, nil) { + t.Errorf("Error from GetSiteInfoConfigData for %s type received: %v, expected: %v", assetType, err, nil) + } + if len(pairs) == 0 { + t.Errorf("GetSiteInfoConfigData returned no pairs for %s", assetType) + } + } +} + func TestOrderUpdate(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index 40fe8288..99d7b63b 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -221,17 +221,6 @@ type Lends struct { Timestamp int64 `json:"timestamp"` } -// SymbolDetails holds currency pair information -type SymbolDetails struct { - Pair string `json:"pair"` - PricePrecision int `json:"price_precision"` - InitialMargin float64 `json:"initial_margin,string"` - MinimumMargin float64 `json:"minimum_margin,string"` - MaximumOrderSize float64 `json:"maximum_order_size,string"` - MinimumOrderSize float64 `json:"minimum_order_size,string"` - Expiration string `json:"expiration"` -} - // AccountInfoFull adds the error message to Account info type AccountInfoFull struct { Info []AccountInfo diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index fefec3bf..e26eece9 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -264,8 +264,18 @@ func (b *Bitfinex) Run(ctx context.Context) { b.PrintEnabledPairs() } - if !b.GetEnabledFeatures().AutoPairUpdates { - return + if b.GetEnabledFeatures().AutoPairUpdates { + if err := b.UpdateTradablePairs(ctx, false); err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + b.Name, + err) + } + } + for _, a := range b.GetAssetTypes(true) { + if err := b.UpdateOrderExecutionLimits(ctx, a); err != nil && err != common.ErrNotYetImplemented { + log.Errorln(log.ExchangeSys, err.Error()) + } } err := b.UpdateTradablePairs(ctx, false) @@ -322,6 +332,21 @@ func (b *Bitfinex) UpdateTradablePairs(ctx context.Context, forceUpdate bool) er return b.EnsureOnePairEnabled() } +// UpdateOrderExecutionLimits sets exchange execution order limits for an asset type +func (b *Bitfinex) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { + if a != asset.Spot { + return common.ErrNotYetImplemented + } + limits, err := b.GetSiteInfoConfigData(ctx, a) + if err != nil { + return err + } + if err := b.LoadLimits(limits); err != nil { + return fmt.Errorf("%s Error loading exchange limits: %v", b.Name, err) + } + return nil +} + // UpdateTickers updates the ticker for all currency pairs of a given asset type func (b *Bitfinex) UpdateTickers(ctx context.Context, a asset.Item) error { enabled, err := b.GetEnabledPairs(a)