diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index 7394676d..3cc0f93c 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -36,6 +36,7 @@ const ( getMarkets = "/markets" getMarketSummaries = "/markets/summaries" getTicker = "/markets/%s/ticker" + getTickers = "/markets/tickers" getMarketSummary = "/markets/%s/summary" getMarketTrades = "/markets/%s/trades" getOrderbook = "/markets/%s/orderbook?depth=%s" @@ -87,8 +88,14 @@ func (b *Bittrex) GetTicker(ctx context.Context, marketName string) (TickerData, return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getTicker, marketName), &resp, nil) } +// GetTickers returns bittrex tickers +func (b *Bittrex) GetTickers(ctx context.Context) ([]TickerData, error) { + var resp []TickerData + return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, getTickers, &resp, nil) +} + // GetMarketSummaries is used to get the last 24 hour summary of all active -// exchanges +// currencies func (b *Bittrex) GetMarketSummaries(ctx context.Context) ([]MarketSummaryData, error) { var resp []MarketSummaryData return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, getMarketSummaries, &resp, nil) diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 75ff9677..347f2440 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -725,3 +725,23 @@ func TestGetHistoricCandlesExtended(t *testing.T) { t.Fatal(err) } } + +func TestGetTickers(t *testing.T) { + t.Parallel() + _, err := b.GetTickers(context.Background()) + if err != nil { + t.Error(err) + } +} + +func TestUpdateTickers(t *testing.T) { + t.Parallel() + err := b.UpdateTickers(context.Background(), asset.Spot) + if err != nil { + t.Error(err) + } + err = b.UpdateTickers(context.Background(), asset.Futures) + if !errors.Is(err, asset.ErrNotSupported) { + t.Fatal(err) + } +} diff --git a/exchanges/bittrex/bittrex_types.go b/exchanges/bittrex/bittrex_types.go index a1c18af1..da217efd 100644 --- a/exchanges/bittrex/bittrex_types.go +++ b/exchanges/bittrex/bittrex_types.go @@ -72,10 +72,11 @@ type MarketData struct { // TickerData stores ticker data type TickerData struct { - Symbol string `json:"symbol"` - LastTradeRate float64 `json:"lastTradeRate,string"` - BidRate float64 `json:"bidRate,string"` - AskRate float64 `json:"askRate,string"` + Symbol string `json:"symbol"` + LastTradeRate float64 `json:"lastTradeRate,string"` + BidRate float64 `json:"bidRate,string"` + AskRate float64 `json:"askRate,string"` + UpdatedAt time.Time `json:"updatedAt"` } // TradeData stores trades data diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index b64c712c..3b2e65c7 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sort" + "strings" "sync" "time" @@ -87,6 +88,7 @@ func (b *Bittrex) SetDefaults() { Websocket: true, RESTCapabilities: protocol.Features{ TickerFetching: true, + TickerBatching: true, KlineFetching: true, TradeFetching: true, OrderbookFetching: true, @@ -297,8 +299,36 @@ func (b *Bittrex) UpdateTradablePairs(ctx context.Context, forceUpdate bool) err } // UpdateTickers updates the ticker for all currency pairs of a given asset type -func (b *Bittrex) UpdateTickers(_ context.Context, _ asset.Item) error { - return common.ErrFunctionNotSupported +func (b *Bittrex) UpdateTickers(ctx context.Context, a asset.Item) error { + if a != asset.Spot { + return fmt.Errorf("%w %v", asset.ErrNotSupported, a) + } + tickers, err := b.GetTickers(ctx) + if err != nil { + return err + } + summaries, err := b.GetMarketSummaries(ctx) + if err != nil { + return err + } + for x := range tickers { + for y := range summaries { + if !strings.EqualFold(summaries[y].Symbol, tickers[x].Symbol) { + continue + } + var pair currency.Pair + pair, err = currency.NewPairFromString(tickers[x].Symbol) + if err != nil { + return err + } + tickerPrice := b.constructTicker(tickers[x], &summaries[y], pair, a) + err = ticker.ProcessTicker(tickerPrice) + if err != nil { + return err + } + } + } + return nil } // UpdateTicker updates and returns the ticker for a currency pair diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 3dc99059..f650ebf4 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -24,7 +24,7 @@ const ( geminiAPIVersion = "1" geminiSymbols = "symbols" - geminiTicker = "pubticker" + geminiSymbolDetails = "symbols/details" geminiAuction = "auction" geminiAuctionHistory = "history" geminiOrderbook = "book" @@ -62,6 +62,21 @@ func (g *Gemini) GetSymbols(ctx context.Context) ([]string, error) { return symbols, g.SendHTTPRequest(ctx, exchange.RestSpot, path, &symbols) } +// GetSymbolDetails returns extra symbol details +// use symbol "all" to get everything +func (g *Gemini) GetSymbolDetails(ctx context.Context, symbol string) ([]SymbolDetails, error) { + if symbol == "all" { + var details []SymbolDetails + return details, g.SendHTTPRequest(ctx, exchange.RestSpot, "/v"+geminiAPIVersion+"/"+geminiSymbolDetails+"/"+symbol, &details) + } + var details SymbolDetails + err := g.SendHTTPRequest(ctx, exchange.RestSpot, "/v"+geminiAPIVersion+"/"+geminiSymbolDetails+"/"+symbol, &details) + if err != nil { + return nil, err + } + return []SymbolDetails{details}, nil +} + // GetTicker returns information about recent trading activity for the symbol func (g *Gemini) GetTicker(ctx context.Context, currencyPair string) (TickerV2, error) { ticker := TickerV2{} diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 954b7f47..c0b79a45 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -1259,3 +1259,42 @@ func TestGetOrderInfo(t *testing.T) { t.Error(err) } } + +func TestGetSymbolDetails(t *testing.T) { + t.Parallel() + _, err := g.GetSymbolDetails(context.Background(), "all") + if err != nil { + t.Error(err) + } + _, err = g.GetSymbolDetails(context.Background(), "btcusd") + if err != nil { + t.Error(err) + } +} + +func TestSetExchangeOrderExecutionLimits(t *testing.T) { + t.Parallel() + err := g.UpdateOrderExecutionLimits(context.Background(), asset.Spot) + if err != nil { + t.Fatal(err) + } + err = g.UpdateOrderExecutionLimits(context.Background(), asset.Futures) + if !errors.Is(err, asset.ErrNotSupported) { + t.Fatal(err) + } + + availPairs, err := g.GetAvailablePairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + for x := range availPairs { + var limit order.MinMaxLevel + limit, err = g.GetOrderExecutionLimits(asset.Spot, availPairs[x]) + if err != nil { + t.Fatal(err, availPairs[x]) + } + if limit == (order.MinMaxLevel{}) { + t.Fatal("exchange limit should be loaded") + } + } +} diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index b12c7c09..da7c76c9 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -1,6 +1,7 @@ package gemini import ( + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" ) @@ -29,6 +30,21 @@ type Ticker struct { } } +// SymbolDetails contains additional symbol details +type SymbolDetails struct { + Symbol string `json:"symbol"` + BaseCurrency string `json:"base_currency"` + QuoteCurrency string `json:"quote_currency"` + TickSize float64 `json:"tick_size"` + QuoteIncrement float64 `json:"quote_increment"` + MinOrderSize convert.StringToFloat64 `json:"min_order_size"` + Status string `json:"status"` + WrapEnabled bool `json:"wrap_enabled"` + ProductType string `json:"product_type"` + ContractType string `json:"contract_type"` + ContractPriceCurrency string `json:"contract_price_currency"` +} + // TickerV2 holds returned ticker data from the exchange type TickerV2 struct { Ask float64 `json:"ask,string"` diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 54c41053..897e55ce 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -7,6 +7,7 @@ import ( "net/url" "sort" "strconv" + "strings" "sync" "time" @@ -259,7 +260,12 @@ func (g *Gemini) Run(ctx context.Context) { } } } - + if err := g.UpdateOrderExecutionLimits(ctx, asset.Spot); err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to set exchange order execution limits. Err: %v", + g.Name, + err) + } if !g.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { return } @@ -278,26 +284,26 @@ func (g *Gemini) FetchTradablePairs(ctx context.Context, a asset.Item) (currency return nil, asset.ErrNotSupported } - symbols, err := g.GetSymbols(ctx) + details, err := g.GetSymbolDetails(ctx, "all") if err != nil { return nil, err } - - pairs := make([]currency.Pair, len(symbols)) - for x := range symbols { - var pair currency.Pair - switch len(symbols[x]) { - case 8: - pair, err = currency.NewPairFromStrings(symbols[x][0:5], symbols[x][5:]) - case 7: - pair, err = currency.NewPairFromStrings(symbols[x][0:4], symbols[x][4:]) - default: - pair, err = currency.NewPairFromStrings(symbols[x][0:3], symbols[x][3:]) + pairs := make([]currency.Pair, 0, len(details)) + for i := range details { + status := strings.ToLower(details[i].Status) + if status != "open" && status != "limit_only" { + continue } + if !strings.EqualFold(details[i].ContractType, "vanilla") { + // TODO: add support for futures + continue + } + + cp, err := currency.NewPairFromStrings(details[i].BaseCurrency, details[i].Symbol[len(details[i].BaseCurrency):]) if err != nil { return nil, err } - pairs[x] = pair + pairs = append(pairs, cp) } return pairs, nil } @@ -918,3 +924,33 @@ func (g *Gemini) GetHistoricCandlesExtended(_ context.Context, _ currency.Pair, func (g *Gemini) GetFuturesContractDetails(context.Context, asset.Item) ([]futures.Contract, error) { return nil, common.ErrFunctionNotSupported } + +// UpdateOrderExecutionLimits sets exchange executions for a required asset type +func (g *Gemini) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { + if a != asset.Spot { + return fmt.Errorf("%w %v", asset.ErrNotSupported, a) + } + details, err := g.GetSymbolDetails(ctx, "all") + if err != nil { + return fmt.Errorf("cannot update exchange execution limits: %w", err) + } + resp := make([]order.MinMaxLevel, 0, len(details)) + for i := range details { + status := strings.ToLower(details[i].Status) + if status != "open" && status != "limit_only" { + continue + } + cp, err := currency.NewPairFromStrings(details[i].BaseCurrency, details[i].QuoteCurrency) + if err != nil { + return err + } + resp = append(resp, order.MinMaxLevel{ + Pair: cp, + Asset: a, + AmountStepIncrementSize: details[i].TickSize, + MinimumBaseAmount: details[i].MinOrderSize.Float64(), + QuoteStepIncrementSize: details[i].QuoteIncrement, + }) + } + return g.LoadLimits(resp) +} diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 93b670a1..6e1fc8ef 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -1727,12 +1727,15 @@ func (k *Kraken) GetFuturesContractDetails(ctx context.Context, item asset.Item) } else { underlyingStr = underlyingBase[1] } - usdIndex := strings.Index(underlyingStr, "usd") + usdIndex := strings.LastIndex(strings.ToLower(underlyingStr), "usd") + if usdIndex <= 0 { + log.Warnf(log.ExchangeSys, "%v unable to find USD index in %v to process contract", k.Name, underlyingStr) + continue + } underlying, err = currency.NewPairFromStrings(underlyingStr[0:usdIndex], underlyingStr[usdIndex:]) if err != nil { return nil, err } - var s, e time.Time if result.Instruments[i].OpeningDate != "" { s, err = time.Parse(time.RFC3339, result.Instruments[i].OpeningDate) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index c4fb0a14..c533e272 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1511,9 +1511,14 @@ func TestMatchFilter(t *testing.T) { } // specific tests for num, tt := range tests { - if tt.o.MatchFilter(&tt.f) != tt.expectedResult { - t.Errorf("tests[%v] failed", num) - } + num := num + tt := tt + t.Run(fmt.Sprintf("%v", num), func(t *testing.T) { + t.Parallel() + if tt.o.MatchFilter(&tt.f) != tt.expectedResult { + t.Errorf("tests[%v] failed", num) + } + }) } } diff --git a/exchanges/stats/stats.go b/exchanges/stats/stats.go index be9bf3e7..417acc78 100644 --- a/exchanges/stats/stats.go +++ b/exchanges/stats/stats.go @@ -8,49 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) -// Item holds various fields for storing currency pair stats -type Item struct { - Exchange string - Pair currency.Pair - AssetType asset.Item - Price float64 - Volume float64 -} - -// Items var array -var Items []Item - -// ByPrice allows sorting by price -type ByPrice []Item - -func (b ByPrice) Len() int { - return len(b) -} - -func (b ByPrice) Less(i, j int) bool { - return b[i].Price < b[j].Price -} - -func (b ByPrice) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// ByVolume allows sorting by volume -type ByVolume []Item - -func (b ByVolume) Len() int { - return len(b) -} - -func (b ByVolume) Less(i, j int) bool { - return b[i].Volume < b[j].Volume -} - -func (b ByVolume) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -// Add adds or updates the item stats +// Add adds or updates the Item stats func Add(exchange string, p currency.Pair, a asset.Item, price, volume float64) error { if exchange == "" || a == asset.Empty || @@ -82,13 +40,14 @@ func Add(exchange string, p currency.Pair, a asset.Item, price, volume float64) return nil } -// Append adds or updates the item stats for a specific -// currency pair and asset type +// Append adds the Item stats for a specific currency pair and asset type +// if it doesn't exist func Append(exchange string, p currency.Pair, a asset.Item, price, volume float64) { - if AlreadyExists(exchange, p, a, price, volume) { + statMutex.Lock() + defer statMutex.Unlock() + if alreadyExistsRequiresLock(exchange, p, a, price, volume) { return } - i := Item{ Exchange: exchange, Pair: p, @@ -97,59 +56,95 @@ func Append(exchange string, p currency.Pair, a asset.Item, price, volume float6 Volume: volume, } - Items = append(Items, i) + items = append(items, i) } -// AlreadyExists checks to see if item info already exists -// for a specific currency pair and asset type -func AlreadyExists(exchange string, p currency.Pair, assetType asset.Item, price, volume float64) bool { - for i := range Items { - if Items[i].Exchange == exchange && - Items[i].Pair.EqualIncludeReciprocal(p) && - Items[i].AssetType == assetType { - Items[i].Price, Items[i].Volume = price, volume +// alreadyExistsRequiresLock checks to see if Item info already exists +// requires a locking beforehand because of globals +func alreadyExistsRequiresLock(exchange string, p currency.Pair, assetType asset.Item, price, volume float64) bool { + for i := range items { + if items[i].Exchange == exchange && + items[i].Pair.EqualIncludeReciprocal(p) && + items[i].AssetType == assetType { + items[i].Price, items[i].Volume = price, volume return true } } return false } -// SortExchangesByVolume sorts item info by volume for a specific +// AlreadyExists checks to see if Item info already exists +// for a specific currency pair and asset type +func AlreadyExists(exchange string, p currency.Pair, assetType asset.Item, price, volume float64) bool { + statMutex.Lock() + defer statMutex.Unlock() + return alreadyExistsRequiresLock(exchange, p, assetType, price, volume) +} + +// SortExchangesByVolume sorts Item info by volume for a specific // currency pair and asset type. Reverse will reverse the order from lowest to // highest func SortExchangesByVolume(p currency.Pair, assetType asset.Item, reverse bool) []Item { var result []Item - for x := range Items { - if Items[x].Pair.EqualIncludeReciprocal(p) && - Items[x].AssetType == assetType { - result = append(result, Items[x]) + statMutex.Lock() + defer statMutex.Unlock() + for x := range items { + if items[x].Pair.EqualIncludeReciprocal(p) && + items[x].AssetType == assetType { + result = append(result, items[x]) } } if reverse { - sort.Sort(sort.Reverse(ByVolume(result))) + sort.Sort(sort.Reverse(byVolume(result))) } else { - sort.Sort(ByVolume(result)) + sort.Sort(byVolume(result)) } return result } -// SortExchangesByPrice sorts item info by volume for a specific +// SortExchangesByPrice sorts Item info by volume for a specific // currency pair and asset type. Reverse will reverse the order from lowest to // highest func SortExchangesByPrice(p currency.Pair, assetType asset.Item, reverse bool) []Item { var result []Item - for x := range Items { - if Items[x].Pair.EqualIncludeReciprocal(p) && - Items[x].AssetType == assetType { - result = append(result, Items[x]) + statMutex.Lock() + defer statMutex.Unlock() + for x := range items { + if items[x].Pair.EqualIncludeReciprocal(p) && + items[x].AssetType == assetType { + result = append(result, items[x]) } } if reverse { - sort.Sort(sort.Reverse(ByPrice(result))) + sort.Sort(sort.Reverse(byPrice(result))) } else { - sort.Sort(ByPrice(result)) + sort.Sort(byPrice(result)) } return result } + +func (b byPrice) Len() int { + return len(b) +} + +func (b byPrice) Less(i, j int) bool { + return b[i].Price < b[j].Price +} + +func (b byPrice) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b byVolume) Len() int { + return len(b) +} + +func (b byVolume) Less(i, j int) bool { + return b[i].Volume < b[j].Volume +} + +func (b byVolume) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} diff --git a/exchanges/stats/stats_test.go b/exchanges/stats/stats_test.go index 2b0b8179..6630d4fe 100644 --- a/exchanges/stats/stats_test.go +++ b/exchanges/stats/stats_test.go @@ -12,11 +12,12 @@ const ( ) func TestLenByPrice(t *testing.T) { + t.Parallel() p, err := currency.NewPairFromStrings("BTC", "USD") if err != nil { t.Fatal(err) } - Items = []Item{ + localItems := []Item{ { Exchange: testExchange, Pair: p, @@ -26,17 +27,18 @@ func TestLenByPrice(t *testing.T) { }, } - if ByPrice.Len(Items) < 1 { + if byPrice.Len(localItems) < 1 { t.Error("stats LenByPrice() length not correct.") } } func TestLessByPrice(t *testing.T) { + t.Parallel() p, err := currency.NewPairFromStrings("BTC", "USD") if err != nil { t.Fatal(err) } - Items = []Item{ + localItems := []Item{ { Exchange: "alphapoint", Pair: p, @@ -53,20 +55,21 @@ func TestLessByPrice(t *testing.T) { }, } - if !ByPrice.Less(Items, 1, 0) { + if !byPrice.Less(localItems, 1, 0) { t.Error("stats LessByPrice() incorrect return.") } - if ByPrice.Less(Items, 0, 1) { + if byPrice.Less(localItems, 0, 1) { t.Error("stats LessByPrice() incorrect return.") } } func TestSwapByPrice(t *testing.T) { + t.Parallel() p, err := currency.NewPairFromStrings("BTC", "USD") if err != nil { t.Fatal(err) } - Items = []Item{ + localItems := []Item{ { Exchange: "bitstamp", Pair: p, @@ -83,37 +86,97 @@ func TestSwapByPrice(t *testing.T) { }, } - ByPrice.Swap(Items, 0, 1) - if Items[0].Exchange != "bitfinex" || Items[1].Exchange != "bitstamp" { + byPrice.Swap(localItems, 0, 1) + if localItems[0].Exchange != "bitfinex" || localItems[1].Exchange != "bitstamp" { t.Error("stats SwapByPrice did not swap values.") } } func TestLenByVolume(t *testing.T) { - if ByVolume.Len(Items) != 2 { + t.Parallel() + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + localItems := []Item{ + { + Exchange: "bitstamp", + Pair: p, + AssetType: asset.Spot, + Price: 1324, + Volume: 5, + }, + { + Exchange: "bitfinex", + Pair: p, + AssetType: asset.Spot, + Price: 7863, + Volume: 20, + }, + } + + if byVolume.Len(localItems) != 2 { t.Error("stats lenByVolume did not swap values.") } } func TestLessByVolume(t *testing.T) { - if !ByVolume.Less(Items, 1, 0) { - t.Error("stats LessByVolume() incorrect return.") + t.Parallel() + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) } - if ByVolume.Less(Items, 0, 1) { - t.Error("stats LessByVolume() incorrect return.") + localItems := []Item{ + { + Exchange: "bitstamp", + Pair: p, + AssetType: asset.Spot, + Price: 1324, + Volume: 5, + }, + { + Exchange: "bitfinex", + Pair: p, + AssetType: asset.Spot, + Price: 7863, + Volume: 20, + }, + } + if !byVolume.Less(localItems, 0, 1) { + t.Error("localItems[0].Volume should be less than localItems[1].Volume") } } func TestSwapByVolume(t *testing.T) { - ByPrice.Swap(Items, 0, 1) - - if Items[1].Exchange != "bitfinex" || Items[0].Exchange != "bitstamp" { + t.Parallel() + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + localItems := []Item{ + { + Exchange: "bitstamp", + Pair: p, + AssetType: asset.Spot, + Price: 1324, + Volume: 5, + }, + { + Exchange: "bitfinex", + Pair: p, + AssetType: asset.Spot, + Price: 7863, + Volume: 20, + }, + } + byVolume.Swap(localItems, 0, 1) + if localItems[0].Exchange != "bitfinex" || localItems[1].Exchange != "bitstamp" { t.Error("stats SwapByVolume did not swap values.") } } func TestAdd(t *testing.T) { - Items = Items[:0] + items = items[:0] p, err := currency.NewPairFromStrings("BTC", "USD") if err != nil { t.Fatal(err) @@ -123,7 +186,7 @@ func TestAdd(t *testing.T) { t.Fatal(err) } - if len(Items) < 1 { + if len(items) < 1 { t.Error("stats Add did not add exchange info.") } @@ -132,7 +195,7 @@ func TestAdd(t *testing.T) { t.Fatal("error cannot be nil") } - if len(Items) != 1 { + if len(items) != 1 { t.Error("stats Add did not add exchange info.") } @@ -142,7 +205,7 @@ func TestAdd(t *testing.T) { t.Fatal(err) } - if Items[1].Pair.String() != "XBTUSD" { + if items[1].Pair.String() != "XBTUSD" { t.Fatal("stats Add did not add exchange info.") } @@ -156,7 +219,7 @@ func TestAdd(t *testing.T) { t.Fatal(err) } - if Items[2].Pair.String() != "ETHUSD" { + if items[2].Pair.String() != "ETHUSD" { t.Fatal("stats Add did not add exchange info.") } } @@ -167,12 +230,12 @@ func TestAppend(t *testing.T) { t.Fatal(err) } Append("sillyexchange", p, asset.Spot, 1234, 45) - if len(Items) < 2 { + if len(items) < 2 { t.Error("stats AppendResults did not add exchange values.") } Append("sillyexchange", p, asset.Spot, 1234, 45) - if len(Items) == 3 { + if len(items) == 3 { t.Error("stats AppendResults added exchange values") } } diff --git a/exchanges/stats/stats_types.go b/exchanges/stats/stats_types.go new file mode 100644 index 00000000..623565d5 --- /dev/null +++ b/exchanges/stats/stats_types.go @@ -0,0 +1,29 @@ +package stats + +import ( + "sync" + + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" +) + +var ( + // items holds stat items + items []Item + statMutex sync.Mutex +) + +// Item holds various fields for storing currency pair stats +type Item struct { + Exchange string + Pair currency.Pair + AssetType asset.Item + Price float64 + Volume float64 +} + +// byPrice allows sorting by price +type byPrice []Item + +// byVolume allows sorting by volume +type byVolume []Item diff --git a/testdata/http_mock/gemini/gemini.json b/testdata/http_mock/gemini/gemini.json index 97e06e59..1045764e 100644 --- a/testdata/http_mock/gemini/gemini.json +++ b/testdata/http_mock/gemini/gemini.json @@ -2191,6 +2191,1482 @@ } ] }, + "/v1/symbols/details/all": { + "GET": [ + { + "data": [ + { + "base_currency": "AAVE", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "AAVEUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ALI", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "2", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.000001, + "status": "open", + "symbol": "ALIUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "AMP", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "10", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "AMPUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ANKR", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "ANKRUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "APE", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.02", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "APEUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "API3", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.03", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "API3USD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ATOM", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "ATOMUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "AVAX", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.00499999", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "AVAXUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "AXS", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.003", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "AXSUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "BAT", + "contract_price_currency": "BTC", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "BTC", + "quote_increment": 1e-8, + "status": "limit_only", + "symbol": "BATBTC", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "BAT", + "contract_price_currency": "ETH", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "ETH", + "quote_increment": 1e-7, + "status": "limit_only", + "symbol": "BATETH", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "BAT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "BATUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "BCH", + "contract_price_currency": "BTC", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "BTC", + "quote_increment": 0.00001, + "status": "open", + "symbol": "BCHBTC", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "BCH", + "contract_price_currency": "ETH", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "ETH", + "quote_increment": 0.0001, + "status": "limit_only", + "symbol": "BCHETH", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "BCH", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "BCHUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "BTC", + "contract_price_currency": "DAI", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "DAI", + "quote_increment": 0.01, + "status": "limit_only", + "symbol": "BTCDAI", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "BTC", + "contract_price_currency": "EUR", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "EUR", + "quote_increment": 0.01, + "status": "open", + "symbol": "BTCEUR", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "BTC", + "contract_price_currency": "GBP", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "GBP", + "quote_increment": 0.01, + "status": "open", + "symbol": "BTCGBP", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "BTC", + "contract_price_currency": "GUSD", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "GUSD", + "quote_increment": 0.01, + "status": "open", + "symbol": "BTCGUSD", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "BTC", + "contract_price_currency": "GUSD", + "contract_type": "linear", + "min_order_size": "0.0001", + "product_type": "swap", + "quote_currency": "GUSD", + "quote_increment": 0.5, + "status": "open", + "symbol": "BTCGUSDPERP", + "tick_size": 0.0001, + "wrap_enabled": false + }, + { + "base_currency": "BTC", + "contract_price_currency": "SGD", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "SGD", + "quote_increment": 0.01, + "status": "open", + "symbol": "BTCSGD", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "BTC", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "BTCUSD", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "BTC", + "contract_price_currency": "USDT", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "USDT", + "quote_increment": 0.01, + "status": "open", + "symbol": "BTCUSDT", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "CHZ", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.5", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "CHZUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "COMP", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "COMPUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "CRV", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "CRVUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "CTX", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.002", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "CTXUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "CUBE", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "CUBEUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "DAI", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "DAIUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "DOGE", + "contract_price_currency": "BTC", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "BTC", + "quote_increment": 1e-9, + "status": "open", + "symbol": "DOGEBTC", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "DOGE", + "contract_price_currency": "ETH", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "ETH", + "quote_increment": 1e-8, + "status": "open", + "symbol": "DOGEETH", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "DOGE", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "DOGEUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "DOT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "DOTUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "EFIL", + "contract_price_currency": "FIL", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "FIL", + "quote_increment": 0.0001, + "status": "closed", + "symbol": "EFILFIL", + "tick_size": 0.000001, + "wrap_enabled": true + }, + { + "base_currency": "ELON", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "60000", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 1e-9, + "status": "open", + "symbol": "ELONUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ENS", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.002", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "ENSUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ERN", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.05", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "ERNUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "BTC", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "BTC", + "quote_increment": 0.00001, + "status": "open", + "symbol": "ETHBTC", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "DAI", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "DAI", + "quote_increment": 0.01, + "status": "limit_only", + "symbol": "ETHDAI", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "EUR", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "EUR", + "quote_increment": 0.01, + "status": "open", + "symbol": "ETHEUR", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "GBP", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "GBP", + "quote_increment": 0.01, + "status": "open", + "symbol": "ETHGBP", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "GUSD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "GUSD", + "quote_increment": 0.01, + "status": "open", + "symbol": "ETHGUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "GUSD", + "contract_type": "linear", + "min_order_size": "0.001", + "product_type": "swap", + "quote_currency": "GUSD", + "quote_increment": 0.05, + "status": "open", + "symbol": "ETHGUSDPERP", + "tick_size": 0.001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "SGD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "SGD", + "quote_increment": 0.01, + "status": "open", + "symbol": "ETHSGD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "ETHUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ETH", + "contract_price_currency": "USDT", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USDT", + "quote_increment": 0.01, + "status": "open", + "symbol": "ETHUSDT", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "FET", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "FETUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "FIL", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "FILUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "FTM", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.03", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "FTMUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "GALA", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.4", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "GALAUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "GAL", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.04", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "GALUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "GMT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "GMTUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "GRT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "GRTUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "GUSD", + "contract_price_currency": "GBP", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "GBP", + "quote_increment": 0.001, + "status": "open", + "symbol": "GUSDGBP", + "tick_size": 0.0001, + "wrap_enabled": false + }, + { + "base_currency": "GUSD", + "contract_price_currency": "SGD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "SGD", + "quote_increment": 0.001, + "status": "open", + "symbol": "GUSDSGD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "GUSD", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "closed", + "symbol": "GUSDUSD", + "tick_size": 0.01, + "wrap_enabled": true + }, + { + "base_currency": "HNT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.04", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "HNTUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "IMX", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "IMXUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "INJ", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "INJUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "IOTX", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "3", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.000001, + "status": "open", + "symbol": "IOTXUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "JAM", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "10", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 1e-7, + "status": "open", + "symbol": "JAMUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "LDO", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.02", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "LDOUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "LINK", + "contract_price_currency": "BTC", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "BTC", + "quote_increment": 1e-8, + "status": "limit_only", + "symbol": "LINKBTC", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "LINK", + "contract_price_currency": "ETH", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "ETH", + "quote_increment": 1e-7, + "status": "limit_only", + "symbol": "LINKETH", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "LINK", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "LINKUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "LPT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "LPTUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "LRC", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "LRCUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "LTC", + "contract_price_currency": "BCH", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "BCH", + "quote_increment": 0.0001, + "status": "open", + "symbol": "LTCBCH", + "tick_size": 0.00001, + "wrap_enabled": false + }, + { + "base_currency": "LTC", + "contract_price_currency": "BTC", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "BTC", + "quote_increment": 1e-7, + "status": "open", + "symbol": "LTCBTC", + "tick_size": 0.00001, + "wrap_enabled": false + }, + { + "base_currency": "LTC", + "contract_price_currency": "ETH", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "ETH", + "quote_increment": 0.00001, + "status": "open", + "symbol": "LTCETH", + "tick_size": 0.00001, + "wrap_enabled": false + }, + { + "base_currency": "LTC", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "LTCUSD", + "tick_size": 0.00001, + "wrap_enabled": false + }, + { + "base_currency": "LUNA", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 1e-8, + "status": "limit_only", + "symbol": "LUNAUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "MANA", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "MANAUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "MASK", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "MASKUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "MATIC", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "MATICUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "MKR", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "MKRUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "OXT", + "contract_price_currency": "BTC", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "BTC", + "quote_increment": 1e-8, + "status": "limit_only", + "symbol": "OXTBTC", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "OXT", + "contract_price_currency": "ETH", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "ETH", + "quote_increment": 1e-7, + "status": "limit_only", + "symbol": "OXTETH", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "OXT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "OXTUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "PAXG", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.0001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "PAXGUSD", + "tick_size": 1e-8, + "wrap_enabled": false + }, + { + "base_currency": "PEPE", + "contract_price_currency": "GUSD", + "contract_type": "linear", + "min_order_size": "1", + "product_type": "swap", + "quote_currency": "GUSD", + "quote_increment": 1e-9, + "status": "open", + "symbol": "PEPEGUSDPERP", + "tick_size": 1, + "wrap_enabled": false + }, + { + "base_currency": "PEPE", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "1000", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 1e-9, + "status": "open", + "symbol": "PEPEUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "QNT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.0004", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "QNTUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "QRDO", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.04", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "QRDOUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "RARE", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "RAREUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "REN", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "RENUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "RLY", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.2", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "RLYUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "RNDR", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.02", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "RNDRUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "SAMO", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "10", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 1e-7, + "status": "open", + "symbol": "SAMOUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "SAND", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "SANDUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "SHIB", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "1000", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 1e-9, + "status": "open", + "symbol": "SHIBUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "SKL", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "SKLUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "SNX", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "SNXUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "SOL", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "SOLUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "STORJ", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "STORJUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "SUSHI", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "SUSHIUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "TOKE", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.002", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.001, + "status": "open", + "symbol": "TOKEUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "UMA", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "UMAUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "UNI", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.01", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "UNIUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "USDC", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "USDCUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "USDT", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "USDTUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "XRP", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "XRPUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "XTZ", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.02", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.0001, + "status": "open", + "symbol": "XTZUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "YFI", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "YFIUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ZBC", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "3", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "ZBCUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ZEC", + "contract_price_currency": "BCH", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "BCH", + "quote_increment": 0.0001, + "status": "limit_only", + "symbol": "ZECBCH", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ZEC", + "contract_price_currency": "BTC", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "BTC", + "quote_increment": 1e-7, + "status": "limit_only", + "symbol": "ZECBTC", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ZEC", + "contract_price_currency": "ETH", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "ETH", + "quote_increment": 0.00001, + "status": "limit_only", + "symbol": "ZECETH", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ZEC", + "contract_price_currency": "LTC", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "LTC", + "quote_increment": 0.001, + "status": "limit_only", + "symbol": "ZECLTC", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ZEC", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "ZECUSD", + "tick_size": 0.000001, + "wrap_enabled": false + }, + { + "base_currency": "ZRX", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.1", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.00001, + "status": "open", + "symbol": "ZRXUSD", + "tick_size": 0.000001, + "wrap_enabled": false + } + ], + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, + "/v1/symbols/details/btcusd": { + "GET": [ + { + "data": { + "base_currency": "BTC", + "contract_price_currency": "USD", + "contract_type": "vanilla", + "min_order_size": "0.00001", + "product_type": "spot", + "quote_currency": "USD", + "quote_increment": 0.01, + "status": "open", + "symbol": "BTCUSD", + "tick_size": 1e-8, + "wrap_enabled": false + }, + "queryString": "", + "bodyParams": "", + "headers": {} + } + ] + }, "/v1/trades/BTCUSD": { "GET": [ {