From 056a809d93bafb2aea3900d25586c30e116b7c54 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 26 Aug 2021 11:40:14 +1000 Subject: [PATCH] Bitstamp: Fix currency pair handling (#762) * Fix Bitstamp pair handling * Fix spelling * Populate namerinos * Address nitterinos * Revert trade currency code, introduces races between engine / this and rely on OB test instead * One liner --- exchanges/bitstamp/bitstamp_live_test.go | 3 +- exchanges/bitstamp/bitstamp_mock_test.go | 3 +- exchanges/bitstamp/bitstamp_test.go | 26 + exchanges/bitstamp/bitstamp_types.go | 4 + exchanges/bitstamp/bitstamp_websocket.go | 53 +- exchanges/bitstamp/bitstamp_wrapper.go | 80 +- testdata/http_mock/bitstamp/bitstamp.json | 1002 ++++++++++++++++++--- 7 files changed, 1047 insertions(+), 124 deletions(-) diff --git a/exchanges/bitstamp/bitstamp_live_test.go b/exchanges/bitstamp/bitstamp_live_test.go index 7dd4aa76..90ea88d1 100644 --- a/exchanges/bitstamp/bitstamp_live_test.go +++ b/exchanges/bitstamp/bitstamp_live_test.go @@ -1,4 +1,5 @@ -//+build mock_test_off +//go:build mock_test_off +// +build mock_test_off // This will build if build tag mock_test_off is parsed and will do live testing // using all tests in (exchange)_test.go diff --git a/exchanges/bitstamp/bitstamp_mock_test.go b/exchanges/bitstamp/bitstamp_mock_test.go index e0fb5c8c..29c1cc58 100644 --- a/exchanges/bitstamp/bitstamp_mock_test.go +++ b/exchanges/bitstamp/bitstamp_mock_test.go @@ -1,4 +1,5 @@ -//+build !mock_test_off +//go:build !mock_test_off +// +build !mock_test_off // This will build if build tag mock_test_off is not parsed and will try to mock // all tests in _test.go diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 716cd7c3..9bdf65fb 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -1,6 +1,7 @@ package bitstamp import ( + "errors" "testing" "time" @@ -173,6 +174,27 @@ func TestGetTradingPairs(t *testing.T) { } } +func TestFetchTradablePairs(t *testing.T) { + t.Parallel() + r, err := b.FetchTradablePairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + pairs, err := currency.NewPairsFromStrings(r) + if err != nil { + t.Fatal(err) + } + if !pairs.Contains(currency.NewPair(currency.COMP, currency.USD), false) { + t.Error("expected pair COMP/USD") + } + if !pairs.Contains(currency.NewPair(currency.BTC, currency.USD), false) { + t.Error("expected pair BTC/USD") + } + if !pairs.Contains(currency.NewPair(currency.USDC, currency.USDT), false) { + t.Error("expected pair USDC/USDT") + } +} + func TestGetTransactions(t *testing.T) { t.Parallel() _, err := b.GetTransactions(currency.BTC.String()+currency.USD.String(), "hour") @@ -612,6 +634,10 @@ func TestWsOrderbook(t *testing.T) { if err != nil { t.Error(err) } + pressXToJSON = []byte(`{"data": {"timestamp": "1580336834", "microtimestamp": "1580336834607546", "bids": [["9328.28", "0.05925332"], ["9327.34", "0.43120000"], ["9327.29", "0.63470860"], ["9326.59", "0.41114619"], ["9326.38", "1.06910000"], ["9323.91", "2.67930000"], ["9322.69", "0.80000000"], ["9322.57", "0.03000000"], ["9322.31", "1.36010820"], ["9319.54", "0.03090000"], ["9318.97", "0.28000000"], ["9317.61", "0.02910000"], ["9316.39", "1.08000000"], ["9316.20", "2.00000000"], ["9315.48", "1.00000000"], ["9314.72", "0.11197459"], ["9314.47", "0.32207398"], ["9312.53", "0.03961501"], ["9312.29", "1.00000000"], ["9311.78", "0.03060000"], ["9311.69", "0.32217221"], ["9310.98", "3.29000000"], ["9310.18", "0.01304192"], ["9310.13", "0.02500000"], ["9309.04", "1.00000000"], ["9309.00", "0.05000000"], ["9308.96", "0.03030000"], ["9308.91", "0.32227154"], ["9307.52", "0.32191362"], ["9307.25", "2.44280000"], ["9305.92", "3.00000000"], ["9305.62", "2.37600000"], ["9305.60", "0.21815312"], ["9305.54", "2.80000000"], ["9305.13", "0.05000000"], ["9305.02", "2.90917302"], ["9303.68", "0.02316372"], ["9303.53", "12.55000000"], ["9303.00", "0.02191430"], ["9302.94", "2.38250000"], ["9302.37", "0.01000000"], ["9301.85", "2.50000000"], ["9300.89", "0.02000000"], ["9300.40", "4.10000000"], ["9300.00", "0.33936139"], ["9298.48", "1.45200000"], ["9297.80", "0.42380000"], ["9295.44", "4.54689328"], ["9295.43", "3.20000000"], ["9295.00", "0.28669566"], ["9291.66", "14.09931321"], ["9290.13", "2.87254900"], ["9290.00", "0.67530840"], ["9285.37", "0.38033002"], ["9285.15", "5.37993528"], ["9285.00", "0.09419278"], ["9283.71", "0.15679830"], ["9280.33", "12.55000000"], ["9280.13", "3.20310000"], ["9280.00", "1.36477909"], ["9276.01", "0.00707488"], ["9275.75", "0.56974291"], ["9275.00", "5.88000000"], ["9274.00", "0.00754205"], ["9271.68", "0.01400000"], ["9271.11", "15.37188500"], ["9270.00", "0.06674325"], ["9268.79", "24.54320000"], ["9257.18", "12.55000000"], ["9256.30", "0.17876365"], ["9255.71", "13.82642967"], ["9254.79", "0.96329407"], ["9250.00", "0.78214958"], ["9245.34", "4.90200000"], ["9245.13", "0.10000000"], ["9240.00", "0.44383459"], ["9238.84", "13.16615207"], ["9234.11", "0.43317656"], ["9234.10", "12.55000000"], ["9231.28", "11.79290000"], ["9230.09", "4.15059441"], ["9227.69", "0.00791097"], ["9225.00", "0.44768346"], ["9224.49", "0.85857203"], ["9223.50", "5.61001041"], ["9216.01", "0.03222653"], ["9216.00", "0.05000000"], ["9213.54", "0.71253866"], ["9212.50", "2.86768195"], ["9211.07", "12.55000000"], ["9210.00", "0.54288817"], ["9208.00", "1.00000000"], ["9206.06", "2.62587578"], ["9205.98", "15.40000000"], ["9205.52", "0.01710603"], ["9205.37", "0.03524953"], ["9205.11", "0.15000000"], ["9205.00", "0.01534763"], ["9204.76", "7.00600000"], ["9203.00", "0.01090000"]], "asks": [["9337.10", "0.03000000"], ["9340.85", "2.67820000"], ["9340.95", "0.02900000"], ["9341.17", "1.00000000"], ["9341.41", "2.13966390"], ["9341.61", "0.20000000"], ["9341.97", "0.11199911"], ["9341.98", "3.00000000"], ["9342.26", "0.32112762"], ["9343.87", "1.00000000"], ["9344.17", "3.57250000"], ["9345.04", "0.32103450"], ["9345.41", "4.90000000"], ["9345.69", "1.03000000"], ["9345.80", "0.03000000"], ["9346.00", "0.10200000"], ["9346.69", "0.02397394"], ["9347.41", "1.00000000"], ["9347.82", "0.32094177"], ["9348.23", "0.02880000"], ["9348.62", "11.96287551"], ["9349.31", "2.44270000"], ["9349.47", "0.96000000"], ["9349.86", "4.50000000"], ["9350.37", "0.03300000"], ["9350.57", "0.34682266"], ["9350.60", "0.32085527"], ["9351.45", "0.31147923"], ["9352.31", "0.28000000"], ["9352.86", "9.80000000"], ["9353.73", "0.02360739"], ["9354.00", "0.45000000"], ["9354.12", "0.03000000"], ["9354.29", "3.82446861"], ["9356.20", "0.64000000"], ["9356.90", "0.02316372"], ["9357.30", "2.50000000"], ["9357.70", "2.38240000"], ["9358.92", "6.00000000"], ["9359.97", "0.34898075"], ["9359.98", "2.30000000"], ["9362.56", "2.37600000"], ["9365.00", "0.64000000"], ["9365.16", "1.70030306"], ["9365.27", "3.03000000"], ["9369.99", "2.47102665"], ["9370.00", "3.15688574"], ["9370.21", "2.32720000"], ["9371.78", "13.20000000"], ["9371.89", "0.96293482"], ["9375.08", "4.74762500"], ["9384.34", "1.45200000"], ["9384.49", "16.42310000"], ["9385.66", "0.34382112"], ["9388.19", "0.00268265"], ["9392.20", "0.20980000"], ["9392.40", "0.10320000"], ["9393.00", "0.20980000"], ["9395.40", "0.40000000"], ["9398.86", "24.54310000"], ["9400.00", "0.05489988"], ["9400.33", "0.00495100"], ["9400.45", "0.00484700"], ["9402.92", "17.20000000"], ["9404.18", "10.00000000"], ["9418.89", "16.38000000"], ["9419.41", "3.06700000"], ["9420.40", "12.50000000"], ["9421.11", "0.10500000"], ["9434.47", "0.03215805"], ["9434.48", "0.28285714"], ["9434.49", "15.83000000"], ["9435.13", "0.15000000"], ["9438.93", "0.00368800"], ["9439.19", "0.69343985"], ["9442.86", "0.10000000"], ["9443.96", "12.50000000"], ["9444.00", "0.06004471"], ["9444.97", "0.01494896"], ["9447.00", "0.01234000"], ["9448.97", "0.14500000"], ["9449.00", "0.05000000"], ["9450.00", "11.13426018"], ["9451.87", "15.90000000"], ["9452.00", "0.20000000"], ["9454.25", "0.01100000"], ["9454.51", "0.02409062"], ["9455.05", "0.00600063"], ["9456.00", "0.27965118"], ["9456.10", "0.17000000"], ["9459.00", "0.00320000"], ["9459.98", "0.02460685"], ["9459.99", "8.11000000"], ["9460.00", "0.08500000"], ["9464.36", "0.56957951"], ["9464.54", "0.69158059"], ["9465.00", "21.00002015"], ["9467.57", "12.50000000"], ["9468.00", "0.08800000"], ["9469.09", "13.94000000"]]}, "event": "data", "channel": ""}`) + if err = b.wsHandleData(pressXToJSON); !errors.Is(err, errWSPairParsingError) { + t.Errorf("expected %s, got %s", errWSPairParsingError, err) + } } func TestWsOrderbook2(t *testing.T) { diff --git a/exchanges/bitstamp/bitstamp_types.go b/exchanges/bitstamp/bitstamp_types.go index 8d9949d9..ecd3bb47 100644 --- a/exchanges/bitstamp/bitstamp_types.go +++ b/exchanges/bitstamp/bitstamp_types.go @@ -1,5 +1,7 @@ package bitstamp +import "errors" + // Transaction types const ( Deposit = iota @@ -14,6 +16,8 @@ const ( SellOrder ) +var errWSPairParsingError = errors.New("unable to parse currency pair from wsResponse.Channel") + // Ticker holds ticker information type Ticker struct { Last float64 `json:"last,string"` diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 3b098aca..9c9788af 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -88,8 +88,24 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error { if err != nil { return err } - currencyPair := strings.Split(wsResponse.Channel, currency.UnderscoreDelimiter) - p, err := currency.NewPairFromString(strings.ToUpper(currencyPair[2])) + var currencyPair string + splitter := strings.Split(wsResponse.Channel, currency.UnderscoreDelimiter) + if len(splitter) == 3 { + currencyPair = splitter[2] + } else { + return errWSPairParsingError + } + pFmt, err := b.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + + enabledPairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + p, err := currency.NewPairFromFormattedPairs(currencyPair, enabledPairs, pFmt) if err != nil { return err } @@ -107,8 +123,25 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error { if err != nil { return err } - currencyPair := strings.Split(wsResponse.Channel, currency.UnderscoreDelimiter) - p, err := currency.NewPairFromString(strings.ToUpper(currencyPair[2])) + + var currencyPair string + splitter := strings.Split(wsResponse.Channel, currency.UnderscoreDelimiter) + if len(splitter) == 3 { + currencyPair = splitter[2] + } else { + return errWSPairParsingError + } + pFmt, err := b.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + + enabledPairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + p, err := currency.NewPairFromFormattedPairs(currencyPair, enabledPairs, pFmt) if err != nil { return err } @@ -151,8 +184,12 @@ func (b *Bitstamp) generateDefaultSubscriptions() ([]stream.ChannelSubscription, var subscriptions []stream.ChannelSubscription for i := range channels { for j := range enabledCurrencies { + p, err := b.FormatExchangeCurrency(enabledCurrencies[j], asset.Spot) + if err != nil { + return nil, err + } subscriptions = append(subscriptions, stream.ChannelSubscription{ - Channel: channels[i] + enabledCurrencies[j].Lower().String(), + Channel: channels[i] + p.String(), Asset: asset.Spot, }) } @@ -256,7 +293,11 @@ func (b *Bitstamp) seedOrderBook() error { } for x := range p { - orderbookSeed, err := b.GetOrderbook(p[x].String()) + pairFmt, err := b.FormatExchangeCurrency(p[x], asset.Spot) + if err != nil { + return err + } + orderbookSeed, err := b.GetOrderbook(pairFmt.String()) if err != nil { return err } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 22aedcfd..5a4d24c3 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -56,8 +56,11 @@ func (b *Bitstamp) SetDefaults() { b.API.CredentialsValidator.RequiresKey = true b.API.CredentialsValidator.RequiresSecret = true b.API.CredentialsValidator.RequiresClientID = true - requestFmt := ¤cy.PairFormat{Uppercase: true} - configFmt := ¤cy.PairFormat{Uppercase: true} + requestFmt := ¤cy.PairFormat{} + configFmt := ¤cy.PairFormat{ + Uppercase: true, + Delimiter: currency.ForwardSlashDelimiter, + } err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) if err != nil { log.Errorln(log.ExchangeSys, err) @@ -204,11 +207,61 @@ func (b *Bitstamp) Run() { b.PrintEnabledPairs() } - if !b.GetEnabledFeatures().AutoPairUpdates { + forceUpdate := false + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to get pair format. Err %s\n", + b.Name, + err) return } - err := b.UpdateTradablePairs(false) + enabled, err := b.CurrencyPairs.GetPairs(asset.Spot, true) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to get enabled currencies. Err %s\n", + b.Name, + err) + return + } + + avail, err := b.CurrencyPairs.GetPairs(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to get available currencies. Err %s\n", + b.Name, + err) + return + } + + if !common.StringDataContains(enabled.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { + var enabledPairs currency.Pairs + enabledPairs, err = currency.NewPairsFromStrings([]string{ + currency.BTC.String() + format.Delimiter + currency.USD.String(), + }) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err %s\n", + b.Name, + err) + } else { + log.Warn(log.ExchangeSys, + "Bitstamp: Enabled and available pairs reset due to config upgrade, please enable the ones you would like to use again") + forceUpdate = true + + err = b.UpdatePairs(enabledPairs, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + b.Name, + err) + } + } + } + + if !b.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + return + } + + err = b.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", @@ -229,9 +282,7 @@ func (b *Bitstamp) FetchTradablePairs(asset asset.Item) ([]string, error) { if pairs[x].Trading != "Enabled" { continue } - - pair := strings.Split(pairs[x].Name, "/") - products = append(products, pair[0]+pair[1]) + products = append(products, pairs[x].Name) } return products, nil @@ -672,9 +723,16 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, "%s GetActiveOrders unable to parse time: %s\n", b.Name, err) } - pair, err := currency.NewPairFromString(resp[i].Currency) - if err != nil { - return nil, err + var p currency.Pair + if currPair == "all" { + // Currency pairs are returned as format "currency_pair": "BTC/USD" + // only when all is specified + p, err = currency.NewPairFromString(resp[i].Currency) + if err != nil { + return nil, err + } + } else { + p = req.Pairs[0] } orders = append(orders, order.Detail{ @@ -684,7 +742,7 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, Type: order.Limit, Side: orderSide, Date: tm, - Pair: pair, + Pair: p, Exchange: b.Name, }) } diff --git a/testdata/http_mock/bitstamp/bitstamp.json b/testdata/http_mock/bitstamp/bitstamp.json index 2ac4b72a..bd1f39b4 100644 --- a/testdata/http_mock/bitstamp/bitstamp.json +++ b/testdata/http_mock/bitstamp/bitstamp.json @@ -63740,142 +63740,934 @@ "data": [ { "base_decimals": 8, - "minimum_order": "5.0 USD", - "name": "LTC/USD", "counter_decimals": 2, + "description": "Bitcoin / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "BTC/USD", "trading": "Enabled", - "url_symbol": "ltcusd", - "description": "Litecoin / U.S. dollar" + "url_symbol": "btcusd" }, { "base_decimals": 8, - "minimum_order": "5.0 USD", - "name": "ETH/USD", "counter_decimals": 2, - "trading": "Enabled", - "url_symbol": "ethusd", - "description": "Ether / U.S. dollar" - }, - { - "base_decimals": 8, - "minimum_order": "5.0 EUR", - "name": "XRP/EUR", - "counter_decimals": 5, - "trading": "Enabled", - "url_symbol": "xrpeur", - "description": "XRP / Euro" - }, - { - "base_decimals": 8, - "minimum_order": "5.0 USD", - "name": "BCH/USD", - "counter_decimals": 2, - "trading": "Enabled", - "url_symbol": "bchusd", - "description": "Bitcoin Cash / U.S. dollar" - }, - { - "base_decimals": 8, - "minimum_order": "5.0 EUR", - "name": "BCH/EUR", - "counter_decimals": 2, - "trading": "Enabled", - "url_symbol": "bcheur", - "description": "Bitcoin Cash / Euro" - }, - { - "base_decimals": 8, - "minimum_order": "5.0 EUR", + "description": "Bitcoin / Euro", + "minimum_order": "20.0 EUR", "name": "BTC/EUR", - "counter_decimals": 2, "trading": "Enabled", - "url_symbol": "btceur", - "description": "Bitcoin / Euro" + "url_symbol": "btceur" }, { "base_decimals": 8, - "minimum_order": "0.001 BTC", - "name": "XRP/BTC", - "counter_decimals": 8, + "counter_decimals": 2, + "description": "Bitcoin / British Pound", + "minimum_order": "20.0 GBP", + "name": "BTC/GBP", "trading": "Enabled", - "url_symbol": "xrpbtc", - "description": "XRP / Bitcoin" + "url_symbol": "btcgbp" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Bitcoin / Paxos Standard", + "minimum_order": "20.0 PAX", + "name": "BTC/PAX", + "trading": "Enabled", + "url_symbol": "btcpax" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Bitcoin / Tether", + "minimum_order": "20.0 USDT", + "name": "BTC/USDT", + "trading": "Enabled", + "url_symbol": "btcusdt" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Bitcoin / USD Coin", + "minimum_order": "20.0 USDC", + "name": "BTC/USDC", + "trading": "Enabled", + "url_symbol": "btcusdc" }, { "base_decimals": 5, - "minimum_order": "5.0 USD", + "counter_decimals": 5, + "description": "British Pound / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "GBP/USD", + "trading": "Enabled", + "url_symbol": "gbpusd" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "British Pound / Euro", + "minimum_order": "20.0 EUR", + "name": "GBP/EUR", + "trading": "Enabled", + "url_symbol": "gbpeur" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Euro / U.S. dollar", + "minimum_order": "20.0 USD", "name": "EUR/USD", - "counter_decimals": 5, "trading": "Enabled", - "url_symbol": "eurusd", - "description": "Euro / U.S. dollar" + "url_symbol": "eurusd" }, { "base_decimals": 8, - "minimum_order": "0.001 BTC", - "name": "BCH/BTC", - "counter_decimals": 8, - "trading": "Enabled", - "url_symbol": "bchbtc", - "description": "Bitcoin Cash / Bitcoin" - }, - { - "base_decimals": 8, - "minimum_order": "5.0 EUR", - "name": "LTC/EUR", "counter_decimals": 2, + "description": "Ether / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "ETH/USD", "trading": "Enabled", - "url_symbol": "ltceur", - "description": "Litecoin / Euro" + "url_symbol": "ethusd" }, { "base_decimals": 8, - "minimum_order": "5.0 USD", - "name": "BTC/USD", "counter_decimals": 2, - "trading": "Enabled", - "url_symbol": "btcusd", - "description": "Bitcoin / U.S. dollar" - }, - { - "base_decimals": 8, - "minimum_order": "0.001 BTC", - "name": "LTC/BTC", - "counter_decimals": 8, - "trading": "Enabled", - "url_symbol": "ltcbtc", - "description": "Litecoin / Bitcoin" - }, - { - "base_decimals": 8, - "minimum_order": "5.0 USD", - "name": "XRP/USD", - "counter_decimals": 5, - "trading": "Enabled", - "url_symbol": "xrpusd", - "description": "XRP / U.S. dollar" - }, - { - "base_decimals": 8, - "minimum_order": "0.001 BTC", - "name": "ETH/BTC", - "counter_decimals": 8, - "trading": "Enabled", - "url_symbol": "ethbtc", - "description": "Ether / Bitcoin" - }, - { - "base_decimals": 8, - "minimum_order": "5.0 EUR", + "description": "Ether / Euro", + "minimum_order": "20.0 EUR", "name": "ETH/EUR", - "counter_decimals": 2, "trading": "Enabled", - "url_symbol": "etheur", - "description": "Ether / Euro" + "url_symbol": "etheur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Ether / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "ETH/BTC", + "trading": "Enabled", + "url_symbol": "ethbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Ether / British Pound", + "minimum_order": "20.0 GBP", + "name": "ETH/GBP", + "trading": "Enabled", + "url_symbol": "ethgbp" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Ether / Paxos Standard", + "minimum_order": "20.0 PAX", + "name": "ETH/PAX", + "trading": "Enabled", + "url_symbol": "ethpax" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Ether / Tether", + "minimum_order": "20.0 USDT", + "name": "ETH/USDT", + "trading": "Enabled", + "url_symbol": "ethusdt" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Ether / USD Coin", + "minimum_order": "20.0 USDC", + "name": "ETH/USDC", + "trading": "Enabled", + "url_symbol": "ethusdc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "XRP / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "XRP/USD", + "trading": "Enabled", + "url_symbol": "xrpusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "XRP / Euro", + "minimum_order": "20.0 EUR", + "name": "XRP/EUR", + "trading": "Enabled", + "url_symbol": "xrpeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "XRP / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "XRP/BTC", + "trading": "Enabled", + "url_symbol": "xrpbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "XRP / British Pound", + "minimum_order": "20.0 GBP", + "name": "XRP/GBP", + "trading": "Enabled", + "url_symbol": "xrpgbp" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "XRP / Paxos Standard", + "minimum_order": "20.0 PAX", + "name": "XRP/PAX", + "trading": "Enabled", + "url_symbol": "xrppax" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "XRP / Tether", + "minimum_order": "20.0 USDT", + "name": "XRP/USDT", + "trading": "Enabled", + "url_symbol": "xrpusdt" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Uniswap / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "UNI/USD", + "trading": "Enabled", + "url_symbol": "uniusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Uniswap / Euro", + "minimum_order": "20.0 EUR", + "name": "UNI/EUR", + "trading": "Enabled", + "url_symbol": "unieur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Uniswap / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "UNI/BTC", + "trading": "Enabled", + "url_symbol": "unibtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Litecoin / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "LTC/USD", + "trading": "Enabled", + "url_symbol": "ltcusd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Litecoin / Euro", + "minimum_order": "20.0 EUR", + "name": "LTC/EUR", + "trading": "Enabled", + "url_symbol": "ltceur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Litecoin / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "LTC/BTC", + "trading": "Enabled", + "url_symbol": "ltcbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Litecoin / British Pound", + "minimum_order": "20.0 GBP", + "name": "LTC/GBP", + "trading": "Enabled", + "url_symbol": "ltcgbp" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Chainlink / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "LINK/USD", + "trading": "Enabled", + "url_symbol": "linkusd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Chainlink / Euro", + "minimum_order": "20.0 EUR", + "name": "LINK/EUR", + "trading": "Enabled", + "url_symbol": "linkeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Chainlink / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "LINK/BTC", + "trading": "Enabled", + "url_symbol": "linkbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Chainlink / British Pound", + "minimum_order": "20.0 GBP", + "name": "LINK/GBP", + "trading": "Enabled", + "url_symbol": "linkgbp" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Chainlink / Ether", + "minimum_order": "0.005 ETH", + "name": "LINK/ETH", + "trading": "Enabled", + "url_symbol": "linketh" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Polygon / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "MATIC/USD", + "trading": "Enabled", + "url_symbol": "maticusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Polygon / Euro", + "minimum_order": "20.0 EUR", + "name": "MATIC/EUR", + "trading": "Enabled", + "url_symbol": "maticeur" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Stellar Lumens / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "XLM/USD", + "trading": "Enabled", + "url_symbol": "xlmusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Stellar Lumens / Euro", + "minimum_order": "20.0 EUR", + "name": "XLM/EUR", + "trading": "Enabled", + "url_symbol": "xlmeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Stellar Lumens / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "XLM/BTC", + "trading": "Enabled", + "url_symbol": "xlmbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Stellar Lumens / British Pound", + "minimum_order": "20.0 GBP", + "name": "XLM/GBP", + "trading": "Enabled", + "url_symbol": "xlmgbp" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Bitcoin Cash / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "BCH/USD", + "trading": "Enabled", + "url_symbol": "bchusd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Bitcoin Cash / Euro", + "minimum_order": "20.0 EUR", + "name": "BCH/EUR", + "trading": "Enabled", + "url_symbol": "bcheur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Bitcoin Cash / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "BCH/BTC", + "trading": "Enabled", + "url_symbol": "bchbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Bitcoin Cash / British Pound", + "minimum_order": "20.0 GBP", + "name": "BCH/GBP", + "trading": "Enabled", + "url_symbol": "bchgbp" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "AAVE / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "AAVE/USD", + "trading": "Enabled", + "url_symbol": "aaveusd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "AAVE / Euro", + "minimum_order": "20.0 EUR", + "name": "AAVE/EUR", + "trading": "Enabled", + "url_symbol": "aaveeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "AAVE / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "AAVE/BTC", + "trading": "Enabled", + "url_symbol": "aavebtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Algorand / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "ALGO/USD", + "trading": "Enabled", + "url_symbol": "algousd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Algorand / Euro", + "minimum_order": "20.0 EUR", + "name": "ALGO/EUR", + "trading": "Enabled", + "url_symbol": "algoeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Algorand / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "ALGO/BTC", + "trading": "Enabled", + "url_symbol": "algobtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Compound / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "COMP/USD", + "trading": "Enabled", + "url_symbol": "compusd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Compound / Euro", + "minimum_order": "20.0 EUR", + "name": "COMP/EUR", + "trading": "Enabled", + "url_symbol": "compeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Compound / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "COMP/BTC", + "trading": "Enabled", + "url_symbol": "compbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Synthetix / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "SNX/USD", + "trading": "Enabled", + "url_symbol": "snxusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Synthetix / Euro", + "minimum_order": "20.0 EUR", + "name": "SNX/EUR", + "trading": "Enabled", + "url_symbol": "snxeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Synthetix / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "SNX/BTC", + "trading": "Enabled", + "url_symbol": "snxbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Enjin Coin / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "ENJ/USD", + "trading": "Enabled", + "url_symbol": "enjusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Enjin Coin / Euro", + "minimum_order": "20.0 EUR", + "name": "ENJ/EUR", + "trading": "Enabled", + "url_symbol": "enjeur" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Basic Attention Token / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "BAT/USD", + "trading": "Enabled", + "url_symbol": "batusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Basic Attention Token / Euro", + "minimum_order": "20.0 EUR", + "name": "BAT/EUR", + "trading": "Enabled", + "url_symbol": "bateur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Basic Attention Token / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "BAT/BTC", + "trading": "Enabled", + "url_symbol": "batbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Maker / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "MKR/USD", + "trading": "Enabled", + "url_symbol": "mkrusd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "Maker / Euro", + "minimum_order": "20.0 EUR", + "name": "MKR/EUR", + "trading": "Enabled", + "url_symbol": "mkreur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Maker / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "MKR/BTC", + "trading": "Enabled", + "url_symbol": "mkrbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "0x / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "ZRX/USD", + "trading": "Enabled", + "url_symbol": "zrxusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "0x / Euro", + "minimum_order": "20.0 EUR", + "name": "ZRX/EUR", + "trading": "Enabled", + "url_symbol": "zrxeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "0x / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "ZRX/BTC", + "trading": "Enabled", + "url_symbol": "zrxbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "yearn.finance / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "YFI/USD", + "trading": "Enabled", + "url_symbol": "yfiusd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "yearn.finance / Euro", + "minimum_order": "20.0 EUR", + "name": "YFI/EUR", + "trading": "Enabled", + "url_symbol": "yfieur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "yearn.finance / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "YFI/BTC", + "trading": "Enabled", + "url_symbol": "yfibtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "SushiSwap / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "SUSHI/USD", + "trading": "Enabled", + "url_symbol": "sushiusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "SushiSwap / Euro", + "minimum_order": "20.0 EUR", + "name": "SUSHI/EUR", + "trading": "Enabled", + "url_symbol": "sushieur" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "The Graph / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "GRT/USD", + "trading": "Enabled", + "url_symbol": "grtusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "The Graph / Euro", + "minimum_order": "20.0 EUR", + "name": "GRT/EUR", + "trading": "Enabled", + "url_symbol": "grteur" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "UMA / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "UMA/USD", + "trading": "Enabled", + "url_symbol": "umausd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "UMA / Euro", + "minimum_order": "20.0 EUR", + "name": "UMA/EUR", + "trading": "Enabled", + "url_symbol": "umaeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "UMA / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "UMA/BTC", + "trading": "Enabled", + "url_symbol": "umabtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "OMG Network / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "OMG/USD", + "trading": "Enabled", + "url_symbol": "omgusd" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "OMG Network / Euro", + "minimum_order": "20.0 EUR", + "name": "OMG/EUR", + "trading": "Enabled", + "url_symbol": "omgeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "OMG Network / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "OMG/BTC", + "trading": "Enabled", + "url_symbol": "omgbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 2, + "description": "OMG Network / British Pound", + "minimum_order": "20.0 GBP", + "name": "OMG/GBP", + "trading": "Enabled", + "url_symbol": "omggbp" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Kyber Network / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "KNC/USD", + "trading": "Enabled", + "url_symbol": "kncusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Kyber Network / Euro", + "minimum_order": "20.0 EUR", + "name": "KNC/EUR", + "trading": "Enabled", + "url_symbol": "knceur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Kyber Network / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "KNC/BTC", + "trading": "Enabled", + "url_symbol": "kncbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Curve / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "CRV/USD", + "trading": "Enabled", + "url_symbol": "crvusd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Curve / Euro", + "minimum_order": "20.0 EUR", + "name": "CRV/EUR", + "trading": "Enabled", + "url_symbol": "crveur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Curve / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "CRV/BTC", + "trading": "Enabled", + "url_symbol": "crvbtc" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Audius / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "AUDIO/USD", + "trading": "Enabled", + "url_symbol": "audiousd" + }, + { + "base_decimals": 8, + "counter_decimals": 5, + "description": "Audius / Euro", + "minimum_order": "20.0 EUR", + "name": "AUDIO/EUR", + "trading": "Enabled", + "url_symbol": "audioeur" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Audius / Bitcoin", + "minimum_order": "0.0002 BTC", + "name": "AUDIO/BTC", + "trading": "Enabled", + "url_symbol": "audiobtc" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Tether / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "USDT/USD", + "trading": "Enabled", + "url_symbol": "usdtusd" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Tether / Euro", + "minimum_order": "20.0 EUR", + "name": "USDT/EUR", + "trading": "Enabled", + "url_symbol": "usdteur" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "USD Coin / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "USDC/USD", + "trading": "Enabled", + "url_symbol": "usdcusd" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "USD Coin / Euro", + "minimum_order": "20.0 EUR", + "name": "USDC/EUR", + "trading": "Enabled", + "url_symbol": "usdceur" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "USD Coin / Tether", + "minimum_order": "20.0 USDT", + "name": "USDC/USDT", + "trading": "Enabled", + "url_symbol": "usdcusdt" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Euro Tether / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "EURT/USD", + "trading": "Enabled", + "url_symbol": "eurtusd" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Euro Tether / Euro", + "minimum_order": "20.0 EUR", + "name": "EURT/EUR", + "trading": "Enabled", + "url_symbol": "eurteur" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "DAI / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "DAI/USD", + "trading": "Enabled", + "url_symbol": "daiusd" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Paxos Standard / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "PAX/USD", + "trading": "Enabled", + "url_symbol": "paxusd" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Paxos Standard / Euro", + "minimum_order": "20.0 EUR", + "name": "PAX/EUR", + "trading": "Enabled", + "url_symbol": "paxeur" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Paxos Standard / British Pound", + "minimum_order": "20.0 GBP", + "name": "PAX/GBP", + "trading": "Enabled", + "url_symbol": "paxgbp" + }, + { + "base_decimals": 8, + "counter_decimals": 8, + "description": "Ethereum 2.0 / Ether", + "minimum_order": "0.005 ETH", + "name": "ETH2/ETH", + "trading": "Disabled", + "url_symbol": "eth2eth" + }, + { + "base_decimals": 5, + "counter_decimals": 5, + "description": "Gemini Dollar / U.S. dollar", + "minimum_order": "20.0 USD", + "name": "GUSD/USD", + "trading": "Enabled", + "url_symbol": "gusdusd" } ], "queryString": "", - "bodyParams": "\u003cnil\u003e", + "bodyParams": "", "headers": { "Referer": [ "https://www.bitstamp.net/api/v2/trading-pairs-info"