mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
Tests: Various race fixes and move TestFixtureToDataHandler (#1534)
* Tests: Move and simplify TestFixtureToDataHandler * Currency: Fix PairsManager.Load breaking matcher * Tests: Add multi-instance cache to UpdatePairsOnce * Kraken: Fix TestUpdateTickers race error Calling StorePairs on global instance can lead to race * Bitfinex: Fix TestUpdateTickers racing intermittently * Currency: Fix concurrent access to PM formats * Currency: Fix SupportsAsset implementation This should delegate entirely to PairManager's IsAssetSupported * Okx: Fix PM intrusion, rm GetPairFromInstrumentID * Exchange: Fix SetGlobalPairsManager to set asset enabled * Bitflyer: Fix race on set TestGetCurrURL TestGetCurrencyTradeURL would fail sometimes due to sequencing of enabling futures but not having pairs for it. * Tests: Simplify usage pattern for FixtureToDH
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/bitstamp"
|
||||
@@ -14,31 +16,23 @@ import (
|
||||
func TestRandomSlippage(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := EstimateSlippagePercentage(decimal.NewFromInt(80), decimal.NewFromInt(100))
|
||||
if resp.LessThan(decimal.NewFromFloat(0.8)) || resp.GreaterThan(decimal.NewFromInt(1)) {
|
||||
t.Error("expected result > 0.8 and < 100")
|
||||
}
|
||||
assert.True(t, resp.GreaterThan(decimal.NewFromFloat(0.8)), "result should be more than 0.8")
|
||||
assert.True(t, resp.LessThan(decimal.NewFromInt(1)), "result should be less than 1")
|
||||
}
|
||||
|
||||
func TestCalculateSlippageByOrderbook(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := bitstamp.Bitstamp{}
|
||||
b.SetDefaults()
|
||||
err := b.CurrencyPairs.SetAssetEnabled(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cp := currency.NewPair(currency.BTC, currency.USD)
|
||||
ob, err := b.FetchOrderbook(context.Background(), cp, asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.NoError(t, err, "FetchOrderbook must not error")
|
||||
|
||||
amountOfFunds := decimal.NewFromInt(1000)
|
||||
feeRate := decimal.NewFromFloat(0.03)
|
||||
price, amount, err := CalculateSlippageByOrderbook(ob, gctorder.Buy, amountOfFunds, feeRate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if price.Mul(amount).Add(price.Mul(amount).Mul(feeRate)).GreaterThan(amountOfFunds) {
|
||||
t.Error("order size must be less than funds")
|
||||
}
|
||||
require.NoError(t, err, "CalculateSlippageByOrderbook must not error")
|
||||
orderSize := price.Mul(amount).Add(price.Mul(amount).Mul(feeRate))
|
||||
assert.True(t, orderSize.LessThan(amountOfFunds), "order size must be less than funds")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
@@ -11,32 +12,22 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
// Public errors
|
||||
var (
|
||||
// ErrAssetAlreadyEnabled defines an error for the pairs management system
|
||||
// that declares the asset is already enabled.
|
||||
ErrAssetAlreadyEnabled = errors.New("asset already enabled")
|
||||
// ErrPairAlreadyEnabled returns when enabling a pair that is already enabled
|
||||
ErrPairAlreadyEnabled = errors.New("pair already enabled")
|
||||
// ErrPairNotEnabled returns when looking for a pair that is not enabled
|
||||
ErrPairNotEnabled = errors.New("pair not enabled")
|
||||
// ErrPairNotFound is returned when a currency pair is not found
|
||||
ErrPairNotFound = errors.New("pair not found")
|
||||
// ErrAssetIsNil is an error when the asset has not been populated by the
|
||||
// configuration
|
||||
ErrAssetIsNil = errors.New("asset is nil")
|
||||
// ErrPairNotContainedInAvailablePairs defines an error when a pair is not
|
||||
// contained in the available pairs list and is not supported by the
|
||||
// exchange for that asset type.
|
||||
ErrAssetAlreadyEnabled = errors.New("asset already enabled")
|
||||
ErrAssetIsNil = errors.New("asset is nil")
|
||||
ErrAssetNotFound = errors.New("asset type not found in pair store")
|
||||
ErrPairAlreadyEnabled = errors.New("pair already enabled")
|
||||
ErrPairFormatIsNil = errors.New("pair format is nil")
|
||||
ErrPairManagerNotInitialised = errors.New("pair manager not initialised")
|
||||
ErrPairNotContainedInAvailablePairs = errors.New("pair not contained in available pairs")
|
||||
// ErrPairManagerNotInitialised is returned when a pairs manager is requested, but has not been setup
|
||||
ErrPairManagerNotInitialised = errors.New("pair manager not initialised")
|
||||
// ErrAssetNotFound is returned when an asset does not exist in the pairstore
|
||||
ErrAssetNotFound = errors.New("asset type not found in pair store")
|
||||
// ErrSymbolStringEmpty is an error when a symbol string is empty
|
||||
ErrSymbolStringEmpty = errors.New("symbol string is empty")
|
||||
ErrPairNotEnabled = errors.New("pair not enabled")
|
||||
ErrPairNotFound = errors.New("pair not found")
|
||||
ErrSymbolStringEmpty = errors.New("symbol string is empty")
|
||||
)
|
||||
|
||||
var (
|
||||
errPairStoreIsNil = errors.New("pair store is nil")
|
||||
errPairFormatIsNil = errors.New("pair format is nil")
|
||||
errPairMatcherIsNil = errors.New("pair matcher is nil")
|
||||
errPairConfigFormatNil = errors.New("pair config format is nil")
|
||||
)
|
||||
@@ -65,10 +56,9 @@ func (p *PairsManager) Get(a asset.Item) (*PairStore, error) {
|
||||
defer p.mutex.RUnlock()
|
||||
c, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return nil,
|
||||
fmt.Errorf("cannot get pair store, %v %w", a, asset.ErrNotSupported)
|
||||
return nil, fmt.Errorf("cannot get pair store, %v %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
return c.copy()
|
||||
return c.clone(), nil
|
||||
}
|
||||
|
||||
// Match returns a currency pair based on the supplied symbol and asset type
|
||||
@@ -94,24 +84,16 @@ func (p *PairsManager) Store(a asset.Item, ps *PairStore) error {
|
||||
if !a.IsValid() {
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
cpy, err := ps.copy()
|
||||
if err != nil {
|
||||
return err
|
||||
if ps == nil {
|
||||
return errPairStoreIsNil
|
||||
}
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
if p.Pairs == nil {
|
||||
p.Pairs = make(map[asset.Item]*PairStore)
|
||||
p.Pairs = FullStore{}
|
||||
}
|
||||
p.Pairs[a] = cpy
|
||||
if p.matcher == nil {
|
||||
p.matcher = make(map[key]*Pair)
|
||||
}
|
||||
for x := range cpy.Available {
|
||||
p.matcher[key{
|
||||
Symbol: cpy.Available[x].Base.Lower().String() + cpy.Available[x].Quote.Lower().String(),
|
||||
Asset: a}] = &cpy.Available[x]
|
||||
}
|
||||
p.mutex.Unlock()
|
||||
p.Pairs[a] = ps.clone()
|
||||
p.reindex()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -145,9 +127,7 @@ func (p *PairsManager) GetPairs(a asset.Item, enabled bool) (Pairs, error) {
|
||||
}
|
||||
|
||||
if !enabled {
|
||||
availPairs := make(Pairs, len(pairStore.Available))
|
||||
copy(availPairs, pairStore.Available)
|
||||
return availPairs, nil
|
||||
return slices.Clone(pairStore.Available), nil
|
||||
}
|
||||
|
||||
lenCheck := len(pairStore.Enabled)
|
||||
@@ -157,8 +137,7 @@ func (p *PairsManager) GetPairs(a asset.Item, enabled bool) (Pairs, error) {
|
||||
|
||||
// NOTE: enabledPairs is declared before the next check for comparison
|
||||
// reasons within exchange update pairs functionality.
|
||||
enabledPairs := make(Pairs, lenCheck)
|
||||
copy(enabledPairs, pairStore.Enabled)
|
||||
enabledPairs := slices.Clone(pairStore.Enabled)
|
||||
|
||||
err := pairStore.Available.ContainsAll(pairStore.Enabled, true)
|
||||
if err != nil {
|
||||
@@ -173,11 +152,9 @@ func (p *PairsManager) StoreFormat(a asset.Item, pFmt *PairFormat, config bool)
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
if pFmt == nil {
|
||||
return errPairFormatIsNil
|
||||
return ErrPairFormatIsNil
|
||||
}
|
||||
|
||||
cpy := *pFmt
|
||||
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
@@ -192,13 +169,42 @@ func (p *PairsManager) StoreFormat(a asset.Item, pFmt *PairFormat, config bool)
|
||||
}
|
||||
|
||||
if config {
|
||||
pairStore.ConfigFormat = &cpy
|
||||
pairStore.ConfigFormat = pFmt.clone()
|
||||
} else {
|
||||
pairStore.RequestFormat = &cpy
|
||||
pairStore.RequestFormat = pFmt.clone()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFormat returns the pair format in a concurrent safe manner
|
||||
func (p *PairsManager) GetFormat(a asset.Item, request bool) (PairFormat, error) {
|
||||
p.mutex.RLock()
|
||||
defer p.mutex.RUnlock()
|
||||
|
||||
var pFmt *PairFormat
|
||||
if p.UseGlobalFormat {
|
||||
if request {
|
||||
pFmt = p.RequestFormat
|
||||
} else {
|
||||
pFmt = p.ConfigFormat
|
||||
}
|
||||
} else {
|
||||
ps, err := p.Get(a)
|
||||
if err != nil {
|
||||
return EMPTYFORMAT, err
|
||||
}
|
||||
if request {
|
||||
pFmt = ps.RequestFormat
|
||||
} else {
|
||||
pFmt = ps.ConfigFormat
|
||||
}
|
||||
}
|
||||
if pFmt == nil {
|
||||
return EMPTYFORMAT, ErrPairFormatIsNil
|
||||
}
|
||||
return *pFmt, nil
|
||||
}
|
||||
|
||||
// StorePairs stores a list of pairs based on the asset type and whether
|
||||
// they're enabled or not
|
||||
func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) error {
|
||||
@@ -206,11 +212,6 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) error
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
// NOTE: Length check not needed in this scenario as it has the ability to
|
||||
// remove the entire stored list if needed.
|
||||
cpy := make(Pairs, len(pairs))
|
||||
copy(cpy, pairs)
|
||||
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
@@ -225,18 +226,10 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) error
|
||||
}
|
||||
|
||||
if enabled {
|
||||
pairStore.Enabled = cpy
|
||||
pairStore.Enabled = slices.Clone(pairs)
|
||||
} else {
|
||||
pairStore.Available = cpy
|
||||
|
||||
if p.matcher == nil {
|
||||
p.matcher = make(map[key]*Pair)
|
||||
}
|
||||
for x := range pairStore.Available {
|
||||
p.matcher[key{
|
||||
Symbol: pairStore.Available[x].Base.Lower().String() + pairStore.Available[x].Quote.Lower().String(),
|
||||
Asset: a}] = &pairStore.Available[x]
|
||||
}
|
||||
pairStore.Available = slices.Clone(pairs)
|
||||
p.reindex()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -405,6 +398,15 @@ func (p *PairsManager) IsAssetEnabled(a asset.Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAssetSupported returns if the asset is supported by an exchange
|
||||
// Does not imply that the Asset is enabled
|
||||
func (p *PairsManager) IsAssetSupported(a asset.Item) bool {
|
||||
p.mutex.RLock()
|
||||
defer p.mutex.RUnlock()
|
||||
_, ok := p.Pairs[a]
|
||||
return ok
|
||||
}
|
||||
|
||||
// SetAssetEnabled sets if an asset is enabled or disabled for first run
|
||||
func (p *PairsManager) SetAssetEnabled(a asset.Item, enabled bool) error {
|
||||
if !a.IsValid() {
|
||||
@@ -435,34 +437,31 @@ func (p *PairsManager) SetAssetEnabled(a asset.Item, enabled bool) error {
|
||||
}
|
||||
|
||||
// Load sets the pair manager from a seed without copying mutexes
|
||||
func (p *PairsManager) Load(seed *PairsManager) error {
|
||||
if seed == nil {
|
||||
return fmt.Errorf("%w PairsManager", common.ErrNilPointer)
|
||||
}
|
||||
func (p *PairsManager) Load(seed *PairsManager) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
seed.mutex.RLock()
|
||||
defer seed.mutex.RUnlock()
|
||||
|
||||
var pN PairsManager
|
||||
j, err := json.Marshal(seed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(j, &pN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.BypassConfigFormatUpgrades = pN.BypassConfigFormatUpgrades
|
||||
if pN.UseGlobalFormat {
|
||||
p.UseGlobalFormat = pN.UseGlobalFormat
|
||||
p.RequestFormat = pN.RequestFormat
|
||||
p.ConfigFormat = pN.ConfigFormat
|
||||
}
|
||||
p.LastUpdated = pN.LastUpdated
|
||||
p.Pairs = pN.Pairs
|
||||
p.BypassConfigFormatUpgrades = seed.BypassConfigFormatUpgrades
|
||||
p.UseGlobalFormat = seed.UseGlobalFormat
|
||||
p.LastUpdated = seed.LastUpdated
|
||||
p.Pairs = seed.Pairs.clone()
|
||||
p.RequestFormat = seed.RequestFormat.clone()
|
||||
p.ConfigFormat = seed.ConfigFormat.clone()
|
||||
p.reindex()
|
||||
}
|
||||
|
||||
return nil
|
||||
// reindex re-indexes the matcher for Available pairs and all assets
|
||||
// This method does not lock for concurrency
|
||||
func (p *PairsManager) reindex() {
|
||||
p.matcher = make(map[key]*Pair)
|
||||
for a, fs := range p.Pairs {
|
||||
for i, pair := range fs.Available {
|
||||
k := key{Symbol: pair.Base.Lower().String() + pair.Quote.Lower().String(), Asset: a}
|
||||
p.matcher[k] = &fs.Available[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PairsManager) getPairStoreRequiresLock(a asset.Item) (*PairStore, error) {
|
||||
@@ -539,39 +538,30 @@ func (fs FullStore) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(temp)
|
||||
}
|
||||
|
||||
// copy copies and segregates pair store from internal and external calls.
|
||||
func (ps *PairStore) copy() (*PairStore, error) {
|
||||
// clone returns a deep clone of the PairStore
|
||||
func (ps *PairStore) clone() *PairStore {
|
||||
if ps == nil {
|
||||
return nil, errPairStoreIsNil
|
||||
return nil
|
||||
}
|
||||
|
||||
var assetEnabled *bool
|
||||
if ps.AssetEnabled != nil {
|
||||
assetEnabled = convert.BoolPtr(*ps.AssetEnabled)
|
||||
}
|
||||
|
||||
enabled := make(Pairs, len(ps.Enabled))
|
||||
copy(enabled, ps.Enabled)
|
||||
|
||||
avail := make(Pairs, len(ps.Available))
|
||||
copy(avail, ps.Available)
|
||||
|
||||
var rFmt *PairFormat
|
||||
if ps.RequestFormat != nil {
|
||||
cpy := *ps.RequestFormat
|
||||
rFmt = &cpy
|
||||
}
|
||||
|
||||
var cFmt *PairFormat
|
||||
if ps.ConfigFormat != nil {
|
||||
cpy := *ps.ConfigFormat
|
||||
cFmt = &cpy
|
||||
}
|
||||
|
||||
return &PairStore{
|
||||
AssetEnabled: assetEnabled,
|
||||
Enabled: enabled,
|
||||
Available: avail,
|
||||
RequestFormat: rFmt,
|
||||
ConfigFormat: cFmt,
|
||||
}, nil
|
||||
Enabled: slices.Clone(ps.Enabled),
|
||||
Available: slices.Clone(ps.Available),
|
||||
RequestFormat: ps.RequestFormat.clone(),
|
||||
ConfigFormat: ps.ConfigFormat.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
func (fs FullStore) clone() FullStore {
|
||||
c := FullStore{}
|
||||
for a, pairStore := range fs {
|
||||
c[a] = pairStore.clone()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"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/exchanges/asset"
|
||||
)
|
||||
@@ -272,8 +271,8 @@ func TestStoreFormat(t *testing.T) {
|
||||
}
|
||||
|
||||
err = p.StoreFormat(asset.Spot, nil, true)
|
||||
if !errors.Is(err, errPairFormatIsNil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errPairFormatIsNil)
|
||||
if !errors.Is(err, ErrPairFormatIsNil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, ErrPairFormatIsNil)
|
||||
}
|
||||
|
||||
err = p.StoreFormat(asset.Spot, &PairFormat{Delimiter: "~"}, true)
|
||||
@@ -799,15 +798,16 @@ func TestLoad(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
assert.ErrorIs(t, base.Load(nil), common.ErrNilPointer, "Load nil should error")
|
||||
if assert.NoError(t, base.Load(&seed), "Loading from seed should not error") {
|
||||
assert.True(t, *base.Pairs[asset.Futures].AssetEnabled, "Futures AssetEnabled should be true")
|
||||
assert.True(t, base.Pairs[asset.Futures].Available.Contains(p, true), "Futures Available Pairs should contain BTCUSDT")
|
||||
assert.False(t, *base.Pairs[asset.Options].AssetEnabled, "Options AssetEnabled should be false")
|
||||
assert.Equal(t, tt, base.LastUpdated, "Last Updated should be correct")
|
||||
assert.Equal(t, fmt1.Uppercase, base.ConfigFormat.Uppercase, "ConfigFormat Uppercase should be correct")
|
||||
assert.Equal(t, fmt2.Delimiter, base.RequestFormat.Delimiter, "RequestFormat Delimiter should be correct")
|
||||
}
|
||||
base.Load(&seed)
|
||||
assert.True(t, *base.Pairs[asset.Futures].AssetEnabled, "Futures AssetEnabled should be true")
|
||||
assert.True(t, base.Pairs[asset.Futures].Available.Contains(p, true), "Futures Available Pairs should contain BTCUSDT")
|
||||
assert.False(t, *base.Pairs[asset.Options].AssetEnabled, "Options AssetEnabled should be false")
|
||||
assert.Equal(t, tt, base.LastUpdated, "Last Updated should be correct")
|
||||
assert.Equal(t, fmt1.Uppercase, base.ConfigFormat.Uppercase, "ConfigFormat Uppercase should be correct")
|
||||
assert.Equal(t, fmt2.Delimiter, base.RequestFormat.Delimiter, "RequestFormat Delimiter should be correct")
|
||||
found, err := base.Match("BTCUSDT", asset.Futures)
|
||||
require.NoError(t, err, "Match must not error")
|
||||
assert.Equal(t, p, found, "Should find the right pair")
|
||||
}
|
||||
|
||||
func checkPairDelimiter(tb testing.TB, p *PairsManager, err error, d, msg string) {
|
||||
@@ -857,3 +857,74 @@ func TestPairManagerSetDelimitersFromConfig(t *testing.T) {
|
||||
assert.ErrorContains(t, err, "spot.enabled.BTC-USDT: delimiter: [_] not found in currencypair string", "SetDelimitersFromConfig should error correctly")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetFormat exercises PairsManager GetFormat
|
||||
func TestGetFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := PairsManager{
|
||||
UseGlobalFormat: true,
|
||||
ConfigFormat: &PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "🦄",
|
||||
},
|
||||
RequestFormat: &PairFormat{
|
||||
Delimiter: "~",
|
||||
},
|
||||
}
|
||||
|
||||
pFmt, err := m.GetFormat(asset.Spot, true)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "~", pFmt.Delimiter, "Global Format Delimiter should be correct")
|
||||
assert.False(t, pFmt.Uppercase, "Global Format Uppercase should be correct")
|
||||
|
||||
pFmt, err = m.GetFormat(asset.Spot, false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "🦄", pFmt.Delimiter, "Global Format Delimiter should be special")
|
||||
assert.True(t, pFmt.Uppercase, "Global Format Uppercase should be correct")
|
||||
|
||||
m.ConfigFormat = nil
|
||||
m.RequestFormat = nil
|
||||
_, err = m.GetFormat(asset.Spot, true)
|
||||
assert.ErrorIs(t, err, ErrPairFormatIsNil, "Global GetFormat Should error correctly on nil request format")
|
||||
_, err = m.GetFormat(asset.Spot, false)
|
||||
assert.ErrorIs(t, err, ErrPairFormatIsNil, "Global GetFormat Should error correctly on nil config format")
|
||||
|
||||
m.UseGlobalFormat = false
|
||||
err = m.Store(asset.Spot, &PairStore{
|
||||
ConfigFormat: &pFmt,
|
||||
RequestFormat: &PairFormat{Delimiter: "/", Uppercase: true},
|
||||
})
|
||||
require.NoError(t, err, "Store must not error")
|
||||
|
||||
pFmt, err = m.GetFormat(asset.Spot, false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "🦄", pFmt.Delimiter, "Per Asset Format Delimiter should be correct")
|
||||
assert.True(t, pFmt.Uppercase, "Per Asset Format Uppercase should be correct")
|
||||
|
||||
pFmt, err = m.GetFormat(asset.Spot, true)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "/", pFmt.Delimiter, "Per Asset Format Delimiter should be correct")
|
||||
assert.True(t, pFmt.Uppercase, "Per Asset Format Uppercase should be correct")
|
||||
|
||||
err = m.Store(asset.Spot, &PairStore{})
|
||||
require.NoError(t, err, "Store must not error")
|
||||
_, err = m.GetFormat(asset.Spot, true)
|
||||
assert.ErrorIs(t, err, ErrPairFormatIsNil, "Per Asset GetFormat Should error correctly on nil request format")
|
||||
_, err = m.GetFormat(asset.Spot, false)
|
||||
assert.ErrorIs(t, err, ErrPairFormatIsNil, "Per Asset GetFormat Should error correctly on nil config format")
|
||||
}
|
||||
|
||||
// TestIsAssetSupported exercises IsAssetSupported
|
||||
func TestIsAssetSupported(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := PairsManager{
|
||||
Pairs: FullStore{
|
||||
asset.Spot: {
|
||||
AssetEnabled: convert.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.True(t, p.IsAssetSupported(asset.Spot), "Spot should be supported")
|
||||
assert.False(t, p.IsAssetSupported(asset.Index), "Index should not be supported")
|
||||
}
|
||||
|
||||
@@ -118,6 +118,15 @@ func (f PairFormat) Format(pair Pair) string {
|
||||
return pair.Format(f).String()
|
||||
}
|
||||
|
||||
// clone returns a clone of the PairFormat
|
||||
func (f *PairFormat) clone() *PairFormat {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
c := *f
|
||||
return &c
|
||||
}
|
||||
|
||||
// MatchPairsWithNoDelimiter will move along a predictable index on the provided currencyPair
|
||||
// it will then split on that index and verify whether that currencypair exists in the
|
||||
// supplied pairs
|
||||
|
||||
@@ -547,6 +547,8 @@ func TestUpdateTicker(t *testing.T) {
|
||||
func TestUpdateTickers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b := new(Bitfinex) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(b), "Test instance Setup must not error")
|
||||
testexch.UpdatePairsOnce(t, b)
|
||||
|
||||
assets := b.GetAssetTypes(false)
|
||||
|
||||
@@ -455,14 +455,14 @@ func TestUpdateTradablePairs(t *testing.T) {
|
||||
func TestGetCurrencyTradeURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
testexch.UpdatePairsOnce(t, b)
|
||||
err := b.CurrencyPairs.SetAssetEnabled(asset.Futures, true)
|
||||
require.NoError(t, err)
|
||||
err := b.CurrencyPairs.SetAssetEnabled(asset.Futures, false)
|
||||
require.NoError(t, err, "SetAssetEnabled must not error")
|
||||
for _, a := range b.GetAssetTypes(false) {
|
||||
pairs, err := b.CurrencyPairs.GetPairs(a, false)
|
||||
require.NoError(t, err, "cannot get pairs for %s", a)
|
||||
require.NotEmpty(t, pairs, "no pairs for %s", a)
|
||||
resp, err := b.GetCurrencyTradeURL(context.Background(), a, pairs[0])
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
require.NoError(t, err, "GetCurrencyTradeURL must not error")
|
||||
assert.NotEmpty(t, resp, "GetCurrencyTradeURL should return an url")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,84 +776,79 @@ func TestWsOrderbook2(t *testing.T) {
|
||||
func TestWsOrderUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
n := new(Bitstamp)
|
||||
sharedtestvalues.TestFixtureToDataHandler(t, b, n, "testdata/wsMyOrders.json", n.wsHandleData)
|
||||
seen := 0
|
||||
for reading := true; reading; {
|
||||
select {
|
||||
default:
|
||||
reading = false
|
||||
case resp := <-n.GetBase().Websocket.DataHandler:
|
||||
seen++
|
||||
switch v := resp.(type) {
|
||||
case *order.Detail:
|
||||
switch seen {
|
||||
case 1:
|
||||
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
|
||||
assert.Equal(t, time.UnixMicro(1693831262313000), v.Date, "Date")
|
||||
assert.Equal(t, "test_market_buy", v.ClientOrderID, "ClientOrderID")
|
||||
assert.Equal(t, order.New, v.Status, "Status")
|
||||
assert.Equal(t, order.Buy, v.Side, "Side")
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USD", "/"), v.Pair, "Pair")
|
||||
assert.Equal(t, 0.0, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 999999999.0, v.Price, "Price") // Market Buy Price
|
||||
// Note: Amount is 0 for market order create messages, oddly
|
||||
case 2:
|
||||
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
|
||||
assert.Equal(t, 0.00038667, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount") // During live tests we consistently got back this Sat remaining
|
||||
assert.Equal(t, 0.00038666, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 25862.0, v.Price, "Price")
|
||||
case 3:
|
||||
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Cancelled, v.Status, "Status") // Even though they probably consider it filled, Deleted + PartialFill = Cancelled
|
||||
assert.Equal(t, 0.00038667, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038666, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 25862.0, v.Price, "Price")
|
||||
case 4:
|
||||
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.New, v.Status, "Status")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, 0.0, v.Price, "Price") // Market Sell Price
|
||||
case 5:
|
||||
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
|
||||
assert.Equal(t, 0.00038679, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038678, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 25854.0, v.Price, "Price")
|
||||
case 6:
|
||||
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Cancelled, v.Status, "Status")
|
||||
assert.Equal(t, 0.00038679, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038678, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 25854.0, v.Price, "Price")
|
||||
case 7:
|
||||
assert.Equal(t, "1658869033291777", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.New, v.Status, "Status")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, 25845.0, v.Price, "Price")
|
||||
assert.Equal(t, 0.00038692, v.Amount, "Amount")
|
||||
case 8:
|
||||
assert.Equal(t, "1658869033291777", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Filled, v.Status, "Status")
|
||||
assert.Equal(t, 25845.0, v.Price, "Price")
|
||||
assert.Equal(t, 0.00038692, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038692, v.ExecutedAmount, "ExecutedAmount")
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
default:
|
||||
t.Errorf("Got unexpected data: %T %v", v, v)
|
||||
b := new(Bitstamp) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(b), "Test instance Setup must not error")
|
||||
testexch.FixtureToDataHandler(t, "testdata/wsMyOrders.json", b.wsHandleData)
|
||||
close(b.Websocket.DataHandler)
|
||||
assert.Len(t, b.Websocket.DataHandler, 8, "Should see 8 orders")
|
||||
for resp := range b.Websocket.DataHandler {
|
||||
switch v := resp.(type) {
|
||||
case *order.Detail:
|
||||
switch len(b.Websocket.DataHandler) {
|
||||
case 7:
|
||||
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
|
||||
assert.Equal(t, time.UnixMicro(1693831262313000), v.Date, "Date")
|
||||
assert.Equal(t, "test_market_buy", v.ClientOrderID, "ClientOrderID")
|
||||
assert.Equal(t, order.New, v.Status, "Status")
|
||||
assert.Equal(t, order.Buy, v.Side, "Side")
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USD", "/"), v.Pair, "Pair")
|
||||
assert.Equal(t, 0.0, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 999999999.0, v.Price, "Price") // Market Buy Price
|
||||
// Note: Amount is 0 for market order create messages, oddly
|
||||
case 6:
|
||||
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
|
||||
assert.Equal(t, 0.00038667, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount") // During live tests we consistently got back this Sat remaining
|
||||
assert.Equal(t, 0.00038666, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 25862.0, v.Price, "Price")
|
||||
case 5:
|
||||
assert.Equal(t, "1658864794234880", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Cancelled, v.Status, "Status") // Even though they probably consider it filled, Deleted + PartialFill = Cancelled
|
||||
assert.Equal(t, 0.00038667, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038666, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 25862.0, v.Price, "Price")
|
||||
case 4:
|
||||
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.New, v.Status, "Status")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, 0.0, v.Price, "Price") // Market Sell Price
|
||||
case 3:
|
||||
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
|
||||
assert.Equal(t, 0.00038679, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038678, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 25854.0, v.Price, "Price")
|
||||
case 2:
|
||||
assert.Equal(t, "1658870500933632", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Cancelled, v.Status, "Status")
|
||||
assert.Equal(t, 0.00038679, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.00000001, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038678, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 25854.0, v.Price, "Price")
|
||||
case 1:
|
||||
assert.Equal(t, "1658869033291777", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.New, v.Status, "Status")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, 25845.0, v.Price, "Price")
|
||||
assert.Equal(t, 0.00038692, v.Amount, "Amount")
|
||||
case 0:
|
||||
assert.Equal(t, "1658869033291777", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Filled, v.Status, "Status")
|
||||
assert.Equal(t, 25845.0, v.Price, "Price")
|
||||
assert.Equal(t, 0.00038692, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038692, v.ExecutedAmount, "ExecutedAmount")
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
default:
|
||||
t.Errorf("Got unexpected data: %T %v", v, v)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 8, seen, "Number of messages")
|
||||
}
|
||||
|
||||
func TestWsRequestReconnect(t *testing.T) {
|
||||
|
||||
@@ -55,10 +55,6 @@ var (
|
||||
errEndpointStringNotFound = errors.New("endpoint string not found")
|
||||
errConfigPairFormatRequiresDelimiter = errors.New("config pair format requires delimiter")
|
||||
errSymbolCannotBeMatched = errors.New("symbol cannot be matched")
|
||||
errGlobalRequestFormatIsNil = errors.New("global request format is nil")
|
||||
errGlobalConfigFormatIsNil = errors.New("global config format is nil")
|
||||
errAssetRequestFormatIsNil = errors.New("asset type request format is nil")
|
||||
errAssetConfigFormatIsNil = errors.New("asset type config format is nil")
|
||||
errSetDefaultsNotCalled = errors.New("set defaults not called")
|
||||
errExchangeIsNil = errors.New("exchange is nil")
|
||||
errBatchSizeZero = errors.New("batch size cannot be 0")
|
||||
@@ -397,39 +393,9 @@ func (b *Base) GetSupportedFeatures() FeaturesSupported {
|
||||
return b.Features.Supports
|
||||
}
|
||||
|
||||
// GetPairFormat returns the pair format based on the exchange and
|
||||
// asset type
|
||||
func (b *Base) GetPairFormat(assetType asset.Item, requestFormat bool) (currency.PairFormat, error) {
|
||||
if b.CurrencyPairs.UseGlobalFormat {
|
||||
if requestFormat {
|
||||
if b.CurrencyPairs.RequestFormat == nil {
|
||||
return currency.EMPTYFORMAT, errGlobalRequestFormatIsNil
|
||||
}
|
||||
return *b.CurrencyPairs.RequestFormat, nil
|
||||
}
|
||||
|
||||
if b.CurrencyPairs.ConfigFormat == nil {
|
||||
return currency.EMPTYFORMAT, errGlobalConfigFormatIsNil
|
||||
}
|
||||
return *b.CurrencyPairs.ConfigFormat, nil
|
||||
}
|
||||
|
||||
ps, err := b.CurrencyPairs.Get(assetType)
|
||||
if err != nil {
|
||||
return currency.EMPTYFORMAT, err
|
||||
}
|
||||
|
||||
if requestFormat {
|
||||
if ps.RequestFormat == nil {
|
||||
return currency.EMPTYFORMAT, errAssetRequestFormatIsNil
|
||||
}
|
||||
return *ps.RequestFormat, nil
|
||||
}
|
||||
|
||||
if ps.ConfigFormat == nil {
|
||||
return currency.EMPTYFORMAT, errAssetConfigFormatIsNil
|
||||
}
|
||||
return *ps.ConfigFormat, nil
|
||||
// GetPairFormat returns the pair format based on the exchange and asset type
|
||||
func (b *Base) GetPairFormat(a asset.Item, r bool) (currency.PairFormat, error) {
|
||||
return b.CurrencyPairs.GetFormat(a, r)
|
||||
}
|
||||
|
||||
// GetEnabledPairs is a method that returns the enabled currency pairs of
|
||||
@@ -1007,11 +973,9 @@ func (b *Base) FormatWithdrawPermissions() string {
|
||||
return NoAPIWithdrawalMethodsText
|
||||
}
|
||||
|
||||
// SupportsAsset whether or not the supplied asset is supported
|
||||
// by the exchange
|
||||
// SupportsAsset whether or not the supplied asset is supported by the exchange
|
||||
func (b *Base) SupportsAsset(a asset.Item) bool {
|
||||
_, ok := b.CurrencyPairs.Pairs[a]
|
||||
return ok
|
||||
return b.CurrencyPairs.IsAssetSupported(a)
|
||||
}
|
||||
|
||||
// PrintEnabledPairs prints the exchanges enabled asset pairs
|
||||
@@ -1082,12 +1046,10 @@ func (b *Base) StoreAssetPairFormat(a asset.Item, f currency.PairStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetGlobalPairsManager sets defined asset and pairs management system with
|
||||
// global formatting
|
||||
// SetGlobalPairsManager sets defined asset and pairs management system with global formatting
|
||||
func (b *Base) SetGlobalPairsManager(request, config *currency.PairFormat, assets ...asset.Item) error {
|
||||
if request == nil {
|
||||
return fmt.Errorf("%s cannot set pairs manager, request pair format not provided",
|
||||
b.Name)
|
||||
return fmt.Errorf("%s cannot set pairs manager, request pair format not provided", b.Name)
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
@@ -1119,10 +1081,10 @@ func (b *Base) SetGlobalPairsManager(request, config *currency.PairFormat, asset
|
||||
for i := range assets {
|
||||
if assets[i].String() == "" {
|
||||
b.CurrencyPairs.Pairs = nil
|
||||
return fmt.Errorf("%s cannot set pairs manager, asset is empty string",
|
||||
b.Name)
|
||||
return fmt.Errorf("%s cannot set pairs manager, asset is empty string", b.Name)
|
||||
}
|
||||
b.CurrencyPairs.Pairs[assets[i]] = new(currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[assets[i]].AssetEnabled = convert.BoolPtr(true)
|
||||
b.CurrencyPairs.Pairs[assets[i]].ConfigFormat = config
|
||||
b.CurrencyPairs.Pairs[assets[i]].RequestFormat = request
|
||||
}
|
||||
|
||||
@@ -696,56 +696,12 @@ func TestGetFeatures(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPairFormat ensures that GetPairFormat delegates to PairsManager.GetFormat
|
||||
func TestGetPairFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test global formatting
|
||||
var b Base
|
||||
b.CurrencyPairs.UseGlobalFormat = true
|
||||
b.CurrencyPairs.ConfigFormat = ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
}
|
||||
b.CurrencyPairs.RequestFormat = ¤cy.PairFormat{
|
||||
Delimiter: "~",
|
||||
}
|
||||
pFmt, err := b.GetPairFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pFmt.Delimiter != "~" && !pFmt.Uppercase {
|
||||
t.Error("incorrect pair format values")
|
||||
}
|
||||
pFmt, err = b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pFmt.Delimiter != "" && pFmt.Uppercase {
|
||||
t.Error("incorrect pair format values")
|
||||
}
|
||||
|
||||
// Test individual asset pair store formatting
|
||||
b.CurrencyPairs.UseGlobalFormat = false
|
||||
err = b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{
|
||||
ConfigFormat: &pFmt,
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "/", Uppercase: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pFmt, err = b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pFmt.Delimiter != "" && pFmt.Uppercase {
|
||||
t.Error("incorrect pair format values")
|
||||
}
|
||||
pFmt, err = b.GetPairFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pFmt.Delimiter != "~" && !pFmt.Uppercase {
|
||||
t.Error("incorrect pair format values")
|
||||
}
|
||||
_, err := new(Base).GetPairFormat(asset.Spot, true)
|
||||
require.ErrorIs(t, err, asset.ErrNotSupported, "Must delegate to GetFormat and error")
|
||||
}
|
||||
|
||||
func TestGetPairs(t *testing.T) {
|
||||
@@ -778,6 +734,7 @@ func TestGetPairs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFormatExchangeCurrencies exercises FormatExchangeCurrencies
|
||||
func TestFormatExchangeCurrencies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -797,32 +754,18 @@ func TestFormatExchangeCurrencies(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
p1, err := currency.NewPairDelimiter("BTC_USD", "_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p2, err := currency.NewPairDelimiter("LTC_BTC", "_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var pairs = []currency.Pair{
|
||||
p1,
|
||||
p2,
|
||||
currency.NewPairWithDelimiter("BTC", "USD", "_"),
|
||||
currency.NewPairWithDelimiter("LTC", "BTC", "_"),
|
||||
}
|
||||
|
||||
actual, err := e.FormatExchangeCurrencies(pairs, asset.Spot)
|
||||
if err != nil {
|
||||
t.Errorf("Exchange TestFormatExchangeCurrencies error %s", err)
|
||||
}
|
||||
if expected := "btc~usd^ltc~btc"; actual != expected {
|
||||
t.Errorf("Exchange TestFormatExchangeCurrencies %s != %s",
|
||||
actual, expected)
|
||||
}
|
||||
got, err := e.FormatExchangeCurrencies(pairs, asset.Spot)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "btc~usd^ltc~btc", got)
|
||||
|
||||
_, err = e.FormatExchangeCurrencies(nil, asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("nil pairs should return an error")
|
||||
}
|
||||
assert.ErrorContains(t, err, "returned empty string", err, "FormatExchangeCurrencies should error correctly")
|
||||
}
|
||||
|
||||
func TestFormatExchangeCurrency(t *testing.T) {
|
||||
@@ -1342,14 +1285,12 @@ func TestSupportsAsset(t *testing.T) {
|
||||
t.Parallel()
|
||||
var b Base
|
||||
b.CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {},
|
||||
}
|
||||
if !b.SupportsAsset(asset.Spot) {
|
||||
t.Error("spot should be supported")
|
||||
}
|
||||
if b.SupportsAsset(asset.Index) {
|
||||
t.Error("index shouldn't be supported")
|
||||
asset.Spot: {
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
},
|
||||
}
|
||||
assert.True(t, b.SupportsAsset(asset.Spot), "Spot should be supported")
|
||||
assert.False(t, b.SupportsAsset(asset.Index), "Index should not be supported")
|
||||
}
|
||||
|
||||
func TestPrintEnabledPairs(t *testing.T) {
|
||||
@@ -1494,58 +1435,36 @@ func TestStoreAssetPairFormat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetGlobalPairsManager(t *testing.T) {
|
||||
b := Base{
|
||||
Config: &config.Exchange{Name: "kitties"},
|
||||
}
|
||||
b := Base{Config: &config.Exchange{Name: "kitties"}}
|
||||
|
||||
err := b.SetGlobalPairsManager(nil, nil, asset.Empty)
|
||||
if err == nil {
|
||||
t.Error("error cannot be nil")
|
||||
}
|
||||
assert.ErrorContains(t, err, "cannot set pairs manager, request pair format not provided")
|
||||
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, nil, asset.Empty)
|
||||
if err == nil {
|
||||
t.Error("error cannot be nil")
|
||||
}
|
||||
assert.ErrorContains(t, err, "cannot set pairs manager, config pair format not provided")
|
||||
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true},
|
||||
¤cy.PairFormat{Uppercase: true})
|
||||
if err == nil {
|
||||
t.Error("error cannot be nil")
|
||||
}
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, ¤cy.PairFormat{Uppercase: true})
|
||||
assert.ErrorContains(t, err, " cannot set pairs manager, no assets provided")
|
||||
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true},
|
||||
¤cy.PairFormat{Uppercase: true}, asset.Empty)
|
||||
if err == nil {
|
||||
t.Error("error cannot be nil")
|
||||
}
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, ¤cy.PairFormat{Uppercase: true}, asset.Empty)
|
||||
assert.ErrorContains(t, err, " cannot set global pairs manager config pair format requires delimiter for assets")
|
||||
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true},
|
||||
¤cy.PairFormat{Uppercase: true},
|
||||
asset.Spot,
|
||||
asset.Binary)
|
||||
if !errors.Is(err, errConfigPairFormatRequiresDelimiter) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errConfigPairFormatRequiresDelimiter)
|
||||
}
|
||||
assert.ErrorIs(t, err, errConfigPairFormatRequiresDelimiter)
|
||||
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true},
|
||||
¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter},
|
||||
asset.Spot,
|
||||
asset.Binary)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}, asset.Spot, asset.Binary)
|
||||
require.NoError(t, err, "SetGlobalPairsManager must not error")
|
||||
|
||||
if !b.SupportsAsset(asset.Binary) || !b.SupportsAsset(asset.Spot) {
|
||||
t.Fatal("global pairs manager not set correctly")
|
||||
}
|
||||
assert.True(t, b.SupportsAsset(asset.Binary), "Pairs Manager must support Binary")
|
||||
assert.True(t, b.SupportsAsset(asset.Spot), "Pairs Manager must support Spot")
|
||||
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true},
|
||||
¤cy.PairFormat{Uppercase: true}, asset.Spot, asset.Binary)
|
||||
if err == nil {
|
||||
t.Error("error cannot be nil")
|
||||
}
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, ¤cy.PairFormat{Uppercase: true}, asset.Spot, asset.Binary)
|
||||
assert.ErrorIs(t, err, errConfigPairFormatRequiresDelimiter, "SetGlobalPairsManager should error correctly")
|
||||
}
|
||||
|
||||
func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -2366,9 +2285,7 @@ func TestGetKlineRequest(t *testing.T) {
|
||||
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.IntervalCapacity{Interval: kline.OneMin})
|
||||
b.Features.Enabled.Kline.GlobalResultLimit = 1439
|
||||
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneHour, time.Time{}, time.Time{}, false)
|
||||
if !errors.Is(err, errAssetRequestFormatIsNil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errAssetRequestFormatIsNil)
|
||||
}
|
||||
assert.ErrorIs(t, err, currency.ErrPairFormatIsNil, "GetKlineRequest should return Format is Nil")
|
||||
|
||||
err = b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
@@ -2533,9 +2450,7 @@ func TestGetKlineExtendedRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = b.GetKlineExtendedRequest(pair, asset.Spot, kline.OneHour, start, end)
|
||||
if !errors.Is(err, errAssetRequestFormatIsNil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errAssetRequestFormatIsNil)
|
||||
}
|
||||
assert.ErrorIs(t, err, currency.ErrPairFormatIsNil, "GetKlineExtendedRequest should error correctly")
|
||||
|
||||
err = b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
|
||||
@@ -3753,8 +3753,12 @@ func (g *Gateio) IsValidPairString(currencyPair string) bool {
|
||||
if len(currencyPair) < 3 {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(currencyPair, g.CurrencyPairs.RequestFormat.Delimiter) {
|
||||
result := strings.Split(currencyPair, g.CurrencyPairs.RequestFormat.Delimiter)
|
||||
pf, err := g.CurrencyPairs.GetFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(currencyPair, pf.Delimiter) {
|
||||
result := strings.Split(currencyPair, pf.Delimiter)
|
||||
return len(result) >= 2
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -130,31 +130,28 @@ func TestUpdateTicker(t *testing.T) {
|
||||
|
||||
func TestUpdateTickers(t *testing.T) {
|
||||
t.Parallel()
|
||||
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(k), "Test instance Setup must not error")
|
||||
testexch.UpdatePairsOnce(t, k)
|
||||
err := k.UpdateTickers(context.Background(), asset.Spot)
|
||||
require.NoError(t, err, "UpdateTickers must not error")
|
||||
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)
|
||||
require.NoError(t, err, "GetAvailablePairs must not error")
|
||||
for i := range ap {
|
||||
_, err = ticker.GetTicker(k.Name, ap[i], asset.Spot)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err, "GetTicker must not error")
|
||||
}
|
||||
|
||||
ap, err = k.GetAvailablePairs(asset.Futures)
|
||||
require.NoError(t, err)
|
||||
err = k.CurrencyPairs.StorePairs(asset.Futures, ap, true)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err, "GetAvailablePairs must not error")
|
||||
err = k.UpdateTickers(context.Background(), asset.Futures)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err, "UpdateTickers must not error")
|
||||
for i := range ap {
|
||||
_, err = ticker.GetTicker(k.Name, ap[i], asset.Futures)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err, "GetTicker must not error")
|
||||
}
|
||||
|
||||
err = k.UpdateTickers(context.Background(), asset.Index)
|
||||
assert.ErrorIs(t, err, asset.ErrNotSupported)
|
||||
assert.ErrorIs(t, err, asset.ErrNotSupported, "UpdateTickers should error correctly on Index asset")
|
||||
}
|
||||
|
||||
func TestUpdateOrderbook(t *testing.T) {
|
||||
@@ -1827,73 +1824,68 @@ func TestWsOwnTrades(t *testing.T) {
|
||||
|
||||
func TestWsOpenOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
n := new(Kraken)
|
||||
k := new(Kraken) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(k), "Test instance Setup must not error")
|
||||
testexch.UpdatePairsOnce(t, k)
|
||||
sharedtestvalues.TestFixtureToDataHandler(t, k, n, "testdata/wsOpenTrades.json", n.wsHandleData)
|
||||
seen := 0
|
||||
for reading := true; reading; {
|
||||
select {
|
||||
default:
|
||||
reading = false
|
||||
case resp := <-n.Websocket.DataHandler:
|
||||
seen++
|
||||
switch v := resp.(type) {
|
||||
case *order.Detail:
|
||||
switch seen {
|
||||
case 1:
|
||||
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Limit, v.Type, "order type")
|
||||
assert.Equal(t, order.Sell, v.Side, "order side")
|
||||
assert.Equal(t, order.Open, v.Status, "order status")
|
||||
assert.Equal(t, 34.5, v.Price, "price")
|
||||
assert.Equal(t, 10.00345345, v.Amount, "amount")
|
||||
case 2:
|
||||
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Market, v.Type, "order type")
|
||||
assert.Equal(t, order.Buy, v.Side, "order side")
|
||||
assert.Equal(t, order.Pending, v.Status, "order status")
|
||||
assert.Equal(t, 0.0, v.Price, "price")
|
||||
assert.Equal(t, 0.0001, v.Amount, "amount")
|
||||
assert.Equal(t, time.UnixMicro(1692851641361371), v.Date, "Date")
|
||||
case 3:
|
||||
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Open, v.Status, "order status")
|
||||
case 4:
|
||||
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
|
||||
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount") // Not in the message; Testing regression to bad derivation
|
||||
assert.Equal(t, 0.00687, v.Fee, "Fee")
|
||||
case 5:
|
||||
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Closed, v.Status, "order status")
|
||||
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
assert.Equal(t, 0.00687, v.Fee, "Fee")
|
||||
assert.Equal(t, time.UnixMicro(1692851641361447), v.LastUpdated, "LastUpdated")
|
||||
case 6:
|
||||
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
|
||||
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.001, v.Fee, "Fee")
|
||||
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
case 7:
|
||||
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Closed, v.Status, "order status")
|
||||
assert.Equal(t, time.UnixMicro(1692675961789052), v.LastUpdated, "LastUpdated")
|
||||
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.001, v.Fee, "Fee")
|
||||
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
reading = false
|
||||
}
|
||||
default:
|
||||
t.Errorf("Unexpected type in DataHandler: %T (%s)", v, v)
|
||||
testexch.FixtureToDataHandler(t, "testdata/wsOpenTrades.json", k.wsHandleData)
|
||||
close(k.Websocket.DataHandler)
|
||||
assert.Len(t, k.Websocket.DataHandler, 7, "Should see 7 orders")
|
||||
for resp := range k.Websocket.DataHandler {
|
||||
switch v := resp.(type) {
|
||||
case *order.Detail:
|
||||
switch len(k.Websocket.DataHandler) {
|
||||
case 6:
|
||||
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Limit, v.Type, "order type")
|
||||
assert.Equal(t, order.Sell, v.Side, "order side")
|
||||
assert.Equal(t, order.Open, v.Status, "order status")
|
||||
assert.Equal(t, 34.5, v.Price, "price")
|
||||
assert.Equal(t, 10.00345345, v.Amount, "amount")
|
||||
case 5:
|
||||
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Market, v.Type, "order type")
|
||||
assert.Equal(t, order.Buy, v.Side, "order side")
|
||||
assert.Equal(t, order.Pending, v.Status, "order status")
|
||||
assert.Equal(t, 0.0, v.Price, "price")
|
||||
assert.Equal(t, 0.0001, v.Amount, "amount")
|
||||
assert.Equal(t, time.UnixMicro(1692851641361371), v.Date, "Date")
|
||||
case 4:
|
||||
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Open, v.Status, "order status")
|
||||
case 3:
|
||||
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
|
||||
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount") // Not in the message; Testing regression to bad derivation
|
||||
assert.Equal(t, 0.00687, v.Fee, "Fee")
|
||||
case 2:
|
||||
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Closed, v.Status, "order status")
|
||||
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
assert.Equal(t, 0.00687, v.Fee, "Fee")
|
||||
assert.Equal(t, time.UnixMicro(1692851641361447), v.LastUpdated, "LastUpdated")
|
||||
case 1:
|
||||
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
|
||||
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.001, v.Fee, "Fee")
|
||||
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
case 0:
|
||||
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
|
||||
assert.Equal(t, order.Closed, v.Status, "order status")
|
||||
assert.Equal(t, time.UnixMicro(1692675961789052), v.LastUpdated, "LastUpdated")
|
||||
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.001, v.Fee, "Fee")
|
||||
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
default:
|
||||
t.Errorf("Unexpected type in DataHandler: %T (%s)", v, v)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, 7, seen, "number of DataHandler emissions")
|
||||
}
|
||||
|
||||
func TestWsAddOrderJSON(t *testing.T) {
|
||||
|
||||
@@ -1988,8 +1988,9 @@ func TestGetAuthenticatedServersInstances(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPushData(t *testing.T) {
|
||||
n := new(Kucoin)
|
||||
sharedtestvalues.TestFixtureToDataHandler(t, ku, n, "testdata/wsHandleData.json", ku.wsHandleData)
|
||||
t.Parallel()
|
||||
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
testexch.FixtureToDataHandler(t, "testdata/wsHandleData.json", ku.wsHandleData)
|
||||
}
|
||||
|
||||
func verifySubs(tb testing.TB, subs []subscription.Subscription, a asset.Item, prefix string, expected ...string) {
|
||||
@@ -2049,14 +2050,10 @@ func TestGenerateDefaultSubscriptions(t *testing.T) {
|
||||
func TestGenerateAuthSubscriptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a parallel safe Kucoin to mess with
|
||||
nu := new(Kucoin)
|
||||
nu.Base.Features = ku.Base.Features
|
||||
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
|
||||
nu.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
nu.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
ku.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
|
||||
subs, err := nu.GenerateDefaultSubscriptions()
|
||||
subs, err := ku.GenerateDefaultSubscriptions()
|
||||
assert.NoError(t, err, "GenerateDefaultSubscriptions with Auth should not error")
|
||||
assert.Len(t, subs, 24, "Should generate the correct number of subs when logged in")
|
||||
|
||||
@@ -2086,17 +2083,12 @@ func TestGenerateAuthSubscriptions(t *testing.T) {
|
||||
func TestGenerateCandleSubscription(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a parallel safe Kucoin to mess with
|
||||
nu := new(Kucoin)
|
||||
nu.Base.Features = ku.Base.Features
|
||||
nu.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
|
||||
|
||||
nu.Features.Subscriptions = []*subscription.Subscription{
|
||||
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
ku.Features.Subscriptions = []*subscription.Subscription{
|
||||
{Channel: subscription.CandlesChannel, Interval: kline.FourHour},
|
||||
}
|
||||
|
||||
subs, err := nu.GenerateDefaultSubscriptions()
|
||||
subs, err := ku.GenerateDefaultSubscriptions()
|
||||
assert.NoError(t, err, "GenerateDefaultSubscriptions with Candles should not error")
|
||||
|
||||
assert.Len(t, subs, 6, "Should generate the correct number of subs for candles")
|
||||
@@ -2111,17 +2103,12 @@ func TestGenerateCandleSubscription(t *testing.T) {
|
||||
func TestGenerateMarketSubscription(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a parallel safe Kucoin to mess with
|
||||
nu := new(Kucoin)
|
||||
nu.Base.Features = ku.Base.Features
|
||||
nu.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
assert.NoError(t, nu.CurrencyPairs.Load(&ku.CurrencyPairs), "Loading Pairs should not error")
|
||||
|
||||
nu.Features.Subscriptions = []*subscription.Subscription{
|
||||
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
ku.Features.Subscriptions = []*subscription.Subscription{
|
||||
{Channel: marketSnapshotChannel},
|
||||
}
|
||||
|
||||
subs, err := nu.GenerateDefaultSubscriptions()
|
||||
subs, err := ku.GenerateDefaultSubscriptions()
|
||||
assert.NoError(t, err, "GenerateDefaultSubscriptions with MarketSnapshot should not error")
|
||||
|
||||
assert.Len(t, subs, 7, "Should generate the correct number of subs for snapshot")
|
||||
@@ -2490,59 +2477,51 @@ func TestProcessOrderbook(t *testing.T) {
|
||||
|
||||
func TestProcessMarketSnapshot(t *testing.T) {
|
||||
t.Parallel()
|
||||
n := new(Kucoin)
|
||||
sharedtestvalues.TestFixtureToDataHandler(t, ku, n, "testdata/wsMarketSnapshot.json", n.wsHandleData)
|
||||
seen := 0
|
||||
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
testexch.FixtureToDataHandler(t, "testdata/wsMarketSnapshot.json", ku.wsHandleData)
|
||||
close(ku.Websocket.DataHandler)
|
||||
assert.Len(t, ku.Websocket.DataHandler, 4, "Should see 4 tickers")
|
||||
seenAssetTypes := map[asset.Item]int{}
|
||||
for reading := true; reading; {
|
||||
select {
|
||||
default:
|
||||
reading = false
|
||||
case resp := <-n.GetBase().Websocket.DataHandler:
|
||||
seen++
|
||||
switch v := resp.(type) {
|
||||
case *ticker.Price:
|
||||
switch seen {
|
||||
case 1:
|
||||
assert.Equal(t, asset.Margin, v.AssetType, "AssetType")
|
||||
assert.Equal(t, time.UnixMilli(1700555342007), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 0.004445, v.High, "high")
|
||||
assert.Equal(t, 0.004415, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.004191, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("TRX", "BTC", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 13097.3357, v.Volume, "volume")
|
||||
assert.Equal(t, 57.44552981, v.QuoteVolume, "volValue")
|
||||
case 2, 3:
|
||||
assert.Equal(t, time.UnixMilli(1700555340197), v.LastUpdated, "datetime")
|
||||
assert.Contains(t, []asset.Item{asset.Spot, asset.Margin}, v.AssetType, "AssetType is Spot or Margin")
|
||||
seenAssetTypes[v.AssetType]++
|
||||
assert.Equal(t, 1, seenAssetTypes[v.AssetType], "Each Asset Type is sent only once per unique snapshot")
|
||||
assert.Equal(t, 0.054846, v.High, "high")
|
||||
assert.Equal(t, 0.053778, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.05364, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("ETH", "BTC", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 2958.3139116, v.Volume, "volume")
|
||||
assert.Equal(t, 160.7847672784213, v.QuoteVolume, "volValue")
|
||||
case 4:
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, time.UnixMilli(1700555342151), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 37750.0, v.High, "high")
|
||||
assert.Equal(t, 37366.8, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 36700.0, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 2900.37846402, v.Volume, "volume")
|
||||
assert.Equal(t, 108210331.34015164, v.QuoteVolume, "volValue")
|
||||
default:
|
||||
t.Errorf("Got an unexpected *ticker.Price: %v", v)
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
default:
|
||||
t.Errorf("Got unexpected data: %T %v", v, v)
|
||||
for resp := range ku.Websocket.DataHandler {
|
||||
switch v := resp.(type) {
|
||||
case *ticker.Price:
|
||||
switch len(ku.Websocket.DataHandler) {
|
||||
case 3:
|
||||
assert.Equal(t, asset.Margin, v.AssetType, "AssetType")
|
||||
assert.Equal(t, time.UnixMilli(1700555342007), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 0.004445, v.High, "high")
|
||||
assert.Equal(t, 0.004415, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.004191, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("TRX", "BTC", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 13097.3357, v.Volume, "volume")
|
||||
assert.Equal(t, 57.44552981, v.QuoteVolume, "volValue")
|
||||
case 2, 1:
|
||||
assert.Equal(t, time.UnixMilli(1700555340197), v.LastUpdated, "datetime")
|
||||
assert.Contains(t, []asset.Item{asset.Spot, asset.Margin}, v.AssetType, "AssetType is Spot or Margin")
|
||||
seenAssetTypes[v.AssetType]++
|
||||
assert.Equal(t, 1, seenAssetTypes[v.AssetType], "Each Asset Type is sent only once per unique snapshot")
|
||||
assert.Equal(t, 0.054846, v.High, "high")
|
||||
assert.Equal(t, 0.053778, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 0.05364, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("ETH", "BTC", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 2958.3139116, v.Volume, "volume")
|
||||
assert.Equal(t, 160.7847672784213, v.QuoteVolume, "volValue")
|
||||
case 0:
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, time.UnixMilli(1700555342151), v.LastUpdated, "datetime")
|
||||
assert.Equal(t, 37750.0, v.High, "high")
|
||||
assert.Equal(t, 37366.8, v.Last, "lastTradedPrice")
|
||||
assert.Equal(t, 36700.0, v.Low, "low")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "symbol")
|
||||
assert.Equal(t, 2900.37846402, v.Volume, "volume")
|
||||
assert.Equal(t, 108210331.34015164, v.QuoteVolume, "volValue")
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
default:
|
||||
t.Errorf("Got unexpected data: %T %v", v, v)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 4, seen, "Number of messages")
|
||||
}
|
||||
|
||||
func TestSubscribeMarketSnapshot(t *testing.T) {
|
||||
@@ -2735,16 +2714,16 @@ func TestUpdateOrderExecutionLimits(t *testing.T) {
|
||||
func TestGetOpenInterest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nu := new(Kucoin)
|
||||
require.NoError(t, testexch.Setup(nu), "Test exchange Setup must not error")
|
||||
_, err := nu.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
|
||||
_, err := ku.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: currency.ETH.Item,
|
||||
Quote: currency.USDT.Item,
|
||||
Asset: asset.USDTMarginedFutures,
|
||||
})
|
||||
assert.ErrorIs(t, err, asset.ErrNotSupported)
|
||||
|
||||
resp, err := nu.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
resp, err := ku.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: futuresTradablePair.Base.Item,
|
||||
Quote: futuresTradablePair.Quote.Item,
|
||||
Asset: asset.Futures,
|
||||
@@ -2753,8 +2732,8 @@ func TestGetOpenInterest(t *testing.T) {
|
||||
assert.NotEmpty(t, resp)
|
||||
|
||||
cp1 := currency.NewPair(currency.ETH, currency.USDTM)
|
||||
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, nu, asset.Futures, cp1)
|
||||
resp, err = nu.GetOpenInterest(context.Background(),
|
||||
sharedtestvalues.SetupCurrencyPairsForExchangeAsset(t, ku, asset.Futures, cp1)
|
||||
resp, err = ku.GetOpenInterest(context.Background(),
|
||||
key.PairAsset{
|
||||
Base: futuresTradablePair.Base.Item,
|
||||
Quote: futuresTradablePair.Quote.Item,
|
||||
@@ -2769,7 +2748,7 @@ func TestGetOpenInterest(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
|
||||
resp, err = nu.GetOpenInterest(context.Background())
|
||||
resp, err = ku.GetOpenInterest(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
}
|
||||
@@ -2786,3 +2765,15 @@ func TestGetCurrencyTradeURL(t *testing.T) {
|
||||
assert.NotEmpty(t, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// testInstance returns a local Kucoin for isolated testing
|
||||
func testInstance(tb testing.TB) *Kucoin {
|
||||
tb.Helper()
|
||||
ku := new(Kucoin)
|
||||
require.NoError(tb, testexch.Setup(ku), "Test instance Setup must not error")
|
||||
ku.obm = &orderbookManager{
|
||||
state: make(map[currency.Code]map[currency.Code]map[asset.Item]*update),
|
||||
jobs: make(chan job, maxWSOrderbookJobs),
|
||||
}
|
||||
return ku
|
||||
}
|
||||
|
||||
@@ -3065,15 +3065,6 @@ func (ok *Okx) GetUnderlying(pair currency.Pair, a asset.Item) (string, error) {
|
||||
return pair.Base.String() + format.Delimiter + pair.Quote.String(), nil
|
||||
}
|
||||
|
||||
// GetPairFromInstrumentID returns a currency pair give an instrument ID and asset Item, which represents the instrument type.
|
||||
func (ok *Okx) GetPairFromInstrumentID(instrumentID string) (currency.Pair, error) {
|
||||
codes := strings.Split(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
|
||||
if len(codes) >= 2 {
|
||||
instrumentID = codes[0] + ok.CurrencyPairs.RequestFormat.Delimiter + strings.Join(codes[1:], ok.CurrencyPairs.RequestFormat.Delimiter)
|
||||
}
|
||||
return currency.NewPairFromString(instrumentID)
|
||||
}
|
||||
|
||||
// GetOrderBookDepth returns the recent order asks and bids before specified timestamp.
|
||||
func (ok *Okx) GetOrderBookDepth(ctx context.Context, instrumentID string, depth int64) (*OrderBookResponse, error) {
|
||||
params := url.Values{}
|
||||
@@ -4320,11 +4311,15 @@ func (ok *Okx) GetAssetsFromInstrumentTypeOrID(instType, instrumentID string) ([
|
||||
if instrumentID == "" {
|
||||
return nil, fmt.Errorf("%w instrumentID", errEmptyArgument)
|
||||
}
|
||||
splitSymbol := strings.Split(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
|
||||
pf, err := ok.CurrencyPairs.GetFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
splitSymbol := strings.Split(instrumentID, pf.Delimiter)
|
||||
if len(splitSymbol) <= 1 {
|
||||
return nil, fmt.Errorf("%w %v", currency.ErrCurrencyNotSupported, instrumentID)
|
||||
}
|
||||
pair, err := currency.NewPairDelimiter(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
|
||||
pair, err := currency.NewPairDelimiter(instrumentID, pf.Delimiter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2206,24 +2206,6 @@ func TestWithdraw(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPairFromInstrumentID(t *testing.T) {
|
||||
t.Parallel()
|
||||
instruments := []string{
|
||||
"BTC-USDT",
|
||||
"BTC-USDT-SWAP",
|
||||
"BTC-USDT-ER33234",
|
||||
}
|
||||
if _, err := ok.GetPairFromInstrumentID(instruments[0]); err != nil {
|
||||
t.Error("Okx GetPairFromInstrumentID() error", err)
|
||||
}
|
||||
if _, ere := ok.GetPairFromInstrumentID(instruments[1]); ere != nil {
|
||||
t.Error("Okx GetPairFromInstrumentID() error", ere)
|
||||
}
|
||||
if _, erf := ok.GetPairFromInstrumentID(instruments[2]); erf != nil {
|
||||
t.Error("Okx GetPairFromInstrumentID() error", erf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok)
|
||||
@@ -2534,68 +2516,63 @@ func TestBalanceAndPosition(t *testing.T) {
|
||||
|
||||
func TestOrderPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
n := new(Okx)
|
||||
sharedtestvalues.TestFixtureToDataHandler(t, ok, n, "testdata/wsOrders.json", n.WsHandleData)
|
||||
seen := 0
|
||||
for reading := true; reading; {
|
||||
select {
|
||||
default:
|
||||
reading = false
|
||||
case resp := <-n.GetBase().Websocket.DataHandler:
|
||||
seen++
|
||||
switch v := resp.(type) {
|
||||
case *order.Detail:
|
||||
switch seen {
|
||||
case 1:
|
||||
assert.Equal(t, "452197707845865472", v.OrderID, "OrderID")
|
||||
assert.Equal(t, "HamsterParty14", v.ClientOrderID, "ClientOrderID")
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, order.Filled, v.Status, "Status")
|
||||
assert.Equal(t, order.Limit, v.Type, "Type")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "Pair")
|
||||
assert.Equal(t, 31527.1, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
assert.Equal(t, time.UnixMilli(1654084334977), v.Date, "Date")
|
||||
assert.Equal(t, time.UnixMilli(1654084353263), v.CloseTime, "CloseTime")
|
||||
assert.Equal(t, 0.001, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.001, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.000, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 31527.1, v.Price, "Price")
|
||||
assert.Equal(t, 0.02522168, v.Fee, "Fee")
|
||||
assert.Equal(t, currency.USDT, v.FeeAsset, "FeeAsset")
|
||||
case 2:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, order.Market, v.Type, "Type")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, order.Active, v.Status, "Status")
|
||||
assert.Equal(t, 0.0, v.Amount, "Amount should be 0 for a market sell")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
case 3:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
assert.Equal(t, 0.00038127046945832905, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
|
||||
case 4:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.00038128, v.Amount, "Amount should be derived because order filled")
|
||||
assert.Equal(t, order.Filled, v.Status, "Status")
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
default:
|
||||
t.Errorf("Got unexpected data: %T %v", v, v)
|
||||
ok := new(Okx) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(ok), "Test instance Setup must not error")
|
||||
testexch.FixtureToDataHandler(t, "testdata/wsOrders.json", ok.WsHandleData)
|
||||
close(ok.Websocket.DataHandler)
|
||||
assert.Len(t, ok.Websocket.DataHandler, 4, "Should see 4 orders")
|
||||
for resp := range ok.Websocket.DataHandler {
|
||||
switch v := resp.(type) {
|
||||
case *order.Detail:
|
||||
switch len(ok.Websocket.DataHandler) {
|
||||
case 3:
|
||||
assert.Equal(t, "452197707845865472", v.OrderID, "OrderID")
|
||||
assert.Equal(t, "HamsterParty14", v.ClientOrderID, "ClientOrderID")
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, order.Filled, v.Status, "Status")
|
||||
assert.Equal(t, order.Limit, v.Type, "Type")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "Pair")
|
||||
assert.Equal(t, 31527.1, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
assert.Equal(t, time.UnixMilli(1654084334977), v.Date, "Date")
|
||||
assert.Equal(t, time.UnixMilli(1654084353263), v.CloseTime, "CloseTime")
|
||||
assert.Equal(t, 0.001, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.001, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.000, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 31527.1, v.Price, "Price")
|
||||
assert.Equal(t, 0.02522168, v.Fee, "Fee")
|
||||
assert.Equal(t, currency.USDT, v.FeeAsset, "FeeAsset")
|
||||
case 2:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, order.Market, v.Type, "Type")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, order.Active, v.Status, "Status")
|
||||
assert.Equal(t, 0.0, v.Amount, "Amount should be 0 for a market sell")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
case 1:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
assert.Equal(t, 0.00038127046945832905, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
|
||||
case 0:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.00038128, v.Amount, "Amount should be derived because order filled")
|
||||
assert.Equal(t, order.Filled, v.Status, "Status")
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
default:
|
||||
t.Errorf("Got unexpected data: %T %v", v, v)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 4, seen, "Saw 4 records")
|
||||
}
|
||||
|
||||
const algoOrdersPushDataJSON = `{"arg": {"channel": "orders-algo","uid": "77982378738415879","instType": "FUTURES","instId": "BTC-USD-200329"},"data": [{"instType": "FUTURES","instId": "BTC-USD-200329","ordId": "312269865356374016","ccy": "BTC","algoId": "1234","px": "999","sz": "3","tdMode": "cross","tgtCcy": "","notionalUsd": "","ordType": "trigger","side": "buy","posSide": "long","state": "live","lever": "20","tpTriggerPx": "","tpTriggerPxType": "","tpOrdPx": "","slTriggerPx": "","slTriggerPxType": "","triggerPx": "99","triggerPxType": "last","ordPx": "12","actualSz": "","actualPx": "","tag": "adadadadad","actualSide": "","triggerTime": "1597026383085","cTime": "1597026383000"}]}`
|
||||
|
||||
@@ -688,7 +688,8 @@ func (ok *Okx) wsProcessIndexCandles(respRaw []byte) error {
|
||||
if len(response.Data) == 0 {
|
||||
return errNoCandlestickDataFound
|
||||
}
|
||||
pair, err := ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
|
||||
|
||||
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -750,7 +751,7 @@ func (ok *Okx) wsProcessOrderbook5(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
pair, err := ok.GetPairFromInstrumentID(resp.Argument.InstrumentID)
|
||||
pair, err := currency.NewPairFromString(resp.Argument.InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -809,13 +810,11 @@ func (ok *Okx) wsProcessOrderBooks(data []byte) error {
|
||||
response.Action != wsOrderbookSnapshot {
|
||||
return errors.New("invalid order book action")
|
||||
}
|
||||
var pair currency.Pair
|
||||
var assets []asset.Item
|
||||
assets, err = ok.GetAssetsFromInstrumentTypeOrID(response.Argument.InstrumentType, response.Argument.InstrumentID)
|
||||
assets, err := ok.GetAssetsFromInstrumentTypeOrID(response.Argument.InstrumentType, response.Argument.InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pair, err = ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
|
||||
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1062,8 +1061,7 @@ func (ok *Okx) wsProcessTrades(data []byte) error {
|
||||
}
|
||||
trades := make([]trade.Data, 0, len(response.Data)*len(assets))
|
||||
for i := range response.Data {
|
||||
var pair currency.Pair
|
||||
pair, err = ok.GetPairFromInstrumentID(response.Data[i].InstrumentID)
|
||||
pair, err := currency.NewPairFromString(response.Data[i].InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1086,7 +1084,6 @@ func (ok *Okx) wsProcessTrades(data []byte) error {
|
||||
// wsProcessOrders handles websocket order push data responses.
|
||||
func (ok *Okx) wsProcessOrders(respRaw []byte) error {
|
||||
var response WsOrderResponse
|
||||
var pair currency.Pair
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1109,7 +1106,7 @@ func (ok *Okx) wsProcessOrders(respRaw []byte) error {
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
pair, err = ok.GetPairFromInstrumentID(response.Data[x].InstrumentID)
|
||||
pair, err := currency.NewPairFromString(response.Data[x].InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1189,7 +1186,7 @@ func (ok *Okx) wsProcessCandles(respRaw []byte) error {
|
||||
if len(response.Data) == 0 {
|
||||
return errNoCandlestickDataFound
|
||||
}
|
||||
pair, err := ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
|
||||
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1260,8 +1257,7 @@ func (ok *Okx) wsProcessTickers(data []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var c currency.Pair
|
||||
c, err = ok.GetPairFromInstrumentID(response.Data[i].InstrumentID)
|
||||
c, err := currency.NewPairFromString(response.Data[i].InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -259,9 +259,13 @@ func (ok *Okx) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.P
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pf, err := ok.CurrencyPairs.GetFormat(a, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pairs := make([]currency.Pair, len(insts))
|
||||
for x := range insts {
|
||||
pairs[x], err = currency.NewPairDelimiter(insts[x].InstrumentID, ok.CurrencyPairs.ConfigFormat.Delimiter)
|
||||
pairs[x], err = currency.NewPairDelimiter(insts[x].InstrumentID, pf.Delimiter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -378,7 +382,7 @@ func (ok *Okx) UpdateTickers(ctx context.Context, assetType asset.Item) error {
|
||||
}
|
||||
|
||||
for y := range ticks {
|
||||
pair, err := ok.GetPairFromInstrumentID(ticks[y].InstrumentID)
|
||||
pair, err := currency.NewPairFromString(ticks[y].InstrumentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1192,8 +1196,7 @@ allOrders:
|
||||
break allOrders
|
||||
}
|
||||
orderSide := orderList[i].Side
|
||||
var pair currency.Pair
|
||||
pair, err = ok.GetPairFromInstrumentID(orderList[i].InstrumentID)
|
||||
pair, err := currency.NewPairFromString(orderList[i].InstrumentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1286,8 +1289,7 @@ allOrders:
|
||||
// reached end of orders to crawl
|
||||
break allOrders
|
||||
}
|
||||
var pair currency.Pair
|
||||
pair, err = ok.GetPairFromInstrumentID(orderList[i].InstrumentID)
|
||||
pair, err := currency.NewPairFromString(orderList[i].InstrumentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package sharedtestvalues
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -9,15 +8,14 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
)
|
||||
|
||||
@@ -64,6 +62,7 @@ func NewTestWebsocket() *stream.Websocket {
|
||||
Subscribe: make(chan []subscription.Subscription, 10),
|
||||
Unsubscribe: make(chan []subscription.Subscription, 10),
|
||||
Match: stream.NewMatch(),
|
||||
Orderbook: buffer.Orderbook{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,38 +153,6 @@ func ForceFileStandard(t *testing.T, pattern string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestFixtureToDataHandler takes a new empty exchange and configures a new websocket handler for it, and squirts the json path contents to it
|
||||
// It accepts a reader function, which is probably e.wsHandleData but could be anything
|
||||
func TestFixtureToDataHandler(t *testing.T, seed, e exchange.IBotExchange, fixturePath string, reader func([]byte) error) {
|
||||
t.Helper()
|
||||
b := e.GetBase()
|
||||
seedBase := seed.GetBase()
|
||||
|
||||
err := b.CurrencyPairs.Load(&seedBase.CurrencyPairs)
|
||||
assert.NoError(t, err, "Loading currency pairs should not error")
|
||||
|
||||
b.Name = "fixture"
|
||||
b.Websocket = &stream.Websocket{
|
||||
Wg: new(sync.WaitGroup),
|
||||
DataHandler: make(chan interface{}, 128),
|
||||
}
|
||||
b.API.Endpoints = b.NewEndpoints()
|
||||
|
||||
fixture, err := os.Open(fixturePath)
|
||||
assert.NoError(t, err, "Opening fixture '%s' should not error", fixturePath)
|
||||
defer func() {
|
||||
assert.NoError(t, fixture.Close(), "Closing the fixture file should not error")
|
||||
}()
|
||||
|
||||
s := bufio.NewScanner(fixture)
|
||||
for s.Scan() {
|
||||
msg := s.Bytes()
|
||||
err := reader(msg)
|
||||
assert.NoErrorf(t, err, "Fixture message should not error:\n%s", msg)
|
||||
}
|
||||
assert.NoError(t, s.Err(), "Fixture Scanner should not error")
|
||||
}
|
||||
|
||||
// SetupCurrencyPairsForExchangeAsset enables an asset for an exchange
|
||||
// and adds the currency pair(s) to the available and enabled list of existing pairs
|
||||
// if it is already enabled or part of the pairs, no error is raised
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package exchange
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -16,6 +18,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/mock"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
@@ -152,6 +155,25 @@ func WsMockUpgrader(tb testing.TB, w http.ResponseWriter, r *http.Request, wsHan
|
||||
}
|
||||
}
|
||||
|
||||
// FixtureToDataHandler squirts the contents of a file to a reader function (probably e.wsHandleData)
|
||||
func FixtureToDataHandler(tb testing.TB, fixturePath string, reader func([]byte) error) {
|
||||
tb.Helper()
|
||||
|
||||
fixture, err := os.Open(fixturePath)
|
||||
assert.NoError(tb, err, "Opening fixture '%s' should not error", fixturePath)
|
||||
defer func() {
|
||||
assert.NoError(tb, fixture.Close(), "Closing the fixture file should not error")
|
||||
}()
|
||||
|
||||
s := bufio.NewScanner(fixture)
|
||||
for s.Scan() {
|
||||
msg := s.Bytes()
|
||||
err := reader(msg)
|
||||
assert.NoErrorf(tb, err, "Fixture message should not error:\n%s", msg)
|
||||
}
|
||||
assert.NoError(tb, s.Err(), "Fixture Scanner should not error")
|
||||
}
|
||||
|
||||
var setupWsMutex sync.Mutex
|
||||
var setupWsOnce = make(map[exchange.IBotExchange]bool)
|
||||
|
||||
@@ -182,21 +204,26 @@ func SetupWs(tb testing.TB, e exchange.IBotExchange) {
|
||||
}
|
||||
|
||||
var updatePairsMutex sync.Mutex
|
||||
var updatePairsOnce = make(map[exchange.IBotExchange]bool)
|
||||
var updatePairsOnce = make(map[string]*currency.PairsManager)
|
||||
|
||||
// UpdatePairsOnce ensures pairs are only updated once in parallel tests
|
||||
// A clone of the cache of the updated pairs is used to populate duplicate requests
|
||||
func UpdatePairsOnce(tb testing.TB, e exchange.IBotExchange) {
|
||||
tb.Helper()
|
||||
|
||||
updatePairsMutex.Lock()
|
||||
defer updatePairsMutex.Unlock()
|
||||
|
||||
if updatePairsOnce[e] {
|
||||
b := e.GetBase()
|
||||
if c, ok := updatePairsOnce[e.GetName()]; ok {
|
||||
b.CurrencyPairs.Load(c)
|
||||
return
|
||||
}
|
||||
|
||||
err := e.UpdateTradablePairs(context.Background(), true)
|
||||
require.NoError(tb, err, "UpdateTradablePairs must not error")
|
||||
|
||||
updatePairsOnce[e] = true
|
||||
cache := new(currency.PairsManager)
|
||||
cache.Load(&b.CurrencyPairs)
|
||||
updatePairsOnce[e.GetName()] = cache
|
||||
}
|
||||
|
||||
10
testdata/configtest.json
vendored
10
testdata/configtest.json
vendored
@@ -667,12 +667,14 @@
|
||||
},
|
||||
"useGlobalFormat": true,
|
||||
"lastUpdated": 1566798411,
|
||||
"assetTypes": [
|
||||
"spot",
|
||||
"futures"
|
||||
],
|
||||
"pairs": {
|
||||
"futures": {
|
||||
"assetEnabled": true,
|
||||
"enabled": "",
|
||||
"available": ""
|
||||
},
|
||||
"spot": {
|
||||
"assetEnabled": true,
|
||||
"enabled": "BTC_JPY,ETH_BTC,BCH_BTC",
|
||||
"available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user