mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-18 15:10:03 +00:00
exchange: upgrade UpdatePair method (#991)
* exchange: upgrade update pair * exchanges: Add enabled string matching and format handling if discrepency is found. * linter: fixes * bithumb: fix tests * BTSE: api change fix ordering * huobi: fix tests * gloriousnits: stage 1 * gloriousnits: stage 2 * currency: more nits * bitmex: add spot and process pairs before currency package call. * bitmex: finished correct orderbook matching and other implementations * linter: fix issue * currency: Fix linter * currency: segregate and protect pair store, update tests * currency/manager: clean code, rm log output * currency: Add store method and make sure formatting stays nil if not stored. * gct: check errors * engine/websocketroutineman: fix tests * bybit: fix duplication bug * huobi: fix test * btse: fix tests? * ob/buffer: fix tests * Update currency/manager.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * glorious: nits strikes again. * exchange: add bypassConfigFormatUpgrades to stop formatting * GLORIOUS LINTER * Update exchanges/bithumb/bithumb_wrapper.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * exchange: fix pair upgrade issue when duplications are in both avail and enabled pairs * linter: fix shadow dec * config: fix test * Update currency/pair_test.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
@@ -201,7 +201,7 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get pair format %v, %w", curr, err)
|
||||
}
|
||||
curr = curr.Format(requestFormat.Delimiter, requestFormat.Uppercase)
|
||||
curr = curr.Format(requestFormat)
|
||||
var avail, enabled currency.Pairs
|
||||
avail, err = exch.GetAvailablePairs(a)
|
||||
if err != nil {
|
||||
|
||||
@@ -172,14 +172,14 @@ func sortSignals(d []data.Handler) (map[currency.Pair]cashCarrySignals, error) {
|
||||
a := l.GetAssetType()
|
||||
switch {
|
||||
case a == asset.Spot:
|
||||
entry := response[l.Pair().Format("", false)]
|
||||
entry := response[l.Pair().Format(currency.EMPTYFORMAT)]
|
||||
entry.spotSignal = d[i]
|
||||
response[l.Pair().Format("", false)] = entry
|
||||
response[l.Pair().Format(currency.EMPTYFORMAT)] = entry
|
||||
case a.IsFutures():
|
||||
u := l.GetUnderlyingPair()
|
||||
entry := response[u.Format("", false)]
|
||||
entry := response[u.Format(currency.EMPTYFORMAT)]
|
||||
entry.futureSignal = d[i]
|
||||
response[u.Format("", false)] = entry
|
||||
response[u.Format(currency.EMPTYFORMAT)] = entry
|
||||
default:
|
||||
return nil, errFuturesOnly
|
||||
}
|
||||
|
||||
@@ -137,11 +137,12 @@ func createFuturesSpotDiffChart(items map[string]map[asset.Item]map[currency.Pai
|
||||
AxisType: "linear",
|
||||
}
|
||||
|
||||
upperFormat := currency.PairFormat{Uppercase: true}
|
||||
for _, assetMap := range items {
|
||||
for item, pairMap := range assetMap {
|
||||
for pair, result := range pairMap {
|
||||
if item.IsFutures() {
|
||||
p := result.UnderlyingPair.Format("", true)
|
||||
p := result.UnderlyingPair.Format(upperFormat)
|
||||
diff, ok := currs[p]
|
||||
if !ok {
|
||||
diff = linkCurrencyDiff{}
|
||||
@@ -151,7 +152,7 @@ func createFuturesSpotDiffChart(items map[string]map[asset.Item]map[currency.Pai
|
||||
diff.FuturesEvents = result.Events
|
||||
currs[p] = diff
|
||||
} else {
|
||||
p := pair.Format("", true)
|
||||
p := pair.Format(upperFormat)
|
||||
diff, ok := currs[p]
|
||||
if !ok {
|
||||
diff = linkCurrencyDiff{}
|
||||
|
||||
@@ -160,6 +160,7 @@ func makeExchange(exchangeDirectory string, configTestFile *config.Config, exch
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -299,31 +299,31 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
for i := range assetTypes {
|
||||
var msg string
|
||||
log.Printf("%v %v", base.GetName(), assetTypes[i])
|
||||
if _, ok := base.Config.CurrencyPairs.Pairs[assetTypes[i]]; !ok {
|
||||
storedPairs, ok := base.Config.CurrencyPairs.Pairs[assetTypes[i]]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var p currency.Pair
|
||||
var err error
|
||||
switch {
|
||||
case currencyPairOverride != "":
|
||||
var err error
|
||||
p, err = currency.NewPairFromString(currencyPairOverride)
|
||||
if err != nil {
|
||||
log.Printf("%v Encountered error: '%v'", base.GetName(), err)
|
||||
continue
|
||||
case len(storedPairs.Enabled) == 0:
|
||||
if len(storedPairs.Available) == 0 {
|
||||
err = fmt.Errorf("%v has no enabled or available currencies. Skipping", base.GetName())
|
||||
break
|
||||
}
|
||||
case len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled) == 0:
|
||||
if len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available) == 0 {
|
||||
log.Printf("%v has no enabled or available currencies. Skipping",
|
||||
base.GetName())
|
||||
continue
|
||||
}
|
||||
p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available.GetRandomPair()
|
||||
p, err = storedPairs.Available.GetRandomPair()
|
||||
default:
|
||||
p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled.GetRandomPair()
|
||||
p, err = storedPairs.Enabled.GetRandomPair()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("%v Encountered error: '%v'", base.GetName(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
p, err = disruptFormatting(p)
|
||||
if err != nil {
|
||||
log.Println("failed to disrupt currency pair formatting:", err)
|
||||
|
||||
@@ -28,8 +28,11 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
|
||||
)
|
||||
|
||||
// errExchangeConfigIsNil defines an error when the config is nil
|
||||
var errExchangeConfigIsNil = errors.New("exchange config is nil")
|
||||
var (
|
||||
// errExchangeConfigIsNil defines an error when the config is nil
|
||||
errExchangeConfigIsNil = errors.New("exchange config is nil")
|
||||
errPairsManagerIsNil = errors.New("currency pairs manager is nil")
|
||||
)
|
||||
|
||||
// GetCurrencyConfig returns currency configurations
|
||||
func (c *Config) GetCurrencyConfig() currency.Config {
|
||||
@@ -346,7 +349,7 @@ func (c *Config) GetExchangeAssetTypes(exchName string) (asset.Items, error) {
|
||||
}
|
||||
|
||||
if exchCfg.CurrencyPairs == nil {
|
||||
return nil, fmt.Errorf("exchange %s currency pairs is nil", exchName)
|
||||
return nil, fmt.Errorf("%s %w", exchName, errPairsManagerIsNil)
|
||||
}
|
||||
|
||||
return exchCfg.CurrencyPairs.GetAssetTypes(false), nil
|
||||
@@ -360,7 +363,7 @@ func (c *Config) SupportsExchangeAssetType(exchName string, assetType asset.Item
|
||||
}
|
||||
|
||||
if exchCfg.CurrencyPairs == nil {
|
||||
return fmt.Errorf("exchange %s currency pairs is nil", exchName)
|
||||
return fmt.Errorf("%s %w", exchName, errPairsManagerIsNil)
|
||||
}
|
||||
|
||||
if !assetType.IsValid() {
|
||||
@@ -389,8 +392,7 @@ func (c *Config) SetPairs(exchName string, assetType asset.Item, enabled bool, p
|
||||
return err
|
||||
}
|
||||
|
||||
exchCfg.CurrencyPairs.StorePairs(assetType, pairs, enabled)
|
||||
return nil
|
||||
return exchCfg.CurrencyPairs.StorePairs(assetType, pairs, enabled)
|
||||
}
|
||||
|
||||
// GetCurrencyPairConfig returns currency pair config for the desired exchange and asset type
|
||||
@@ -507,10 +509,13 @@ func (c *Config) CheckPairConsistency(exchName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.SetPairs(exchName,
|
||||
assetTypes[x],
|
||||
true,
|
||||
currency.Pairs{availPairs.GetRandomPair()})
|
||||
var rPair currency.Pair
|
||||
rPair, err = availPairs.GetRandomPair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.SetPairs(exchName, assetTypes[x], true, currency.Pairs{rPair})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -565,10 +570,13 @@ func (c *Config) CheckPairConsistency(exchName string) error {
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.SetPairs(exchName,
|
||||
assetTypes[x],
|
||||
true,
|
||||
currency.Pairs{availPairs.GetRandomPair()})
|
||||
var rPair currency.Pair
|
||||
rPair, err = availPairs.GetRandomPair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.SetPairs(exchName, assetTypes[x], true, currency.Pairs{rPair})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -583,8 +591,16 @@ func (c *Config) CheckPairConsistency(exchName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
newPair := avail.GetRandomPair()
|
||||
err = c.SetPairs(exchName, assetTypes[0], true, currency.Pairs{newPair})
|
||||
if len(avail) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rPair, err := avail.GetRandomPair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.SetPairs(exchName, assetTypes[0], true, currency.Pairs{rPair})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -592,7 +608,7 @@ func (c *Config) CheckPairConsistency(exchName string) error {
|
||||
"Exchange %s: [%v] No enabled pairs found in available pairs list, randomly added %v pair.\n",
|
||||
exchName,
|
||||
assetTypes[0],
|
||||
newPair)
|
||||
rPair)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -611,12 +627,12 @@ func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType asset.
|
||||
func (c *Config) GetPairFormat(exchName string, assetType asset.Item) (currency.PairFormat, error) {
|
||||
exchCfg, err := c.GetExchangeConfig(exchName)
|
||||
if err != nil {
|
||||
return currency.PairFormat{}, err
|
||||
return currency.EMPTYFORMAT, err
|
||||
}
|
||||
|
||||
err = c.SupportsExchangeAssetType(exchName, assetType)
|
||||
if err != nil {
|
||||
return currency.PairFormat{}, err
|
||||
return currency.EMPTYFORMAT, err
|
||||
}
|
||||
|
||||
if exchCfg.CurrencyPairs.UseGlobalFormat {
|
||||
@@ -625,18 +641,18 @@ func (c *Config) GetPairFormat(exchName string, assetType asset.Item) (currency.
|
||||
|
||||
p, err := exchCfg.CurrencyPairs.Get(assetType)
|
||||
if err != nil {
|
||||
return currency.PairFormat{}, err
|
||||
return currency.EMPTYFORMAT, err
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return currency.PairFormat{},
|
||||
return currency.EMPTYFORMAT,
|
||||
fmt.Errorf("exchange %s pair store for asset type %s is nil",
|
||||
exchName,
|
||||
assetType)
|
||||
}
|
||||
|
||||
if p.ConfigFormat == nil {
|
||||
return currency.PairFormat{},
|
||||
return currency.EMPTYFORMAT,
|
||||
fmt.Errorf("exchange %s pair config format for asset type %s is nil",
|
||||
exchName,
|
||||
assetType)
|
||||
@@ -666,8 +682,7 @@ func (c *Config) GetAvailablePairs(exchName string, assetType asset.Item) (curre
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return pairs.Format(pairFormat.Delimiter, pairFormat.Index,
|
||||
pairFormat.Uppercase), nil
|
||||
return pairs.Format(pairFormat), nil
|
||||
}
|
||||
|
||||
// GetEnabledPairs returns a list of currency pairs for a specifc exchange
|
||||
@@ -691,10 +706,7 @@ func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) (currenc
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return pairs.Format(pairFormat.Delimiter,
|
||||
pairFormat.Index,
|
||||
pairFormat.Uppercase),
|
||||
nil
|
||||
return pairs.Format(pairFormat), nil
|
||||
}
|
||||
|
||||
// GetEnabledExchanges returns a list of enabled exchanges
|
||||
@@ -853,13 +865,14 @@ func (c *Config) CheckExchangeConfigValues() error {
|
||||
}
|
||||
|
||||
c.Exchanges[i].CurrencyPairs.UseGlobalFormat = true
|
||||
c.Exchanges[i].CurrencyPairs.Store(asset.Spot,
|
||||
currency.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Available: availPairs,
|
||||
Enabled: enabledPairs,
|
||||
},
|
||||
)
|
||||
err := c.Exchanges[i].CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Available: availPairs,
|
||||
Enabled: enabledPairs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// flush old values
|
||||
c.Exchanges[i].PairsLastUpdated = nil
|
||||
|
||||
@@ -675,12 +675,12 @@ func TestCheckPairConfigFormats(t *testing.T) {
|
||||
c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
RequestFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
RequestFormat: ¤cy.EMPTYFORMAT,
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
},
|
||||
asset.Futures: {
|
||||
RequestFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
RequestFormat: ¤cy.EMPTYFORMAT,
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -748,8 +748,9 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var c Config
|
||||
if err := c.CheckPairConsistency("asdf"); err == nil {
|
||||
t.Error("non-existent exchange should return an error")
|
||||
err := c.CheckPairConsistency("asdf")
|
||||
if !errors.Is(err, ErrExchangeNotFound) {
|
||||
t.Fatalf("received: '%v' buy expected: '%v'", err, ErrExchangeNotFound)
|
||||
}
|
||||
|
||||
c.Exchanges = append(c.Exchanges,
|
||||
@@ -759,8 +760,9 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
)
|
||||
|
||||
// Test nil pair store
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err == nil {
|
||||
t.Error("nil pair store should return an error")
|
||||
err = c.CheckPairConsistency(testFakeExchangeName)
|
||||
if !errors.Is(err, errPairsManagerIsNil) {
|
||||
t.Fatalf("received: '%v' buy expected: '%v'", err, errPairsManagerIsNil)
|
||||
}
|
||||
|
||||
enabled, err := currency.NewPairDelimiter("BTC_USD", "_")
|
||||
@@ -788,8 +790,8 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
|
||||
// Test for nil avail pairs
|
||||
err = c.CheckPairConsistency(testFakeExchangeName)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' buy expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
p1, err := currency.NewPairDelimiter("LTC_USD", "_")
|
||||
@@ -802,17 +804,19 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
p1,
|
||||
}
|
||||
|
||||
// LTC_USD is only found in the available pairs list and should therefor
|
||||
// LTC_USD is only found in the available pairs list and should therefore
|
||||
// be added to the enabled pairs list due to the atLestOneEnabled code
|
||||
err = c.CheckPairConsistency(testFakeExchangeName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, item := range c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled {
|
||||
if !item.Equal(p1) {
|
||||
t.Fatal("LTC_USD should be contained in the enabled pairs list")
|
||||
}
|
||||
if len(c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled) != 1 {
|
||||
t.Fatal("there should be atleast one pair located in this list")
|
||||
}
|
||||
|
||||
if !c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled[0].Equal(p1) {
|
||||
t.Fatal("LTC_USD should be contained in the enabled pairs list")
|
||||
}
|
||||
|
||||
p2, err := currency.NewPairDelimiter("BTC_USD", "_")
|
||||
@@ -822,8 +826,7 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
|
||||
// Add the BTC_USD pair and see result
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{
|
||||
p1,
|
||||
p2,
|
||||
p1, p2,
|
||||
}
|
||||
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
@@ -833,7 +836,7 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
// Test that an empty enabled pair is populated with an available pair
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = nil
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
t.Error("unexpected result", err)
|
||||
}
|
||||
|
||||
if len(c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled) != 1 {
|
||||
@@ -900,7 +903,7 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
|
||||
func TestSupportsPair(t *testing.T) {
|
||||
t.Parallel()
|
||||
fmt := ¤cy.PairFormat{}
|
||||
fmt := ¤cy.EMPTYFORMAT
|
||||
cfg := &Config{
|
||||
Exchanges: []Exchange{
|
||||
{
|
||||
@@ -1858,8 +1861,8 @@ func TestCheckConfig(t *testing.T) {
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Available: currency.Pairs{cp1, cp2},
|
||||
Enabled: currency.Pairs{cp1},
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
RequestFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
RequestFormat: ¤cy.EMPTYFORMAT,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -83,7 +83,9 @@ const (
|
||||
)
|
||||
|
||||
// delimiters is a delimiter list
|
||||
var delimiters = []string{UnderscoreDelimiter,
|
||||
var delimiters = []string{
|
||||
DashDelimiter,
|
||||
UnderscoreDelimiter,
|
||||
ForwardSlashDelimiter,
|
||||
ColonDelimiter}
|
||||
ColonDelimiter,
|
||||
}
|
||||
|
||||
@@ -23,13 +23,20 @@ var (
|
||||
// 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.
|
||||
ErrPairNotContainedInAvailablePairs = errors.New("pair not contained in available pairs")
|
||||
|
||||
errPairStoreIsNil = errors.New("pair store is nil")
|
||||
errPairFormatIsNil = errors.New("pair format is nil")
|
||||
)
|
||||
|
||||
// GetAssetTypes returns a list of stored asset types
|
||||
func (p *PairsManager) GetAssetTypes(enabled bool) asset.Items {
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
var assetTypes asset.Items
|
||||
assetTypes := make(asset.Items, 0, len(p.Pairs))
|
||||
for k, ps := range p.Pairs {
|
||||
if enabled && (ps.AssetEnabled == nil || !*ps.AssetEnabled) {
|
||||
continue
|
||||
@@ -41,6 +48,10 @@ func (p *PairsManager) GetAssetTypes(enabled bool) asset.Items {
|
||||
|
||||
// Get gets the currency pair config based on the asset type
|
||||
func (p *PairsManager) Get(a asset.Item) (*PairStore, error) {
|
||||
if !a.IsValid() {
|
||||
return nil, fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
c, ok := p.Pairs[a]
|
||||
@@ -48,60 +59,82 @@ func (p *PairsManager) Get(a asset.Item) (*PairStore, error) {
|
||||
return nil,
|
||||
fmt.Errorf("cannot get pair store, %v %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
return c, nil
|
||||
return c.copy()
|
||||
}
|
||||
|
||||
// Store stores a new currency pair config based on its asset type
|
||||
func (p *PairsManager) Store(a asset.Item, ps PairStore) {
|
||||
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
|
||||
}
|
||||
p.m.Lock()
|
||||
if p.Pairs == nil {
|
||||
p.Pairs = make(map[asset.Item]*PairStore)
|
||||
}
|
||||
p.Pairs[a] = &ps
|
||||
p.Pairs[a] = cpy
|
||||
p.m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a map entry based on the supplied asset type
|
||||
func (p *PairsManager) Delete(a asset.Item) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
if p.Pairs == nil {
|
||||
return
|
||||
}
|
||||
delete(p.Pairs, a)
|
||||
p.m.Unlock()
|
||||
}
|
||||
|
||||
// GetPairs gets a list of stored pairs based on the asset type and whether
|
||||
// they're enabled or not
|
||||
func (p *PairsManager) GetPairs(a asset.Item, enabled bool) (Pairs, error) {
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
if p.Pairs == nil {
|
||||
return nil, nil
|
||||
if !a.IsValid() {
|
||||
return nil, fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
c, ok := p.Pairs[a]
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
pairStore, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if enabled {
|
||||
for i := range c.Enabled {
|
||||
if !c.Available.Contains(c.Enabled[i], true) {
|
||||
return c.Enabled,
|
||||
fmt.Errorf("enabled pair %s of asset type %s not contained in available list",
|
||||
c.Enabled[i],
|
||||
a)
|
||||
}
|
||||
}
|
||||
return c.Enabled, nil
|
||||
if !enabled {
|
||||
availPairs := make(Pairs, len(pairStore.Available))
|
||||
copy(availPairs, pairStore.Available)
|
||||
return availPairs, nil
|
||||
}
|
||||
return c.Available, nil
|
||||
|
||||
lenCheck := len(pairStore.Enabled)
|
||||
if lenCheck == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NOTE: enabledPairs is declared before the next check for comparison
|
||||
// reasons within exchange update pairs functionality.
|
||||
enabledPairs := make(Pairs, lenCheck)
|
||||
copy(enabledPairs, pairStore.Enabled)
|
||||
|
||||
err := pairStore.Available.ContainsAll(pairStore.Enabled, true)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%w of asset type %s", err, a)
|
||||
}
|
||||
return enabledPairs, err
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// StoreFormat stores a new format for request or config format.
|
||||
func (p *PairsManager) StoreFormat(a asset.Item, pFmt *PairFormat, config bool) error {
|
||||
if !a.IsValid() {
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
if pFmt == nil {
|
||||
return errPairFormatIsNil
|
||||
}
|
||||
|
||||
cpy := *pFmt
|
||||
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
@@ -109,76 +142,130 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) {
|
||||
p.Pairs = make(map[asset.Item]*PairStore)
|
||||
}
|
||||
|
||||
c, ok := p.Pairs[a]
|
||||
pairStore, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
p.Pairs[a] = new(PairStore)
|
||||
c = p.Pairs[a]
|
||||
pairStore = new(PairStore)
|
||||
p.Pairs[a] = pairStore
|
||||
}
|
||||
|
||||
if config {
|
||||
pairStore.ConfigFormat = &cpy
|
||||
} else {
|
||||
pairStore.RequestFormat = &cpy
|
||||
}
|
||||
return 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 {
|
||||
if !a.IsValid() {
|
||||
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.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.Pairs == nil {
|
||||
p.Pairs = make(map[asset.Item]*PairStore)
|
||||
}
|
||||
|
||||
pairStore, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
pairStore = new(PairStore)
|
||||
p.Pairs[a] = pairStore
|
||||
}
|
||||
|
||||
if enabled {
|
||||
c.Enabled = pairs
|
||||
pairStore.Enabled = cpy
|
||||
} else {
|
||||
c.Available = pairs
|
||||
pairStore.Available = cpy
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisablePair removes the pair from the enabled pairs list if found
|
||||
func (p *PairsManager) DisablePair(a asset.Item, pair Pair) error {
|
||||
if !a.IsValid() {
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
if pair.IsEmpty() {
|
||||
return ErrCurrencyPairEmpty
|
||||
}
|
||||
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
c, err := p.getPairStore(a)
|
||||
pairStore, err := p.getPairStoreRequiresLock(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.Enabled.Contains(pair, true) {
|
||||
return errors.New("specified pair is not enabled")
|
||||
enabled, err := pairStore.Enabled.Remove(pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Enabled = c.Enabled.Remove(pair)
|
||||
pairStore.Enabled = enabled
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnablePair adds a pair to the list of enabled pairs if it exists in the list
|
||||
// of available pairs and isn't already added
|
||||
func (p *PairsManager) EnablePair(a asset.Item, pair Pair) error {
|
||||
if !a.IsValid() {
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
if pair.IsEmpty() {
|
||||
return ErrCurrencyPairEmpty
|
||||
}
|
||||
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
c, err := p.getPairStore(a)
|
||||
pairStore, err := p.getPairStoreRequiresLock(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.Available.Contains(pair, true) {
|
||||
return fmt.Errorf("%s %w in the list of available pairs",
|
||||
pair, ErrPairNotFound)
|
||||
}
|
||||
|
||||
if c.Enabled.Contains(pair, true) {
|
||||
if pairStore.Enabled.Contains(pair, true) {
|
||||
return fmt.Errorf("%s %w", pair, ErrPairAlreadyEnabled)
|
||||
}
|
||||
|
||||
c.Enabled = c.Enabled.Add(pair)
|
||||
if !pairStore.Available.Contains(pair, true) {
|
||||
return fmt.Errorf("%s %w in the list of available pairs",
|
||||
pair, ErrPairNotFound)
|
||||
}
|
||||
pairStore.Enabled = pairStore.Enabled.Add(pair)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAssetEnabled checks to see if an asset is enabled
|
||||
func (p *PairsManager) IsAssetEnabled(a asset.Item) error {
|
||||
if !a.IsValid() {
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
|
||||
c, err := p.getPairStore(a)
|
||||
pairStore, err := p.getPairStoreRequiresLock(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.AssetEnabled == nil {
|
||||
if pairStore.AssetEnabled == nil {
|
||||
return fmt.Errorf("%s %w", a, ErrAssetIsNil)
|
||||
}
|
||||
|
||||
if !*c.AssetEnabled {
|
||||
if !*pairStore.AssetEnabled {
|
||||
return fmt.Errorf("%s %w", a, errAssetNotEnabled)
|
||||
}
|
||||
return nil
|
||||
@@ -186,44 +273,48 @@ func (p *PairsManager) IsAssetEnabled(a asset.Item) error {
|
||||
|
||||
// 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() {
|
||||
return fmt.Errorf("%s %w", a, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
c, err := p.getPairStore(a)
|
||||
pairStore, err := p.getPairStoreRequiresLock(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.AssetEnabled == nil {
|
||||
c.AssetEnabled = convert.BoolPtr(enabled)
|
||||
if pairStore.AssetEnabled == nil {
|
||||
pairStore.AssetEnabled = convert.BoolPtr(enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !*c.AssetEnabled && !enabled {
|
||||
if !*pairStore.AssetEnabled && !enabled {
|
||||
return errors.New("asset already disabled")
|
||||
} else if *c.AssetEnabled && enabled {
|
||||
} else if *pairStore.AssetEnabled && enabled {
|
||||
return ErrAssetAlreadyEnabled
|
||||
}
|
||||
|
||||
*c.AssetEnabled = enabled
|
||||
*pairStore.AssetEnabled = enabled
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PairsManager) getPairStore(a asset.Item) (*PairStore, error) {
|
||||
func (p *PairsManager) getPairStoreRequiresLock(a asset.Item) (*PairStore, error) {
|
||||
if p.Pairs == nil {
|
||||
return nil, errors.New("pair manager not initialised")
|
||||
}
|
||||
|
||||
c, ok := p.Pairs[a]
|
||||
pairStore, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return nil, errors.New("asset type not found")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
if pairStore == nil {
|
||||
return nil, errors.New("currency store is nil")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
return pairStore, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the unmarshal json interface so that the key can be
|
||||
@@ -255,3 +346,40 @@ 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) {
|
||||
if ps == nil {
|
||||
return nil, errPairStoreIsNil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
var p PairsManager
|
||||
|
||||
func initTest(t *testing.T) {
|
||||
func initTest(t *testing.T) *PairsManager {
|
||||
t.Helper()
|
||||
spotAvailable, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"})
|
||||
if err != nil {
|
||||
@@ -23,7 +21,7 @@ func initTest(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
spot := PairStore{
|
||||
spot := &PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Available: spotAvailable,
|
||||
Enabled: spotEnabled,
|
||||
@@ -31,7 +29,7 @@ func initTest(t *testing.T) {
|
||||
ConfigFormat: &PairFormat{Uppercase: true, Delimiter: "-"},
|
||||
}
|
||||
|
||||
futures := PairStore{
|
||||
futures := &PairStore{
|
||||
AssetEnabled: convert.BoolPtr(false),
|
||||
Available: spotAvailable,
|
||||
Enabled: spotEnabled,
|
||||
@@ -39,12 +37,23 @@ func initTest(t *testing.T) {
|
||||
ConfigFormat: &PairFormat{Uppercase: true, Delimiter: "-"},
|
||||
}
|
||||
|
||||
p.Store(asset.Spot, spot)
|
||||
p.Store(asset.Futures, futures)
|
||||
var p PairsManager
|
||||
|
||||
err = p.Store(asset.Spot, spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = p.Store(asset.Futures, futures)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func TestGetAssetTypes(t *testing.T) {
|
||||
initTest(t)
|
||||
t.Parallel()
|
||||
p := initTest(t)
|
||||
|
||||
a := p.GetAssetTypes(false)
|
||||
if len(a) != 2 {
|
||||
@@ -62,20 +71,27 @@ func TestGetAssetTypes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
initTest(t)
|
||||
t.Parallel()
|
||||
p := initTest(t)
|
||||
|
||||
_, err := p.Get(asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = p.Get(asset.Empty)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: '%v' bu expected: '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
_, err = p.Get(asset.CoinMarginedFutures)
|
||||
if err == nil {
|
||||
t.Error("CoinMarginedFutures should be nil")
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: '%v' bu expected: '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
t.Parallel()
|
||||
availPairs, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -86,8 +102,10 @@ func TestStore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p.Store(asset.Futures,
|
||||
PairStore{
|
||||
p := initTest(t)
|
||||
|
||||
err = p.Store(asset.Futures,
|
||||
&PairStore{
|
||||
Available: availPairs,
|
||||
Enabled: enabledPairs,
|
||||
RequestFormat: &PairFormat{
|
||||
@@ -99,6 +117,9 @@ func TestStore(t *testing.T) {
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := p.Get(asset.Futures)
|
||||
if err != nil {
|
||||
@@ -108,9 +129,22 @@ func TestStore(t *testing.T) {
|
||||
if f == nil {
|
||||
t.Error("Futures assets shouldn't be nil")
|
||||
}
|
||||
|
||||
err = p.Store(asset.Empty, nil)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: '%v' bu expected: '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
err = p.Store(asset.Futures, nil)
|
||||
if !errors.Is(err, errPairStoreIsNil) {
|
||||
t.Fatalf("received: '%v' bu expected: '%v'", err, errPairStoreIsNil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := initTest(t)
|
||||
|
||||
p.Pairs = nil
|
||||
p.Delete(asset.Spot)
|
||||
|
||||
@@ -119,9 +153,10 @@ func TestDelete(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p.Store(asset.Spot, PairStore{
|
||||
Available: btcusdPairs,
|
||||
})
|
||||
err = p.Store(asset.Spot, &PairStore{Available: btcusdPairs})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p.Delete(asset.UpsideProfitContract)
|
||||
spotPS, err := p.Get(asset.Spot)
|
||||
@@ -141,6 +176,9 @@ func TestDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetPairs(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := initTest(t)
|
||||
|
||||
p.Pairs = nil
|
||||
pairs, err := p.GetPairs(asset.Spot, true)
|
||||
if err != nil {
|
||||
@@ -151,7 +189,7 @@ func TestGetPairs(t *testing.T) {
|
||||
t.Fatal("pairs shouldn't be populated")
|
||||
}
|
||||
|
||||
initTest(t)
|
||||
p = initTest(t)
|
||||
pairs, err = p.GetPairs(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -161,8 +199,8 @@ func TestGetPairs(t *testing.T) {
|
||||
}
|
||||
|
||||
pairs, err = p.GetPairs(asset.Empty, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: '%v' but expetced: '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
if pairs != nil {
|
||||
@@ -179,7 +217,57 @@ func TestGetPairs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := &PairsManager{}
|
||||
|
||||
err := p.StoreFormat(0, &PairFormat{Delimiter: "~"}, true)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: %v but expected: %v", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
err = p.StoreFormat(asset.Spot, nil, true)
|
||||
if !errors.Is(err, errPairFormatIsNil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errPairFormatIsNil)
|
||||
}
|
||||
|
||||
err = p.StoreFormat(asset.Spot, &PairFormat{Delimiter: "~"}, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
ps, err := p.Get(asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ps.ConfigFormat.Delimiter != "~" {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
err = p.StoreFormat(asset.Spot, &PairFormat{Delimiter: "/"}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
|
||||
ps, err = p.Get(asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ps.RequestFormat.Delimiter != "/" {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorePairs(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := initTest(t)
|
||||
|
||||
err := p.StorePairs(0, nil, false)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: %v but expected: %v", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
p.Pairs = nil
|
||||
|
||||
ethusdPairs, err := NewPairsFromStrings([]string{"ETH-USD"})
|
||||
@@ -187,7 +275,11 @@ func TestStorePairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p.StorePairs(asset.Spot, ethusdPairs, false)
|
||||
err = p.StorePairs(asset.Spot, ethusdPairs, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
|
||||
pairs, err := p.GetPairs(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -202,8 +294,11 @@ func TestStorePairs(t *testing.T) {
|
||||
t.Errorf("TestStorePairs failed, unexpected result")
|
||||
}
|
||||
|
||||
initTest(t)
|
||||
p.StorePairs(asset.Spot, ethusdPairs, false)
|
||||
p = initTest(t)
|
||||
err = p.StorePairs(asset.Spot, ethusdPairs, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
pairs, err = p.GetPairs(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -222,8 +317,16 @@ func TestStorePairs(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
p.StorePairs(asset.Futures, ethkrwPairs, true)
|
||||
p.StorePairs(asset.Futures, ethkrwPairs, false)
|
||||
err = p.StorePairs(asset.Futures, ethkrwPairs, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
|
||||
err = p.StorePairs(asset.Futures, ethkrwPairs, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
|
||||
pairs, err = p.GetPairs(asset.Futures, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -244,6 +347,17 @@ func TestStorePairs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDisablePair(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := initTest(t)
|
||||
|
||||
if err := p.DisablePair(asset.Empty, EMPTYPAIR); !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
if err := p.DisablePair(asset.Spot, EMPTYPAIR); !errors.Is(err, ErrCurrencyPairEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
|
||||
}
|
||||
|
||||
p.Pairs = nil
|
||||
// Test disabling a pair when the pair manager is not initialised
|
||||
if err := p.DisablePair(asset.Spot, NewPair(BTC, USD)); err == nil {
|
||||
@@ -251,7 +365,7 @@ func TestDisablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test asset type which doesn't exist
|
||||
initTest(t)
|
||||
p = initTest(t)
|
||||
if err := p.DisablePair(asset.Futures, EMPTYPAIR); err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -263,7 +377,7 @@ func TestDisablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test disabling a pair which isn't enabled
|
||||
initTest(t)
|
||||
p = initTest(t)
|
||||
if err := p.DisablePair(asset.Spot, NewPair(LTC, USD)); err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -275,6 +389,13 @@ func TestDisablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnablePair(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := initTest(t)
|
||||
|
||||
if err := p.EnablePair(asset.Empty, NewPair(BTC, USD)); !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
p.Pairs = nil
|
||||
// Test enabling a pair when the pair manager is not initialised
|
||||
if err := p.EnablePair(asset.Spot, NewPair(BTC, USD)); err == nil {
|
||||
@@ -282,7 +403,7 @@ func TestEnablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test asset type which doesn't exist
|
||||
initTest(t)
|
||||
p = initTest(t)
|
||||
if err := p.EnablePair(asset.Futures, EMPTYPAIR); err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -294,7 +415,7 @@ func TestEnablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test enabling a pair which isn't in the list of available pairs
|
||||
initTest(t)
|
||||
p = initTest(t)
|
||||
if err := p.EnablePair(asset.Spot, NewPair(ETH, USD)); err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -311,9 +432,16 @@ func TestEnablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsAssetEnabled_SetAssetEnabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := initTest(t)
|
||||
|
||||
err := p.IsAssetEnabled(asset.Empty)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
p.Pairs = nil
|
||||
// Test enabling a pair when the pair manager is not initialised
|
||||
err := p.IsAssetEnabled(asset.Spot)
|
||||
err = p.IsAssetEnabled(asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -324,7 +452,7 @@ func TestIsAssetEnabled_SetAssetEnabled(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test asset type which doesn't exist
|
||||
initTest(t)
|
||||
p = initTest(t)
|
||||
|
||||
p.Pairs[asset.Spot].AssetEnabled = nil
|
||||
|
||||
|
||||
@@ -112,8 +112,8 @@ func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFor
|
||||
}
|
||||
|
||||
// Format formats the given pair as a string
|
||||
func (f *PairFormat) Format(pair Pair) string {
|
||||
return pair.Format(f.Delimiter, f.Uppercase).String()
|
||||
func (f PairFormat) Format(pair Pair) string {
|
||||
return pair.Format(f).String()
|
||||
}
|
||||
|
||||
// MatchPairsWithNoDelimiter will move along a predictable index on the provided currencyPair
|
||||
@@ -123,7 +123,7 @@ func (f *PairFormat) Format(pair Pair) string {
|
||||
// infer where the delimiter is located eg BETHERETH is BETHER ETH
|
||||
func MatchPairsWithNoDelimiter(currencyPair string, pairs Pairs, pairFmt PairFormat) (Pair, error) {
|
||||
for i := range pairs {
|
||||
fPair := pairs[i].Format(pairFmt.Delimiter, pairFmt.Uppercase)
|
||||
fPair := pairs[i].Format(pairFmt)
|
||||
maxLen := 6
|
||||
if len(currencyPair) < maxLen {
|
||||
maxLen = len(currencyPair)
|
||||
|
||||
@@ -4,6 +4,9 @@ import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// EMPTYFORMAT defines an empty pair format
|
||||
var EMPTYFORMAT = PairFormat{}
|
||||
|
||||
// String returns a currency pair string
|
||||
func (p Pair) String() string {
|
||||
return p.Base.String() + p.Delimiter + p.Quote.String()
|
||||
@@ -47,9 +50,9 @@ func (p Pair) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// Format changes the currency based on user preferences overriding the default
|
||||
// String() display
|
||||
func (p Pair) Format(delimiter string, uppercase bool) Pair {
|
||||
p.Delimiter = delimiter
|
||||
if uppercase {
|
||||
func (p Pair) Format(pf PairFormat) Pair {
|
||||
p.Delimiter = pf.Delimiter
|
||||
if pf.Uppercase {
|
||||
return p.Upper()
|
||||
}
|
||||
return p.Lower()
|
||||
|
||||
@@ -210,7 +210,7 @@ func TestDisplay(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
actual = pair.Format("", false).String()
|
||||
actual = EMPTYFORMAT.Format(pair)
|
||||
expected = "btcusd"
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
@@ -219,7 +219,7 @@ func TestDisplay(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
actual = pair.Format("~", true).String()
|
||||
actual = pair.Format(PairFormat{Delimiter: "~", Uppercase: true}).String()
|
||||
expected = "BTC~USD"
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
@@ -529,7 +529,7 @@ func TestNewPairFromFormattedPairs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Now a wrong one, will default to NewPairFromString
|
||||
p, err = NewPairFromFormattedPairs("ethusdt", pairs, PairFormat{})
|
||||
p, err = NewPairFromFormattedPairs("ethusdt", pairs, EMPTYFORMAT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -618,34 +618,75 @@ func TestFindPairDifferences(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test new pair update
|
||||
newPairs, removedPairs := pairList.FindDifferences(dash)
|
||||
if len(newPairs) != 1 && len(removedPairs) != 3 {
|
||||
diff, err := pairList.FindDifferences(dash, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(diff.New) != 1 && len(diff.Remove) != 3 && diff.FormatDifference {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
emptyPairsList, err := NewPairsFromStrings([]string{""})
|
||||
if !errors.Is(err, errCannotCreatePair) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errCannotCreatePair)
|
||||
diff, err = pairList.FindDifferences(Pairs{}, EMPTYFORMAT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test that we don't allow empty strings for new pairs
|
||||
newPairs, removedPairs = pairList.FindDifferences(emptyPairsList)
|
||||
if len(newPairs) != 0 && len(removedPairs) != 3 {
|
||||
if len(diff.New) != 0 && len(diff.Remove) != 3 && !diff.FormatDifference {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
// Test that we don't allow empty strings for new pairs
|
||||
newPairs, removedPairs = emptyPairsList.FindDifferences(pairList)
|
||||
if len(newPairs) != 3 && len(removedPairs) != 0 {
|
||||
diff, err = Pairs{}.FindDifferences(pairList, EMPTYFORMAT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(diff.New) != 3 && len(diff.Remove) != 0 && diff.FormatDifference {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
// Test that the supplied pair lists are the same, so
|
||||
// no newPairs or removedPairs
|
||||
newPairs, removedPairs = pairList.FindDifferences(pairList)
|
||||
if len(newPairs) != 0 && len(removedPairs) != 0 {
|
||||
diff, err = pairList.FindDifferences(pairList, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(diff.New) != 0 && len(diff.Remove) != 0 && !diff.FormatDifference {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
_, err = pairList.FindDifferences(Pairs{EMPTYPAIR}, EMPTYFORMAT)
|
||||
if !errors.Is(err, ErrCurrencyPairEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
|
||||
}
|
||||
|
||||
_, err = Pairs{EMPTYPAIR}.FindDifferences(pairList, EMPTYFORMAT)
|
||||
if !errors.Is(err, ErrCurrencyPairEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
|
||||
}
|
||||
|
||||
// Test duplication
|
||||
duplication, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD", "ETH-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = pairList.FindDifferences(duplication, EMPTYFORMAT)
|
||||
if !errors.Is(err, ErrPairDuplication) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairDuplication)
|
||||
}
|
||||
|
||||
// This will allow for the removal of the duplicated item to be returned if
|
||||
// contained in the original list.
|
||||
diff, err = duplication.FindDifferences(pairList, EMPTYFORMAT)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if len(diff.Remove) != 1 {
|
||||
t.Fatal("expected removal value in pair difference struct")
|
||||
}
|
||||
|
||||
if !diff.Remove[0].Equal(pairList[1]) {
|
||||
t.Fatal("unexpected value returned", diff.Remove[0], pairList[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPairsToStringArray(t *testing.T) {
|
||||
@@ -663,7 +704,10 @@ func TestPairsToStringArray(t *testing.T) {
|
||||
func TestRandomPairFromPairs(t *testing.T) {
|
||||
// Test that an empty pairs array returns an empty currency pair
|
||||
var emptyPairs Pairs
|
||||
result := emptyPairs.GetRandomPair()
|
||||
result, err := emptyPairs.GetRandomPair()
|
||||
if !errors.Is(err, ErrCurrencyPairsEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairsEmpty)
|
||||
}
|
||||
if !result.IsEmpty() {
|
||||
t.Error("TestRandomPairFromPairs: Unexpected values")
|
||||
}
|
||||
@@ -671,7 +715,10 @@ func TestRandomPairFromPairs(t *testing.T) {
|
||||
// Test that a populated pairs array returns a non-empty currency pair
|
||||
var pairs Pairs
|
||||
pairs = append(pairs, NewPair(BTC, USD))
|
||||
result = pairs.GetRandomPair()
|
||||
result, err = pairs.GetRandomPair()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.IsEmpty() {
|
||||
t.Error("TestRandomPairFromPairs: Unexpected values")
|
||||
@@ -682,16 +729,15 @@ func TestRandomPairFromPairs(t *testing.T) {
|
||||
pairs = append(pairs, NewPair(ETH, USD))
|
||||
expectedResults := make(map[string]bool)
|
||||
for i := 0; i < 50; i++ {
|
||||
p := pairs.GetRandomPair().String()
|
||||
_, ok := expectedResults[p]
|
||||
if !ok {
|
||||
expectedResults[p] = true
|
||||
result, err = pairs.GetRandomPair()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
expectedResults[result.String()] = true
|
||||
}
|
||||
|
||||
for x := range pairs {
|
||||
_, ok := expectedResults[pairs[x].String()]
|
||||
if !ok {
|
||||
if !expectedResults[pairs[x].String()] {
|
||||
t.Error("TestRandomPairFromPairs: Unexpected values")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,11 @@ type Pair struct {
|
||||
|
||||
// Pairs defines a list of pairs
|
||||
type Pairs []Pair
|
||||
|
||||
// PairDifference defines the difference between a set of pairs including a
|
||||
// change in format.
|
||||
type PairDifference struct {
|
||||
New Pairs
|
||||
Remove Pairs
|
||||
FormatDifference bool
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ var (
|
||||
errSymbolEmpty = errors.New("symbol is empty")
|
||||
errPairsEmpty = errors.New("pairs are empty")
|
||||
errNoDelimiter = errors.New("no delimiter was supplied")
|
||||
|
||||
// ErrPairDuplication defines an error when there is multiple of the same
|
||||
// currency pairs found.
|
||||
ErrPairDuplication = errors.New("currency pair duplication")
|
||||
)
|
||||
|
||||
// NewPairsFromStrings takes in currency pair strings and returns a currency
|
||||
@@ -54,24 +58,22 @@ func (p Pairs) Join() string {
|
||||
}
|
||||
|
||||
// Format formats the pair list to the exchange format configuration
|
||||
func (p Pairs) Format(delimiter, index string, uppercase bool) Pairs {
|
||||
pairs := make(Pairs, 0, len(p))
|
||||
func (p Pairs) Format(pairFmt PairFormat) Pairs {
|
||||
pairs := make(Pairs, len(p))
|
||||
copy(pairs, p)
|
||||
|
||||
var err error
|
||||
for _, format := range p {
|
||||
if index != "" {
|
||||
format, err = NewPairFromIndex(format.String(), index)
|
||||
for x := range pairs {
|
||||
if pairFmt.Index != "" {
|
||||
pairs[x], err = NewPairFromIndex(p[x].String(), pairFmt.Index)
|
||||
if err != nil {
|
||||
log.Errorf(log.Global,
|
||||
"failed to create NewPairFromIndex. Err: %s\n", err)
|
||||
continue
|
||||
log.Errorf(log.Global, "failed to create NewPairFromIndex. Err: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
format.Delimiter = delimiter
|
||||
if uppercase {
|
||||
pairs = append(pairs, format.Upper())
|
||||
} else {
|
||||
pairs = append(pairs, format.Lower())
|
||||
}
|
||||
pairs[x].Base.UpperCase = pairFmt.Uppercase
|
||||
pairs[x].Quote.UpperCase = pairFmt.Uppercase
|
||||
pairs[x].Delimiter = pairFmt.Delimiter
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
@@ -120,19 +122,45 @@ func (p Pairs) Lower() Pairs {
|
||||
// array
|
||||
func (p Pairs) Contains(check Pair, exact bool) bool {
|
||||
for i := range p {
|
||||
if exact {
|
||||
if p[i].Equal(check) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if p[i].EqualIncludeReciprocal(check) {
|
||||
return true
|
||||
}
|
||||
if (exact && p[i].Equal(check)) ||
|
||||
(!exact && p[i].EqualIncludeReciprocal(check)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAll checks to see if all pairs supplied are contained within the
|
||||
// original pairs list.
|
||||
func (p Pairs) ContainsAll(check Pairs, exact bool) error {
|
||||
if len(check) == 0 {
|
||||
return errPairsEmpty
|
||||
}
|
||||
|
||||
comparative := make(Pairs, len(p))
|
||||
copy(comparative, p)
|
||||
list:
|
||||
for x := range check {
|
||||
for y := range comparative {
|
||||
if (exact && check[x].Equal(comparative[y])) ||
|
||||
(!exact && check[x].EqualIncludeReciprocal(comparative[y])) {
|
||||
// Reduce list size to decrease array traversal speed on iteration.
|
||||
comparative[y] = comparative[len(comparative)-1]
|
||||
comparative = comparative[:len(comparative)-1]
|
||||
continue list
|
||||
}
|
||||
}
|
||||
|
||||
// Opted for in error original check for duplication.
|
||||
if p.Contains(check[x], exact) {
|
||||
return fmt.Errorf("%s %w", check[x], ErrPairDuplication)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s %w", check[x], ErrPairNotContainedInAvailablePairs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainsCurrency checks to see if a specified currency code exists inside a
|
||||
// currency pair array
|
||||
func (p Pairs) ContainsCurrency(check Code) bool {
|
||||
@@ -184,15 +212,15 @@ func (p Pairs) GetPairsByCurrencies(currencies Currencies) Pairs {
|
||||
}
|
||||
|
||||
// Remove removes the specified pair from the list of pairs if it exists
|
||||
func (p Pairs) Remove(pair Pair) Pairs {
|
||||
pairs := make(Pairs, 0, len(p))
|
||||
func (p Pairs) Remove(pair Pair) (Pairs, error) {
|
||||
pairs := make(Pairs, len(p))
|
||||
copy(pairs, p)
|
||||
for x := range p {
|
||||
if p[x].Equal(pair) {
|
||||
continue
|
||||
return append(pairs[:x], pairs[x+1:]...), nil
|
||||
}
|
||||
pairs = append(pairs, p[x])
|
||||
}
|
||||
return pairs
|
||||
return nil, fmt.Errorf("%s %w", pair, ErrPairNotFound)
|
||||
}
|
||||
|
||||
// Add adds a specified pair to the list of pairs if it doesn't exist
|
||||
@@ -217,32 +245,59 @@ func (p Pairs) GetMatch(pair Pair) (Pair, error) {
|
||||
}
|
||||
|
||||
// FindDifferences returns pairs which are new or have been removed
|
||||
func (p Pairs) FindDifferences(pairs Pairs) (newPairs, removedPairs Pairs) {
|
||||
for x := range pairs {
|
||||
if pairs[x].String() == "" {
|
||||
continue
|
||||
func (p Pairs) FindDifferences(incoming Pairs, pairFmt PairFormat) (PairDifference, error) {
|
||||
newPairs := make(Pairs, 0, len(incoming))
|
||||
check := make(map[string]bool)
|
||||
for x := range incoming {
|
||||
if incoming[x].IsEmpty() {
|
||||
return PairDifference{}, fmt.Errorf("contained in the incoming pairs a %w", ErrCurrencyPairEmpty)
|
||||
}
|
||||
if !p.Contains(pairs[x], true) {
|
||||
newPairs = append(newPairs, pairs[x])
|
||||
format := EMPTYFORMAT.Format(incoming[x])
|
||||
if check[format] {
|
||||
return PairDifference{}, fmt.Errorf("contained in the incoming pairs %w", ErrPairDuplication)
|
||||
}
|
||||
check[format] = true
|
||||
if !p.Contains(incoming[x], true) {
|
||||
newPairs = append(newPairs, incoming[x])
|
||||
}
|
||||
}
|
||||
removedPairs := make(Pairs, 0, len(p))
|
||||
check = make(map[string]bool)
|
||||
for x := range p {
|
||||
if p[x].String() == "" {
|
||||
continue
|
||||
if p[x].IsEmpty() {
|
||||
return PairDifference{}, fmt.Errorf("contained in the existing pairs a %w", ErrCurrencyPairEmpty)
|
||||
}
|
||||
if !pairs.Contains(p[x], true) {
|
||||
format := EMPTYFORMAT.Format(p[x])
|
||||
if !incoming.Contains(p[x], true) || check[format] {
|
||||
removedPairs = append(removedPairs, p[x])
|
||||
}
|
||||
check[format] = true
|
||||
}
|
||||
return
|
||||
return PairDifference{
|
||||
New: newPairs,
|
||||
Remove: removedPairs,
|
||||
FormatDifference: p.HasFormatDifference(pairFmt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HasFormatDifference checks and validates full formatting across a pairs list
|
||||
func (p Pairs) HasFormatDifference(pairFmt PairFormat) bool {
|
||||
for x := range p {
|
||||
if p[x].Delimiter != pairFmt.Delimiter ||
|
||||
(!p[x].Base.IsEmpty() && p[x].Base.UpperCase != pairFmt.Uppercase) ||
|
||||
(!p[x].Quote.IsEmpty() && p[x].Quote.UpperCase != pairFmt.Uppercase) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetRandomPair returns a random pair from a list of pairs
|
||||
func (p Pairs) GetRandomPair() Pair {
|
||||
if pairsLen := len(p); pairsLen != 0 {
|
||||
return p[rand.Intn(pairsLen)] //nolint:gosec // basic number generation required, no need for crypo/rand
|
||||
func (p Pairs) GetRandomPair() (Pair, error) {
|
||||
if len(p) == 0 {
|
||||
return EMPTYPAIR, ErrCurrencyPairsEmpty
|
||||
}
|
||||
return EMPTYPAIR
|
||||
return p[rand.Intn(len(p))], nil //nolint:gosec // basic number generation required, no need for crypo/rand
|
||||
}
|
||||
|
||||
// DeriveFrom matches symbol string to the available pairs list when no
|
||||
@@ -355,3 +410,33 @@ func (p Pairs) GetStablesMatch(code Code) Pairs {
|
||||
}
|
||||
return stablePairs
|
||||
}
|
||||
|
||||
// ValidateAndConform checks for duplications and empty pairs then conforms the
|
||||
// entire pairs list to the supplied formatting (unless bypassed).
|
||||
// Map[string]bool type is used to make sure delimiters are not included so
|
||||
// different formatting entry duplications can be found e.g. `LINKUSDTM21`,
|
||||
// `LIN-KUSDTM21` or `LINK-USDTM21 are all the same instances but with different
|
||||
// unintentional processes for formatting.
|
||||
func (p Pairs) ValidateAndConform(pFmt PairFormat, bypassFormatting bool) (Pairs, error) {
|
||||
processedPairs := make(map[string]bool, len(p))
|
||||
formatted := make(Pairs, len(p))
|
||||
var target int
|
||||
for x := range p {
|
||||
if p[x].IsEmpty() {
|
||||
return nil, fmt.Errorf("cannot update pairs %w", ErrCurrencyPairEmpty)
|
||||
}
|
||||
strippedPair := EMPTYFORMAT.Format(p[x])
|
||||
if processedPairs[strippedPair] {
|
||||
return nil, fmt.Errorf("cannot update pairs %w with [%s]", ErrPairDuplication, p[x])
|
||||
}
|
||||
// Force application of supplied formatting
|
||||
processedPairs[strippedPair] = true
|
||||
if !bypassFormatting {
|
||||
formatted[target] = p[x].Format(pFmt)
|
||||
} else {
|
||||
formatted[target] = p[x]
|
||||
}
|
||||
target++
|
||||
}
|
||||
return formatted, nil
|
||||
}
|
||||
|
||||
@@ -96,20 +96,23 @@ func TestPairsFormat(t *testing.T) {
|
||||
}
|
||||
|
||||
expected := "BTC-USD,BTC-AUD,BTC-LTC"
|
||||
if pairs.Format("-", "", true).Join() != expected {
|
||||
formatting := PairFormat{Delimiter: "-", Index: "", Uppercase: true}
|
||||
if pairs.Format(formatting).Join() != expected {
|
||||
t.Errorf("Pairs Join() error expected %s but received %s",
|
||||
expected, pairs.Format("-", "", true).Join())
|
||||
expected, pairs.Format(formatting).Join())
|
||||
}
|
||||
|
||||
expected = "btc:usd,btc:aud,btc:ltc"
|
||||
if pairs.Format(":", "", false).Join() != expected {
|
||||
formatting = PairFormat{Delimiter: ":", Index: "", Uppercase: false}
|
||||
if pairs.Format(formatting).Join() != expected {
|
||||
t.Errorf("Pairs Join() error expected %s but received %s",
|
||||
expected, pairs.Format(":", "", false).Join())
|
||||
expected, pairs.Format(formatting).Join())
|
||||
}
|
||||
|
||||
if pairs.Format(":", "KRW", false).Join() != "" {
|
||||
formatting = PairFormat{Delimiter: ":", Index: "KRW", Uppercase: false}
|
||||
if pairs.Format(formatting).Join() != "" {
|
||||
t.Errorf("Pairs Join() error expected %s but received %s",
|
||||
expected, pairs.Format(":", "KRW", true).Join())
|
||||
expected, pairs.Format(formatting).Join())
|
||||
}
|
||||
|
||||
pairs, err = NewPairsFromStrings([]string{"DASHKRW", "BTCKRW"})
|
||||
@@ -117,9 +120,10 @@ func TestPairsFormat(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected = "dash-krw,btc-krw"
|
||||
if pairs.Format("-", "KRW", false).Join() != expected {
|
||||
formatting = PairFormat{Delimiter: "-", Index: "KRW", Uppercase: false}
|
||||
if pairs.Format(formatting).Join() != expected {
|
||||
t.Errorf("Pairs Join() error expected %s but received %s",
|
||||
expected, pairs.Format("-", "KRW", false).Join())
|
||||
expected, pairs.Format(formatting).Join())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,17 +224,68 @@ func TestGetPairsByFilter(t *testing.T) {
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
t.Parallel()
|
||||
var pairs = Pairs{
|
||||
var oldPairs = Pairs{
|
||||
NewPair(BTC, USD),
|
||||
NewPair(LTC, USD),
|
||||
NewPair(LTC, USDT),
|
||||
}
|
||||
|
||||
compare := make(Pairs, len(oldPairs))
|
||||
copy(compare, oldPairs)
|
||||
|
||||
p := NewPair(BTC, USD)
|
||||
pairs = pairs.Remove(p)
|
||||
if pairs.Contains(p, true) || len(pairs) != 2 {
|
||||
newPairs, err := oldPairs.Remove(p)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
err = compare.ContainsAll(oldPairs, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if newPairs.Contains(p, true) || len(newPairs) != 2 {
|
||||
t.Error("TestRemove unexpected result")
|
||||
}
|
||||
|
||||
_, err = newPairs.Remove(p)
|
||||
if !errors.Is(err, ErrPairNotFound) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, ErrPairNotFound)
|
||||
}
|
||||
|
||||
newPairs, err = oldPairs.Remove(p)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
newPairs, err = newPairs.Remove(NewPair(LTC, USD))
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
err = compare.ContainsAll(oldPairs, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = newPairs.Remove(NewPair(LTC, USD))
|
||||
if !errors.Is(err, ErrPairNotFound) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, ErrPairNotFound)
|
||||
}
|
||||
|
||||
newPairs, err = newPairs.Remove(NewPair(LTC, USDT))
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
if len(newPairs) != 0 {
|
||||
t.Error("unexpected value")
|
||||
}
|
||||
|
||||
_, err = newPairs.Remove(NewPair(LTC, USDT))
|
||||
if !errors.Is(err, ErrPairNotFound) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, ErrPairNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
@@ -280,6 +335,62 @@ func TestContains(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
var pairs = Pairs{
|
||||
NewPair(BTC, USD),
|
||||
NewPair(LTC, USD),
|
||||
NewPair(USD, ZRX),
|
||||
}
|
||||
|
||||
err := pairs.ContainsAll(nil, true)
|
||||
if !errors.Is(err, errPairsEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errPairsEmpty)
|
||||
}
|
||||
|
||||
err = pairs.ContainsAll(Pairs{NewPair(BTC, USD)}, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
err = pairs.ContainsAll(Pairs{NewPair(USD, BTC)}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
err = pairs.ContainsAll(Pairs{NewPair(XRP, BTC)}, false)
|
||||
if !errors.Is(err, ErrPairNotContainedInAvailablePairs) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairNotContainedInAvailablePairs)
|
||||
}
|
||||
|
||||
err = pairs.ContainsAll(Pairs{NewPair(XRP, BTC)}, true)
|
||||
if !errors.Is(err, ErrPairNotContainedInAvailablePairs) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairNotContainedInAvailablePairs)
|
||||
}
|
||||
|
||||
err = pairs.ContainsAll(pairs, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
err = pairs.ContainsAll(pairs, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
var duplication = Pairs{
|
||||
NewPair(BTC, USD),
|
||||
NewPair(LTC, USD),
|
||||
NewPair(USD, ZRX),
|
||||
NewPair(USD, ZRX),
|
||||
}
|
||||
|
||||
err = pairs.ContainsAll(duplication, false)
|
||||
if !errors.Is(err, ErrPairDuplication) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairDuplication)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveFrom(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := Pairs{}.DeriveFrom("")
|
||||
@@ -502,8 +613,10 @@ func BenchmarkPairsFormat(b *testing.B) {
|
||||
NewPair(DAI, XRP),
|
||||
}
|
||||
|
||||
formatting := PairFormat{Delimiter: "/", Index: "", Uppercase: false}
|
||||
|
||||
for x := 0; x < b.N; x++ {
|
||||
_ = pairs.Format("/", "", false)
|
||||
_ = pairs.Format(formatting)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,3 +697,89 @@ func TestGetPairsByCurrencies(t *testing.T) {
|
||||
t.Fatalf("received %v but expected %v", enabled, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAndConform(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conformMe := Pairs{
|
||||
NewPair(BTC, USD),
|
||||
NewPair(LTC, USD),
|
||||
NewPair(USD, NZD),
|
||||
NewPair(LTC, USDT),
|
||||
NewPair(LTC, DAI),
|
||||
NewPair(USDT, XRP),
|
||||
NewPair(EMPTYCODE, EMPTYCODE),
|
||||
}
|
||||
|
||||
_, err := conformMe.ValidateAndConform(EMPTYFORMAT, false)
|
||||
if !errors.Is(err, ErrCurrencyPairEmpty) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, ErrCurrencyPairEmpty)
|
||||
}
|
||||
|
||||
duplication, err := NewPairFromString("linkusdt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
conformMe = Pairs{
|
||||
NewPair(BTC, USD),
|
||||
NewPair(LTC, USD),
|
||||
NewPair(LINK, USDT),
|
||||
NewPair(USD, NZD),
|
||||
NewPair(LTC, USDT),
|
||||
NewPair(LTC, DAI),
|
||||
NewPair(USDT, XRP),
|
||||
duplication,
|
||||
}
|
||||
|
||||
_, err = conformMe.ValidateAndConform(EMPTYFORMAT, false)
|
||||
if !errors.Is(err, ErrPairDuplication) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, ErrPairDuplication)
|
||||
}
|
||||
|
||||
conformMe = Pairs{
|
||||
NewPair(BTC, USD),
|
||||
NewPair(LTC, USD),
|
||||
NewPair(LINK, USDT),
|
||||
NewPair(USD, NZD),
|
||||
NewPair(LTC, USDT),
|
||||
NewPair(LTC, DAI),
|
||||
NewPair(USDT, XRP),
|
||||
}
|
||||
|
||||
formatted, err := conformMe.ValidateAndConform(EMPTYFORMAT, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
expected := "btcusd,ltcusd,linkusdt,usdnzd,ltcusdt,ltcdai,usdtxrp"
|
||||
|
||||
if formatted.Join() != expected {
|
||||
t.Fatalf("received: '%v' but expected '%v'", formatted.Join(), expected)
|
||||
}
|
||||
|
||||
formatted, err = formatted.ValidateAndConform(PairFormat{Delimiter: DashDelimiter, Uppercase: true}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
expected = "BTC-USD,LTC-USD,LINK-USDT,USD-NZD,LTC-USDT,LTC-DAI,USDT-XRP"
|
||||
|
||||
if formatted.Join() != expected {
|
||||
t.Fatalf("received: '%v' but expected '%v'", formatted.Join(), expected)
|
||||
}
|
||||
|
||||
formatted, err = formatted.ValidateAndConform(PairFormat{
|
||||
Delimiter: UnderscoreDelimiter,
|
||||
Uppercase: false},
|
||||
true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
expected = "BTC-USD,LTC-USD,LINK-USDT,USD-NZD,LTC-USDT,LTC-DAI,USDT-XRP"
|
||||
|
||||
if formatted.Join() != expected {
|
||||
t.Fatalf("received: '%v' but expected '%v'", formatted.Join(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,7 +792,10 @@ func (bot *Engine) LoadExchange(name string, wg *sync.WaitGroup) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exchCfg.CurrencyPairs.StorePairs(assets[x], pairs, true)
|
||||
err = exchCfg.CurrencyPairs.StorePairs(assets[x], pairs, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1065,7 +1065,7 @@ func createDepositEngine(opts *fakeDepositExchangeOpts) *Engine {
|
||||
Enabled: true,
|
||||
CurrencyPairs: ¤cy.PairsManager{
|
||||
UseGlobalFormat: true,
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: &ps,
|
||||
},
|
||||
|
||||
@@ -76,6 +76,7 @@ var (
|
||||
errShutdownNotAllowed = errors.New("shutting down this bot instance is not allowed via gRPC, please enable by command line flag --grpcshutdown or config.json field grpcAllowBotShutdown")
|
||||
errGRPCShutdownSignalIsNil = errors.New("cannot shutdown, gRPC shutdown channel is nil")
|
||||
errInvalidStrategy = errors.New("invalid strategy")
|
||||
errSpecificPairNotEnabled = errors.New("specified pair is not enabled")
|
||||
)
|
||||
|
||||
// RPCServer struct
|
||||
@@ -344,14 +345,21 @@ func (s *RPCServer) GetExchangeInfo(_ context.Context, r *gctrpc.GenericExchange
|
||||
resp.SupportedAssets = make(map[string]*gctrpc.PairsSupported)
|
||||
assets := exchCfg.CurrencyPairs.GetAssetTypes(false)
|
||||
for i := range assets {
|
||||
ps, err := exchCfg.CurrencyPairs.Get(assets[i])
|
||||
var enabled currency.Pairs
|
||||
enabled, err = exchCfg.CurrencyPairs.GetPairs(assets[i], true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var available currency.Pairs
|
||||
available, err = exchCfg.CurrencyPairs.GetPairs(assets[i], false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.SupportedAssets[assets[i].String()] = &gctrpc.PairsSupported{
|
||||
EnabledPairs: ps.Enabled.Join(),
|
||||
AvailablePairs: ps.Available.Join(),
|
||||
EnabledPairs: enabled.Join(),
|
||||
AvailablePairs: available.Join(),
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
@@ -1970,14 +1978,21 @@ func (s *RPCServer) GetExchangePairs(_ context.Context, r *gctrpc.GetExchangePai
|
||||
continue
|
||||
}
|
||||
|
||||
ps, err := exchCfg.CurrencyPairs.Get(assetTypes[x])
|
||||
var enabled currency.Pairs
|
||||
enabled, err = exchCfg.CurrencyPairs.GetPairs(assetTypes[x], true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var available currency.Pairs
|
||||
available, err = exchCfg.CurrencyPairs.GetPairs(assetTypes[x], false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.SupportedAssets[assetTypes[x].String()] = &gctrpc.PairsSupported{
|
||||
AvailablePairs: ps.Available.Join(),
|
||||
EnabledPairs: ps.Enabled.Join(),
|
||||
AvailablePairs: available.Join(),
|
||||
EnabledPairs: enabled.Join(),
|
||||
}
|
||||
}
|
||||
return &resp, nil
|
||||
@@ -2024,31 +2039,36 @@ func (s *RPCServer) SetExchangePair(_ context.Context, r *gctrpc.SetExchangePair
|
||||
}
|
||||
|
||||
if r.Enable {
|
||||
err = exchCfg.CurrencyPairs.EnablePair(a,
|
||||
p.Format(pairFmt.Delimiter, pairFmt.Uppercase))
|
||||
err = exchCfg.CurrencyPairs.EnablePair(a, p.Format(pairFmt))
|
||||
if err != nil {
|
||||
newErrors = append(newErrors, err)
|
||||
newErrors = append(newErrors, fmt.Errorf("%s %w", r.Pairs[i], err))
|
||||
continue
|
||||
}
|
||||
err = base.CurrencyPairs.EnablePair(a, p)
|
||||
if err != nil {
|
||||
newErrors = append(newErrors, err)
|
||||
newErrors = append(newErrors, fmt.Errorf("%s %w", r.Pairs[i], err))
|
||||
continue
|
||||
}
|
||||
pass = true
|
||||
continue
|
||||
}
|
||||
|
||||
err = exchCfg.CurrencyPairs.DisablePair(a,
|
||||
p.Format(pairFmt.Delimiter, pairFmt.Uppercase))
|
||||
err = exchCfg.CurrencyPairs.DisablePair(a, p.Format(pairFmt))
|
||||
if err != nil {
|
||||
newErrors = append(newErrors, err)
|
||||
continue
|
||||
if errors.Is(err, currency.ErrPairNotFound) {
|
||||
newErrors = append(newErrors, fmt.Errorf("%s %w", r.Pairs[i], errSpecificPairNotEnabled))
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = base.CurrencyPairs.DisablePair(a, p)
|
||||
if err != nil {
|
||||
newErrors = append(newErrors, err)
|
||||
continue
|
||||
if errors.Is(err, currency.ErrPairNotFound) {
|
||||
newErrors = append(newErrors, fmt.Errorf("%s %w", r.Pairs[i], errSpecificPairNotEnabled))
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
pass = true
|
||||
}
|
||||
@@ -2913,13 +2933,25 @@ func (s *RPCServer) SetAllExchangePairs(_ context.Context, r *gctrpc.SetExchange
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exchCfg.CurrencyPairs.StorePairs(assets[i], pairs, true)
|
||||
base.CurrencyPairs.StorePairs(assets[i], pairs, true)
|
||||
err = exchCfg.CurrencyPairs.StorePairs(assets[i], pairs, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = base.CurrencyPairs.StorePairs(assets[i], pairs, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := range assets {
|
||||
exchCfg.CurrencyPairs.StorePairs(assets[i], nil, true)
|
||||
base.CurrencyPairs.StorePairs(assets[i], nil, true)
|
||||
err = exchCfg.CurrencyPairs.StorePairs(assets[i], nil, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = base.CurrencyPairs.StorePairs(assets[i], nil, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1464,6 +1464,7 @@ func TestCheckVars(t *testing.T) {
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter,
|
||||
},
|
||||
}
|
||||
err = e.GetBase().StoreAssetPairFormat(asset.Spot, fmt1)
|
||||
@@ -1492,7 +1493,10 @@ func TestCheckVars(t *testing.T) {
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.USDT},
|
||||
}
|
||||
|
||||
e.GetBase().CurrencyPairs.StorePairs(asset.Spot, data, false)
|
||||
err = e.GetBase().CurrencyPairs.StorePairs(asset.Spot, data, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = checkParams("Binance", e, asset.Spot, currency.NewPair(currency.BTC, currency.USDT))
|
||||
if !errors.Is(err, errCurrencyNotEnabled) {
|
||||
@@ -2174,7 +2178,7 @@ func TestCurrencyStateTradingPair(t *testing.T) {
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
@@ -2351,13 +2355,13 @@ func TestGetCollateral(t *testing.T) {
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Futures] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
|
||||
@@ -87,8 +87,7 @@ func setupSyncManager(c *SyncManagerConfig, exchangeManager iExchangeManager, re
|
||||
exchangeManager: exchangeManager,
|
||||
websocketRoutineManagerEnabled: websocketRoutineManagerEnabled,
|
||||
fiatDisplayCurrency: c.FiatDisplayCurrency,
|
||||
delimiter: c.PairFormatDisplay.Delimiter,
|
||||
uppercase: c.PairFormatDisplay.Uppercase,
|
||||
format: *c.PairFormatDisplay,
|
||||
tickerBatchLastRequested: make(map[string]time.Time),
|
||||
}
|
||||
|
||||
@@ -813,7 +812,7 @@ func (m *syncManager) FormatCurrency(p currency.Pair) currency.Pair {
|
||||
if m == nil || atomic.LoadInt32(&m.started) == 0 {
|
||||
return p
|
||||
}
|
||||
return p.Format(m.delimiter, m.uppercase)
|
||||
return p.Format(m.format)
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestSetupSyncManager(t *testing.T) {
|
||||
t.Errorf("error '%v', expected '%v'", err, common.ErrNilPointer)
|
||||
}
|
||||
|
||||
m, err := setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, &ExchangeManager{}, &config.RemoteControlConfig{}, true)
|
||||
m, err := setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.EMPTYFORMAT}, &ExchangeManager{}, &config.RemoteControlConfig{}, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func TestSetupSyncManager(t *testing.T) {
|
||||
|
||||
func TestSyncManagerStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
m, err := setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, &ExchangeManager{}, &config.RemoteControlConfig{}, true)
|
||||
m, err := setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.EMPTYFORMAT}, &ExchangeManager{}, &config.RemoteControlConfig{}, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func TestSyncManagerStop(t *testing.T) {
|
||||
}
|
||||
exch.SetDefaults()
|
||||
em.Add(exch)
|
||||
m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, em, &config.RemoteControlConfig{}, false)
|
||||
m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.EMPTYFORMAT}, em, &config.RemoteControlConfig{}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func TestPrintTickerSummary(t *testing.T) {
|
||||
}
|
||||
exch.SetDefaults()
|
||||
em.Add(exch)
|
||||
m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, em, &config.RemoteControlConfig{}, false)
|
||||
m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.EMPTYFORMAT}, em, &config.RemoteControlConfig{}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -192,7 +192,7 @@ func TestPrintOrderbookSummary(t *testing.T) {
|
||||
}
|
||||
exch.SetDefaults()
|
||||
em.Add(exch)
|
||||
m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, em, &config.RemoteControlConfig{}, false)
|
||||
m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.EMPTYFORMAT}, em, &config.RemoteControlConfig{}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
@@ -49,8 +49,7 @@ type syncManager struct {
|
||||
initSyncCompleted int32
|
||||
initSyncStarted int32
|
||||
started int32
|
||||
delimiter string
|
||||
uppercase bool
|
||||
format currency.PairFormat
|
||||
initSyncStartTime time.Time
|
||||
fiatDisplayCurrency currency.Code
|
||||
websocketRoutineManagerEnabled bool
|
||||
|
||||
@@ -30,7 +30,7 @@ func setupWebsocketRoutineManager(exchangeManager iExchangeManager, orderManager
|
||||
if cfg == nil {
|
||||
return nil, errNilCurrencyConfig
|
||||
}
|
||||
if cfg.CurrencyPairFormat == nil && verbose {
|
||||
if cfg.CurrencyPairFormat == nil {
|
||||
return nil, errNilCurrencyPairFormat
|
||||
}
|
||||
man := &websocketRoutineManager{
|
||||
@@ -49,6 +49,15 @@ func (m *websocketRoutineManager) Start() error {
|
||||
if m == nil {
|
||||
return fmt.Errorf("websocket routine manager %w", ErrNilSubsystem)
|
||||
}
|
||||
|
||||
if m.currencyConfig == nil {
|
||||
return errNilCurrencyConfig
|
||||
}
|
||||
|
||||
if m.currencyConfig.CurrencyPairFormat == nil {
|
||||
return errNilCurrencyPairFormat
|
||||
}
|
||||
|
||||
if !atomic.CompareAndSwapInt32(&m.started, 0, 1) {
|
||||
return ErrSubSystemAlreadyStarted
|
||||
}
|
||||
@@ -288,8 +297,7 @@ func (m *websocketRoutineManager) FormatCurrency(p currency.Pair) currency.Pair
|
||||
if m == nil || atomic.LoadInt32(&m.started) == 0 {
|
||||
return p
|
||||
}
|
||||
return p.Format(m.currencyConfig.CurrencyPairFormat.Delimiter,
|
||||
m.currencyConfig.CurrencyPairFormat.Uppercase)
|
||||
return p.Format(*m.currencyConfig.CurrencyPairFormat)
|
||||
}
|
||||
|
||||
// printOrderSummary this function will be deprecated when a order manager
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestWebsocketRoutineManagerSetup(t *testing.T) {
|
||||
t.Errorf("error '%v', expected '%v'", err, errNilCurrencyPairFormat)
|
||||
}
|
||||
|
||||
m, err := setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{}, false)
|
||||
m, err := setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{CurrencyPairFormat: ¤cy.PairFormat{}}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func TestWebsocketRoutineManagerIsRunning(t *testing.T) {
|
||||
t.Error("expected false")
|
||||
}
|
||||
|
||||
m, err := setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{}, false)
|
||||
m, err := setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{CurrencyPairFormat: ¤cy.PairFormat{}}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func TestWebsocketRoutineManagerStop(t *testing.T) {
|
||||
t.Errorf("error '%v', expected '%v'", err, ErrNilSubsystem)
|
||||
}
|
||||
|
||||
m, err = setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{}, false)
|
||||
m, err = setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{CurrencyPairFormat: ¤cy.PairFormat{}}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
@@ -1893,7 +1893,7 @@ func (b *Binance) FormatExchangeCurrency(p currency.Pair, a asset.Item) (currenc
|
||||
if a == asset.USDTMarginedFutures {
|
||||
return b.formatUSDTMarginedFuturesPair(p, pairFmt), nil
|
||||
}
|
||||
return p.Format(pairFmt.Delimiter, pairFmt.Uppercase), nil
|
||||
return p.Format(pairFmt), nil
|
||||
}
|
||||
|
||||
// FormatSymbol formats the given pair to a string suitable for exchange API requests
|
||||
@@ -1917,10 +1917,11 @@ func (b *Binance) formatUSDTMarginedFuturesPair(p currency.Pair, pairFmt currenc
|
||||
for _, c := range quote {
|
||||
if c < '0' || c > '9' {
|
||||
// character rune is alphabetic, cannot be expiring contract
|
||||
return p.Format(pairFmt.Delimiter, pairFmt.Uppercase)
|
||||
return p.Format(pairFmt)
|
||||
}
|
||||
}
|
||||
return p.Format(currency.UnderscoreDelimiter, pairFmt.Uppercase)
|
||||
pairFmt.Delimiter = currency.UnderscoreDelimiter
|
||||
return p.Format(pairFmt)
|
||||
}
|
||||
|
||||
// GetServerTime returns the current exchange server time.
|
||||
|
||||
@@ -65,7 +65,7 @@ func (b *Bitfinex) SetDefaults() {
|
||||
|
||||
fmt1 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter},
|
||||
}
|
||||
|
||||
fmt2 := currency.PairStore{
|
||||
@@ -323,9 +323,20 @@ func (b *Bitfinex) UpdateTradablePairs(ctx context.Context, forceUpdate bool) er
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
var p currency.Pairs
|
||||
if assets[i] == asset.MarginFunding {
|
||||
p = make(currency.Pairs, len(pairs))
|
||||
for x := range pairs {
|
||||
p[x], err = currency.NewPairFromStrings(pairs[x], "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p, err = currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(p, assets[i], false, forceUpdate)
|
||||
|
||||
@@ -51,6 +51,11 @@ func TestMain(m *testing.M) {
|
||||
log.Fatal("Bithumb setup error", err)
|
||||
}
|
||||
|
||||
err = b.UpdateTradablePairs(context.Background(), false)
|
||||
if err != nil {
|
||||
log.Fatal("Bithumb Setup() init error")
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
|
||||
@@ -175,11 +175,16 @@ func (b *Bithumb) GenerateSubscriptions() ([]stream.ChannelSubscription, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pFmt, err := b.GetPairFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for x := range pairs {
|
||||
for y := range channels {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[y],
|
||||
Currency: pairs[x].Format("_", true),
|
||||
Currency: pairs[x].Format(pFmt),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ func TestWsHandleData(t *testing.T) {
|
||||
Enabled: pairs,
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -67,7 +67,7 @@ func (b *Bithumb) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true, Index: "KRW"}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}
|
||||
err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
@@ -237,7 +237,7 @@ func (b *Bithumb) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]s
|
||||
}
|
||||
|
||||
for x := range currencies {
|
||||
currencies[x] += "KRW"
|
||||
currencies[x] += currency.DashDelimiter + "KRW"
|
||||
}
|
||||
|
||||
return currencies, nil
|
||||
|
||||
@@ -103,6 +103,14 @@ const (
|
||||
ContractDownsideProfit
|
||||
// ContractUpsideProfit upside profit contract type
|
||||
ContractUpsideProfit
|
||||
|
||||
perpetualContractID = "FFWCSX"
|
||||
spotID = "IFXXXP"
|
||||
futuresID = "FFCCSX"
|
||||
bitMEXBasketIndexID = "MRBXXX"
|
||||
bitMEXPriceIndexID = "MRCXXX"
|
||||
bitMEXLendingPremiumIndexID = "MRRXXX"
|
||||
bitMEXVolatilityIndexID = "MRIXXX"
|
||||
)
|
||||
|
||||
// GetAnnouncement returns the general announcements from Bitmex
|
||||
|
||||
@@ -3,7 +3,6 @@ package bitmex
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
)
|
||||
|
||||
@@ -122,107 +121,107 @@ type Funding struct {
|
||||
|
||||
// Instrument Tradeable Contracts, Indices, and History
|
||||
type Instrument struct {
|
||||
AskPrice float64 `json:"askPrice"`
|
||||
BankruptLimitDownPrice float64 `json:"bankruptLimitDownPrice"`
|
||||
BankruptLimitUpPrice float64 `json:"bankruptLimitUpPrice"`
|
||||
BidPrice float64 `json:"bidPrice"`
|
||||
BuyLeg string `json:"buyLeg"`
|
||||
CalcInterval string `json:"calcInterval"`
|
||||
Capped bool `json:"capped"`
|
||||
ClosingTimestamp time.Time `json:"closingTimestamp"`
|
||||
Deleverage bool `json:"deleverage"`
|
||||
Expiry string `json:"expiry"`
|
||||
FairBasis float64 `json:"fairBasis"`
|
||||
FairBasisRate float64 `json:"fairBasisRate"`
|
||||
FairMethod string `json:"fairMethod"`
|
||||
FairPrice float64 `json:"fairPrice"`
|
||||
Front string `json:"front"`
|
||||
FundingBaseSymbol string `json:"fundingBaseSymbol"`
|
||||
FundingInterval string `json:"fundingInterval"`
|
||||
FundingPremiumSymbol string `json:"fundingPremiumSymbol"`
|
||||
FundingQuoteSymbol string `json:"fundingQuoteSymbol"`
|
||||
FundingRate float64 `json:"fundingRate"`
|
||||
FundingTimestamp time.Time `json:"fundingTimestamp"`
|
||||
HasLiquidity bool `json:"hasLiquidity"`
|
||||
HighPrice float64 `json:"highPrice"`
|
||||
ImpactAskPrice float64 `json:"impactAskPrice"`
|
||||
ImpactBidPrice float64 `json:"impactBidPrice"`
|
||||
ImpactMidPrice float64 `json:"impactMidPrice"`
|
||||
IndicativeFundingRate float64 `json:"indicativeFundingRate"`
|
||||
IndicativeSettlePrice float64 `json:"indicativeSettlePrice"`
|
||||
IndicativeTaxRate float64 `json:"indicativeTaxRate"`
|
||||
InitMargin float64 `json:"initMargin"`
|
||||
InsuranceFee float64 `json:"insuranceFee"`
|
||||
InverseLeg string `json:"inverseLeg"`
|
||||
IsInverse bool `json:"isInverse"`
|
||||
IsQuanto bool `json:"isQuanto"`
|
||||
LastChangePcnt float64 `json:"lastChangePcnt"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
LastPriceProtected float64 `json:"lastPriceProtected"`
|
||||
LastTickDirection string `json:"lastTickDirection"`
|
||||
Limit float64 `json:"limit"`
|
||||
LimitDownPrice float64 `json:"limitDownPrice"`
|
||||
LimitUpPrice float64 `json:"limitUpPrice"`
|
||||
Listing string `json:"listing"`
|
||||
LotSize int64 `json:"lotSize"`
|
||||
LowPrice float64 `json:"lowPrice"`
|
||||
MaintMargin float64 `json:"maintMargin"`
|
||||
MakerFee float64 `json:"makerFee"`
|
||||
MarkMethod string `json:"markMethod"`
|
||||
MarkPrice float64 `json:"markPrice"`
|
||||
MaxOrderQty int64 `json:"maxOrderQty"`
|
||||
MaxPrice float64 `json:"maxPrice"`
|
||||
MidPrice float64 `json:"midPrice"`
|
||||
Multiplier int64 `json:"multiplier"`
|
||||
OpenInterest int64 `json:"openInterest"`
|
||||
OpenValue int64 `json:"openValue"`
|
||||
OpeningTimestamp time.Time `json:"openingTimestamp"`
|
||||
OptionMultiplier float64 `json:"optionMultiplier"`
|
||||
OptionStrikePcnt float64 `json:"optionStrikePcnt"`
|
||||
OptionStrikePrice float64 `json:"optionStrikePrice"`
|
||||
OptionStrikeRound float64 `json:"optionStrikeRound"`
|
||||
OptionUnderlyingPrice float64 `json:"optionUnderlyingPrice"`
|
||||
PositionCurrency string `json:"positionCurrency"`
|
||||
PrevClosePrice float64 `json:"prevClosePrice"`
|
||||
PrevPrice24h float64 `json:"prevPrice24h"`
|
||||
PrevTotalTurnover int64 `json:"prevTotalTurnover"`
|
||||
PrevTotalVolume int64 `json:"prevTotalVolume"`
|
||||
PublishInterval string `json:"publishInterval"`
|
||||
PublishTime string `json:"publishTime"`
|
||||
QuoteCurrency string `json:"quoteCurrency"`
|
||||
QuoteToSettleMultiplier int64 `json:"quoteToSettleMultiplier"`
|
||||
RebalanceInterval string `json:"rebalanceInterval"`
|
||||
RebalanceTimestamp time.Time `json:"rebalanceTimestamp"`
|
||||
Reference string `json:"reference"`
|
||||
ReferenceSymbol string `json:"referenceSymbol"`
|
||||
RelistInterval string `json:"relistInterval"`
|
||||
RiskLimit int64 `json:"riskLimit"`
|
||||
RiskStep int64 `json:"riskStep"`
|
||||
RootSymbol string `json:"rootSymbol"`
|
||||
SellLeg string `json:"sellLeg"`
|
||||
SessionInterval string `json:"sessionInterval"`
|
||||
SettlCurrency string `json:"settlCurrency"`
|
||||
Settle string `json:"settle"`
|
||||
SettledPrice float64 `json:"settledPrice"`
|
||||
SettlementFee float64 `json:"settlementFee"`
|
||||
State string `json:"state"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
TakerFee float64 `json:"takerFee"`
|
||||
Taxed bool `json:"taxed"`
|
||||
TickSize float64 `json:"tickSize"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TotalTurnover int64 `json:"totalTurnover"`
|
||||
TotalVolume int64 `json:"totalVolume"`
|
||||
Turnover int64 `json:"turnover"`
|
||||
Turnover24h int64 `json:"turnover24h"`
|
||||
Typ string `json:"typ"`
|
||||
Underlying string `json:"underlying"`
|
||||
UnderlyingSymbol string `json:"underlyingSymbol"`
|
||||
UnderlyingToPositionMultiplier int64 `json:"underlyingToPositionMultiplier"`
|
||||
UnderlyingToSettleMultiplier int64 `json:"underlyingToSettleMultiplier"`
|
||||
Volume float64 `json:"volume"`
|
||||
Volume24h float64 `json:"volume24h"`
|
||||
Vwap float64 `json:"vwap"`
|
||||
AskPrice float64 `json:"askPrice"`
|
||||
BankruptLimitDownPrice float64 `json:"bankruptLimitDownPrice"`
|
||||
BankruptLimitUpPrice float64 `json:"bankruptLimitUpPrice"`
|
||||
BidPrice float64 `json:"bidPrice"`
|
||||
BuyLeg string `json:"buyLeg"`
|
||||
CalcInterval string `json:"calcInterval"`
|
||||
Capped bool `json:"capped"`
|
||||
ClosingTimestamp time.Time `json:"closingTimestamp"`
|
||||
Deleverage bool `json:"deleverage"`
|
||||
Expiry string `json:"expiry"`
|
||||
FairBasis float64 `json:"fairBasis"`
|
||||
FairBasisRate float64 `json:"fairBasisRate"`
|
||||
FairMethod string `json:"fairMethod"`
|
||||
FairPrice float64 `json:"fairPrice"`
|
||||
Front string `json:"front"`
|
||||
FundingBaseSymbol string `json:"fundingBaseSymbol"`
|
||||
FundingInterval string `json:"fundingInterval"`
|
||||
FundingPremiumSymbol string `json:"fundingPremiumSymbol"`
|
||||
FundingQuoteSymbol string `json:"fundingQuoteSymbol"`
|
||||
FundingRate float64 `json:"fundingRate"`
|
||||
FundingTimestamp time.Time `json:"fundingTimestamp"`
|
||||
HasLiquidity bool `json:"hasLiquidity"`
|
||||
HighPrice float64 `json:"highPrice"`
|
||||
ImpactAskPrice float64 `json:"impactAskPrice"`
|
||||
ImpactBidPrice float64 `json:"impactBidPrice"`
|
||||
ImpactMidPrice float64 `json:"impactMidPrice"`
|
||||
IndicativeFundingRate float64 `json:"indicativeFundingRate"`
|
||||
IndicativeSettlePrice float64 `json:"indicativeSettlePrice"`
|
||||
IndicativeTaxRate float64 `json:"indicativeTaxRate"`
|
||||
InitMargin float64 `json:"initMargin"`
|
||||
InsuranceFee float64 `json:"insuranceFee"`
|
||||
InverseLeg string `json:"inverseLeg"`
|
||||
IsInverse bool `json:"isInverse"`
|
||||
IsQuanto bool `json:"isQuanto"`
|
||||
LastChangePcnt float64 `json:"lastChangePcnt"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
LastPriceProtected float64 `json:"lastPriceProtected"`
|
||||
LastTickDirection string `json:"lastTickDirection"`
|
||||
Limit float64 `json:"limit"`
|
||||
LimitDownPrice float64 `json:"limitDownPrice"`
|
||||
LimitUpPrice float64 `json:"limitUpPrice"`
|
||||
Listing string `json:"listing"`
|
||||
LotSize int64 `json:"lotSize"`
|
||||
LowPrice float64 `json:"lowPrice"`
|
||||
MaintMargin float64 `json:"maintMargin"`
|
||||
MakerFee float64 `json:"makerFee"`
|
||||
MarkMethod string `json:"markMethod"`
|
||||
MarkPrice float64 `json:"markPrice"`
|
||||
MaxOrderQty int64 `json:"maxOrderQty"`
|
||||
MaxPrice float64 `json:"maxPrice"`
|
||||
MidPrice float64 `json:"midPrice"`
|
||||
Multiplier int64 `json:"multiplier"`
|
||||
OpenInterest int64 `json:"openInterest"`
|
||||
OpenValue int64 `json:"openValue"`
|
||||
OpeningTimestamp time.Time `json:"openingTimestamp"`
|
||||
OptionMultiplier float64 `json:"optionMultiplier"`
|
||||
OptionStrikePcnt float64 `json:"optionStrikePcnt"`
|
||||
OptionStrikePrice float64 `json:"optionStrikePrice"`
|
||||
OptionStrikeRound float64 `json:"optionStrikeRound"`
|
||||
OptionUnderlyingPrice float64 `json:"optionUnderlyingPrice"`
|
||||
PositionCurrency string `json:"positionCurrency"`
|
||||
PrevClosePrice float64 `json:"prevClosePrice"`
|
||||
PrevPrice24h float64 `json:"prevPrice24h"`
|
||||
PrevTotalTurnover int64 `json:"prevTotalTurnover"`
|
||||
PrevTotalVolume int64 `json:"prevTotalVolume"`
|
||||
PublishInterval string `json:"publishInterval"`
|
||||
PublishTime string `json:"publishTime"`
|
||||
QuoteCurrency string `json:"quoteCurrency"`
|
||||
QuoteToSettleMultiplier int64 `json:"quoteToSettleMultiplier"`
|
||||
RebalanceInterval string `json:"rebalanceInterval"`
|
||||
RebalanceTimestamp time.Time `json:"rebalanceTimestamp"`
|
||||
Reference string `json:"reference"`
|
||||
ReferenceSymbol string `json:"referenceSymbol"`
|
||||
RelistInterval string `json:"relistInterval"`
|
||||
RiskLimit int64 `json:"riskLimit"`
|
||||
RiskStep int64 `json:"riskStep"`
|
||||
RootSymbol string `json:"rootSymbol"`
|
||||
SellLeg string `json:"sellLeg"`
|
||||
SessionInterval string `json:"sessionInterval"`
|
||||
SettlCurrency string `json:"settlCurrency"`
|
||||
Settle string `json:"settle"`
|
||||
SettledPrice float64 `json:"settledPrice"`
|
||||
SettlementFee float64 `json:"settlementFee"`
|
||||
State string `json:"state"`
|
||||
Symbol string `json:"symbol"`
|
||||
TakerFee float64 `json:"takerFee"`
|
||||
Taxed bool `json:"taxed"`
|
||||
TickSize float64 `json:"tickSize"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TotalTurnover int64 `json:"totalTurnover"`
|
||||
TotalVolume int64 `json:"totalVolume"`
|
||||
Turnover int64 `json:"turnover"`
|
||||
Turnover24h int64 `json:"turnover24h"`
|
||||
Typ string `json:"typ"`
|
||||
Underlying string `json:"underlying"`
|
||||
UnderlyingSymbol string `json:"underlyingSymbol"`
|
||||
UnderlyingToPositionMultiplier int64 `json:"underlyingToPositionMultiplier"`
|
||||
UnderlyingToSettleMultiplier int64 `json:"underlyingToSettleMultiplier"`
|
||||
Volume float64 `json:"volume"`
|
||||
Volume24h float64 `json:"volume24h"`
|
||||
Vwap float64 `json:"vwap"`
|
||||
}
|
||||
|
||||
// InstrumentInterval instrument interval
|
||||
|
||||
@@ -186,26 +186,18 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
if len(orderbooks.Data) == 0 {
|
||||
return fmt.Errorf("%s - Empty orderbook data received: %s", b.Name, respRaw)
|
||||
}
|
||||
var p currency.Pair
|
||||
p, err = currency.NewPairFromString(orderbooks.Data[0].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pair currency.Pair
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
pair, a, err = b.GetPairAndAssetTypeRequestFormatted(orderbooks.Data[0].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.processOrderbook(orderbooks.Data,
|
||||
orderbooks.Action,
|
||||
p,
|
||||
a)
|
||||
err = b.processOrderbook(orderbooks.Data, orderbooks.Action, pair, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case bitmexWSTrade:
|
||||
if !b.IsSaveTradeDataEnabled() {
|
||||
return nil
|
||||
@@ -223,13 +215,8 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
continue
|
||||
}
|
||||
var p currency.Pair
|
||||
p, err = currency.NewPairFromString(tradeHolder.Data[i].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
p, a, err = b.GetPairAndAssetTypeRequestFormatted(tradeHolder.Data[i].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -285,13 +272,8 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
|
||||
for i := range response.Data {
|
||||
var p currency.Pair
|
||||
p, err = currency.NewPairFromString(response.Data[i].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
p, a, err = b.GetPairAndAssetTypeRequestFormatted(response.Data[i].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -570,6 +552,10 @@ func (b *Bitmex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
|
||||
assets := b.GetAssetTypes(true)
|
||||
for x := range assets {
|
||||
pFmt, err := b.GetPairFormat(assets[x], true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contracts, err := b.GetEnabledPairs(assets[x])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -581,7 +567,7 @@ func (b *Bitmex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[z] + ":" + contracts[y].String(),
|
||||
Channel: channels[z] + ":" + pFmt.Format(contracts[y]),
|
||||
Currency: contracts[y],
|
||||
Asset: assets[x],
|
||||
})
|
||||
@@ -596,6 +582,11 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscript
|
||||
if !b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pFmt, err := b.GetPairFormat(asset.PerpetualContract, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contracts, err := b.GetEnabledPairs(asset.PerpetualContract)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -626,7 +617,7 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscript
|
||||
for i := range channels {
|
||||
for j := range contracts {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i] + ":" + contracts[j].String(),
|
||||
Channel: channels[i] + ":" + pFmt.Format(contracts[j]),
|
||||
Currency: contracts[j],
|
||||
Asset: asset.PerpetualContract,
|
||||
})
|
||||
@@ -639,7 +630,6 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscript
|
||||
func (b *Bitmex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
var subscriber WebsocketRequest
|
||||
subscriber.Command = "subscribe"
|
||||
|
||||
for i := range channelsToSubscribe {
|
||||
subscriber.Arguments = append(subscriber.Arguments,
|
||||
channelsToSubscribe[i].Channel)
|
||||
|
||||
@@ -62,13 +62,30 @@ func (b *Bitmex) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresKey = true
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
err := b.SetGlobalPairsManager(requestFmt,
|
||||
configFmt,
|
||||
asset.PerpetualContract,
|
||||
asset.Futures,
|
||||
asset.Index)
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}
|
||||
standardRequestFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
spotRequestFormat := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter}
|
||||
|
||||
spot := currency.PairStore{RequestFormat: spotRequestFormat, ConfigFormat: configFmt}
|
||||
err := b.StoreAssetPairFormat(asset.Spot, spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
perp := currency.PairStore{RequestFormat: standardRequestFmt, ConfigFormat: configFmt}
|
||||
err = b.StoreAssetPairFormat(asset.PerpetualContract, perp)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
futures := currency.PairStore{RequestFormat: standardRequestFmt, ConfigFormat: configFmt}
|
||||
err = b.StoreAssetPairFormat(asset.Futures, futures)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
index := currency.PairStore{RequestFormat: standardRequestFmt, ConfigFormat: configFmt}
|
||||
err = b.StoreAssetPairFormat(asset.Index, index)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
@@ -227,66 +244,99 @@ func (b *Bitmex) Run() {
|
||||
}
|
||||
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (b *Bitmex) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]string, error) {
|
||||
func (b *Bitmex) FetchTradablePairs(ctx context.Context, a asset.Item) ([]string, error) {
|
||||
marketInfo, err := b.GetActiveAndIndexInstruments(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
products := make([]string, len(marketInfo))
|
||||
products := make([]string, 0, len(marketInfo))
|
||||
for x := range marketInfo {
|
||||
products[x] = marketInfo[x].Symbol.String()
|
||||
}
|
||||
if marketInfo[x].State != "Open" && a != asset.Index {
|
||||
continue
|
||||
}
|
||||
|
||||
switch a {
|
||||
case asset.Spot:
|
||||
if marketInfo[x].Typ == spotID {
|
||||
products = append(products, marketInfo[x].Symbol)
|
||||
}
|
||||
case asset.PerpetualContract:
|
||||
if marketInfo[x].Typ == perpetualContractID {
|
||||
var settleTrail string
|
||||
if strings.Contains(marketInfo[x].Symbol, currency.UnderscoreDelimiter) {
|
||||
// Example: ETHUSD_ETH quoted in USD, paid out in ETH.
|
||||
settlement := strings.Split(marketInfo[x].Symbol, currency.UnderscoreDelimiter)
|
||||
if len(settlement) != 2 {
|
||||
log.Warnf(log.ExchangeSys, "%s currency %s %s cannot be added to tradable pairs",
|
||||
b.Name,
|
||||
marketInfo[x].Symbol,
|
||||
a)
|
||||
break
|
||||
}
|
||||
settleTrail = currency.UnderscoreDelimiter + settlement[1]
|
||||
}
|
||||
products = append(products, marketInfo[x].Underlying+
|
||||
currency.DashDelimiter+
|
||||
marketInfo[x].QuoteCurrency+settleTrail)
|
||||
}
|
||||
case asset.Futures:
|
||||
if marketInfo[x].Typ == futuresID {
|
||||
isolate := strings.Split(marketInfo[x].Symbol, currency.UnderscoreDelimiter)
|
||||
if len(isolate[0]) < 3 {
|
||||
log.Warnf(log.ExchangeSys, "%s currency %s %s be cannot added to tradable pairs",
|
||||
b.Name,
|
||||
marketInfo[x].Symbol,
|
||||
a)
|
||||
break
|
||||
}
|
||||
var settleTrail string
|
||||
if len(isolate) == 2 {
|
||||
// Example: ETHUSDU22_ETH quoted in USD, paid out in ETH.
|
||||
settleTrail = currency.UnderscoreDelimiter + isolate[1]
|
||||
}
|
||||
|
||||
root := isolate[0][:len(isolate[0])-3]
|
||||
contract := isolate[0][len(isolate[0])-3:]
|
||||
|
||||
products = append(products, root+currency.DashDelimiter+contract+settleTrail)
|
||||
}
|
||||
case asset.Index:
|
||||
// TODO: This can be expanded into individual assets later.
|
||||
if marketInfo[x].Typ == bitMEXBasketIndexID ||
|
||||
marketInfo[x].Typ == bitMEXPriceIndexID ||
|
||||
marketInfo[x].Typ == bitMEXLendingPremiumIndexID ||
|
||||
marketInfo[x].Typ == bitMEXVolatilityIndexID {
|
||||
products = append(products, marketInfo[x].Symbol)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unhandled asset type")
|
||||
}
|
||||
}
|
||||
return products, nil
|
||||
}
|
||||
|
||||
// UpdateTradablePairs updates the exchanges available pairs and stores
|
||||
// them in the exchanges config
|
||||
func (b *Bitmex) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error {
|
||||
pairs, err := b.FetchTradablePairs(ctx, asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assets := b.GetAssetTypes(false)
|
||||
|
||||
// Zerovalue current list which will remove old asset pairs when contract
|
||||
// types expire or become obsolete
|
||||
var assetPairs = map[asset.Item][]string{
|
||||
asset.Index: {},
|
||||
asset.PerpetualContract: {},
|
||||
asset.Futures: {},
|
||||
}
|
||||
|
||||
for x := range pairs {
|
||||
if strings.Contains(pairs[x], ".") {
|
||||
assetPairs[asset.Index] = append(assetPairs[asset.Index], pairs[x])
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(pairs[x], "USD") {
|
||||
assetPairs[asset.PerpetualContract] = append(assetPairs[asset.PerpetualContract],
|
||||
pairs[x])
|
||||
continue
|
||||
}
|
||||
|
||||
assetPairs[asset.Futures] = append(assetPairs[asset.Futures], pairs[x])
|
||||
}
|
||||
|
||||
for a, values := range assetPairs {
|
||||
p, err := currency.NewPairsFromStrings(values)
|
||||
for x := range assets {
|
||||
pairsStr, err := b.FetchTradablePairs(ctx, assets[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(p, a, false, false)
|
||||
pairs, err := currency.NewPairsFromStrings(pairsStr)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s failed to update available pairs. Err: %v",
|
||||
b.Name,
|
||||
err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(pairs, assets[x], false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -303,7 +353,13 @@ func (b *Bitmex) UpdateTickers(ctx context.Context, a asset.Item) error {
|
||||
}
|
||||
|
||||
for j := range tick {
|
||||
if !pairs.Contains(tick[j].Symbol, true) {
|
||||
var pair currency.Pair
|
||||
pair, err = currency.NewPairFromString(tick[j].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !pairs.Contains(pair, true) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -315,7 +371,7 @@ func (b *Bitmex) UpdateTickers(ctx context.Context, a asset.Item) error {
|
||||
Ask: tick[j].AskPrice,
|
||||
Volume: tick[j].Volume24h,
|
||||
Close: tick[j].PrevClosePrice,
|
||||
Pair: tick[j].Symbol,
|
||||
Pair: pair,
|
||||
LastUpdated: tick[j].Timestamp,
|
||||
ExchangeName: b.Name,
|
||||
AssetType: a})
|
||||
|
||||
@@ -58,7 +58,7 @@ func (b *Bitstamp) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresKey = true
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
b.API.CredentialsValidator.RequiresClientID = true
|
||||
requestFmt := ¤cy.PairFormat{}
|
||||
requestFmt := ¤cy.EMPTYFORMAT
|
||||
configFmt := ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.ForwardSlashDelimiter,
|
||||
|
||||
@@ -82,6 +82,7 @@ func (b *BTSE) SetDefaults() {
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter,
|
||||
},
|
||||
}
|
||||
err = b.StoreAssetPairFormat(asset.Futures, fmt2)
|
||||
@@ -379,7 +380,7 @@ func (b *BTSE) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType a
|
||||
Amount: a.SellQuote[x].Size,
|
||||
})
|
||||
}
|
||||
book.Asks.SortAsks() // Sort asks for correct alignment
|
||||
book.Asks.SortAsks()
|
||||
book.Pair = p
|
||||
book.Exchange = b.Name
|
||||
book.Asset = assetType
|
||||
|
||||
@@ -303,7 +303,17 @@ func (by *Bybit) FetchTradablePairs(ctx context.Context, a asset.Item) ([]string
|
||||
if allPairs[x].Status != "Trading" || allPairs[x].QuoteCurrency != "USD" {
|
||||
continue
|
||||
}
|
||||
symbol := allPairs[x].BaseCurrency + currency.DashDelimiter + allPairs[x].QuoteCurrency
|
||||
|
||||
contractSplit := strings.Split(allPairs[x].Name, allPairs[x].BaseCurrency)
|
||||
if len(contractSplit) != 2 {
|
||||
log.Warnf(log.ExchangeSys, "%s base currency %s cannot split contract name %s cannot add to tradable pairs",
|
||||
by.Name,
|
||||
allPairs[x].BaseCurrency,
|
||||
allPairs[x].Name)
|
||||
continue
|
||||
}
|
||||
|
||||
symbol := allPairs[x].BaseCurrency + currency.DashDelimiter + contractSplit[1]
|
||||
pairs = append(pairs, symbol)
|
||||
}
|
||||
return pairs, nil
|
||||
|
||||
@@ -21,10 +21,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
wsFuturesPath = "realtime"
|
||||
)
|
||||
|
||||
// WsFuturesConnect connects to a Futures websocket feed
|
||||
func (by *Bybit) WsFuturesConnect() error {
|
||||
if !by.Websocket.IsEnabled() || !by.IsEnabled() {
|
||||
|
||||
@@ -21,12 +21,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
wsUSDTMarginedPathPublic = "realtime_public"
|
||||
wsUSDTMarginedPathPrivate = "realtime_private"
|
||||
|
||||
wsUSDTKline = "candle"
|
||||
)
|
||||
const wsUSDTKline = "candle"
|
||||
|
||||
// WsUSDTConnect connects to a USDT websocket feed
|
||||
func (by *Bybit) WsUSDTConnect() error {
|
||||
|
||||
@@ -41,7 +41,11 @@ const (
|
||||
ResetConfigPairsWarningMessage = "%s Enabled and available pairs for %s reset due to config upgrade, please enable the ones you would like to use again. Defaulting to %v"
|
||||
)
|
||||
|
||||
var errEndpointStringNotFound = errors.New("endpoint string not found")
|
||||
var (
|
||||
errEndpointStringNotFound = errors.New("endpoint string not found")
|
||||
errConfigPairFormatRequiresDelimiter = errors.New("config pair format requires delimiter")
|
||||
errSymbolCannotBeMatched = errors.New("symbol cannot be matched")
|
||||
)
|
||||
|
||||
// SetClientProxyAddress sets a proxy address for REST and websocket requests
|
||||
func (b *Base) SetClientProxyAddress(addr string) error {
|
||||
@@ -140,11 +144,8 @@ func (b *Base) SupportsRESTTickerBatchUpdates() bool {
|
||||
// SupportsAutoPairUpdates returns whether or not the exchange supports
|
||||
// auto currency pair updating
|
||||
func (b *Base) SupportsAutoPairUpdates() bool {
|
||||
if b.Features.Supports.RESTCapabilities.AutoPairUpdates ||
|
||||
b.Features.Supports.WebsocketCapabilities.AutoPairUpdates {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return b.Features.Supports.RESTCapabilities.AutoPairUpdates ||
|
||||
b.Features.Supports.WebsocketCapabilities.AutoPairUpdates
|
||||
}
|
||||
|
||||
// GetLastPairsUpdateTime returns the unix timestamp of when the exchanges
|
||||
@@ -176,6 +177,33 @@ func (b *Base) GetPairAssetType(c currency.Pair) (asset.Item, error) {
|
||||
return asset.Empty, errors.New("asset type not associated with currency pair")
|
||||
}
|
||||
|
||||
// GetPairAndAssetTypeRequestFormatted returns the pair and the asset type
|
||||
// when there is distinct differentiation between exchange request symbols asset
|
||||
// types. e.g. "BTC-USD" Spot and "BTC_USD" PERP request formatted.
|
||||
func (b *Base) GetPairAndAssetTypeRequestFormatted(symbol string) (currency.Pair, asset.Item, error) {
|
||||
if symbol == "" {
|
||||
return currency.Pair{}, asset.Empty, currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
assetTypes := b.GetAssetTypes(true)
|
||||
for i := range assetTypes {
|
||||
pFmt, err := b.GetPairFormat(assetTypes[i], true)
|
||||
if err != nil {
|
||||
return currency.Pair{}, asset.Empty, err
|
||||
}
|
||||
|
||||
enabled, err := b.GetEnabledPairs(assetTypes[i])
|
||||
if err != nil {
|
||||
return currency.Pair{}, asset.Empty, err
|
||||
}
|
||||
for j := range enabled {
|
||||
if pFmt.Format(enabled[j]) == symbol {
|
||||
return enabled[j], assetTypes[i], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return currency.Pair{}, asset.Empty, errSymbolCannotBeMatched
|
||||
}
|
||||
|
||||
// GetClientBankAccounts returns banking details associated with
|
||||
// a client for withdrawal purposes
|
||||
func (b *Base) GetClientBankAccounts(exchangeName, withdrawalCurrency string) (*banking.Account, error) {
|
||||
@@ -192,7 +220,7 @@ func (b *Base) GetExchangeBankAccounts(id, depositCurrency string) (*banking.Acc
|
||||
|
||||
// SetCurrencyPairFormat checks the exchange request and config currency pair
|
||||
// formats and syncs it with the exchanges SetDefault settings
|
||||
func (b *Base) SetCurrencyPairFormat() {
|
||||
func (b *Base) SetCurrencyPairFormat() error {
|
||||
if b.Config.CurrencyPairs == nil {
|
||||
b.Config.CurrencyPairs = new(currency.PairsManager)
|
||||
}
|
||||
@@ -201,7 +229,7 @@ func (b *Base) SetCurrencyPairFormat() {
|
||||
if b.Config.CurrencyPairs.UseGlobalFormat {
|
||||
b.Config.CurrencyPairs.RequestFormat = b.CurrencyPairs.RequestFormat
|
||||
b.Config.CurrencyPairs.ConfigFormat = b.CurrencyPairs.ConfigFormat
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if b.Config.CurrencyPairs.ConfigFormat != nil {
|
||||
@@ -216,11 +244,15 @@ func (b *Base) SetCurrencyPairFormat() {
|
||||
if _, err := b.Config.CurrencyPairs.Get(assetTypes[x]); err != nil {
|
||||
ps, err := b.CurrencyPairs.Get(assetTypes[x])
|
||||
if err != nil {
|
||||
continue
|
||||
return err
|
||||
}
|
||||
err = b.Config.CurrencyPairs.Store(assetTypes[x], ps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Config.CurrencyPairs.Store(assetTypes[x], *ps)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetConfigPairs sets the exchanges currency pairs to the pairs set in the config
|
||||
@@ -235,37 +267,62 @@ func (b *Base) SetConfigPairs() error {
|
||||
assetTypes[x])
|
||||
continue // If there are unsupported assets contained in config, skip.
|
||||
}
|
||||
cfgPS, err := b.Config.CurrencyPairs.Get(assetTypes[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var enabledAsset bool
|
||||
if b.Config.CurrencyPairs.IsAssetEnabled(assetTypes[x]) == nil {
|
||||
enabledAsset = true
|
||||
}
|
||||
|
||||
err = b.CurrencyPairs.SetAssetEnabled(assetTypes[x], enabledAsset)
|
||||
err := b.CurrencyPairs.SetAssetEnabled(assetTypes[x], enabledAsset)
|
||||
// Suppress error when assets are enabled by default and they are being
|
||||
// enabled by config. A check for the inverse
|
||||
// e.g. currency.ErrAssetAlreadyDisabled is not needed.
|
||||
if err != nil && err != currency.ErrAssetAlreadyEnabled {
|
||||
if err != nil && !errors.Is(err, currency.ErrAssetAlreadyEnabled) {
|
||||
return err
|
||||
}
|
||||
|
||||
cfgPS, err := b.Config.CurrencyPairs.Get(assetTypes[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.Config.CurrencyPairs.UseGlobalFormat {
|
||||
b.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false)
|
||||
b.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true)
|
||||
err = b.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = b.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
exchPS, err := b.CurrencyPairs.Get(assetTypes[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfgPS.ConfigFormat = exchPS.ConfigFormat
|
||||
cfgPS.RequestFormat = exchPS.RequestFormat
|
||||
b.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false)
|
||||
b.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true)
|
||||
|
||||
if exchPS.ConfigFormat != nil {
|
||||
err = b.Config.CurrencyPairs.StoreFormat(assetTypes[x], exchPS.ConfigFormat, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if exchPS.RequestFormat != nil {
|
||||
err = b.Config.CurrencyPairs.StoreFormat(assetTypes[x], exchPS.RequestFormat, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = b.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = b.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -291,14 +348,14 @@ func (b *Base) GetPairFormat(assetType asset.Item, requestFormat bool) (currency
|
||||
if b.CurrencyPairs.UseGlobalFormat {
|
||||
if requestFormat {
|
||||
if b.CurrencyPairs.RequestFormat == nil {
|
||||
return currency.PairFormat{},
|
||||
return currency.EMPTYFORMAT,
|
||||
errors.New("global request format is nil")
|
||||
}
|
||||
return *b.CurrencyPairs.RequestFormat, nil
|
||||
}
|
||||
|
||||
if b.CurrencyPairs.ConfigFormat == nil {
|
||||
return currency.PairFormat{},
|
||||
return currency.EMPTYFORMAT,
|
||||
errors.New("global config format is nil")
|
||||
}
|
||||
return *b.CurrencyPairs.ConfigFormat, nil
|
||||
@@ -306,19 +363,19 @@ func (b *Base) GetPairFormat(assetType asset.Item, requestFormat bool) (currency
|
||||
|
||||
ps, err := b.CurrencyPairs.Get(assetType)
|
||||
if err != nil {
|
||||
return currency.PairFormat{}, err
|
||||
return currency.EMPTYFORMAT, err
|
||||
}
|
||||
|
||||
if requestFormat {
|
||||
if ps.RequestFormat == nil {
|
||||
return currency.PairFormat{},
|
||||
return currency.EMPTYFORMAT,
|
||||
errors.New("asset type request format is nil")
|
||||
}
|
||||
return *ps.RequestFormat, nil
|
||||
}
|
||||
|
||||
if ps.ConfigFormat == nil {
|
||||
return currency.PairFormat{},
|
||||
return currency.EMPTYFORMAT,
|
||||
errors.New("asset type config format is nil")
|
||||
}
|
||||
return *ps.ConfigFormat, nil
|
||||
@@ -336,14 +393,11 @@ func (b *Base) GetEnabledPairs(a asset.Item) (currency.Pairs, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enabledpairs, err := b.CurrencyPairs.GetPairs(a, true)
|
||||
enabledPairs, err := b.CurrencyPairs.GetPairs(a, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enabledpairs.Format(format.Delimiter,
|
||||
format.Index,
|
||||
format.Uppercase),
|
||||
nil
|
||||
return enabledPairs.Format(format), nil
|
||||
}
|
||||
|
||||
// GetRequestFormattedPairAndAssetType is a method that returns the enabled currency pair of
|
||||
@@ -362,7 +416,7 @@ func (b *Base) GetRequestFormattedPairAndAssetType(p string) (currency.Pair, ass
|
||||
}
|
||||
|
||||
for j := range pairs {
|
||||
formattedPair := pairs[j].Format(format.Delimiter, format.Uppercase)
|
||||
formattedPair := pairs[j].Format(format)
|
||||
if strings.EqualFold(formattedPair.String(), p) {
|
||||
return formattedPair, assetTypes[i], nil
|
||||
}
|
||||
@@ -382,28 +436,23 @@ func (b *Base) GetAvailablePairs(assetType asset.Item) (currency.Pairs, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pairs.Format(format.Delimiter, format.Index, format.Uppercase), nil
|
||||
return pairs.Format(format), nil
|
||||
}
|
||||
|
||||
// SupportsPair returns true or not whether a currency pair exists in the
|
||||
// exchange available currencies or not
|
||||
func (b *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType asset.Item) error {
|
||||
var pairs currency.Pairs
|
||||
var err error
|
||||
if enabledPairs {
|
||||
pairs, err := b.GetEnabledPairs(assetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pairs.Contains(p, false) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("pair not supported")
|
||||
pairs, err = b.GetEnabledPairs(assetType)
|
||||
} else {
|
||||
pairs, err = b.GetAvailablePairs(assetType)
|
||||
}
|
||||
|
||||
avail, err := b.GetAvailablePairs(assetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if avail.Contains(p, false) {
|
||||
if pairs.Contains(p, false) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("pair not supported")
|
||||
@@ -443,7 +492,7 @@ func (b *Base) FormatExchangeCurrency(p currency.Pair, assetType asset.Item) (cu
|
||||
if err != nil {
|
||||
return currency.EMPTYPAIR, err
|
||||
}
|
||||
return p.Format(pairFmt.Delimiter, pairFmt.Uppercase), nil
|
||||
return p.Format(pairFmt), nil
|
||||
}
|
||||
|
||||
// SetEnabled is a method that sets if the exchange is enabled
|
||||
@@ -503,7 +552,11 @@ func (b *Base) SetupDefaults(exch *config.Exchange) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.SetCurrencyPairFormat()
|
||||
|
||||
err = b.SetCurrencyPairFormat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.SetConfigPairs()
|
||||
if err != nil {
|
||||
@@ -551,95 +604,166 @@ func (b *Base) SetPairs(pairs currency.Pairs, assetType asset.Item, enabled bool
|
||||
return err
|
||||
}
|
||||
|
||||
var newPairs currency.Pairs
|
||||
for x := range pairs {
|
||||
newPairs = append(newPairs, pairs[x].Format(pairFmt.Delimiter,
|
||||
pairFmt.Uppercase))
|
||||
pairs[x] = pairs[x].Format(pairFmt)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(assetType, newPairs, enabled)
|
||||
b.Config.CurrencyPairs.StorePairs(assetType, newPairs, enabled)
|
||||
return nil
|
||||
err = b.CurrencyPairs.StorePairs(assetType, pairs, enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Config.CurrencyPairs.StorePairs(assetType, pairs, enabled)
|
||||
}
|
||||
|
||||
// UpdatePairs updates the exchange currency pairs for either enabledPairs or
|
||||
// availablePairs
|
||||
func (b *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType asset.Item, enabled, force bool) error {
|
||||
exchangeProducts = exchangeProducts.Upper()
|
||||
var products currency.Pairs
|
||||
for x := range exchangeProducts {
|
||||
if exchangeProducts[x].String() == "" {
|
||||
continue
|
||||
}
|
||||
products = append(products, exchangeProducts[x])
|
||||
}
|
||||
|
||||
var updateType string
|
||||
targetPairs, err := b.CurrencyPairs.GetPairs(assetType, enabled)
|
||||
func (b *Base) UpdatePairs(incoming currency.Pairs, a asset.Item, enabled, force bool) error {
|
||||
pFmt, err := b.GetPairFormat(a, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enabled {
|
||||
updateType = "enabled"
|
||||
} else {
|
||||
updateType = "available"
|
||||
incoming, err = incoming.ValidateAndConform(pFmt, b.BypassConfigFormatUpgrades)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newPairs, removedPairs := targetPairs.FindDifferences(products)
|
||||
if force || len(newPairs) > 0 || len(removedPairs) > 0 {
|
||||
oldPairs, err := b.CurrencyPairs.GetPairs(a, enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
diff, err := oldPairs.FindDifferences(incoming, pFmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if force || len(diff.New) != 0 || len(diff.Remove) != 0 || diff.FormatDifference {
|
||||
var updateType string
|
||||
if enabled {
|
||||
updateType = "enabled"
|
||||
} else {
|
||||
updateType = "available"
|
||||
}
|
||||
|
||||
if force {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s forced update of %s [%v] pairs.",
|
||||
b.Name,
|
||||
updateType,
|
||||
strings.ToUpper(assetType.String()))
|
||||
strings.ToUpper(a.String()))
|
||||
} else {
|
||||
if len(newPairs) > 0 {
|
||||
if len(diff.New) > 0 {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Updating %s pairs [%v] - Added: %s.\n",
|
||||
b.Name,
|
||||
updateType,
|
||||
strings.ToUpper(assetType.String()),
|
||||
newPairs)
|
||||
strings.ToUpper(a.String()),
|
||||
diff.New)
|
||||
}
|
||||
if len(removedPairs) > 0 {
|
||||
if len(diff.Remove) > 0 {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Updating %s pairs [%v] - Removed: %s.\n",
|
||||
b.Name,
|
||||
updateType,
|
||||
strings.ToUpper(assetType.String()),
|
||||
removedPairs)
|
||||
strings.ToUpper(a.String()),
|
||||
diff.Remove)
|
||||
}
|
||||
}
|
||||
|
||||
b.Config.CurrencyPairs.StorePairs(assetType, products, enabled)
|
||||
b.CurrencyPairs.StorePairs(assetType, products, enabled)
|
||||
|
||||
if !enabled {
|
||||
// If available pairs are changed we will remove currency pair items
|
||||
// that are still included in the enabled pairs list.
|
||||
enabledPairs, err := b.CurrencyPairs.GetPairs(assetType, true)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
_, remove := enabledPairs.FindDifferences(products)
|
||||
for i := range remove {
|
||||
enabledPairs = enabledPairs.Remove(remove[i])
|
||||
}
|
||||
|
||||
if len(remove) > 0 {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Checked and updated enabled pairs [%v] - Removed: %s.\n",
|
||||
b.Name,
|
||||
strings.ToUpper(assetType.String()),
|
||||
remove)
|
||||
b.Config.CurrencyPairs.StorePairs(assetType, enabledPairs, true)
|
||||
b.CurrencyPairs.StorePairs(assetType, enabledPairs, true)
|
||||
}
|
||||
err = b.Config.CurrencyPairs.StorePairs(a, incoming, enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = b.CurrencyPairs.StorePairs(a, incoming, enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
if enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This section checks for differences after an available pairs adjustment
|
||||
// which will remove currency pairs from enabled pairs that have been
|
||||
// disabled by an exchange, adjust the entire list of enabled pairs if there
|
||||
// is a required formatting change and it will also capture unintentional
|
||||
// client inputs e.g. a client can enter `linkusd` via config and loaded
|
||||
// into memory that might be unintentionally formatted too `lin-kusd` it
|
||||
// will match that against the correct available pair in memory and apply
|
||||
// correct formatting (LINK-USD) instead of being removed altogether which
|
||||
// will require a shutdown and update of the config file to enable that
|
||||
// asset.
|
||||
|
||||
enabledPairs, err := b.CurrencyPairs.GetPairs(a, true)
|
||||
if err != nil &&
|
||||
!errors.Is(err, currency.ErrPairNotContainedInAvailablePairs) &&
|
||||
!errors.Is(err, currency.ErrPairDuplication) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == nil && !enabledPairs.HasFormatDifference(pFmt) {
|
||||
return nil
|
||||
}
|
||||
|
||||
diff, err = enabledPairs.FindDifferences(incoming, pFmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
check := make(map[string]bool)
|
||||
var target int
|
||||
for x := range enabledPairs {
|
||||
pairNoFmt := currency.EMPTYFORMAT.Format(enabledPairs[x])
|
||||
if check[pairNoFmt] {
|
||||
diff.Remove = diff.Remove.Add(enabledPairs[x])
|
||||
continue
|
||||
}
|
||||
check[pairNoFmt] = true
|
||||
|
||||
if !diff.Remove.Contains(enabledPairs[x], true) {
|
||||
enabledPairs[target] = enabledPairs[x].Format(pFmt)
|
||||
} else {
|
||||
var match currency.Pair
|
||||
match, err = incoming.DeriveFrom(pairNoFmt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
diff.Remove, err = diff.Remove.Remove(enabledPairs[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enabledPairs[target] = match.Format(pFmt)
|
||||
}
|
||||
target++
|
||||
}
|
||||
|
||||
enabledPairs = enabledPairs[:target]
|
||||
if len(enabledPairs) == 0 {
|
||||
// NOTE: If enabled pairs are not populated for any reason.
|
||||
var randomPair currency.Pair
|
||||
randomPair, err = incoming.GetRandomPair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf(log.ExchangeSys, "%s Enabled pairs missing for %s. Added %s.\n",
|
||||
b.Name,
|
||||
strings.ToUpper(a.String()),
|
||||
randomPair)
|
||||
enabledPairs = currency.Pairs{randomPair}
|
||||
}
|
||||
|
||||
if len(diff.Remove) > 0 {
|
||||
log.Debugf(log.ExchangeSys, "%s Checked and updated enabled pairs [%v] - Removed: %s.\n",
|
||||
b.Name,
|
||||
strings.ToUpper(a.String()),
|
||||
diff.Remove)
|
||||
}
|
||||
err = b.Config.CurrencyPairs.StorePairs(a, enabledPairs, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.CurrencyPairs.StorePairs(a, enabledPairs, true)
|
||||
}
|
||||
|
||||
// SetAPIURL sets configuration API URL for an exchange
|
||||
@@ -865,6 +989,11 @@ func (b *Base) StoreAssetPairFormat(a asset.Item, f currency.PairStore) error {
|
||||
b.Name)
|
||||
}
|
||||
|
||||
if f.ConfigFormat.Delimiter == "" {
|
||||
return fmt.Errorf("exchange %s cannot set asset %s pair format %w",
|
||||
b.Name, a, errConfigPairFormatRequiresDelimiter)
|
||||
}
|
||||
|
||||
if b.CurrencyPairs.Pairs == nil {
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
}
|
||||
@@ -891,6 +1020,11 @@ func (b *Base) SetGlobalPairsManager(request, config *currency.PairFormat, asset
|
||||
b.Name)
|
||||
}
|
||||
|
||||
if config.Delimiter == "" {
|
||||
return fmt.Errorf("exchange %s cannot set global pairs manager %w for assets %s",
|
||||
b.Name, errConfigPairFormatRequiresDelimiter, assets)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.UseGlobalFormat = true
|
||||
b.CurrencyPairs.RequestFormat = request
|
||||
b.CurrencyPairs.ConfigFormat = config
|
||||
|
||||
@@ -418,7 +418,10 @@ func TestSetCurrencyPairFormat(t *testing.T) {
|
||||
b := Base{
|
||||
Config: &config.Exchange{},
|
||||
}
|
||||
b.SetCurrencyPairFormat()
|
||||
err := b.SetCurrencyPairFormat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if b.Config.CurrencyPairs == nil {
|
||||
t.Error("currencyPairs shouldn't be nil")
|
||||
}
|
||||
@@ -431,7 +434,10 @@ func TestSetCurrencyPairFormat(t *testing.T) {
|
||||
}
|
||||
b.CurrencyPairs.RequestFormat = pFmt
|
||||
b.CurrencyPairs.ConfigFormat = pFmt
|
||||
b.SetCurrencyPairFormat()
|
||||
err = b.SetCurrencyPairFormat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
spot, err := b.GetPairFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -444,17 +450,22 @@ func TestSetCurrencyPairFormat(t *testing.T) {
|
||||
// Test individual asset type formatting logic
|
||||
b.CurrencyPairs.UseGlobalFormat = false
|
||||
// Store non-nil pair stores
|
||||
b.CurrencyPairs.Store(asset.Spot, currency.PairStore{
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Delimiter: "~",
|
||||
},
|
||||
err = b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "~"},
|
||||
})
|
||||
b.CurrencyPairs.Store(asset.Futures, currency.PairStore{
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Delimiter: ":)",
|
||||
},
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.CurrencyPairs.Store(asset.Futures, ¤cy.PairStore{
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: ":)"},
|
||||
})
|
||||
b.SetCurrencyPairFormat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.SetCurrencyPairFormat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
spot, err = b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -492,8 +503,8 @@ func TestLoadConfigPairs(t *testing.T) {
|
||||
},
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
RequestFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
RequestFormat: ¤cy.EMPTYFORMAT,
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -529,7 +540,11 @@ func TestLoadConfigPairs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test UseGlobalFormat setting of pairs
|
||||
b.SetCurrencyPairFormat()
|
||||
err = b.SetCurrencyPairFormat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = b.SetConfigPairs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -548,7 +563,7 @@ func TestLoadConfigPairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p := pairs[0].Format(pFmt.Delimiter, pFmt.Uppercase).String()
|
||||
p := pairs[0].Format(pFmt).String()
|
||||
if p != "BTC^USD" {
|
||||
t.Errorf("incorrect value, expected BTC^USD")
|
||||
}
|
||||
@@ -575,17 +590,23 @@ func TestLoadConfigPairs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test !UseGlobalFormat setting of pairs
|
||||
exchPS, err := b.CurrencyPairs.Get(asset.Spot)
|
||||
err = b.CurrencyPairs.StoreFormat(asset.Spot, ¤cy.PairFormat{Delimiter: "~"}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.CurrencyPairs.StoreFormat(asset.Spot, ¤cy.PairFormat{Delimiter: "/"}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exchPS.RequestFormat.Delimiter = "~"
|
||||
exchPS.RequestFormat.Uppercase = false
|
||||
exchPS.ConfigFormat.Delimiter = "/"
|
||||
exchPS.ConfigFormat.Uppercase = false
|
||||
pairs = append(pairs, currency.Pair{Base: currency.XRP, Quote: currency.USD})
|
||||
b.Config.CurrencyPairs.StorePairs(asset.Spot, pairs, false)
|
||||
b.Config.CurrencyPairs.StorePairs(asset.Spot, pairs, true)
|
||||
err = b.Config.CurrencyPairs.StorePairs(asset.Spot, pairs, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.Config.CurrencyPairs.StorePairs(asset.Spot, pairs, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.Config.CurrencyPairs.UseGlobalFormat = false
|
||||
b.CurrencyPairs.UseGlobalFormat = false
|
||||
|
||||
@@ -598,7 +619,7 @@ func TestLoadConfigPairs(t *testing.T) {
|
||||
// 2) pair format is set for RequestFormat
|
||||
// 3) pair format is set for ConfigFormat
|
||||
// 4) Config pair store formats are the same as the exchanges
|
||||
pFmt, err = b.GetPairFormat(asset.Spot, false)
|
||||
configFmt, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -606,7 +627,7 @@ func TestLoadConfigPairs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p = pairs[2].Format(pFmt.Delimiter, pFmt.Uppercase).String()
|
||||
p = pairs[2].Format(configFmt).String()
|
||||
if p != "xrp/usd" {
|
||||
t.Error("incorrect value, expected xrp/usd", p)
|
||||
}
|
||||
@@ -700,13 +721,13 @@ func TestGetPairFormat(t *testing.T) {
|
||||
|
||||
// Test individual asset pair store formatting
|
||||
b.CurrencyPairs.UseGlobalFormat = false
|
||||
b.CurrencyPairs.Store(asset.Spot, currency.PairStore{
|
||||
ConfigFormat: &pFmt,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Delimiter: "/",
|
||||
Uppercase: true,
|
||||
},
|
||||
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)
|
||||
@@ -735,8 +756,14 @@ func TestGetEnabledPairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, true)
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
format := currency.PairFormat{
|
||||
Delimiter: "-",
|
||||
Index: "",
|
||||
@@ -786,8 +813,14 @@ func TestGetEnabledPairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, true)
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
format.Index = currency.BTC.String()
|
||||
b.CurrencyPairs.ConfigFormat = &format
|
||||
c, err = b.GetEnabledPairs(asset.Spot)
|
||||
@@ -803,8 +836,14 @@ func TestGetEnabledPairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, true)
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.CurrencyPairs.RequestFormat.Delimiter = ""
|
||||
b.CurrencyPairs.ConfigFormat.Delimiter = "_"
|
||||
c, err = b.GetEnabledPairs(asset.Spot)
|
||||
@@ -815,8 +854,14 @@ func TestGetEnabledPairs(t *testing.T) {
|
||||
t.Error("Exchange GetAvailablePairs() incorrect string")
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, true)
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.CurrencyPairs.RequestFormat.Delimiter = ""
|
||||
b.CurrencyPairs.ConfigFormat.Delimiter = ""
|
||||
b.CurrencyPairs.ConfigFormat.Index = currency.BTC.String()
|
||||
@@ -833,8 +878,14 @@ func TestGetEnabledPairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcusd, true)
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcusd, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcusd, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcusd, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.CurrencyPairs.ConfigFormat.Index = ""
|
||||
c, err = b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
@@ -857,7 +908,10 @@ func TestGetAvailablePairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
format := currency.PairFormat{
|
||||
Delimiter: "-",
|
||||
Index: "",
|
||||
@@ -905,7 +959,11 @@ func TestGetAvailablePairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, dogePairs, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, dogePairs, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
format.Index = currency.BTC.String()
|
||||
b.CurrencyPairs.ConfigFormat = &format
|
||||
c, err = b.GetAvailablePairs(assetType)
|
||||
@@ -922,7 +980,11 @@ func TestGetAvailablePairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.RequestFormat.Delimiter = ""
|
||||
b.CurrencyPairs.ConfigFormat.Delimiter = "_"
|
||||
c, err = b.GetAvailablePairs(assetType)
|
||||
@@ -934,7 +996,11 @@ func TestGetAvailablePairs(t *testing.T) {
|
||||
t.Error("Exchange GetAvailablePairs() incorrect string")
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, dogePairs, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, dogePairs, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.RequestFormat.Delimiter = ""
|
||||
b.CurrencyPairs.ConfigFormat.Delimiter = "_"
|
||||
b.CurrencyPairs.ConfigFormat.Index = currency.BTC.String()
|
||||
@@ -952,7 +1018,11 @@ func TestGetAvailablePairs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, btcusd, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, btcusd, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.ConfigFormat.Index = ""
|
||||
c, err = b.GetAvailablePairs(assetType)
|
||||
if err != nil {
|
||||
@@ -984,14 +1054,20 @@ func TestSupportsPair(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, pairs, false)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, pairs, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defaultpairs, err := currency.NewPairsFromStrings([]string{defaultTestCurrencyPair})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.StorePairs(asset.Spot, defaultpairs, true)
|
||||
err = b.CurrencyPairs.StorePairs(asset.Spot, defaultpairs, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
format := ¤cy.PairFormat{
|
||||
Delimiter: "-",
|
||||
@@ -1161,13 +1237,12 @@ func TestSetupDefaults(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.CurrencyPairs.Store(asset.Spot,
|
||||
currency.PairStore{
|
||||
Enabled: currency.Pairs{
|
||||
p,
|
||||
},
|
||||
},
|
||||
)
|
||||
err = b.CurrencyPairs.Store(asset.Spot, ¤cy.PairStore{
|
||||
Enabled: currency.Pairs{p},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = b.SetupDefaults(&cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1289,6 +1364,8 @@ func TestUpdatePairs(t *testing.T) {
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter},
|
||||
UseGlobalFormat: true,
|
||||
},
|
||||
}
|
||||
UAC.Config = exchCfg
|
||||
@@ -1363,7 +1440,7 @@ func TestUpdatePairs(t *testing.T) {
|
||||
}
|
||||
err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false)
|
||||
if err != nil {
|
||||
t.Errorf("Forced Exchange UpdatePairs() error: %s", err)
|
||||
t.Errorf("Exchange UpdatePairs() error: %s", err)
|
||||
}
|
||||
|
||||
// Test empty pair
|
||||
@@ -1371,18 +1448,29 @@ func TestUpdatePairs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pairs := currency.Pairs{
|
||||
currency.EMPTYPAIR,
|
||||
p,
|
||||
}
|
||||
pairs := currency.Pairs{currency.EMPTYPAIR, p}
|
||||
err = UAC.UpdatePairs(pairs, asset.Spot, true, true)
|
||||
if err != nil {
|
||||
t.Errorf("Forced Exchange UpdatePairs() error: %s", err)
|
||||
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, currency.ErrCurrencyPairEmpty)
|
||||
}
|
||||
|
||||
pairs = currency.Pairs{p, p}
|
||||
err = UAC.UpdatePairs(pairs, asset.Spot, false, true)
|
||||
if err != nil {
|
||||
t.Errorf("Forced Exchange UpdatePairs() error: %s", err)
|
||||
if !errors.Is(err, currency.ErrPairDuplication) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, currency.ErrPairDuplication)
|
||||
}
|
||||
|
||||
pairs = currency.Pairs{p}
|
||||
err = UAC.UpdatePairs(pairs, asset.Spot, false, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
err = UAC.UpdatePairs(pairs, asset.Spot, true, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
UAC.CurrencyPairs.UseGlobalFormat = true
|
||||
UAC.CurrencyPairs.ConfigFormat = ¤cy.PairFormat{
|
||||
Delimiter: "-",
|
||||
@@ -1395,6 +1483,86 @@ func TestUpdatePairs(t *testing.T) {
|
||||
if !uacPairs.Contains(p, true) {
|
||||
t.Fatal("expected currency pair not found")
|
||||
}
|
||||
|
||||
pairs = currency.Pairs{
|
||||
currency.NewPair(currency.XRP, currency.USD),
|
||||
currency.NewPair(currency.BTC, currency.USD),
|
||||
currency.NewPair(currency.LTC, currency.USD),
|
||||
currency.NewPair(currency.LTC, currency.USDT),
|
||||
}
|
||||
err = UAC.UpdatePairs(pairs, asset.Spot, true, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
pairs = currency.Pairs{
|
||||
currency.NewPair(currency.WABI, currency.USD),
|
||||
currency.NewPair(currency.EASY, currency.USD),
|
||||
currency.NewPair(currency.LARIX, currency.USD),
|
||||
currency.NewPair(currency.LTC, currency.USDT),
|
||||
}
|
||||
err = UAC.UpdatePairs(pairs, asset.Spot, false, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
uacEnabledPairs, err := UAC.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if uacEnabledPairs.Contains(currency.NewPair(currency.XRP, currency.USD), true) {
|
||||
t.Fatal("expected currency pair not found")
|
||||
}
|
||||
if uacEnabledPairs.Contains(currency.NewPair(currency.BTC, currency.USD), true) {
|
||||
t.Fatal("expected currency pair not found")
|
||||
}
|
||||
if uacEnabledPairs.Contains(currency.NewPair(currency.LTC, currency.USD), true) {
|
||||
t.Fatal("expected currency pair not found")
|
||||
}
|
||||
if !uacEnabledPairs.Contains(currency.NewPair(currency.LTC, currency.USDT), true) {
|
||||
t.Fatal("expected currency pair not found")
|
||||
}
|
||||
|
||||
// This should be matched and formatted to `link-usd`
|
||||
unintentionalInput, err := currency.NewPairFromString("linkusd")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
pairs = currency.Pairs{
|
||||
currency.NewPair(currency.WABI, currency.USD),
|
||||
currency.NewPair(currency.EASY, currency.USD),
|
||||
currency.NewPair(currency.LARIX, currency.USD),
|
||||
currency.NewPair(currency.LTC, currency.USDT),
|
||||
unintentionalInput,
|
||||
}
|
||||
|
||||
err = UAC.UpdatePairs(pairs, asset.Spot, true, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
pairs = currency.Pairs{
|
||||
currency.NewPair(currency.WABI, currency.USD),
|
||||
currency.NewPair(currency.EASY, currency.USD),
|
||||
currency.NewPair(currency.LARIX, currency.USD),
|
||||
currency.NewPair(currency.LTC, currency.USDT),
|
||||
currency.NewPair(currency.LINK, currency.USD),
|
||||
}
|
||||
|
||||
err = UAC.UpdatePairs(pairs, asset.Spot, false, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
uacEnabledPairs, err = UAC.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !uacEnabledPairs.Contains(currency.NewPair(currency.LINK, currency.USD), true) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", false, true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportsWebsocket(t *testing.T) {
|
||||
@@ -1604,7 +1772,10 @@ func TestGetFormattedPairAndAssetType(t *testing.T) {
|
||||
b := Base{
|
||||
Config: &config.Exchange{},
|
||||
}
|
||||
b.SetCurrencyPairFormat()
|
||||
err := b.SetCurrencyPairFormat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.Config.CurrencyPairs.UseGlobalFormat = true
|
||||
b.CurrencyPairs.UseGlobalFormat = true
|
||||
pFmt := ¤cy.PairFormat{
|
||||
@@ -1662,13 +1833,20 @@ func TestStoreAssetPairFormat(t *testing.T) {
|
||||
err = b.StoreAssetPairFormat(asset.Spot, currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true}})
|
||||
if !errors.Is(err, errConfigPairFormatRequiresDelimiter) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errConfigPairFormatRequiresDelimiter)
|
||||
}
|
||||
|
||||
err = b.StoreAssetPairFormat(asset.Futures, currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = b.StoreAssetPairFormat(asset.Futures, currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true}})
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -1702,7 +1880,17 @@ func TestSetGlobalPairsManager(t *testing.T) {
|
||||
}
|
||||
|
||||
err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true},
|
||||
¤cy.PairFormat{Uppercase: true}, asset.Spot, asset.Binary)
|
||||
¤cy.PairFormat{Uppercase: true},
|
||||
asset.Spot,
|
||||
asset.Binary)
|
||||
if !errors.Is(err, errConfigPairFormatRequiresDelimiter) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", 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)
|
||||
}
|
||||
@@ -2164,10 +2352,11 @@ func TestAssetWebsocketFunctionality(t *testing.T) {
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
err = b.DisableAssetWebsocketSupport(asset.Spot)
|
||||
@@ -2372,3 +2561,72 @@ func TestIsPerpetualFutureCurrency(t *testing.T) {
|
||||
t.Errorf("received: %v, expected: %v", err, common.ErrNotYetImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPairAndAssetTypeRequestFormatted(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expected := currency.Pair{Base: currency.BTC, Quote: currency.USDT}
|
||||
enabledPairs := currency.Pairs{expected}
|
||||
availablePairs := currency.Pairs{
|
||||
currency.Pair{Base: currency.BTC, Quote: currency.USDT},
|
||||
currency.Pair{Base: currency.BTC, Quote: currency.AUD},
|
||||
}
|
||||
|
||||
b := Base{
|
||||
CurrencyPairs: currency.PairsManager{
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Enabled: enabledPairs,
|
||||
Available: availablePairs,
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "-", Uppercase: true},
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
},
|
||||
asset.PerpetualContract: {
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Enabled: enabledPairs,
|
||||
Available: availablePairs,
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "_", Uppercase: true},
|
||||
ConfigFormat: ¤cy.EMPTYFORMAT,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, _, err := b.GetPairAndAssetTypeRequestFormatted("")
|
||||
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, currency.ErrCurrencyPairEmpty)
|
||||
}
|
||||
|
||||
_, _, err = b.GetPairAndAssetTypeRequestFormatted("BTCAUD")
|
||||
if !errors.Is(err, errSymbolCannotBeMatched) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errSymbolCannotBeMatched)
|
||||
}
|
||||
|
||||
_, _, err = b.GetPairAndAssetTypeRequestFormatted("BTCUSDT")
|
||||
if !errors.Is(err, errSymbolCannotBeMatched) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errSymbolCannotBeMatched)
|
||||
}
|
||||
|
||||
p, a, err := b.GetPairAndAssetTypeRequestFormatted("BTC-USDT")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
if a != asset.Spot {
|
||||
t.Fatal("unexpected value", a)
|
||||
}
|
||||
if !p.Equal(expected) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", p, expected)
|
||||
}
|
||||
|
||||
p, a, err = b.GetPairAndAssetTypeRequestFormatted("BTC_USDT")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
if a != asset.PerpetualContract {
|
||||
t.Fatal("unexpected value", a)
|
||||
}
|
||||
if !p.Equal(expected) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", p, expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1348,8 +1348,12 @@ func TestGetHistoricTrades(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rPair, err := enabledPairs.GetRandomPair()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
_, err = f.GetHistoricTrades(context.Background(),
|
||||
enabledPairs.GetRandomPair(),
|
||||
rPair,
|
||||
assets[i],
|
||||
time.Now().Add(-time.Minute*15),
|
||||
time.Now())
|
||||
@@ -1367,8 +1371,12 @@ func TestGetRecentTrades(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rPair, err := enabledPairs.GetRandomPair()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
_, err = f.GetRecentTrades(context.Background(),
|
||||
enabledPairs.GetRandomPair(), assets[i])
|
||||
rPair, assets[i])
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -2067,7 +2067,7 @@ func (f *FTX) GetFundingRates(ctx context.Context, request *order.FundingRatesRe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Pairs = request.Pairs.Format(pairFmt.Delimiter, pairFmt.Index, pairFmt.Uppercase)
|
||||
request.Pairs = request.Pairs.Format(pairFmt)
|
||||
response := make([]order.FundingRates, 0, len(request.Pairs))
|
||||
for x := range request.Pairs {
|
||||
var isPerp bool
|
||||
|
||||
@@ -174,7 +174,10 @@ func TestUpdateTicker(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.CurrencyPairs.StorePairs(asset.Spot, pairs, true)
|
||||
err = h.CurrencyPairs.StorePairs(asset.Spot, pairs, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = h.UpdateTicker(context.Background(),
|
||||
currency.NewPair(currency.BTC, currency.USD),
|
||||
asset.Spot)
|
||||
|
||||
@@ -1219,7 +1219,7 @@ func (h *HUOBI) formatFuturesCode(p currency.Code) (string, error) {
|
||||
// formatFuturesPair handles pairs in the format as "BTC-NW" and "BTC210827"
|
||||
func (h *HUOBI) formatFuturesPair(p currency.Pair) (string, error) {
|
||||
if common.StringDataCompareInsensitive(validContractShortTypes, p.Quote.String()) {
|
||||
return p.Format("_", true).String(), nil
|
||||
return p.Format(currency.PairFormat{Delimiter: "_", Uppercase: true}).String(), nil
|
||||
}
|
||||
return h.FormatSymbol(p, asset.Futures)
|
||||
}
|
||||
|
||||
@@ -2752,7 +2752,10 @@ func TestFormatFuturesPair(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r != availInstruments[0] {
|
||||
t.Errorf("expected %s, got %s", availInstruments[0], r)
|
||||
|
||||
// Test for upper case 'BTC' not lower case 'btc', disregarded numerals
|
||||
// as they not deterministic from this endpoint.
|
||||
if !strings.Contains(r, "BTC") {
|
||||
t.Errorf("expected %s, got %s", "BTC220708", r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ func (h *HUOBI) SetDefaults() {
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter,
|
||||
},
|
||||
}
|
||||
futures := currency.PairStore{
|
||||
@@ -82,6 +83,7 @@ func (h *HUOBI) SetDefaults() {
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter,
|
||||
},
|
||||
}
|
||||
err := h.StoreAssetPairFormat(asset.Spot, fmt1)
|
||||
|
||||
@@ -59,7 +59,7 @@ func (i *ItBit) SetDefaults() {
|
||||
i.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}
|
||||
err := i.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
@@ -67,8 +67,7 @@ func (i *ItBit) SetDefaults() {
|
||||
|
||||
i.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
REST: true,
|
||||
Websocket: false,
|
||||
REST: true,
|
||||
RESTCapabilities: protocol.Features{
|
||||
TickerFetching: true,
|
||||
TradeFetching: true,
|
||||
@@ -88,9 +87,6 @@ func (i *ItBit) SetDefaults() {
|
||||
WithdrawPermissions: exchange.WithdrawCryptoViaWebsiteOnly |
|
||||
exchange.WithdrawFiatViaWebsiteOnly,
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: false,
|
||||
},
|
||||
}
|
||||
|
||||
i.Requester, err = request.New(i.Name,
|
||||
|
||||
@@ -61,7 +61,9 @@ func (l *LocalBitcoins) SetDefaults() {
|
||||
l.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter}
|
||||
err := l.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
|
||||
@@ -370,7 +370,7 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) {
|
||||
}
|
||||
|
||||
for x := range pairs {
|
||||
ord.InstrumentID = pairs[x].Format("-", false).String()
|
||||
ord.InstrumentID = pairs[x].Format(currency.PairFormat{Delimiter: "-"}).String()
|
||||
request = append(request, ord)
|
||||
}
|
||||
|
||||
@@ -674,7 +674,7 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) {
|
||||
}
|
||||
|
||||
for x := range pairs {
|
||||
ord.InstrumentID = pairs[x].Format("-", false).String()
|
||||
ord.InstrumentID = pairs[x].Format(currency.PairFormat{Delimiter: "-"}).String()
|
||||
request = append(request, ord)
|
||||
}
|
||||
|
||||
|
||||
@@ -500,7 +500,7 @@ func TestPlaceMultipleSpotOrdersOverPairLimits(t *testing.T) {
|
||||
}
|
||||
|
||||
for x := range pairs {
|
||||
ord.InstrumentID = pairs[x].Format("-", false).String()
|
||||
ord.InstrumentID = pairs[x].Format(currency.PairFormat{Delimiter: "-"}).String()
|
||||
request = append(request, ord)
|
||||
}
|
||||
|
||||
@@ -884,7 +884,7 @@ func TestPlaceMultipleMarginOrdersOverPairLimits(t *testing.T) {
|
||||
}
|
||||
|
||||
for x := range pairs {
|
||||
ord.InstrumentID = pairs[x].Format("-", false).String()
|
||||
ord.InstrumentID = pairs[x].Format(currency.PairFormat{Delimiter: "-"}).String()
|
||||
request = append(request, ord)
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ func (o *OKEX) SetDefaults() {
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1054,7 +1054,7 @@ func TestUpdateByIDAndAction(t *testing.T) {
|
||||
t.Fatal("did not adjust ask item placement and details")
|
||||
}
|
||||
|
||||
book.LoadSnapshot(append(bids[:0:0], bids...), append(bids[:0:0], bids...), 0, time.Time{}, true) //nolint:gocritic
|
||||
book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Time{}, true) //nolint:gocritic
|
||||
// Delete - not found
|
||||
err = holder.updateByIDAndAction(&orderbook.Update{
|
||||
Action: orderbook.Delete,
|
||||
@@ -1070,7 +1070,7 @@ func TestUpdateByIDAndAction(t *testing.T) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errDeleteFailure)
|
||||
}
|
||||
|
||||
book.LoadSnapshot(append(bids[:0:0], bids...), append(bids[:0:0], bids...), 0, time.Time{}, true) //nolint:gocritic
|
||||
book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Time{}, true) //nolint:gocritic
|
||||
// Delete - found
|
||||
err = holder.updateByIDAndAction(&orderbook.Update{
|
||||
Action: orderbook.Delete,
|
||||
|
||||
@@ -263,7 +263,7 @@ func (z *ZB) UpdateTickers(ctx context.Context, a asset.Item) error {
|
||||
for x := range enabledPairs {
|
||||
// We can't use either pair format here, so format it to lower-
|
||||
// case and without any delimiter
|
||||
curr := enabledPairs[x].Format("", false).String()
|
||||
curr := enabledPairs[x].Format(currency.EMPTYFORMAT).String()
|
||||
if _, ok := result[curr]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user