Kraken: Improve ticker batch performance (#1451)

* Speeds up ticks

* lintBONK

* WHOOPS

* fixes silly old typo 👀

* better error handling

* add some extra fields, use types.Num

* rm redundancy

* Inline single-use function, fix tests

* lint
This commit is contained in:
Scott
2024-01-25 15:14:11 +11:00
committed by GitHub
parent e007f69f7c
commit 130642bab6
6 changed files with 117 additions and 89 deletions

View File

@@ -322,6 +322,9 @@ type FuturesTicker struct {
Suspended bool `json:"suspended"`
FundingRate float64 `json:"fundingRate"`
FundingRatePrediction float64 `json:"fundingRatePrediction"`
IndexPrice float64 `json:"indexPrice"`
PostOnly bool `json:"postOnly"`
Change24H float64 `json:"change24h"`
}
// FuturesSendOrderData stores send order data

View File

@@ -141,17 +141,18 @@ func (k *Kraken) GetTicker(ctx context.Context, symbol currency.Pair) (Ticker, e
if len(resp.Error) > 0 {
return tick, fmt.Errorf("%s error: %s", k.Name, resp.Error)
}
for i := range resp.Data {
tick.Ask, _ = strconv.ParseFloat(resp.Data[i].Ask[0], 64)
tick.Bid, _ = strconv.ParseFloat(resp.Data[i].Bid[0], 64)
tick.Last, _ = strconv.ParseFloat(resp.Data[i].Last[0], 64)
tick.Volume, _ = strconv.ParseFloat(resp.Data[i].Volume[1], 64)
tick.VolumeWeightedAveragePrice, _ = strconv.ParseFloat(resp.Data[i].VolumeWeightedAveragePrice[1], 64)
tick.Ask = resp.Data[i].Ask[0].Float64()
tick.AskSize = resp.Data[i].Ask[2].Float64()
tick.Bid = resp.Data[i].Bid[0].Float64()
tick.BidSize = resp.Data[i].Bid[2].Float64()
tick.Last = resp.Data[i].Last[0].Float64()
tick.Volume = resp.Data[i].Volume[1].Float64()
tick.VolumeWeightedAveragePrice = resp.Data[i].VolumeWeightedAveragePrice[1].Float64()
tick.Trades = resp.Data[i].Trades[1]
tick.Low, _ = strconv.ParseFloat(resp.Data[i].Low[1], 64)
tick.High, _ = strconv.ParseFloat(resp.Data[i].High[1], 64)
tick.Open, _ = strconv.ParseFloat(resp.Data[i].Open, 64)
tick.Low = resp.Data[i].Low[1].Float64()
tick.High = resp.Data[i].High[1].Float64()
tick.Open = resp.Data[i].Open.Float64()
}
return tick, nil
}
@@ -161,7 +162,9 @@ func (k *Kraken) GetTicker(ctx context.Context, symbol currency.Pair) (Ticker, e
// ("LTCUSD,ETCUSD")
func (k *Kraken) GetTickers(ctx context.Context, pairList string) (map[string]Ticker, error) {
values := url.Values{}
values.Set("pair", pairList)
if pairList != "" {
values.Set("pair", pairList)
}
type Response struct {
Error []interface{} `json:"error"`
@@ -180,20 +183,21 @@ func (k *Kraken) GetTickers(ctx context.Context, pairList string) (map[string]Ti
return nil, fmt.Errorf("%s error: %s", k.Name, resp.Error)
}
tickers := make(map[string]Ticker)
tickers := make(map[string]Ticker, len(resp.Data))
for i := range resp.Data {
tick := Ticker{}
tick.Ask, _ = strconv.ParseFloat(resp.Data[i].Ask[0], 64)
tick.Bid, _ = strconv.ParseFloat(resp.Data[i].Bid[0], 64)
tick.Last, _ = strconv.ParseFloat(resp.Data[i].Last[0], 64)
tick.Volume, _ = strconv.ParseFloat(resp.Data[i].Volume[1], 64)
tick.VolumeWeightedAveragePrice, _ = strconv.ParseFloat(resp.Data[i].VolumeWeightedAveragePrice[1], 64)
tick.Trades = resp.Data[i].Trades[1]
tick.Low, _ = strconv.ParseFloat(resp.Data[i].Low[1], 64)
tick.High, _ = strconv.ParseFloat(resp.Data[i].High[1], 64)
tick.Open, _ = strconv.ParseFloat(resp.Data[i].Open, 64)
tickers[i] = tick
tickers[i] = Ticker{
Ask: resp.Data[i].Ask[0].Float64(),
AskSize: resp.Data[i].Ask[2].Float64(),
Bid: resp.Data[i].Bid[0].Float64(),
BidSize: resp.Data[i].Bid[2].Float64(),
Last: resp.Data[i].Last[0].Float64(),
Volume: resp.Data[i].Volume[1].Float64(),
VolumeWeightedAveragePrice: resp.Data[i].VolumeWeightedAveragePrice[1].Float64(),
Trades: resp.Data[i].Trades[1],
Low: resp.Data[i].Low[1].Float64(),
High: resp.Data[i].High[1].Float64(),
Open: resp.Data[i].Open.Float64(),
}
}
return tickers, nil
}
@@ -1206,8 +1210,8 @@ func (k *Kraken) GetWebsocketToken(ctx context.Context) (string, error) {
return response.Result.Token, nil
}
// LookupAltname converts a currency into its altname (ZUSD -> USD)
func (a *assetTranslatorStore) LookupAltname(target string) string {
// LookupAltName converts a currency into its altName (ZUSD -> USD)
func (a *assetTranslatorStore) LookupAltName(target string) string {
a.l.RLock()
alt, ok := a.Assets[target]
if !ok {
@@ -1218,7 +1222,7 @@ func (a *assetTranslatorStore) LookupAltname(target string) string {
return alt
}
// LookupAltname converts an altname to its original type (USD -> ZUSD)
// LookupCurrency converts an altName to its original type (USD -> ZUSD)
func (a *assetTranslatorStore) LookupCurrency(target string) string {
a.l.RLock()
for k, v := range a.Assets {
@@ -1247,7 +1251,7 @@ func (a *assetTranslatorStore) Seed(orig, alt string) {
a.l.Unlock()
}
// Seeded returns whether or not the asset translator has been seeded
// Seeded checks if assets have been seeded
func (a *assetTranslatorStore) Seeded() bool {
a.l.RLock()
isSeeded := len(a.Assets) > 0

View File

@@ -14,6 +14,7 @@ import (
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/common/key"
@@ -30,6 +31,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
@@ -173,15 +175,30 @@ func TestUpdateTicker(t *testing.T) {
func TestUpdateTickers(t *testing.T) {
t.Parallel()
err := k.UpdateTickers(context.Background(), asset.Spot)
if err != nil {
t.Error(err)
ap, err := k.GetAvailablePairs(asset.Spot)
require.NoError(t, err)
err = k.CurrencyPairs.StorePairs(asset.Spot, ap, true)
require.NoError(t, err)
err = k.UpdateTickers(context.Background(), asset.Spot)
assert.NoError(t, err)
for i := range ap {
_, err = ticker.GetTicker(k.Name, ap[i], asset.Spot)
assert.NoError(t, err)
}
ap, err = k.GetAvailablePairs(asset.Futures)
require.NoError(t, err)
err = k.CurrencyPairs.StorePairs(asset.Futures, ap, true)
require.NoError(t, err)
err = k.UpdateTickers(context.Background(), asset.Futures)
if err != nil {
t.Error(err)
assert.NoError(t, err)
for i := range ap {
_, err = ticker.GetTicker(k.Name, ap[i], asset.Futures)
assert.NoError(t, err)
}
err = k.UpdateTickers(context.Background(), asset.Index)
assert.ErrorIs(t, err, asset.ErrNotSupported)
}
func TestUpdateOrderbook(t *testing.T) {
@@ -455,7 +472,7 @@ func TestGetAssets(t *testing.T) {
func TestSeedAssetTranslator(t *testing.T) {
t.Parallel()
// Test currency pair
if r := assetTranslator.LookupAltname("XXBTZUSD"); r != "XBTUSD" {
if r := assetTranslator.LookupAltName("XXBTZUSD"); r != "XBTUSD" {
t.Error("unexpected result")
}
if r := assetTranslator.LookupCurrency("XBTUSD"); r != "XXBTZUSD" {
@@ -463,7 +480,7 @@ func TestSeedAssetTranslator(t *testing.T) {
}
// Test fiat currency
if r := assetTranslator.LookupAltname("ZUSD"); r != "USD" {
if r := assetTranslator.LookupAltName("ZUSD"); r != "USD" {
t.Error("unexpected result")
}
if r := assetTranslator.LookupCurrency("USD"); r != "ZUSD" {
@@ -471,7 +488,7 @@ func TestSeedAssetTranslator(t *testing.T) {
}
// Test cryptocurrency
if r := assetTranslator.LookupAltname("XXBT"); r != "XBT" {
if r := assetTranslator.LookupAltName("XXBT"); r != "XBT" {
t.Error("unexpected result")
}
if r := assetTranslator.LookupCurrency("XBT"); r != "XXBT" {
@@ -482,15 +499,15 @@ func TestSeedAssetTranslator(t *testing.T) {
func TestSeedAssets(t *testing.T) {
t.Parallel()
var a assetTranslatorStore
if r := a.LookupAltname("ZUSD"); r != "" {
if r := a.LookupAltName("ZUSD"); r != "" {
t.Error("unexpected result")
}
a.Seed("ZUSD", "USD")
if r := a.LookupAltname("ZUSD"); r != "USD" {
if r := a.LookupAltName("ZUSD"); r != "USD" {
t.Error("unexpected result")
}
a.Seed("ZUSD", "BLA")
if r := a.LookupAltname("ZUSD"); r != "USD" {
if r := a.LookupAltName("ZUSD"); r != "USD" {
t.Error("unexpected result")
}
}
@@ -1951,7 +1968,7 @@ func TestGetHistoricCandles(t *testing.T) {
t.Error(err)
}
err = k.CurrencyPairs.EnablePair(asset.Futures, pairs[0])
if err != nil && errors.Is(err, currency.ErrPairAlreadyEnabled) {
if err != nil && !errors.Is(err, currency.ErrPairAlreadyEnabled) {
t.Error(err)
}
_, err = k.GetHistoricCandles(context.Background(), pairs[0], asset.Futures, kline.OneHour, time.Now().Add(-time.Hour*12), time.Now())

View File

@@ -6,6 +6,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/types"
)
const (
@@ -130,7 +131,9 @@ type AssetPairs struct {
// Ticker is a standard ticker type
type Ticker struct {
Ask float64
AskSize float64
Bid float64
BidSize float64
Last float64
Volume float64
VolumeWeightedAveragePrice float64
@@ -145,15 +148,15 @@ type Tickers map[string]Ticker
// TickerResponse holds ticker information before its put into the Ticker struct
type TickerResponse struct {
Ask []string `json:"a"`
Bid []string `json:"b"`
Last []string `json:"c"`
Volume []string `json:"v"`
VolumeWeightedAveragePrice []string `json:"p"`
Trades []int64 `json:"t"`
Low []string `json:"l"`
High []string `json:"h"`
Open string `json:"o"`
Ask [3]types.Number `json:"a"`
Bid [3]types.Number `json:"b"`
Last [2]types.Number `json:"c"`
Volume [2]types.Number `json:"v"`
VolumeWeightedAveragePrice [2]types.Number `json:"p"`
Trades [2]int64 `json:"t"`
Low [2]types.Number `json:"l"`
High [2]types.Number `json:"h"`
Open types.Number `json:"o"`
}
// OpenHighLowClose contains ticker event information

View File

@@ -405,7 +405,7 @@ func (k *Kraken) fetchSpotPairInfo(ctx context.Context) (map[currency.Pair]*Asse
if info.Status != "online" {
continue
}
base := assetTranslator.LookupAltname(info.Base)
base := assetTranslator.LookupAltName(info.Base)
if base == "" {
log.Warnf(log.ExchangeSys,
"%s unable to lookup altname for base currency %s",
@@ -413,7 +413,7 @@ func (k *Kraken) fetchSpotPairInfo(ctx context.Context) (map[currency.Pair]*Asse
info.Base)
continue
}
quote := assetTranslator.LookupAltname(info.Quote)
quote := assetTranslator.LookupAltName(info.Quote)
if quote == "" {
log.Warnf(log.ExchangeSys,
"%s unable to lookup altname for quote currency %s",
@@ -489,50 +489,43 @@ func (k *Kraken) UpdateTradablePairs(ctx context.Context, forceUpdate bool) erro
func (k *Kraken) UpdateTickers(ctx context.Context, a asset.Item) error {
switch a {
case asset.Spot:
pairs, err := k.GetEnabledPairs(a)
tickers, err := k.GetTickers(ctx, "")
if err != nil {
return err
}
pairsCollated, err := k.FormatExchangeCurrencies(pairs, a)
if err != nil {
return err
}
tickers, err := k.GetTickers(ctx, pairsCollated)
if err != nil {
return err
}
for i := range pairs {
for c, t := range tickers {
pairFmt, err := k.FormatExchangeCurrency(pairs[i], a)
if err != nil {
for c, t := range tickers {
var cp currency.Pair
cp, err = k.MatchSymbolWithAvailablePairs(c, a, false)
if err != nil {
if !errors.Is(err, currency.ErrPairNotFound) {
return err
}
if !strings.EqualFold(pairFmt.String(), c) {
altCurrency := assetTranslator.LookupAltname(c)
if altCurrency == "" {
continue
}
if !strings.EqualFold(pairFmt.String(), altCurrency) {
continue
}
altName := assetTranslator.LookupAltName(c)
if altName == "" {
continue
}
err = ticker.ProcessTicker(&ticker.Price{
Last: t.Last,
High: t.High,
Low: t.Low,
Bid: t.Bid,
Ask: t.Ask,
Volume: t.Volume,
Open: t.Open,
Pair: pairs[i],
ExchangeName: k.Name,
AssetType: a})
cp, err = k.MatchSymbolWithAvailablePairs(altName, a, false)
if err != nil {
return err
continue
}
}
err = ticker.ProcessTicker(&ticker.Price{
Last: t.Last,
High: t.High,
Low: t.Low,
Bid: t.Bid,
BidSize: t.BidSize,
Ask: t.Ask,
AskSize: t.AskSize,
Volume: t.Volume,
Open: t.Open,
Pair: cp,
ExchangeName: k.Name,
AssetType: a,
})
if err != nil {
return err
}
}
case asset.Futures:
t, err := k.GetFuturesTickers(ctx)
@@ -540,20 +533,26 @@ func (k *Kraken) UpdateTickers(ctx context.Context, a asset.Item) error {
return err
}
for x := range t.Tickers {
pair, err := currency.NewPairFromString(t.Tickers[x].Symbol)
var cp currency.Pair
cp, err = currency.NewPairFromString(t.Tickers[x].Symbol)
if err != nil {
return err
}
err = ticker.ProcessTicker(&ticker.Price{
Last: t.Tickers[x].Last,
Bid: t.Tickers[x].Bid,
BidSize: t.Tickers[x].BidSize,
Ask: t.Tickers[x].Ask,
AskSize: t.Tickers[x].AskSize,
Volume: t.Tickers[x].Vol24h,
Open: t.Tickers[x].Open24H,
OpenInterest: t.Tickers[x].OpenInterest,
Pair: pair,
MarkPrice: t.Tickers[x].MarkPrice,
IndexPrice: t.Tickers[x].IndexPrice,
Pair: cp,
ExchangeName: k.Name,
AssetType: a})
AssetType: a,
})
if err != nil {
return err
}
@@ -669,7 +668,7 @@ func (k *Kraken) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
return info, err
}
for key := range bal {
translatedCurrency := assetTranslator.LookupAltname(key)
translatedCurrency := assetTranslator.LookupAltName(key)
if translatedCurrency == "" {
log.Warnf(log.ExchangeSys, "%s unable to translate currency: %s\n",
k.Name,

View File

@@ -45,6 +45,8 @@ type Price struct {
Open float64 `json:"Open"`
Close float64 `json:"Close"`
OpenInterest float64 `json:"OpenInterest"`
MarkPrice float64 `json:"MarkPrice"`
IndexPrice float64 `json:"IndexPrice"`
Pair currency.Pair `json:"Pair"`
ExchangeName string `json:"exchangeName"`
AssetType asset.Item `json:"assetType"`