diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 6d20f709..6afc6fdf 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -210,6 +210,47 @@ func TestFetchTradablePairs(t *testing.T) { } } +func TestUpdateTradablePairs(t *testing.T) { + t.Parallel() + if err := b.UpdateTradablePairs(context.Background(), true); err != nil { + t.Error("Bitstamp UpdateTradablePairs() error", err) + } +} + +func TestUpdateOrderExecutionLimits(t *testing.T) { + t.Parallel() + type limitTest struct { + pair currency.Pair + step float64 + min float64 + } + + tests := map[asset.Item][]limitTest{ + asset.Spot: { + {currency.NewPair(currency.ETH, currency.USDT), 0.01, 20}, + {currency.NewPair(currency.BTC, currency.USDT), 0.01, 20}, + }, + } + for assetItem, limitTests := range tests { + if err := b.UpdateOrderExecutionLimits(context.Background(), assetItem); err != nil { + t.Errorf("Error fetching %s pairs for test: %v", assetItem, err) + } + for _, limitTest := range limitTests { + limits, err := b.GetOrderExecutionLimits(assetItem, limitTest.pair) + if err != nil { + t.Errorf("Bitstamp GetOrderExecutionLimits() error during TestExecutionLimits; Asset: %s Pair: %s Err: %v", assetItem, limitTest.pair, err) + continue + } + if got := limits.PriceStepIncrementSize; got != limitTest.step { + t.Errorf("Bitstamp UpdateOrderExecutionLimits wrong PriceStepIncrementSize; Asset: %s Pair: %s Expected: %v Got: %v", assetItem, limitTest.pair, limitTest.step, got) + } + if got := limits.MinimumQuoteAmount; got != limitTest.min { + t.Errorf("Bitstamp UpdateOrderExecutionLimits wrong MinAmount; Pair: %s Expected: %v Got: %v", limitTest.pair, limitTest.min, got) + } + } + } +} + func TestGetTransactions(t *testing.T) { t.Parallel() _, err := b.GetTransactions(context.Background(), diff --git a/exchanges/bitstamp/bitstamp_type_convert.go b/exchanges/bitstamp/bitstamp_type_convert.go new file mode 100644 index 00000000..5c172f4d --- /dev/null +++ b/exchanges/bitstamp/bitstamp_type_convert.go @@ -0,0 +1,29 @@ +package bitstamp + +import ( + "encoding/json" + "strconv" + "strings" +) + +// UnmarshalJSON deserializes JSON, and timestamp information. +func (p *TradingPair) UnmarshalJSON(data []byte) error { + type Alias TradingPair + t := &struct { + *Alias + MinimumOrder string `json:"minimum_order"` + }{ + Alias: (*Alias)(p), + } + + err := json.Unmarshal(data, t) + if err != nil { + return err + } + minOrderStr := t.MinimumOrder + if prefix, _, found := strings.Cut(t.MinimumOrder, " "); found { + minOrderStr = prefix + } + p.MinimumOrder, err = strconv.ParseFloat(minOrderStr, 64) + return err +} diff --git a/exchanges/bitstamp/bitstamp_types.go b/exchanges/bitstamp/bitstamp_types.go index 0d6c26b0..957b0e18 100644 --- a/exchanges/bitstamp/bitstamp_types.go +++ b/exchanges/bitstamp/bitstamp_types.go @@ -55,7 +55,7 @@ type TradingPair struct { URLSymbol string `json:"url_symbol"` BaseDecimals int `json:"base_decimals"` CounterDecimals int `json:"counter_decimals"` - MinimumOrder string `json:"minimum_order"` + MinimumOrder float64 Trading string `json:"trading"` Description string `json:"description"` } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 1e3f1a48..c22b1ddb 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "sort" "strconv" "sync" @@ -266,16 +267,19 @@ func (b *Bitstamp) Run(ctx context.Context) { } } - if !b.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { - return + if b.GetEnabledFeatures().AutoPairUpdates || forceUpdate { + if err := b.UpdateTradablePairs(ctx, forceUpdate); err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + b.Name, + err) + } } - err := b.UpdateTradablePairs(ctx, forceUpdate) - if 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()) + } } } @@ -285,13 +289,12 @@ func (b *Bitstamp) FetchTradablePairs(ctx context.Context, _ asset.Item) (curren if err != nil { return nil, err } - + var pair currency.Pair pairs := make([]currency.Pair, 0, len(symbols)) for x := range symbols { if symbols[x].Trading != "Enabled" { continue } - var pair currency.Pair pair, err = currency.NewPairFromString(symbols[x].Name) if err != nil { return nil, err @@ -315,6 +318,38 @@ func (b *Bitstamp) UpdateTradablePairs(ctx context.Context, forceUpdate bool) er return b.EnsureOnePairEnabled() } +// UpdateOrderExecutionLimits sets exchange execution order limits for an asset type +func (b *Bitstamp) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { + if a != asset.Spot { + return common.ErrNotYetImplemented + } + symbols, err := b.GetTradingPairs(ctx) + if err != nil { + return err + } + limits := make([]order.MinMaxLevel, 0, len(symbols)) + for x, info := range symbols { + if symbols[x].Trading != "Enabled" { + continue + } + pair, err := currency.NewPairFromString(symbols[x].Name) + if err != nil { + return err + } + limits = append(limits, order.MinMaxLevel{ + Asset: a, + Pair: pair, + PriceStepIncrementSize: math.Pow10(-info.CounterDecimals), + AmountStepIncrementSize: math.Pow10(-info.BaseDecimals), + MinimumQuoteAmount: info.MinimumOrder, + }) + } + 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 *Bitstamp) UpdateTickers(_ context.Context, _ asset.Item) error { return common.ErrFunctionNotSupported