diff --git a/config/config.go b/config/config.go index 0d20ef5b..026cc934 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,8 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/currency/forexprovider" + "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/portfolio" "github.com/thrasher-/gocryptotrader/smsglobal" @@ -95,17 +97,17 @@ type CurrencyPairFormatConfig struct { // Config is the overarching object that holds all the information for // prestart management of portfolio, SMSGlobal, webserver and enabled exchange type Config struct { - Name string - EncryptConfig int - Cryptocurrencies string - CurrencyExchangeProvider string - CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat"` - FiatDisplayCurrency string - GlobalHTTPTimeout time.Duration - Portfolio portfolio.Base `json:"PortfolioAddresses"` - SMS SMSGlobalConfig `json:"SMSGlobal"` - Webserver WebserverConfig `json:"Webserver"` - Exchanges []ExchangeConfig `json:"Exchanges"` + Name string + EncryptConfig int + Cryptocurrencies string `json:"Cryptocurrencies,omitempty"` + Currency CurrencyConfig `json:"CurrencyConfig,omitempty"` + CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat,omitempty"` + FiatDisplayCurrency string `json:"FiatDispayCurrency,omitempty"` + GlobalHTTPTimeout time.Duration + Portfolio portfolio.Base `json:"PortfolioAddresses"` + SMS SMSGlobalConfig `json:"SMSGlobal"` + Webserver WebserverConfig `json:"Webserver"` + Exchanges []ExchangeConfig `json:"Exchanges"` } // ExchangeConfig holds all the information needed for each enabled Exchange. @@ -131,6 +133,19 @@ type ExchangeConfig struct { RequestCurrencyPairFormat *CurrencyPairFormatConfig `json:"RequestCurrencyPairFormat"` } +// CurrencyConfig holds all the information needed for currency related manipulation +type CurrencyConfig struct { + ForexProviders []base.Settings `json:"ForexProviders"` + Cryptocurrencies string `json:"Cryptocurrencies"` + CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat"` + FiatDisplayCurrency string +} + +// GetCurrencyConfig returns currency configurations +func (c *Config) GetCurrencyConfig() CurrencyConfig { + return c.Currency +} + // SupportsPair returns true or not whether the exchange supports the supplied // pair func (c *Config) SupportsPair(exchName string, p pair.CurrencyPair) (bool, error) { @@ -222,7 +237,14 @@ func (c *Config) GetRequestCurrencyPairFormat(exchName string) (*CurrencyPairFor // GetCurrencyPairDisplayConfig retrieves the currency pair display preference func (c *Config) GetCurrencyPairDisplayConfig() *CurrencyPairFormatConfig { - return c.CurrencyPairFormat + return c.Currency.CurrencyPairFormat +} + +// GetAllExchangeConfigs returns all exchange configurations +func (c *Config) GetAllExchangeConfigs() []ExchangeConfig { + m.Lock() + defer m.Unlock() + return c.Exchanges } // GetExchangeConfig returns exchange configurations by its indivdual name @@ -237,6 +259,30 @@ func (c *Config) GetExchangeConfig(name string) (ExchangeConfig, error) { return ExchangeConfig{}, fmt.Errorf(ErrExchangeNotFound, name) } +// GetForexProviderConfig returns a forex provider configuration by its name +func (c *Config) GetForexProviderConfig(name string) (base.Settings, error) { + m.Lock() + defer m.Unlock() + for i := range c.Currency.ForexProviders { + if c.Currency.ForexProviders[i].Name == name { + return c.Currency.ForexProviders[i], nil + } + } + return base.Settings{}, errors.New("provider not found") +} + +// GetPrimaryForexProvider returns the primary forex provider +func (c *Config) GetPrimaryForexProvider() string { + m.Lock() + defer m.Unlock() + for i := range c.Currency.ForexProviders { + if c.Currency.ForexProviders[i].PrimaryProvider { + return c.Currency.ForexProviders[i].Name + } + } + return "" +} + // UpdateExchangeConfig updates exchange configurations func (c *Config) UpdateExchangeConfig(e ExchangeConfig) error { m.Lock() @@ -274,10 +320,6 @@ func (c *Config) CheckSMSGlobalConfigValues() error { // CheckExchangeConfigValues returns configuation values for all enabled // exchanges func (c *Config) CheckExchangeConfigValues() error { - if c.Cryptocurrencies == "" { - return errors.New(ErrCryptocurrenciesEmpty) - } - exchanges := 0 for i, exch := range c.Exchanges { if exch.Enabled { @@ -357,6 +399,90 @@ func (c *Config) CheckWebserverConfigValues() error { return nil } +// CheckCurrencyConfigValues checks to see if the currency config values are correct or not +func (c *Config) CheckCurrencyConfigValues() error { + if len(c.Currency.ForexProviders) == 0 { + if len(forexprovider.GetAvailableForexProviders()) == 0 { + return errors.New("no forex providers available") + } + var providers []base.Settings + availProviders := forexprovider.GetAvailableForexProviders() + for x := range availProviders { + providers = append(providers, + base.Settings{ + Name: availProviders[x], + Enabled: false, + Verbose: false, + RESTPollingDelay: 600, + APIKey: "Key", + APIKeyLvl: -1, + PrimaryProvider: false, + }, + ) + } + c.Currency.ForexProviders = providers + } + + count := 0 + for i := range c.Currency.ForexProviders { + if c.Currency.ForexProviders[i].Enabled == true { + if c.Currency.ForexProviders[i].APIKey == "Key" { + log.Printf("WARNING -- %s forex provider API key not set. Please set this in your config.json file", c.Currency.ForexProviders[i].Name) + c.Currency.ForexProviders[i].Enabled = false + c.Currency.ForexProviders[i].PrimaryProvider = false + continue + } + if c.Currency.ForexProviders[i].APIKeyLvl == -1 { + log.Printf("WARNING -- %s APIKey Level not set, functions limited. Please set this in your config.json file", + c.Currency.ForexProviders[i].Name) + } + count++ + } + } + + if count == 0 { + for x := range c.Currency.ForexProviders { + if c.Currency.ForexProviders[x].Name == "CurrencyConverter" { + c.Currency.ForexProviders[x].Enabled = true + c.Currency.ForexProviders[x].APIKey = "" + c.Currency.ForexProviders[x].PrimaryProvider = true + log.Printf("WARNING -- No forex providers set, defaulting to free provider CurrencyConverterAPI.") + } + } + } + + if len(c.Currency.Cryptocurrencies) == 0 { + if len(c.Cryptocurrencies) != 0 { + c.Currency.Cryptocurrencies = c.Cryptocurrencies + c.Cryptocurrencies = "" + } else { + c.Currency.Cryptocurrencies = currency.DefaultCryptoCurrencies + } + } + + if c.Currency.CurrencyPairFormat == nil { + if c.CurrencyPairFormat != nil { + c.Currency.CurrencyPairFormat = c.CurrencyPairFormat + c.CurrencyPairFormat = nil + } else { + c.Currency.CurrencyPairFormat = &CurrencyPairFormatConfig{ + Delimiter: "-", + Uppercase: true, + } + } + } + + if c.Currency.FiatDisplayCurrency == "" { + if c.FiatDisplayCurrency != "" { + c.Currency.FiatDisplayCurrency = c.FiatDisplayCurrency + c.FiatDisplayCurrency = "" + } else { + c.Currency.FiatDisplayCurrency = "USD" + } + } + return nil +} + // RetrieveConfigCurrencyPairs splits, assigns and verifies enabled currency // pairs either cryptoCurrencies or fiatCurrencies func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool) error { @@ -583,24 +709,9 @@ func (c *Config) CheckConfig() error { } } - if c.CurrencyExchangeProvider == "" { - c.CurrencyExchangeProvider = FXProviderFixer - } else { - if c.CurrencyExchangeProvider != "yahoo" && c.CurrencyExchangeProvider != FXProviderFixer { - log.Println(WarningCurrencyExchangeProvider) - c.CurrencyExchangeProvider = FXProviderFixer - } - } - - if c.CurrencyPairFormat == nil { - c.CurrencyPairFormat = &CurrencyPairFormatConfig{ - Delimiter: "-", - Uppercase: true, - } - } - - if c.FiatDisplayCurrency == "" { - c.FiatDisplayCurrency = "USD" + err = c.CheckCurrencyConfigValues() + if err != nil { + return err } if c.GlobalHTTPTimeout <= 0 { @@ -630,10 +741,7 @@ func (c *Config) UpdateConfig(configPath string, newCfg Config) error { c.Name = newCfg.Name c.EncryptConfig = newCfg.EncryptConfig - c.Cryptocurrencies = newCfg.Cryptocurrencies - c.CurrencyExchangeProvider = newCfg.CurrencyExchangeProvider - c.CurrencyPairFormat = newCfg.CurrencyPairFormat - c.FiatDisplayCurrency = newCfg.FiatDisplayCurrency + c.Currency = newCfg.Currency c.GlobalHTTPTimeout = newCfg.GlobalHTTPTimeout c.Portfolio = newCfg.Portfolio c.SMS = newCfg.SMS diff --git a/config/config_test.go b/config/config_test.go index 6f2b075b..468170f9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -591,9 +591,9 @@ func TestUpdateConfig(t *testing.T) { t.Fatalf("Test failed. Error should of been thrown for invalid path") } - newCfg.Cryptocurrencies = "" + newCfg.Currency.Cryptocurrencies = "" err = c.UpdateConfig("", newCfg) - if err == nil { - t.Fatalf("Test failed. Error should of been thrown for empty cryptocurrencies") + if len(c.Currency.Cryptocurrencies) == 0 { + t.Fatalf("Test failed. Cryptocurrencies should have been repopulated") } } diff --git a/config_example.json b/config_example.json index ed129411..95f46e16 100644 --- a/config_example.json +++ b/config_example.json @@ -1,13 +1,52 @@ { "Name": "Skynet", "EncryptConfig": 0, - "Cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH", - "CurrencyExchangeProvider": "fixer", - "CurrencyPairFormat": { - "Uppercase": true, - "Delimiter": "-" + "CurrencyConfig": { + "ForexProviders": [ + { + "Name": "CurrencyConverter", + "Enabled": true, + "Verbose": false, + "RESTPollingDelay": 600, + "APIKey": "", + "APIKeyLvl": -1, + "PrimaryProvider": true + }, + { + "Name": "CurrencyLayer", + "Enabled": false, + "Verbose": false, + "RESTPollingDelay": 600, + "APIKey": "Key", + "APIKeyLvl": -1, + "PrimaryProvider": false + }, + { + "Name": "Fixer", + "Enabled": false, + "Verbose": false, + "RESTPollingDelay": 600, + "APIKey": "Key", + "APIKeyLvl": -1, + "PrimaryProvider": false + }, + { + "Name": "OpenExchangeRates", + "Enabled": false, + "Verbose": false, + "RESTPollingDelay": 600, + "APIKey": "Key", + "APIKeyLvl": -1, + "PrimaryProvider": false + } + ], + "Cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH", + "CurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + }, + "FiatDisplayCurrency": "USD" }, - "FiatDisplayCurrency": "USD", "GlobalHTTPTimeout": 15000000000, "PortfolioAddresses": { "Addresses": [ @@ -96,7 +135,7 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,BCC-BTC,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,HSR-BTC,OAX-ETH,DNT-ETH,MCO-ETH,ICN-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,SNGLS-ETH,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,SALT-BTC,SALT-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,SUB-BTC,SUB-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,ICN-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,HSR-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,MOD-BTC,MOD-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,VEN-BNB,YOYO-BNB,POWR-BNB,VEN-BTC,VEN-ETH,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BCC-ETH,BCC-USDT,BCC-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,BTS-BNB,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,FUEL-ETH,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADX-BNB,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,WINGS-BTC,WINGS-ETH,NAV-BTC,NAV-ETH,NAV-BNB,LUN-BTC,LUN-ETH,TRIG-BTC,TRIG-ETH,TRIG-BNB,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,CHAT-BTC,CHAT-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,RPX-BTC,RPX-ETH,RPX-BNB,NCASH-BTC,NCASH-ETH,NCASH-BNB,POA-BTC,POA-ETH,POA-BNB,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,CLOAK-BTC,CLOAK-ETH,GNT-BTC,GNT-ETH,GNT-BNB,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,BCN-BTC,BCN-ETH,BCN-BNB,REP-BTC,REP-ETH,REP-BNB,TUSD-BTC,TUSD-ETH,TUSD-BNB,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,CVC-BNB,THETA-BTC,THETA-ETH,THETA-BNB", + "AvailablePairs": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,BCC-BTC,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,HSR-BTC,OAX-ETH,DNT-ETH,MCO-ETH,ICN-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,SNGLS-ETH,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,SALT-BTC,SALT-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,SUB-BTC,SUB-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,ICN-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,HSR-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,MOD-BTC,MOD-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,VEN-BNB,YOYO-BNB,POWR-BNB,VEN-BTC,VEN-ETH,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BCC-ETH,BCC-USDT,BCC-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,BTS-BNB,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,FUEL-ETH,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADX-BNB,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,WINGS-BTC,WINGS-ETH,NAV-BTC,NAV-ETH,NAV-BNB,LUN-BTC,LUN-ETH,TRIG-BTC,TRIG-ETH,TRIG-BNB,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,CHAT-BTC,CHAT-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,RPX-BTC,RPX-ETH,RPX-BNB,NCASH-BTC,NCASH-ETH,NCASH-BNB,POA-BTC,POA-ETH,POA-BNB,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,CLOAK-BTC,CLOAK-ETH,GNT-BTC,GNT-ETH,GNT-BNB,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,BCN-BTC,BCN-ETH,BCN-BNB,REP-BTC,REP-ETH,REP-BNB,TUSD-BTC,TUSD-ETH,TUSD-BNB,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,CVC-BNB,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB", "EnabledPairs": "BTC-USDT", "BaseCurrencies": "USD", "AssetTypes": "SPOT", @@ -120,7 +159,7 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,BCHUSD,BCHBTC,BCHETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,CFIUSD,CFIBTC,CFIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,VENUSD,VENBTC,VENETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH", + "AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,BCHUSD,BCHBTC,BCHETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,CFIUSD,CFIBTC,CFIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,VENUSD,VENBTC,VENETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH", "EnabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", "BaseCurrencies": "USD", "AssetTypes": "SPOT", @@ -219,7 +258,7 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-EMC2,BTC-XMY,BTC-AUR,BTC-EFL,BTC-GLD,BTC-SLR,BTC-PTC,BTC-GRS,BTC-NLG,BTC-RBY,BTC-XWC,BTC-MONA,BTC-THC,BTC-ENRG,BTC-ERC,BTC-VRC,BTC-CURE,BTC-XMR,BTC-CLOAK,BTC-KORE,BTC-XDN,BTC-TRUST,BTC-NAV,BTC-XST,BTC-VIA,BTC-PINK,BTC-IOC,BTC-CANN,BTC-SYS,BTC-NEOS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-SWIFT,BTC-DOPE,BTC-BLOCK,BTC-ABY,BTC-BYC,BTC-XMG,BTC-BAY,BTC-SPR,BTC-VTR,BTC-XRP,BTC-COVAL,BTC-NXS,BTC-XCP,BTC-BITB,BTC-GEO,BTC-FLDC,BTC-GRC,BTC-FLO,BTC-NBT,BTC-MUE,BTC-XEM,BTC-CLAM,BTC-DMD,BTC-GAM,BTC-SPHR,BTC-OK,BTC-AEON,BTC-ETH,BTC-TX,BTC-BCY,BTC-EXP,BTC-OMNI,BTC-AMP,BTC-XLM,USDT-BTC,BTC-RVR,BTC-EMC,BTC-FCT,BTC-EGC,BTC-SLS,BTC-RADS,BTC-DCR,BTC-BSD,BTC-XVG,BTC-PIVX,BTC-MEME,BTC-STEEM,BTC-2GIVE,BTC-LSK,BTC-BRK,BTC-WAVES,BTC-LBC,BTC-SBD,BTC-BRX,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-UNB,BTC-SYNX,BTC-EBST,BTC-VRM,BTC-SEQ,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-ZCL,BTC-IOP,BTC-GOLOS,BTC-UBQ,BTC-KMD,BTC-GBG,BTC-SIB,BTC-ION,BTC-LMC,BTC-QWARK,BTC-CRW,BTC-SWT,BTC-MLN,BTC-ARK,BTC-DYN,BTC-TKS,BTC-MUSIC,BTC-DTB,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-NXC,BTC-EDG,BTC-LGD,BTC-TRST,ETH-GNT,ETH-REP,USDT-ETH,ETH-WINGS,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,BTC-LUN,ETH-GUP,ETH-RLC,ETH-LUN,ETH-GNO,BTC-HMQ,ETH-HMQ,BTC-ANT,ETH-TRST,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-QRL,ETH-QRL,BTC-CRB,ETH-CRB,ETH-LGD,BTC-PTOY,ETH-PTOY,BTC-CFI,ETH-CFI,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,ETH-ADT,BTC-PAY,ETH-PAY,BTC-STORJ,ETH-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCC,USDT-BCC,BTC-BCC,BTC-DNT,ETH-DNT,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,ETH-FCT,USDT-OMG,BTC-ADA,BTC-MANA,ETH-MANA,BTC-SALT,ETH-SALT,BTC-TIX,ETH-TIX,BTC-RCN,ETH-RCN,BTC-VIB,ETH-VIB,BTC-MER,BTC-POWR,ETH-POWR,BTC-BTG,ETH-BTG,USDT-BTG,ETH-ADA,BTC-ENG,ETH-ENG,USDT-ADA,USDT-XVG,USDT-NXT,BTC-UKG,ETH-UKG,BTC-IGNIS,BTC-SRN,ETH-SRN,BTC-WAX,ETH-WAX,BTC-ZRX,ETH-ZRX,BTC-VEE,ETH-VEE,BTC-BCPT,ETH-BCPT,BTC-TRX,ETH-TRX,BTC-TUSD,BTC-LRC,ETH-LRC,ETH-TUSD,BTC-UP,ETH-UP,BTC-DMT,ETH-DMT,USDT-TUSD,BTC-POLY,ETH-POLY,BTC-PRO,ETH-PRO,USDT-SC,USDT-TRX,BTC-BLT,ETH-BLT,BTC-STORM,ETH-STORM,BTC-AID,ETH-AID,BTC-NGC,ETH-NGC,BTC-GTO,ETH-GTO,USDT-DCR,BTC-OCN,ETH-OCN", + "AvailablePairs": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-EMC2,BTC-XMY,BTC-AUR,BTC-EFL,BTC-GLD,BTC-SLR,BTC-PTC,BTC-GRS,BTC-NLG,BTC-RBY,BTC-XWC,BTC-MONA,BTC-THC,BTC-ENRG,BTC-ERC,BTC-VRC,BTC-CURE,BTC-XMR,BTC-CLOAK,BTC-KORE,BTC-XDN,BTC-TRUST,BTC-NAV,BTC-XST,BTC-VIA,BTC-PINK,BTC-IOC,BTC-CANN,BTC-SYS,BTC-NEOS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-SWIFT,BTC-DOPE,BTC-BLOCK,BTC-ABY,BTC-BYC,BTC-XMG,BTC-BAY,BTC-SPR,BTC-VTR,BTC-XRP,BTC-GAME,BTC-COVAL,BTC-NXS,BTC-XCP,BTC-BITB,BTC-GEO,BTC-FLDC,BTC-GRC,BTC-FLO,BTC-NBT,BTC-MUE,BTC-XEM,BTC-CLAM,BTC-DMD,BTC-GAM,BTC-SPHR,BTC-OK,BTC-AEON,BTC-ETH,BTC-TX,BTC-BCY,BTC-EXP,BTC-OMNI,BTC-AMP,BTC-XLM,USDT-BTC,BTC-RVR,BTC-EMC,BTC-FCT,BTC-EGC,BTC-SLS,BTC-RADS,BTC-DCR,BTC-BSD,BTC-XVG,BTC-PIVX,BTC-MEME,BTC-STEEM,BTC-2GIVE,BTC-LSK,BTC-BRK,BTC-WAVES,BTC-LBC,BTC-SBD,BTC-BRX,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-UNB,BTC-SYNX,BTC-EBST,BTC-VRM,BTC-SEQ,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-ZCL,BTC-IOP,BTC-GOLOS,BTC-UBQ,BTC-KMD,BTC-GBG,BTC-SIB,BTC-ION,BTC-LMC,BTC-QWARK,BTC-CRW,BTC-SWT,BTC-MLN,BTC-ARK,BTC-DYN,BTC-TKS,BTC-MUSIC,BTC-DTB,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-NXC,BTC-EDG,BTC-LGD,BTC-TRST,ETH-GNT,ETH-REP,USDT-ETH,ETH-WINGS,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,BTC-LUN,ETH-GUP,ETH-RLC,ETH-LUN,ETH-GNO,BTC-HMQ,ETH-HMQ,BTC-ANT,ETH-TRST,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-QRL,ETH-QRL,BTC-CRB,ETH-CRB,ETH-LGD,BTC-PTOY,ETH-PTOY,BTC-CFI,ETH-CFI,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,ETH-ADT,BTC-PAY,ETH-PAY,BTC-STORJ,ETH-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCC,USDT-BCC,BTC-BCC,BTC-DNT,ETH-DNT,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,ETH-FCT,USDT-OMG,BTC-ADA,BTC-MANA,ETH-MANA,BTC-SALT,ETH-SALT,BTC-TIX,ETH-TIX,BTC-RCN,ETH-RCN,BTC-VIB,ETH-VIB,BTC-MER,BTC-POWR,ETH-POWR,BTC-BTG,ETH-BTG,USDT-BTG,ETH-ADA,BTC-ENG,ETH-ENG,USDT-ADA,USDT-XVG,USDT-NXT,BTC-UKG,ETH-UKG,BTC-IGNIS,BTC-SRN,ETH-SRN,BTC-WAX,ETH-WAX,BTC-ZRX,ETH-ZRX,BTC-VEE,ETH-VEE,BTC-BCPT,ETH-BCPT,BTC-TRX,ETH-TRX,BTC-TUSD,BTC-LRC,ETH-LRC,ETH-TUSD,BTC-UP,ETH-UP,BTC-DMT,ETH-DMT,USDT-TUSD,BTC-POLY,ETH-POLY,BTC-PRO,ETH-PRO,USDT-SC,USDT-TRX,BTC-BLT,ETH-BLT,BTC-STORM,ETH-STORM,BTC-AID,ETH-AID,BTC-NGC,ETH-NGC,BTC-GTO,ETH-GTO,USDT-DCR,BTC-OCN,ETH-OCN,USD-BTC,USD-USDT,USD-TUSD,BTC-TUBE", "EnabledPairs": "USDT-BTC", "BaseCurrencies": "USD", "AssetTypes": "SPOT", @@ -388,7 +427,7 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,DSH-BTC,EMC-BTC,ETH-BTC,FCN-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,QCN-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,AMP-BTC,BUS-BTC,DGD-BTC,ICN-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,TAAS-BTC,NXC-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,CFI-BTC,PLBT-BTC,BNT-BTC,XDNCO-BTC,FYN-ETH,SNM-BTC,SNM-ETH,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,CFI-ETH,PTOY-ETH,1ST-ETH,XAUR-ETH,TAAS-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,NET-ETH,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,MNE-BTC,MSP-ETH,DDF-ETH,XTZ-ETH,XTZ-USD,UET-ETH,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,NDC-ETH,PRO-ETH,AVT-ETH,COSS-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,QAU-BTC,QAU-ETH,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,IFT-BTC,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,CAT-BTC,CAT-ETH,CAT-USD,BCH-BTC,BCH-ETH,BCH-USD,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,BAS-ETH,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,ICOS-BTC,ICOS-ETH,ICOS-USD,PPC-BTC,PPC-USD,QTUM-ETH,VERI-BTC,VERI-ETH,VERI-USD,IGNIS-ETH,PRG-BTC,PRG-ETH,PRG-USD,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,SKIN-BTC,EMGO-BTC,EMGO-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,FUEL-BTC,FUEL-ETH,FUEL-USD,POE-BTC,POE-ETH,MCAP-BTC,AIR-BTC,AIR-ETH,AIR-USD,AMB-USD,AMB-ETH,AMB-BTC,NTO-BTC,ICO-BTC,PING-BTC,GAME-BTC,TKR-ETH,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,WMGO-BTC,WMGO-USD,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ORME-BTC,ICX-USD,PIX-BTC,PIX-ETH,IND-ETH,KICK-BTC,YOYOW-BTC,MIPS-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-BTC,DCN-ETH,DCN-USD,LAT-BTC,CCT-ETH,EBET-ETH,VIBE-BTC,VOISE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,ETBS-BTC,TRX-BTC,TRX-ETH,TRX-USD,VEN-BTC,VEN-ETH,VEN-USD,ART-BTC,EVX-BTC,EVX-ETH,QVT-ETH,EBTCOLD-BTC,EBTCOLD-ETH,EBTCOLD-USD,BKB-BTC,EXN-BTC,TGT-BTC,ATS-ETH,CTR-BTC,CTR-ETH,CTR-USD,BMT-BTC,BMT-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,CNX-BTC,ATB-BTC,ATB-ETH,ATB-USD,ODN-BTC,BTM-BTC,BTM-ETH,BTM-USD,B2X-BTC,B2X-ETH,B2X-USD,ATM-BTC,ATM-ETH,ATM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,COSS-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,CL-BTC,CL-ETH,CL-USD,LA-ETH,CLD-BTC,CLD-ETH,CLD-USD,ELM-BTC,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,POLL-BTC,IXT-BTC,ATS-BTC,SCL-BTC,ATL-BTC,EBTC-BTC,EBTC-ETH,EBTC-USD,ETP-BTC,ETP-ETH,ETP-USD,OTX-BTC,CDX-ETH,DRPU-BTC,NEBL-BTC,NEBL-ETH,HAC-BTC,CTX-BTC,CTX-ETH,ELE-BTC,ARN-BTC,ARN-ETH,SISA-BTC,SISA-ETH,STU-BTC,STU-ETH,GVT-ETH,INDI-BTC,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,ITS-BTC,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,KBR-BTC,TBT-BTC,ERO-BTC,SMS-BTC,SMS-ETH,SMS-USD,ZAP-BTC,DOV-BTC,DOV-ETH,FRD-BTC,DRPU-ETH,OTN-BTC,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,BTCA-BTC,BTCA-ETH,BTCA-USD,WRC-BTC,WRC-ETH,WRC-USD,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,ECH-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,TIO-BTC,TIO-ETH,TIO-USD,WAX-BTC,WAX-ETH,WAX-USD,EET-BTC,EET-ETH,EET-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CVH-ETH,CVH-USD,CAS-BTC,CAS-ETH,CAS-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,GRMD-BTC,AVH-BTC,TRAC-ETH,JNT-ETH,PCL-BTC,PCL-ETH,CLOUT-BTC,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,AVH-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,AVH-USD,CLOUT-ETH,CLOUT-USD,TAU-BTC,MEK-BTC,BAR-BTC,BAR-ETH,BAR-USD,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,WLK-BTC,WLK-ETH,WLK-USD,EVN-BTC,CPG-BTC,CPG-ETH,BPTN-BTC,BPTN-ETH,BPTN-USD,BETR-BTC,BETR-ETH,ARCT-BTC,ARCT-USD,DBET-BTC,DBET-ETH,DBET-USD,RNTB-ETH,HAND-ETH,HAND-USD,BEZ-BTC,BEZ-ETH,BEZ-USD,ACO-ETH,CTE-BTC,CTE-ETH,CTE-USD,UTNP-BTC,UTNP-ETH,UTNP-USD,CPY-BTC,CPY-ETH,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,HIRE-ETH,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,RPM-BTC,RPM-ETH,MTX-BTC,MTX-ETH,MTX-USD,BGG-BTC,BGG-ETH,BGG-USD,SETH-ETH,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,NOAH-BTC,SOC-BTC,WILD-BTC,INSUR-BTC,INSUR-ETH,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,MLD-BTC,MLD-ETH,MLD-USD,BETR-USD,CGC-ETH,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,HLW-ETH,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,BUBO-BTC,BUBO-ETH,BUBO-USD,VIT-BTC,VIT-ETH,VIT-USD,CLR-BTC,NCT-BTC,NCT-ETH,NCT-USD,AXP-BTC,AXP-ETH,BMH-BTC,BANCA-USD,NOAH-ETH,NOAH-USD,HQX-BTC,LDC-BTC,XMO-BTC,XMO-USD,XMO-ETH,BERRY-BTC,BERRY-ETH,BERRY-USD,BSTN-BTC,BSTN-ETH,BSTN-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,UNC-BTC,UNC-ETH,RPX-BTC,RPX-ETH,RPX-USD,KIN-ETH,ARDR-USD,DAXT-BTC,DAXT-ETH,FOTA-ETH,FOTA-BTC,SETH-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,ADH-BTC,ADH-ETH,BBC-BTC,BBC-ETH,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,SUNC-ETH,DADI-USD,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,VIO-ETH,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,FREC-BTC,NAVI-BTC,FREC-ETH,FREC-USD,VME-ETH,NAVI-ETH,BTCP-BTC,LND-ETH,CSM-BTC,NANJ-BTC,MTC-BTC,MTC-ETH,MTC-USD,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,HIRE-BTC,TKA-BTC,TKA-ETH,TKA-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,CHX-BTC,CHX-ETH,CHX-USD,PAT-BTC,PAT-ETH,XMC-BTC,EJOY-BTC,EJOY-ETH,EJOY-USD,TBAR-BTC,TBAR-ETH,TBAR-USD,FXT-ETH,HERO-BTC,HERO-ETH,XMC-ETH,XMC-USD,STAK-BTC,STAK-ETH,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,LUC-BTC,MITX-BTC,B2G-BTC,B2G-USD,LATX-BTC,ZPT-BTC,ZPT-ETH,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,ZPT-USD,MORPH-BTC,MORPH-ETH,MORPH-USD,CPT-BTC,PAT-USD,EBKC-BTC,MITX-ETH", + "AvailablePairs": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,DSH-BTC,EMC-BTC,ETH-BTC,FCN-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,QCN-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,AMP-BTC,BUS-BTC,DGD-BTC,ICN-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,TAAS-BTC,NXC-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,CFI-BTC,PLBT-BTC,BNT-BTC,XDNCO-BTC,FYN-ETH,SNM-BTC,SNM-ETH,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,CFI-ETH,PTOY-ETH,1ST-ETH,XAUR-ETH,TAAS-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,NET-ETH,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,MNE-BTC,MSP-ETH,DDF-ETH,XTZ-ETH,XTZ-USD,UET-ETH,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,NDC-ETH,PRO-ETH,AVT-ETH,COSS-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,QAU-BTC,QAU-ETH,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,IFT-BTC,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,CAT-BTC,CAT-ETH,CAT-USD,BCH-BTC,BCH-ETH,BCH-USD,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,BAS-ETH,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,ICOS-BTC,ICOS-ETH,ICOS-USD,PPC-BTC,PPC-USD,QTUM-ETH,VERI-BTC,VERI-ETH,VERI-USD,IGNIS-ETH,PRG-BTC,PRG-ETH,PRG-USD,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,SKIN-BTC,EMGO-BTC,EMGO-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,FUEL-BTC,FUEL-ETH,FUEL-USD,POE-BTC,POE-ETH,MCAP-BTC,AIR-BTC,AIR-ETH,AIR-USD,AMB-USD,AMB-ETH,AMB-BTC,NTO-BTC,ICO-BTC,PING-BTC,GAME-BTC,TKR-ETH,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,WMGO-BTC,WMGO-USD,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ORME-BTC,ICX-USD,PIX-BTC,PIX-ETH,IND-ETH,KICK-BTC,YOYOW-BTC,MIPS-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-BTC,DCN-ETH,DCN-USD,LAT-BTC,CCT-ETH,EBET-ETH,VIBE-BTC,VOISE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,ETBS-BTC,TRX-BTC,TRX-ETH,TRX-USD,VEN-BTC,VEN-ETH,VEN-USD,ART-BTC,EVX-BTC,EVX-ETH,QVT-ETH,EBTCOLD-BTC,EBTCOLD-ETH,EBTCOLD-USD,BKB-BTC,EXN-BTC,TGT-BTC,ATS-ETH,BMT-BTC,BMT-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,CNX-BTC,ATB-BTC,ATB-ETH,ATB-USD,ODN-BTC,BTM-BTC,BTM-ETH,BTM-USD,B2X-BTC,B2X-ETH,B2X-USD,ATM-BTC,ATM-ETH,ATM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,COSS-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,CL-BTC,CL-ETH,CL-USD,LA-ETH,CLD-BTC,CLD-ETH,CLD-USD,ELM-BTC,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,POLL-BTC,IXT-BTC,ATS-BTC,SCL-BTC,ATL-BTC,EBTC-BTC,EBTC-ETH,EBTC-USD,ETP-BTC,ETP-ETH,ETP-USD,OTX-BTC,CDX-ETH,DRPU-BTC,NEBL-BTC,NEBL-ETH,HAC-BTC,CTX-BTC,CTX-ETH,ELE-BTC,ARN-BTC,ARN-ETH,SISA-BTC,SISA-ETH,STU-BTC,STU-ETH,GVT-ETH,INDI-BTC,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,ITS-BTC,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,KBR-BTC,TBT-BTC,ERO-BTC,SMS-BTC,SMS-ETH,SMS-USD,ZAP-BTC,DOV-BTC,DOV-ETH,FRD-BTC,DRPU-ETH,OTN-BTC,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,BTCA-BTC,BTCA-ETH,BTCA-USD,WRC-BTC,WRC-ETH,WRC-USD,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,ECH-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,TIO-BTC,TIO-ETH,TIO-USD,WAX-BTC,WAX-ETH,WAX-USD,EET-BTC,EET-ETH,EET-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CVH-ETH,CVH-USD,CAS-BTC,CAS-ETH,CAS-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,GRMD-BTC,AVH-BTC,TRAC-ETH,JNT-ETH,PCL-BTC,PCL-ETH,CLOUT-BTC,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,AVH-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,AVH-USD,CLOUT-ETH,CLOUT-USD,TAU-BTC,MEK-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,WLK-BTC,WLK-ETH,WLK-USD,EVN-BTC,CPG-BTC,CPG-ETH,BPTN-BTC,BPTN-ETH,BPTN-USD,BETR-BTC,BETR-ETH,ARCT-BTC,ARCT-USD,DBET-BTC,DBET-ETH,DBET-USD,RNTB-ETH,HAND-ETH,HAND-USD,BEZ-BTC,BEZ-ETH,BEZ-USD,ACO-ETH,CTE-BTC,CTE-ETH,CTE-USD,UTNP-BTC,UTNP-ETH,UTNP-USD,CPY-BTC,CPY-ETH,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,HIRE-ETH,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,RPM-BTC,RPM-ETH,MTX-BTC,MTX-ETH,MTX-USD,BGG-BTC,BGG-ETH,BGG-USD,SETH-ETH,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,NOAH-BTC,SOC-BTC,WILD-BTC,INSUR-BTC,INSUR-ETH,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,MLD-BTC,MLD-ETH,MLD-USD,BETR-USD,CGC-ETH,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,HLW-ETH,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,BUBO-BTC,BUBO-ETH,BUBO-USD,VIT-BTC,VIT-ETH,VIT-USD,CLR-BTC,NCT-BTC,NCT-ETH,NCT-USD,AXP-BTC,AXP-ETH,BMH-BTC,BANCA-USD,NOAH-ETH,NOAH-USD,HQX-BTC,LDC-BTC,XMO-BTC,XMO-USD,XMO-ETH,BERRY-BTC,BERRY-ETH,BERRY-USD,BSTN-BTC,BSTN-ETH,BSTN-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,UNC-BTC,UNC-ETH,RPX-BTC,RPX-ETH,RPX-USD,KIN-ETH,ARDR-USD,DAXT-BTC,DAXT-ETH,FOTA-ETH,FOTA-BTC,SETH-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,ADH-BTC,ADH-ETH,BBC-BTC,BBC-ETH,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,SUNC-ETH,DADI-USD,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,VIO-ETH,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,FREC-BTC,NAVI-BTC,FREC-ETH,FREC-USD,VME-ETH,NAVI-ETH,BTCP-BTC,LND-ETH,CSM-BTC,NANJ-BTC,MTC-BTC,MTC-ETH,MTC-USD,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,HIRE-BTC,TKA-BTC,TKA-ETH,TKA-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,CHX-BTC,CHX-ETH,CHX-USD,PAT-BTC,PAT-ETH,XMC-BTC,EJOY-BTC,EJOY-ETH,EJOY-USD,FXT-ETH,HERO-BTC,HERO-ETH,XMC-ETH,XMC-USD,STAK-BTC,STAK-ETH,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,LUC-BTC,MITX-BTC,B2G-BTC,B2G-USD,LATX-BTC,ZPT-BTC,ZPT-ETH,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,ZPT-USD,MORPH-BTC,MORPH-ETH,MORPH-USD,EBKC-BTC,CPT-BTC,PAT-USD,WITH-BTC,MITX-ETH,JBC-BTC,JBC-ETH,JOT-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-BTC,TIV-ETH,TIV-USD,LUC-ETH,LUC-USD,CSM-ETH,CSM-USD,INK-ETH,INK-BTC,INK-USD,TKY-BTC", "EnabledPairs": "BTC-USD", "BaseCurrencies": "USD", "AssetTypes": "SPOT", @@ -412,7 +451,7 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "BTC-USDT,BCH-USDT,ETH-USDT,ETC-USDT,LTC-USDT,EOS-USDT,XRP-USDT,OMG-USDT,DASH-USDT,ZEC-USDT,ADA-USDT,STEEM-USDT,IOTA-USDT,SOC-USDT,CTXC-USDT,ACT-USDT,BTM-USDT,BTS-USDT,ONT-USDT,IOST-USDT,HT-USDT,TRX-USDT,DTA-USDT,NEO-USDT,QTUM-USDT,SMT-USDT,ELA-USDT,VEN-USDT,THETA-USDT,SNT-USDT,ZIL-USDT,XEM-USDT,NAS-USDT,RUFF-USDT,HSR-USDT,LET-USDT,MDS-USDT,STORJ-USDT,ELF-USDT,ITC-USDT,CVC-USDT,GNT-USDT,BCH-BTC,ETH-BTC,LTC-BTC,ETC-BTC,EOS-BTC,OMG-BTC,XRP-BTC,DASH-BTC,ZEC-BTC,ADA-BTC,STEEM-BTC,IOTA-BTC,POLY-BTC,KAN-BTC,LBA-BTC,WAN-BTC,BFT-BTC,BTM-BTC,ONT-BTC,IOST-BTC,HT-BTC,TRX-BTC,SMT-BTC,ELA-BTC,WICC-BTC,OCN-BTC,ZLA-BTC,ABT-BTC,MTX-BTC,NAS-BTC,VEN-BTC,DTA-BTC,NEO-BTC,WAX-BTC,BTS-BTC,ZIL-BTC,THETA-BTC,CTXC-BTC,SRN-BTC,XEM-BTC,ICX-BTC,DGD-BTC,CHAT-BTC,WPR-BTC,LUN-BTC,SWFTC-BTC,SNT-BTC,MEET-BTC,YEE-BTC,ELF-BTC,LET-BTC,QTUM-BTC,LSK-BTC,ITC-BTC,SOC-BTC,QASH-BTC,MDS-BTC,EKO-BTC,TOPC-BTC,MTN-BTC,ACT-BTC,HSR-BTC,STK-BTC,STORJ-BTC,GNX-BTC,DBC-BTC,SNC-BTC,CMT-BTC,TNB-BTC,RUFF-BTC,QUN-BTC,ZRX-BTC,KNC-BTC,BLZ-BTC,PROPY-BTC,RPX-BTC,APPC-BTC,AIDOC-BTC,POWR-BTC,CVC-BTC,PAY-BTC,QSP-BTC,DAT-BTC,RDN-BTC,MCO-BTC,RCN-BTC,MANA-BTC,UTK-BTC,TNT-BTC,GAS-BTC,BAT-BTC,OST-BTC,LINK-BTC,GNT-BTC,MTL-BTC,EVX-BTC,REQ-BTC,ADX-BTC,AST-BTC,ENG-BTC,SALT-BTC,EDU-BTC,BIFI-BTC,BCX-BTC,BCD-BTC,SBTC-BTC,BTG-BTC,EOS-ETH,OMG-ETH,IOTA-ETH,ADA-ETH,STEEM-ETH,POLY-ETH,KAN-ETH,LBA-ETH,WAN-ETH,BFT-ETH,ZRX-ETH,AST-ETH,KNC-ETH,ONT-ETH,HT-ETH,BTM-ETH,IOST-ETH,SMT-ETH,ELA-ETH,TRX-ETH,ABT-ETH,NAS-ETH,OCN-ETH,WICC-ETH,ZIL-ETH,CTXC-ETH,ZLA-ETH,WPR-ETH,DTA-ETH,MTX-ETH,THETA-ETH,SRN-ETH,VEN-ETH,BTS-ETH,WAX-ETH,HSR-ETH,ICX-ETH,MTN-ETH,ACT-ETH,BLZ-ETH,QASH-ETH,RUFF-ETH,CMT-ETH,ELF-ETH,MEET-ETH,SOC-ETH,QTUM-ETH,ITC-ETH,SWFTC-ETH,YEE-ETH,LSK-ETH,LUN-ETH,LET-ETH,GNX-ETH,CHAT-ETH,EKO-ETH,TOPC-ETH,DGD-ETH,STK-ETH,MDS-ETH,DBC-ETH,SNC-ETH,PAY-ETH,QUN-ETH,AIDOC-ETH,TNB-ETH,APPC-ETH,RDN-ETH,UTK-ETH,POWR-ETH,BAT-ETH,PROPY-ETH,MANA-ETH,REQ-ETH,CVC-ETH,QSP-ETH,EVX-ETH,DAT-ETH,MCO-ETH,GNT-ETH,GAS-ETH,OST-ETH,LINK-ETH,RCN-ETH,TNT-ETH,ENG-ETH,SALT-ETH,ADX-ETH,EDU-ETH", + "AvailablePairs": "BTC-USDT,BCH-USDT,ETH-USDT,ETC-USDT,LTC-USDT,EOS-USDT,XRP-USDT,OMG-USDT,DASH-USDT,ZEC-USDT,ADA-USDT,STEEM-USDT,IOTA-USDT,SOC-USDT,CTXC-USDT,ACT-USDT,BTM-USDT,BTS-USDT,ONT-USDT,IOST-USDT,HT-USDT,TRX-USDT,DTA-USDT,NEO-USDT,QTUM-USDT,SMT-USDT,ELA-USDT,VEN-USDT,THETA-USDT,SNT-USDT,ZIL-USDT,XEM-USDT,NAS-USDT,RUFF-USDT,HSR-USDT,LET-USDT,MDS-USDT,STORJ-USDT,ELF-USDT,ITC-USDT,CVC-USDT,GNT-USDT,BCH-BTC,ETH-BTC,LTC-BTC,ETC-BTC,EOS-BTC,OMG-BTC,XRP-BTC,DASH-BTC,ZEC-BTC,ADA-BTC,STEEM-BTC,IOTA-BTC,POLY-BTC,KAN-BTC,LBA-BTC,WAN-BTC,BFT-BTC,BTM-BTC,ONT-BTC,IOST-BTC,HT-BTC,TRX-BTC,SMT-BTC,ELA-BTC,WICC-BTC,OCN-BTC,ZLA-BTC,ABT-BTC,MTX-BTC,NAS-BTC,VEN-BTC,DTA-BTC,NEO-BTC,WAX-BTC,BTS-BTC,ZIL-BTC,THETA-BTC,CTXC-BTC,SRN-BTC,XEM-BTC,ICX-BTC,DGD-BTC,CHAT-BTC,WPR-BTC,LUN-BTC,SWFTC-BTC,SNT-BTC,MEET-BTC,YEE-BTC,ELF-BTC,LET-BTC,QTUM-BTC,LSK-BTC,ITC-BTC,SOC-BTC,QASH-BTC,MDS-BTC,EKO-BTC,TOPC-BTC,MTN-BTC,ACT-BTC,HSR-BTC,STK-BTC,STORJ-BTC,GNX-BTC,DBC-BTC,SNC-BTC,CMT-BTC,TNB-BTC,RUFF-BTC,QUN-BTC,ZRX-BTC,KNC-BTC,BLZ-BTC,PROPY-BTC,RPX-BTC,APPC-BTC,AIDOC-BTC,POWR-BTC,CVC-BTC,PAY-BTC,QSP-BTC,DAT-BTC,RDN-BTC,MCO-BTC,RCN-BTC,MANA-BTC,UTK-BTC,TNT-BTC,GAS-BTC,BAT-BTC,OST-BTC,LINK-BTC,GNT-BTC,MTL-BTC,EVX-BTC,REQ-BTC,ADX-BTC,AST-BTC,ENG-BTC,SALT-BTC,EDU-BTC,BIFI-BTC,BCX-BTC,BCD-BTC,SBTC-BTC,BTG-BTC,EOS-ETH,OMG-ETH,IOTA-ETH,ADA-ETH,STEEM-ETH,POLY-ETH,KAN-ETH,LBA-ETH,WAN-ETH,BFT-ETH,ZRX-ETH,AST-ETH,KNC-ETH,ONT-ETH,HT-ETH,BTM-ETH,IOST-ETH,SMT-ETH,ELA-ETH,TRX-ETH,ABT-ETH,NAS-ETH,OCN-ETH,WICC-ETH,ZIL-ETH,CTXC-ETH,ZLA-ETH,WPR-ETH,DTA-ETH,MTX-ETH,THETA-ETH,SRN-ETH,VEN-ETH,BTS-ETH,WAX-ETH,HSR-ETH,ICX-ETH,MTN-ETH,ACT-ETH,BLZ-ETH,QASH-ETH,RUFF-ETH,CMT-ETH,ELF-ETH,MEET-ETH,SOC-ETH,QTUM-ETH,ITC-ETH,SWFTC-ETH,YEE-ETH,LSK-ETH,LUN-ETH,LET-ETH,GNX-ETH,CHAT-ETH,EKO-ETH,TOPC-ETH,DGD-ETH,STK-ETH,MDS-ETH,DBC-ETH,SNC-ETH,PAY-ETH,QUN-ETH,AIDOC-ETH,TNB-ETH,APPC-ETH,RDN-ETH,UTK-ETH,POWR-ETH,BAT-ETH,PROPY-ETH,MANA-ETH,REQ-ETH,CVC-ETH,QSP-ETH,EVX-ETH,DAT-ETH,MCO-ETH,GNT-ETH,GAS-ETH,OST-ETH,LINK-ETH,RCN-ETH,TNT-ETH,ENG-ETH,SALT-ETH,ADX-ETH,EDU-ETH,WICC-USDT", "EnabledPairs": "BTC-USDT", "BaseCurrencies": "USD", "AssetTypes": "SPOT", diff --git a/currency/currency.go b/currency/currency.go index 5b15941a..98605ded 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -1,154 +1,115 @@ package currency import ( - "errors" "fmt" "log" - "net/url" - "strings" - "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/forexprovider" "github.com/thrasher-/gocryptotrader/currency/pair" ) -// Rate holds the current exchange rates for the currency pair. -type Rate struct { - ID string `json:"id"` - Name string `json:"Name"` - Rate float64 `json:",string"` - Date string `json:"Date"` - Time string `json:"Time"` - Ask float64 `json:",string"` - Bid float64 `json:",string"` -} - -// YahooJSONResponseInfo is a sub type that holds JSON response info -type YahooJSONResponseInfo struct { - Count int `json:"count"` - Created time.Time `json:"created"` - Lang string `json:"lang"` -} - -// YahooJSONResponse holds Yahoo API responses -type YahooJSONResponse struct { - Query struct { - YahooJSONResponseInfo - Results struct { - Rate []Rate `json:"rate"` - } - } -} - -// FixerResponse contains the data fields for the Fixer API response -type FixerResponse struct { - Base string `json:"base"` - Date string `json:"date"` - Rates map[string]float64 `json:"rates"` -} - const ( - maxCurrencyPairsPerRequest = 350 - yahooYQLURL = "https://query.yahooapis.com/v1/public/yql?" - yahooDatabase = "store://datatables.org/alltableswithkeys" - fixerAPI = "http://api.fixer.io/latest" + // DefaultBaseCurrency is the base currency used for conversion + DefaultBaseCurrency = "USD" // DefaultCurrencies has the default minimum of FIAT values DefaultCurrencies = "USD,AUD,EUR,CNY" // DefaultCryptoCurrencies has the default minimum of crytpocurrency values DefaultCryptoCurrencies = "BTC,LTC,ETH,DOGE,DASH,XRP,XMR" ) -// Variables for package which includes base error strings & exportable -// queries +// Manager is the overarching type across this package var ( - CurrencyStore map[string]Rate - CurrencyStoreFixer map[string]float64 - BaseCurrencies []string - CryptoCurrencies []string - ErrCurrencyDataNotFetched = errors.New("yahoo currency data has not been fetched yet") - ErrCurrencyNotFound = "unable to find specified currency" - ErrQueryingYahoo = errors.New("unable to query Yahoo currency values") - ErrQueryingYahooZeroCount = errors.New("yahoo returned zero currency data") - YahooEnabled = false + FXRates map[string]float64 + + FiatCurrencies []string + CryptoCurrencies []string + + BaseCurrency string + FXProviders *forexprovider.ForexProviders ) -// SetProvider sets the currency exchange service used by the currency -// converter -func SetProvider(yahooEnabled bool) { - if yahooEnabled { - YahooEnabled = true +// SetDefaults sets the default currency provider and settings for +// currency conversion used outside of the bot setting +func SetDefaults() { + FXRates = make(map[string]float64) + BaseCurrency = DefaultBaseCurrency + + FXProviders = forexprovider.NewDefaultFXProvider() + err := SeedCurrencyData(DefaultCurrencies) + if err != nil { + log.Printf("Failed to seed currency data. Err: %s", err) return } - YahooEnabled = false } -// SwapProvider swaps the currency exchange service used by the curency -// converter -func SwapProvider() { - if YahooEnabled { - YahooEnabled = false - return +// SeedCurrencyData returns rates correlated with suported currencies +func SeedCurrencyData(currencies string) error { + if FXRates == nil { + FXRates = make(map[string]float64) } - YahooEnabled = true -} -// GetProvider returns the currency exchange service used by the currency -// converter -func GetProvider() string { - if YahooEnabled { - return "yahoo" + if FXProviders == nil { + FXProviders = forexprovider.NewDefaultFXProvider() } - return "fixer" + + newRates, err := FXProviders.GetCurrencyData(BaseCurrency, currencies) + if err != nil { + return err + } + + for key, value := range newRates { + FXRates[key] = value + } + + return nil } -// IsDefaultCurrency checks if the currency passed in matches the default -// FIAT currency +// GetExchangeRates returns the currency exchange rates +func GetExchangeRates() map[string]float64 { + return FXRates +} + +// IsDefaultCurrency checks if the currency passed in matches the default fiat +// currency func IsDefaultCurrency(currency string) bool { defaultCurrencies := common.SplitStrings(DefaultCurrencies, ",") return common.StringDataCompare(defaultCurrencies, common.StringToUpper(currency)) } // IsDefaultCryptocurrency checks if the currency passed in matches the default -// CRYPTO currency +// cryptocurrency func IsDefaultCryptocurrency(currency string) bool { cryptoCurrencies := common.SplitStrings(DefaultCryptoCurrencies, ",") return common.StringDataCompare(cryptoCurrencies, common.StringToUpper(currency)) } -// IsFiatCurrency checks if the currency passed is an enabled FIAT currency +// IsFiatCurrency checks if the currency passed is an enabled fiat currency func IsFiatCurrency(currency string) bool { - if len(BaseCurrencies) == 0 { - log.Println("IsFiatCurrency: BaseCurrencies string variable not populated") - return false - } - return common.StringDataCompare(BaseCurrencies, common.StringToUpper(currency)) + return common.StringDataCompare(FiatCurrencies, common.StringToUpper(currency)) } // IsCryptocurrency checks if the currency passed is an enabled CRYPTO currency. func IsCryptocurrency(currency string) bool { - if len(CryptoCurrencies) == 0 { - log.Println( - "IsCryptocurrency: CryptoCurrencies string variable not populated", - ) - return false - } return common.StringDataCompare(CryptoCurrencies, common.StringToUpper(currency)) } -// IsCryptoPair checks to see if the pair is a crypto pair. For example, BTCLTC +// IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC func IsCryptoPair(p pair.CurrencyPair) bool { - return IsCryptocurrency(p.FirstCurrency.String()) && IsCryptocurrency(p.SecondCurrency.String()) + return IsCryptocurrency(p.FirstCurrency.String()) && + IsCryptocurrency(p.SecondCurrency.String()) } -// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair. For example, BTCUSD +// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD func IsCryptoFiatPair(p pair.CurrencyPair) bool { return IsCryptocurrency(p.FirstCurrency.String()) && !IsCryptocurrency(p.SecondCurrency.String()) || !IsCryptocurrency(p.FirstCurrency.String()) && IsCryptocurrency(p.SecondCurrency.String()) } -// IsFiatPair checks to see if the pair is a fiar pair. For example. EURUSD +// IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD func IsFiatPair(p pair.CurrencyPair) bool { - return IsFiatCurrency(p.FirstCurrency.String()) && IsFiatCurrency(p.SecondCurrency.String()) + return IsFiatCurrency(p.FirstCurrency.String()) && + IsFiatCurrency(p.SecondCurrency.String()) } // Update updates the local crypto currency or base currency store @@ -159,47 +120,27 @@ func Update(input []string, cryptos bool) { CryptoCurrencies = append(CryptoCurrencies, common.StringToUpper(input[x])) } } else { - if !common.StringDataCompare(BaseCurrencies, input[x]) { - BaseCurrencies = append(BaseCurrencies, common.StringToUpper(input[x])) + if !common.StringDataCompare(FiatCurrencies, input[x]) { + FiatCurrencies = append(FiatCurrencies, common.StringToUpper(input[x])) } } } } -// SeedCurrencyData takes the desired FIAT currency string, if not defined the -// function will assign it the default values. The function will query -// yahoo for the currency values and will seed currency data. -func SeedCurrencyData(fiatCurrencies string) error { - if fiatCurrencies == "" { - fiatCurrencies = DefaultCurrencies +func extractBaseCurrency() string { + for k := range FXRates { + return k[0:3] } - - if YahooEnabled { - return QueryYahooCurrencyValues(fiatCurrencies) - } - - return FetchFixerCurrencyData() -} - -// MakecurrencyPairs takes all supported currency and turns them into pairs. -func MakecurrencyPairs(supportedCurrencies string) string { - currencies := common.SplitStrings(supportedCurrencies, ",") - var pairs []string - count := len(currencies) - for i := 0; i < count; i++ { - currency := currencies[i] - for j := 0; j < count; j++ { - if currency != currencies[j] { - pairs = append(pairs, currency+currencies[j]) - } - } - } - return common.JoinStrings(pairs, ",") + return "" } // ConvertCurrency for example converts $1 USD to the equivalent Japanese Yen // or vice versa. func ConvertCurrency(amount float64, from, to string) (float64, error) { + if FXProviders == nil { + SetDefaults() + } + from = common.StringToUpper(from) to = common.StringToUpper(to) @@ -215,131 +156,46 @@ func ConvertCurrency(amount float64, from, to string) (float64, error) { to = "RUB" } - if YahooEnabled { - currency := from + to - _, ok := CurrencyStore[currency] - if !ok { - err := SeedCurrencyData(currency[:len(from)] + "," + currency[len(to):]) - if err != nil { - return 0, err - } - } - - result, ok := CurrencyStore[currency] - if !ok { - return 0, errors.New(ErrCurrencyNotFound + " currency not found in CurrencyStore " + currency) - } - return amount * result.Rate, nil + if len(FXRates) == 0 { + SeedCurrencyData(from + "," + to) } - if len(CurrencyStoreFixer) == 0 { - err := FetchFixerCurrencyData() - if err != nil { - return 0, err - } - } + // Need to extract the base currency to see if we actually got it from the Forex API + // Fixer free API sets the base currency to EUR + baseCurr := extractBaseCurrency() var resultFrom float64 var resultTo float64 - // First check if we're converting to USD, USD doesn't exist in the rates map - if to == "USD" { - resultFrom, ok := CurrencyStoreFixer[from] + // check to see if we're converting from the base currency + if to == baseCurr { + resultFrom, ok := FXRates[baseCurr+from] if !ok { - return 0, errors.New(ErrCurrencyNotFound + " " + from + " in CurrencyStoreFixer with pair " + from + to) + return 0, fmt.Errorf("Currency conversion failed. Unable to find %s in currency map [%s -> %s]", from, from, to) } return amount / resultFrom, nil } - // Check to see if we're converting from USD - if from == "USD" { - resultTo, ok := CurrencyStoreFixer[to] + // Check to see if we're converting from the base currency + if from == baseCurr { + resultTo, ok := FXRates[baseCurr+to] if !ok { - return 0, errors.New(ErrCurrencyNotFound + " " + to + " in CurrencyStoreFixer with pair " + from + to) + return 0, fmt.Errorf("Currency conversion failed. Unable to find %s in currency map [%s -> %s]", to, from, to) } return resultTo * amount, nil } - // Otherwise convert to USD, then to the target currency - resultFrom, ok := CurrencyStoreFixer[from] + // Otherwise convert to base currency, then to the target currency + resultFrom, ok := FXRates[baseCurr+from] if !ok { - return 0, errors.New(ErrCurrencyNotFound + " " + from + " in CurrencyStoreFixer with pair " + from + to) + return 0, fmt.Errorf("Currency conversion failed. Unable to find %s in currency map [%s -> %s]", from, from, to) } converted := amount / resultFrom - resultTo, ok = CurrencyStoreFixer[to] + resultTo, ok = FXRates[baseCurr+to] if !ok { - return 0, errors.New(ErrCurrencyNotFound + " " + to + " in CurrencyStoreFixer with pair " + from + to) + return 0, fmt.Errorf("Currency conversion failed. Unable to find %s in currency map [%s -> %s]", to, from, to) } return converted * resultTo, nil } - -// FetchFixerCurrencyData seeds the variable C -func FetchFixerCurrencyData() error { - var result FixerResponse - values := url.Values{} - values.Set("base", "USD") - url := common.EncodeURLValues(fixerAPI, values) - - CurrencyStoreFixer = make(map[string]float64) - - err := common.SendHTTPGetRequest(url, true, false, &result) - if err != nil { - return err - } - - CurrencyStoreFixer = result.Rates - return nil -} - -// FetchYahooCurrencyData seeds the variable CurrencyStore; this is a -// map[string]Rate -func FetchYahooCurrencyData(currencyPairs []string) error { - values := url.Values{} - values.Set( - "q", fmt.Sprintf("SELECT * from yahoo.finance.xchange WHERE pair in (\"%s\")", - common.JoinStrings(currencyPairs, ",")), - ) - values.Set("format", "json") - values.Set("env", yahooDatabase) - - headers := make(map[string]string) - headers["Content-Type"] = "application/x-www-form-urlencoded" - - resp, err := common.SendHTTPRequest( - "POST", yahooYQLURL, headers, strings.NewReader(values.Encode()), - ) - if err != nil { - return err - } - - log.Printf("Currency recv: %s", resp) - - yahooResp := YahooJSONResponse{} - err = common.JSONDecode([]byte(resp), &yahooResp) - if err != nil { - return err - } - - if yahooResp.Query.Count == 0 { - return ErrQueryingYahooZeroCount - } - - for i := 0; i < yahooResp.Query.YahooJSONResponseInfo.Count; i++ { - CurrencyStore[yahooResp.Query.Results.Rate[i].ID] = yahooResp.Query.Results.Rate[i] - } - return nil -} - -// QueryYahooCurrencyValues takes in desired currencies, creates pairs then -// uses FetchYahooCurrencyData to seed CurrencyStore -func QueryYahooCurrencyValues(currencies string) error { - CurrencyStore = make(map[string]Rate) - currencyPairs := common.SplitStrings(MakecurrencyPairs(currencies), ",") - log.Printf( - "%d fiat currency pairs generated. Fetching Yahoo currency data (this may take a minute)..\n", - len(currencyPairs), - ) - return FetchYahooCurrencyData(currencyPairs) -} diff --git a/currency/currency_test.go b/currency/currency_test.go index 2a253131..5bd655c9 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -1,70 +1,49 @@ package currency import ( - "reflect" "testing" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" ) -func TestSetProvider(t *testing.T) { - defaultVal := YahooEnabled - expected := "yahoo" - SetProvider(true) - actual := GetProvider() - if expected != actual { - t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) +func TestSetDefaults(t *testing.T) { + FXRates = nil + BaseCurrency = "BLAH" + FXProviders = nil + + SetDefaults() + + if FXRates == nil { + t.Fatal("Expected FXRates to be non-nil") } - SetProvider(false) - expected = "fixer" - actual = GetProvider() - if expected != actual { - t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) + if BaseCurrency != DefaultBaseCurrency { + t.Fatal("Expected BaseCurrency to be 'USD'") } - SetProvider(defaultVal) + if FXProviders == nil { + t.Fatal("Expected FXRates to be non-nil") + } } -func TestSwapProvider(t *testing.T) { - defaultVal := YahooEnabled - expected := "fixer" - SetProvider(true) - SwapProvider() - actual := GetProvider() - if expected != actual { - t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) +func TestSeedCurrencyData(t *testing.T) { + err := SeedCurrencyData("AUD") + if err != nil { + t.Fatal(err) } - - SetProvider(false) - SwapProvider() - expected = "yahoo" - actual = GetProvider() - if expected != actual { - t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) - } - - SetProvider(defaultVal) } -func TestGetProvider(t *testing.T) { - defaultVal := YahooEnabled - SetProvider(true) - expected := "yahoo" - actual := GetProvider() - if expected != actual { - t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) +func TestGetExchangeRates(t *testing.T) { + result := GetExchangeRates() + backup := FXRates + + FXRates = nil + result = GetExchangeRates() + if result != nil { + t.Fatal("Expected nil map") } - SetProvider(false) - expected = "fixer" - actual = GetProvider() - if expected != actual { - t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) - } - - SetProvider(defaultVal) + FXRates = backup } func TestIsDefaultCurrency(t *testing.T) { @@ -120,7 +99,7 @@ func TestIsFiatCurrency(t *testing.T) { t.Error("Test failed. TestIsFiatCurrency returned true on an empty string") } - BaseCurrencies = []string{"USD", "AUD"} + FiatCurrencies = []string{"USD", "AUD"} var str1, str2, str3 string = "BTC", "USD", "birds123" if IsFiatCurrency(str1) { @@ -171,7 +150,7 @@ func TestIsCryptoPair(t *testing.T) { } CryptoCurrencies = []string{"BTC", "LTC", "DASH"} - BaseCurrencies = []string{"USD"} + FiatCurrencies = []string{"USD"} if !IsCryptoPair(pair.NewCurrencyPair("BTC", "LTC")) { t.Error("Test Failed. TestIsCryptoPair. Expected true result") @@ -188,7 +167,7 @@ func TestIsCryptoFiatPair(t *testing.T) { } CryptoCurrencies = []string{"BTC", "LTC", "DASH"} - BaseCurrencies = []string{"USD"} + FiatCurrencies = []string{"USD"} if !IsCryptoFiatPair(pair.NewCurrencyPair("BTC", "USD")) { t.Error("Test Failed. TestIsCryptoPair. Expected true result") @@ -201,7 +180,7 @@ func TestIsCryptoFiatPair(t *testing.T) { func TestIsFiatPair(t *testing.T) { CryptoCurrencies = []string{"BTC", "LTC", "DASH"} - BaseCurrencies = []string{"USD", "AUD", "EUR"} + FiatCurrencies = []string{"USD", "AUD", "EUR"} if !IsFiatPair(pair.NewCurrencyPair("AUD", "USD")) { t.Error("Test Failed. TestIsFiatPair. Expected true result") @@ -214,7 +193,7 @@ func TestIsFiatPair(t *testing.T) { func TestUpdate(t *testing.T) { CryptoCurrencies = []string{"BTC", "LTC", "DASH"} - BaseCurrencies = []string{"USD", "AUD"} + FiatCurrencies = []string{"USD", "AUD"} Update([]string{"ETH"}, true) Update([]string{"JPY"}, false) @@ -232,163 +211,46 @@ func TestUpdate(t *testing.T) { } } -func TestSeedCurrencyData(t *testing.T) { - // SetProvider(true) - if YahooEnabled { - currencyRequestDefault := "" - currencyRequestUSDAUD := "USD,AUD" - currencyRequestObtuse := "WigWham" +func TestExtractBaseCurrency(t *testing.T) { + backup := FXRates + FXRates = nil + FXRates = make(map[string]float64) - err := SeedCurrencyData(currencyRequestDefault) - if err != nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err, currencyRequestDefault, - ) - } - err2 := SeedCurrencyData(currencyRequestUSDAUD) - if err2 != nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err2, currencyRequestUSDAUD, - ) - } - err3 := SeedCurrencyData(currencyRequestObtuse) - if err3 == nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err3, currencyRequestObtuse, - ) - } + if extractBaseCurrency() != "" { + t.Fatalf("Test failed. Expected '' as base currency") } - //SetProvider(false) - err := SeedCurrencyData("") - if err != nil { - t.Errorf("Test failed. SeedCurrencyData via Fixer. Error: %s", err) + FXRates["USDAUD"] = 120 + + if extractBaseCurrency() != "USD" { + t.Fatalf("Test failed. Expected 'USD' as base currency") } + FXRates = backup } - -func TestMakecurrencyPairs(t *testing.T) { - t.Parallel() - - lengthDefault := len(common.SplitStrings(DefaultCurrencies, ",")) - fiatPairsLength := len( - common.SplitStrings(MakecurrencyPairs(DefaultCurrencies), ","), - ) - - if lengthDefault*(lengthDefault-1) > fiatPairsLength { - t.Error("Test Failed. MakecurrencyPairs: Error, mismatched length") - } -} - func TestConvertCurrency(t *testing.T) { - // SetProvider(true) - if YahooEnabled { - fiatCurrencies := DefaultCurrencies - for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") { - for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") { - floatyMcfloat, err := ConvertCurrency(1000, currencyFrom, currencyTo) - if err != nil { - t.Errorf( - "Test Failed. ConvertCurrency: Error %s with return: %.2f Currency 1: %s Currency 2: %s", - err, floatyMcfloat, currencyFrom, currencyTo, - ) - } - if reflect.TypeOf(floatyMcfloat).String() != "float64" { - t.Error("Test Failed. ConvertCurrency: Error, incorrect return type") - } - if floatyMcfloat <= 0 { - t.Error( - "Test Failed. ConvertCurrency: Error, negative return or a serious issue with current fiat", - ) - } - } - } - } - - // SetProvider(false) - _, err := ConvertCurrency(1000, "USD", "AUD") + _, err := ConvertCurrency(100, "AUD", "USD") if err != nil { - t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err) + t.Fatal(err) } - _, err = ConvertCurrency(1000, "AUD", "USD") + _, err = ConvertCurrency(100, "USD", "AUD") if err != nil { - t.Errorf("Test failed. ConvertCurrency AUD -> AUD. Error %s", err) + t.Fatal(err) } - _, err = ConvertCurrency(1000, "CNY", "AUD") + _, err = ConvertCurrency(100, "CNY", "AUD") if err != nil { - t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err) + t.Fatal(err) } - // Test non-existent currencies - - _, err = ConvertCurrency(1000, "ASDF", "USD") + _, err = ConvertCurrency(100, "meow", "USD") if err == nil { - t.Errorf("Test failed. ConvertCurrency non-existent currency -> USD. Error %s", err) + t.Fatal("Expected err on non-existent currency") } - _, err = ConvertCurrency(1000, "USD", "ASDF") + _, err = ConvertCurrency(100, "USD", "meow") if err == nil { - t.Errorf("Test failed. ConvertCurrency USD -> non-existent currency. Error %s", err) + t.Fatal("Expected err on non-existent currency") } - _, err = ConvertCurrency(1000, "CNY", "UAHF") - if err == nil { - t.Errorf("Test failed. ConvertCurrency non-USD currency CNY -> non-existent currency. Error %s", err) - } - - _, err = ConvertCurrency(1000, "UASF", "UAHF") - if err == nil { - t.Errorf("Test failed. ConvertCurrency non-existent currency -> non-existent currency. Error %s", err) - } -} - -func TestFetchFixerCurrencyData(t *testing.T) { - err := FetchFixerCurrencyData() - if err != nil { - t.Errorf("Test failed. FetchFixerCurrencyData returned %s", err) - } -} - -func TestFetchYahooCurrencyData(t *testing.T) { - if !YahooEnabled { - t.Skip() - } - - t.Parallel() - var fetchData []string - fiatCurrencies := DefaultCurrencies - - for _, currencyOne := range common.SplitStrings(fiatCurrencies, ",") { - for _, currencyTwo := range common.SplitStrings(fiatCurrencies, ",") { - if currencyOne == currencyTwo { - continue - } else { - fetchData = append(fetchData, currencyOne+currencyTwo) - } - } - } - err := FetchYahooCurrencyData(fetchData) - if err != nil { - t.Errorf("Test Failed. FetchYahooCurrencyData: Error %s", err) - } -} - -func TestQueryYahooCurrencyValues(t *testing.T) { - if !YahooEnabled { - t.Skip() - } - - err := QueryYahooCurrencyValues(DefaultCurrencies) - if err != nil { - t.Errorf("Test Failed. QueryYahooCurrencyValues: Error, %s", err) - } - - err = QueryYahooCurrencyValues(DefaultCryptoCurrencies) - if err == nil { - t.Errorf("Test Failed. QueryYahooCurrencyValues: Error, %s", err) - } } diff --git a/currency/forexprovider/base/base.go b/currency/forexprovider/base/base.go new file mode 100644 index 00000000..1d05e99a --- /dev/null +++ b/currency/forexprovider/base/base.go @@ -0,0 +1,36 @@ +package base + +import ( + "time" +) + +// Settings enforces standard variables across the provider packages +type Settings struct { + Name string + Enabled bool + Verbose bool + RESTPollingDelay time.Duration + APIKey string + APIKeyLvl int + PrimaryProvider bool +} + +// Base enforces standard variables across the provider packages +type Base struct { + Settings +} + +// GetName returns name of provider +func (b *Base) GetName() string { + return b.Name +} + +// IsEnabled returns true if enabled +func (b *Base) IsEnabled() bool { + return b.Enabled +} + +// IsPrimaryProvider returns true if primary provider +func (b *Base) IsPrimaryProvider() bool { + return b.PrimaryProvider +} diff --git a/currency/forexprovider/base/base_interface.go b/currency/forexprovider/base/base_interface.go new file mode 100644 index 00000000..e638fa44 --- /dev/null +++ b/currency/forexprovider/base/base_interface.go @@ -0,0 +1,44 @@ +package base + +import ( + "errors" + "log" +) + +// IFXProviders contains an array of foreign exchange interfaces +type IFXProviders []IFXProvider + +// IFXProvider enforces standard functions for all foreign exchange providers +// supported in GoCryptoTrader +type IFXProvider interface { + Setup(config Settings) + GetRates(baseCurrency, symbols string) (map[string]float64, error) + GetName() string + IsEnabled() bool + IsPrimaryProvider() bool +} + +// GetCurrencyData returns currency data from enabled FX providers +func (fxp IFXProviders) GetCurrencyData(baseCurrency, symbols string) (map[string]float64, error) { + for x := range fxp { + if fxp[x].IsPrimaryProvider() && fxp[x].IsEnabled() { + rates, err := fxp[x].GetRates(baseCurrency, symbols) + if err != nil { + log.Println(err) + for y := range fxp { + if !fxp[y].IsPrimaryProvider() && fxp[x].IsEnabled() { + rates, err = fxp[y].GetRates(baseCurrency, symbols) + if err != nil { + log.Println(err) + continue + } + return rates, nil + } + } + return nil, errors.New("ForexProvider error GetCurrencyData() failed to aquire data") + } + return rates, nil + } + } + return nil, errors.New("ForexProvider error GetCurrencyData() no providers enabled") +} diff --git a/currency/forexprovider/base/base_test.go b/currency/forexprovider/base/base_test.go new file mode 100644 index 00000000..22f11114 --- /dev/null +++ b/currency/forexprovider/base/base_test.go @@ -0,0 +1 @@ +package base diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go new file mode 100644 index 00000000..d82eb6b6 --- /dev/null +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go @@ -0,0 +1,168 @@ +package currencyconverter + +import ( + "errors" + "fmt" + "log" + "net/url" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" +) + +// const declarations consist of endpoints +const ( + APIEndpointURL = "https://currencyconverterapi.com/api/" + APIEndpointFreeURL = "https://free.currencyconverterapi.com/api/" + APIEndpointVersion = "v5" + + APIEndpointConvert = "convert" + APIEndpointCurrencies = "currencies" + APIEndpointCountries = "countries" + APIEndpointUsage = "usage" +) + +// CurrencyConverter stores the struct for the CurrencyConverter API +type CurrencyConverter struct { + base.Base +} + +// Setup sets appropriate values for CurrencyLayer +func (c *CurrencyConverter) Setup(config base.Settings) { + c.Name = config.Name + c.APIKey = config.APIKey + c.APIKeyLvl = config.APIKeyLvl + c.Enabled = config.Enabled + c.RESTPollingDelay = config.RESTPollingDelay + c.Verbose = config.Verbose + c.PrimaryProvider = config.PrimaryProvider +} + +// GetRates is a wrapper function to return rates +func (c *CurrencyConverter) GetRates(baseCurrency, symbols string) (map[string]float64, error) { + splitSymbols := common.SplitStrings(symbols, ",") + + if len(splitSymbols) == 1 { + return c.Convert(baseCurrency, symbols) + } + + var completedStrings []string + for x := range splitSymbols { + completedStrings = append(completedStrings, baseCurrency+"_"+splitSymbols[x]) + } + + if (c.APIKey != "" && c.APIKey != "Key") || len(completedStrings) == 2 { + return c.ConvertMany(completedStrings) + } + + rates := make(map[string]float64) + processBatch := func(length int) { + for i := 0; i < length; i += 2 { + batch := completedStrings[i : i+2] + result, err := c.ConvertMany(batch) + if err != nil { + log.Printf("Failed to get batch err: %s", err) + continue + } + for k, v := range result { + rates[common.ReplaceString(k, "_", "", -1)] = v + } + } + } + + currLen := len(completedStrings) + mod := currLen % 2 + if mod == 0 { + processBatch(currLen) + return rates, nil + } + + processBatch(currLen - 1) + result, err := c.ConvertMany(completedStrings[currLen-1:]) + if err != nil { + return nil, err + } + + for k, v := range result { + rates[common.ReplaceString(k, "_", "", -1)] = v + } + + return rates, nil +} + +// ConvertMany takes 2 or more currencies depending on if using the free +// or paid API +func (c *CurrencyConverter) ConvertMany(currencies []string) (map[string]float64, error) { + if len(currencies) > 2 && (c.APIKey == "" || c.APIKey == "Key") { + return nil, errors.New("currency fetching is limited to two currencies per request") + } + + result := make(map[string]float64) + v := url.Values{} + joined := common.JoinStrings(currencies, ",") + v.Set("q", joined) + v.Set("compact", "ultra") + + err := c.SendHTTPRequest(APIEndpointConvert, v, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +// Convert gets the conversion rate for the supplied currencies +func (c *CurrencyConverter) Convert(from, to string) (map[string]float64, error) { + result := make(map[string]float64) + v := url.Values{} + v.Set("q", from+"_"+to) + v.Set("compact", "ultra") + + err := c.SendHTTPRequest(APIEndpointConvert, v, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetCurrencies returns a list of the supported currencies +func (c *CurrencyConverter) GetCurrencies() (map[string]CurrencyItem, error) { + var result Currencies + + err := c.SendHTTPRequest(APIEndpointCurrencies, url.Values{}, &result) + if err != nil { + return nil, err + } + + return result.Results, nil +} + +// GetCountries returns a list of the supported countries and +// their symbols +func (c *CurrencyConverter) GetCountries() (map[string]CountryItem, error) { + var result Countries + + err := c.SendHTTPRequest(APIEndpointCountries, url.Values{}, &result) + if err != nil { + return nil, err + } + + return result.Results, nil +} + +// SendHTTPRequest sends a HTTP request, if account is not free it automatically +// upgrades request to SSL. +func (c *CurrencyConverter) SendHTTPRequest(endPoint string, values url.Values, result interface{}) error { + var path string + + if c.APIKey == "" || c.APIKey == "Key" { + path = fmt.Sprintf("%s%s/%s?", APIEndpointFreeURL, APIEndpointVersion, endPoint) + } else { + path = fmt.Sprintf("%s%s%s?", APIEndpointURL, APIEndpointVersion, endPoint) + values.Set("apiKey", c.APIKey) + } + path = path + values.Encode() + + return common.SendHTTPGetRequest(path, true, c.Verbose, &result) +} diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi_test.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi_test.go new file mode 100644 index 00000000..98bf4df5 --- /dev/null +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi_test.go @@ -0,0 +1,79 @@ +package currencyconverter + +import ( + "testing" +) + +var c CurrencyConverter + +func TestGetRates(t *testing.T) { + result, err := c.GetRates("USD", "AUD") + if err != nil { + t.Error("Test Error. CurrencyConverter GetRates() error", err) + } + + if len(result) != 1 { + t.Fatal("Test error. Expected 2 rates") + } + + result, err = c.GetRates("USD", "AUD,EUR") + if err != nil { + t.Error("Test Error. CurrencyConverter GetRates() error", err) + } + + if len(result) != 2 { + t.Fatal("Test error. Expected 2 rates") + } + + result, err = c.GetRates("USD", "AUD,EUR,GBP") + if err != nil { + t.Error("Test Error. CurrencyConverter GetRates() error", err) + } + + if len(result) != 3 { + t.Fatal("Test error. Expected 3 rates") + } + + result, err = c.GetRates("USD", "AUD,EUR,GBP,CNY") + if err != nil { + t.Error("Test Error. CurrencyConverter GetRates() error", err) + } + + if len(result) != 4 { + t.Fatal("Test error. Expected 4 rates") + } +} +func TestConvertMany(t *testing.T) { + currencies := []string{"USD_AUD", "USD_EUR"} + _, err := c.ConvertMany(currencies) + if err != nil { + t.Fatal(err) + } + + currencies = []string{"USD_AUD", "USD_EUR", "USD_GBP"} + _, err = c.ConvertMany(currencies) + if err == nil { + t.Fatal("non error on supplying 3 or more currencies using the free API") + } +} + +func TestConvert(t *testing.T) { + _, err := c.Convert("AUD", "USD") + if err != nil { + t.Fatal(err) + } +} + +func TestGetCurrencies(t *testing.T) { + _, err := c.GetCurrencies() + if err != nil { + t.Fatal(err) + } +} + +func TestGetCountries(t *testing.T) { + _, err := c.GetCountries() + if err != nil { + t.Fatal(err) + } +} diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi_types.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi_types.go new file mode 100644 index 00000000..6c310176 --- /dev/null +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi_types.go @@ -0,0 +1,34 @@ +package currencyconverter + +// Error stores the error message +type Error struct { + Status int `json:"status"` + Error string `json:"error"` +} + +// CurrencyItem stores variables related to the currency response +type CurrencyItem struct { + CurrencyName string `json:"currencyName"` + CurrencySymbol string `json:"currencySymbol"` + ID string `json:"ID"` +} + +// Currencies stores the currency result data +type Currencies struct { + Results map[string]CurrencyItem +} + +// CountryItem stores variables related to the country response +type CountryItem struct { + Alpha3 string `json:"alpha3"` + CurrencyID string `json:"currencyId"` + CurrencyName string `json:"currencyName"` + CurrencySymbol string `json:"currencySymbol"` + ID string `json:"ID"` + Name string `json:"Name"` +} + +// Countries stores the country result data +type Countries struct { + Results map[string]CountryItem +} diff --git a/currency/forexprovider/currencylayer/currencylayer.go b/currency/forexprovider/currencylayer/currencylayer.go new file mode 100644 index 00000000..5b87dea1 --- /dev/null +++ b/currency/forexprovider/currencylayer/currencylayer.go @@ -0,0 +1,209 @@ +// Currencylayer provides a simple REST API with real-time and historical +// exchange rates for 168 world currencies, delivering currency pairs in +// universally usable JSON format - compatible with any of your applications. +// Spot exchange rate data is retrieved from several major forex data providers +// in real-time, validated, processed and delivered hourly, every 10 minutes, or +// even within the 60-second market window. +// Providing the most representative forex market value available +// ("midpoint" value) for every API request, the currencylayer API powers +// currency converters, mobile applications, financial software components and +// back-office systems all around the world. + +package currencylayer + +import ( + "errors" + "fmt" + "net/url" + "strconv" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" +) + +// const declarations consist of endpoints and APIKey privileges +const ( + AccountFree = iota + AccountBasic + AccountPro + AccountEnterprise + + APIEndpointURL = "http://apilayer.net/api/" + APIEndpointURLSSL = "https://apilayer.net/api/" + APIEndpointList = "list" + APIEndpointLive = "live" + APIEndpointHistorical = "historical" + APIEndpointConversion = "convert" + APIEndpointTimeframe = "timeframe" + APIEndpointChange = "change" +) + +// CurrencyLayer is a foreign exchange rate provider at +// https://currencylayer.com NOTE default base currency is USD when using a free +// account. Has automatic upgrade to a SSL connection. +type CurrencyLayer struct { + base.Base +} + +// Setup sets appropriate values for CurrencyLayer +func (c *CurrencyLayer) Setup(config base.Settings) { + c.Name = config.Name + c.APIKey = config.APIKey + c.APIKeyLvl = config.APIKeyLvl + c.Enabled = config.Enabled + c.RESTPollingDelay = config.RESTPollingDelay + c.Verbose = config.Verbose + c.PrimaryProvider = config.PrimaryProvider +} + +// GetRates is a wrapper function to return rates for GoCryptoTrader +func (c *CurrencyLayer) GetRates(baseCurrency, symbols string) (map[string]float64, error) { + return c.GetliveData(symbols, baseCurrency) +} + +// GetSupportedCurrencies returns supported currencies +func (c *CurrencyLayer) GetSupportedCurrencies() (map[string]string, error) { + var resp SupportedCurrencies + + if err := c.SendHTTPRequest(APIEndpointList, url.Values{}, &resp); err != nil { + return nil, err + } + + if !resp.Success { + return nil, errors.New(resp.Error.Info) + } + return resp.Currencies, nil +} + +// GetliveData returns live quotes for foreign exchange currencies +func (c *CurrencyLayer) GetliveData(currencies, source string) (map[string]float64, error) { + var resp LiveRates + v := url.Values{} + v.Set("currencies", currencies) + v.Set("source", source) + + err := c.SendHTTPRequest(APIEndpointLive, v, &resp) + if err != nil { + return nil, err + } + + if !resp.Success { + return nil, errors.New(resp.Error.Info) + } + + return resp.Quotes, nil +} + +// GetHistoricalData returns historical exchange rate data for every past day of +// the last 16 years. +func (c *CurrencyLayer) GetHistoricalData(date string, currencies []string, source string) (map[string]float64, error) { + var resp HistoricalRates + v := url.Values{} + v.Set("currencies", common.JoinStrings(currencies, ",")) + v.Set("source", source) + v.Set("date", date) + + err := c.SendHTTPRequest(APIEndpointHistorical, v, &resp) + if err != nil { + return nil, err + } + + if !resp.Success { + return nil, errors.New(resp.Error.Info) + } + + return resp.Quotes, nil +} + +// Convert converts one currency amount to another currency amount. +func (c *CurrencyLayer) Convert(from, to, date string, amount float64) (float64, error) { + if c.APIKeyLvl >= AccountBasic { + return 0, errors.New("insufficient API privileges, upgrade to basic to use this function") + } + + var resp ConversionRate + + v := url.Values{} + v.Set("from", from) + v.Set("to", to) + v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + v.Set("date", date) + + err := c.SendHTTPRequest(APIEndpointConversion, v, &resp) + if err != nil { + return resp.Result, err + } + + if !resp.Success { + return resp.Result, errors.New(resp.Error.Info) + } + return resp.Result, nil +} + +// QueryTimeFrame returns historical exchange rates for a time-period. +// (maximum range: 365 days) +func (c *CurrencyLayer) QueryTimeFrame(startDate, endDate, base string, currencies []string) (map[string]interface{}, error) { + if c.APIKeyLvl >= AccountPro { + return nil, errors.New("insufficient API privileges, upgrade to basic to use this function") + } + + var resp TimeFrame + + v := url.Values{} + v.Set("start_date", startDate) + v.Set("end_date", endDate) + v.Set("base", base) + v.Set("currencies", common.JoinStrings(currencies, ",")) + + err := c.SendHTTPRequest(APIEndpointTimeframe, v, &resp) + if err != nil { + return nil, err + } + + if !resp.Success { + return nil, errors.New(resp.Error.Info) + } + return resp.Quotes, nil +} + +// QueryCurrencyChange returns the change (both margin and percentage) of one or +// more currencies, relative to a Source Currency, within a specific +// time-frame (optional). +func (c *CurrencyLayer) QueryCurrencyChange(startDate, endDate, base string, currencies []string) (map[string]Changes, error) { + if c.APIKeyLvl != AccountEnterprise { + return nil, errors.New("insufficient API privileges, upgrade to basic to use this function") + } + var resp ChangeRate + + v := url.Values{} + v.Set("start_date", startDate) + v.Set("end_date", endDate) + v.Set("base", base) + v.Set("currencies", common.JoinStrings(currencies, ",")) + + err := c.SendHTTPRequest(APIEndpointChange, v, &resp) + if err != nil { + return nil, err + } + + if !resp.Success { + return nil, errors.New(resp.Error.Info) + } + return resp.Quotes, nil +} + +// SendHTTPRequest sends a HTTP request, if account is not free it automatically +// upgrades request to SSL. +func (c *CurrencyLayer) SendHTTPRequest(endPoint string, values url.Values, result interface{}) error { + var path string + values.Set("access_key", c.APIKey) + + if c.APIKeyLvl == AccountFree { + path = fmt.Sprintf("%s%s%s", APIEndpointURL, endPoint, "?") + } else { + path = fmt.Sprintf("%s%s%s", APIEndpointURLSSL, endPoint, "?") + } + path = path + values.Encode() + + return common.SendHTTPGetRequest(path, true, c.Verbose, result) +} diff --git a/currency/forexprovider/currencylayer/currencylayer_test.go b/currency/forexprovider/currencylayer/currencylayer_test.go new file mode 100644 index 00000000..606f2815 --- /dev/null +++ b/currency/forexprovider/currencylayer/currencylayer_test.go @@ -0,0 +1,63 @@ +package currencylayer + +import ( + "testing" +) + +var c CurrencyLayer + +// please set your API key here for due diligence testing NOTE be aware you will +// minimize your API calls using this test. +const ( + APIkey = "" + Apilevel = 3 +) + +func TestGetRates(t *testing.T) { + _, err := c.GetRates("USD", "AUD") + if err == nil { + t.Error("test error - currencylayer GetRates() error", err) + } +} + +func TestGetSupportedCurrencies(t *testing.T) { + _, err := c.GetSupportedCurrencies() + if err == nil { + t.Error("test error - currencylayer GetSupportedCurrencies() error", err) + } +} + +func TestGetliveData(t *testing.T) { + _, err := c.GetliveData("AUD", "USD") + if err == nil { + t.Error("test error - currencylayer GetliveData() error", err) + } +} + +func TestGetHistoricalData(t *testing.T) { + _, err := c.GetHistoricalData("2016-12-15", []string{"AUD"}, "USD") + if err == nil { + t.Error("test error - currencylayer GetHistoricalData() error", err) + } +} + +func TestConvert(t *testing.T) { + _, err := c.Convert("USD", "AUD", "", 1) + if err == nil { + t.Error("test error - currencylayer Convert() error") + } +} + +func TestQueryTimeFrame(t *testing.T) { + _, err := c.QueryTimeFrame("2010-12-0", "2010-12-5", "USD", []string{"AUD"}) + if err == nil { + t.Error("test error - currencylayer QueryTimeFrame() error") + } +} + +func TestQueryCurrencyChange(t *testing.T) { + _, err := c.QueryCurrencyChange("2010-12-0", "2010-12-5", "USD", []string{"AUD"}) + if err == nil { + t.Error("test error - currencylayer QueryCurrencyChange() error") + } +} diff --git a/currency/forexprovider/currencylayer/currencylayer_types.go b/currency/forexprovider/currencylayer/currencylayer_types.go new file mode 100644 index 00000000..dedfe93b --- /dev/null +++ b/currency/forexprovider/currencylayer/currencylayer_types.go @@ -0,0 +1,106 @@ +package currencylayer + +// LiveRates is a response type holding rates priced now. +type LiveRates struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Info string `json:"info"` + } `json:"error"` + Terms string `json:"terms"` + Privacy string `json:"privacy"` + Timestamp int64 `json:"timestamp"` + Source string `json:"source"` + Quotes map[string]float64 `json:"quotes"` +} + +// SupportedCurrencies holds supported currency information +type SupportedCurrencies struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Info string `json:"info"` + } `json:"error"` + Terms string `json:"terms"` + Privacy string `json:"privacy"` + Currencies map[string]string `json:"currencies"` +} + +// HistoricalRates is a response type holding rates priced from the past. +type HistoricalRates struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Info string `json:"info"` + } `json:"error"` + Terms string `json:"terms"` + Privacy string `json:"privacy"` + Historical bool `json:"historical"` + Date string `json:"date"` + Timestamp int64 `json:"timestamp"` + Source string `json:"source"` + Quotes map[string]float64 `json:"quotes"` +} + +// ConversionRate is a response type holding a converted rate. +type ConversionRate struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Info string `json:"info"` + } `json:"error"` + Privacy string `json:"privacy"` + Terms string `json:"terms"` + Query struct { + From string `json:"from"` + To string `json:"to"` + Amount float64 `json:"amount"` + } `json:"query"` + Info struct { + Timestamp int64 `json:"timestamp"` + Quote float64 `json:"quote"` + } `json:"info"` + Historical bool `json:"historical"` + Date string `json:"date"` + Result float64 `json:"result"` +} + +// TimeFrame is a response type holding exchange rates for a time period +type TimeFrame struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Info string `json:"info"` + } `json:"error"` + Terms string `json:"terms"` + Privacy string `json:"privacy"` + Timeframe bool `json:"timeframe"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Source string `json:"source"` + Quotes map[string]interface{} `json:"quotes"` +} + +// ChangeRate is the response type that holds rate change data. +type ChangeRate struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Info string `json:"info"` + } `json:"error"` + Terms string `json:"terms"` + Privacy string `json:"privacy"` + Change bool `json:"change"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Source string `json:"source"` + Quotes map[string]Changes `json:"quotes"` +} + +// Changes is a sub-type of ChangeRate that holds the actual changes of rates. +type Changes struct { + StartRate float64 `json:"start_rate"` + EndRate float64 `json:"end_rate"` + Change float64 `json:"change"` + ChangePCT float64 `json:"change_pct"` +} diff --git a/currency/forexprovider/fixer.io/fixer.go b/currency/forexprovider/fixer.io/fixer.go new file mode 100644 index 00000000..3908d235 --- /dev/null +++ b/currency/forexprovider/fixer.io/fixer.go @@ -0,0 +1,212 @@ +// Powered by 15+ exchange rate data sources, the Fixer API is capable of +// delivering real-time exchange rate data for 170 world currencies. The API +// comes with multiple endpoints, each serving a different use case. Endpoint +// functionalities include getting the latest exchange rate data for all or a +// specific set of currencies, converting amounts from one currency to another, +// retrieving Time-Series data for one or multiple currencies and querying the +// API for daily fluctuation data. + +package fixer + +import ( + "errors" + "net/url" + "strconv" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" +) + +const ( + fixerAPIFree = iota + fixerAPIBasic + fixerAPIProfessional + fixerAPIProfessionalPlus + fixerAPIEnterprise + + fixerAPI = "http://data.fixer.io/api/" + fixerAPISSL = "https://data.fixer.io/api/" + fixerAPILatest = "latest" + fixerAPIConvert = "convert" + fixerAPITimeSeries = "timeseries" + fixerAPIFluctuation = "fluctuation" +) + +// Fixer is a foreign exchange rate provider at https://fixer.io/ +// NOTE DEFAULT BASE CURRENCY IS EUR upgrade to basic to change +type Fixer struct { + base.Base +} + +// Setup sets appropriate values for fixer object +func (f *Fixer) Setup(config base.Settings) { + f.APIKey = config.APIKey + f.APIKeyLvl = config.APIKeyLvl + f.Enabled = config.Enabled + f.Name = config.Name + f.RESTPollingDelay = config.RESTPollingDelay + f.Verbose = config.Verbose + f.PrimaryProvider = config.PrimaryProvider +} + +// GetRates is a wrapper function to return rates +func (f *Fixer) GetRates(baseCurrency, symbols string) (map[string]float64, error) { + rates, err := f.GetLatestRates(baseCurrency, symbols) + if err != nil { + return nil, err + } + + if f.APIKeyLvl == fixerAPIFree { + baseCurrency = "EUR" + } + + standardisedRates := make(map[string]float64) + for k, v := range rates { + curr := baseCurrency + k + standardisedRates[curr] = v + } + + return standardisedRates, nil +} + +// GetLatestRates returns real-time exchange rate data for all available or a +// specific set of currencies. NOTE DEFAULT BASE CURRENCY IS EUR +func (f *Fixer) GetLatestRates(base, symbols string) (map[string]float64, error) { + var resp Rates + + v := url.Values{} + + if f.APIKeyLvl > fixerAPIFree { + v.Add("base", base) + } + v.Add("symbols", symbols) + + err := f.SendOpenHTTPRequest(fixerAPILatest, v, &resp) + if err != nil { + return resp.Rates, err + } + + if !resp.Success { + return resp.Rates, errors.New(resp.Error.Type + resp.Error.Info) + } + + return resp.Rates, nil +} + +// GetHistoricalRates returns historical exchange rate data for all available or +// a specific set of currencies. +// date - YYYY-MM-DD [required] A date in the past +func (f *Fixer) GetHistoricalRates(date, base string, symbols []string) (map[string]float64, error) { + var resp Rates + + v := url.Values{} + v.Set("symbols", common.JoinStrings(symbols, ",")) + + err := f.SendOpenHTTPRequest(date, v, &resp) + if err != nil { + return resp.Rates, err + } + + if !resp.Success { + return resp.Rates, errors.New(resp.Error.Type + resp.Error.Info) + } + return resp.Rates, nil +} + +// ConvertCurrency allows for conversion of any amount from one currency to +// another. +// from - The three-letter currency code of the currency you would like to +// convert from. +// to - The three-letter currency code of the currency you would like to convert +// to. +// amount - The amount to be converted. +// date - [optional] Specify a date (format YYYY-MM-DD) to use historical rates +// for this conversion. +func (f *Fixer) ConvertCurrency(from, to, date string, amount float64) (float64, error) { + if f.APIKeyLvl < fixerAPIBasic { + return 0, errors.New("insufficient API privileges, upgrade to basic to use this function") + } + + var resp Conversion + + v := url.Values{} + v.Set("from", from) + v.Set("to", to) + v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + v.Set("date", date) + + err := f.SendOpenHTTPRequest(fixerAPIConvert, v, &resp) + if err != nil { + return resp.Result, err + } + + if !resp.Success { + return resp.Result, errors.New(resp.Error.Type + resp.Error.Info) + } + return resp.Result, nil +} + +// GetTimeSeriesData returns daily historical exchange rate data between two +// specified dates for all available or a specific set of currencies. +func (f *Fixer) GetTimeSeriesData(startDate, endDate, base string, symbols []string) (map[string]interface{}, error) { + if f.APIKeyLvl < fixerAPIProfessional { + return nil, errors.New("insufficient API privileges, upgrade to professional to use this function") + } + + var resp TimeSeries + + v := url.Values{} + v.Set("start_date", startDate) + v.Set("end_date", endDate) + v.Set("base", base) + v.Set("symbols", common.JoinStrings(symbols, ",")) + + err := f.SendOpenHTTPRequest(fixerAPITimeSeries, v, &resp) + if err != nil { + return resp.Rates, err + } + + if !resp.Success { + return resp.Rates, errors.New(resp.Error.Type + resp.Error.Info) + } + return resp.Rates, nil +} + +// GetFluctuationData returns fluctuation data between two specified dates for +// all available or a specific set of currencies. +func (f *Fixer) GetFluctuationData(startDate, endDate, base string, symbols []string) (map[string]Flux, error) { + if f.APIKeyLvl < fixerAPIProfessionalPlus { + return nil, errors.New("insufficient API privileges, upgrade to professional plus or enterprise to use this function") + } + + var resp Fluctuation + + v := url.Values{} + v.Set("start_date", startDate) + v.Set("end_date", endDate) + v.Set("base", base) + v.Set("symbols", common.JoinStrings(symbols, ",")) + + err := f.SendOpenHTTPRequest(fixerAPIFluctuation, v, &resp) + if err != nil { + return resp.Rates, err + } + + if !resp.Success { + return resp.Rates, errors.New(resp.Error.Type + resp.Error.Info) + } + return resp.Rates, nil +} + +// SendOpenHTTPRequest sends a typical get request +func (f *Fixer) SendOpenHTTPRequest(endpoint string, v url.Values, result interface{}) error { + var path string + v.Set("access_key", f.APIKey) + + if f.APIKeyLvl == fixerAPIFree { + path = fixerAPI + endpoint + "?" + v.Encode() + } else { + path = fixerAPISSL + endpoint + "?" + v.Encode() + } + return common.SendHTTPGetRequest(path, true, f.Verbose, result) +} diff --git a/currency/forexprovider/fixer.io/fixer_test.go b/currency/forexprovider/fixer.io/fixer_test.go new file mode 100644 index 00000000..dfc91413 --- /dev/null +++ b/currency/forexprovider/fixer.io/fixer_test.go @@ -0,0 +1,57 @@ +package fixer + +import ( + "testing" +) + +// Please set API key and apikey subscription level for correct due diligence +// testing - NOTE please be aware tests will diminish your monthly API calls + +const ( + apikey = "" + apiKeyLvl = 3 +) + +var f Fixer + +func TestGetRates(t *testing.T) { + _, err := f.GetRates("EUR", "AUD") + if err == nil { + t.Error("test failed - fixer GetRates() error", err) + } +} + +func TestGetLatestRates(t *testing.T) { + _, err := f.GetLatestRates("EUR", "AUD") + if err == nil { + t.Error("test failed - fixer GetLatestRates() error", err) + } +} + +func TestGetHistoricalRates(t *testing.T) { + _, err := f.GetHistoricalRates("2013-12-24", "EUR", []string{"AUD,KRW"}) + if err == nil { + t.Error("test failed - fixer GetHistoricalRates() error", err) + } +} + +func TestConvertCurrency(t *testing.T) { + _, err := f.ConvertCurrency("AUD", "EUR", "", 1337) + if err == nil { + t.Error("test failed - fixer ConvertCurrency() error", err) + } +} + +func TestGetTimeSeriesData(t *testing.T) { + _, err := f.GetTimeSeriesData("2013-12-24", "2013-12-25", "EUR", []string{"AUD,KRW"}) + if err == nil { + t.Error("test failed - fixer GetTimeSeriesData() error", err) + } +} + +func TestGetFluctuationData(t *testing.T) { + _, err := f.GetFluctuationData("2013-12-24", "2013-12-25", "EUR", []string{"AUD,KRW"}) + if err == nil { + t.Error("test failed - fixer GetFluctuationData() error", err) + } +} diff --git a/currency/forexprovider/fixer.io/fixer_types.go b/currency/forexprovider/fixer.io/fixer_types.go new file mode 100644 index 00000000..6f17e948 --- /dev/null +++ b/currency/forexprovider/fixer.io/fixer_types.go @@ -0,0 +1,76 @@ +package fixer + +// Rates contains the data fields for the currencies you have requested. +type Rates struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Type string `json:"type"` + Info string `json:"info"` + } `json:"error"` + Historical bool `json:"historical"` + Timestamp int64 `json:"timestamp"` + Base string `json:"base"` + Date string `json:"date"` + Rates map[string]float64 `json:"rates"` +} + +// Conversion contains data for currency conversion +type Conversion struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Type string `json:"type"` + Info string `json:"info"` + } `json:"error"` + Query struct { + From string `json:"from"` + To string `json:"to"` + Amount float64 `json:"amount"` + } `json:"query"` + Info struct { + Timestamp int64 `json:"timestamp"` + Rate float64 `json:"rate"` + } `json:"info"` + Historical bool `json:"historical"` + Date string `json:"date"` + Result float64 `json:"result"` +} + +// TimeSeries holds timeseries data +type TimeSeries struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Type string `json:"type"` + Info string `json:"info"` + } `json:"error"` + Timeseries bool `json:"timeseries"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Base string `json:"base"` + Rates map[string]interface{} `json:"rates"` +} + +// Fluctuation holds fluctuation data +type Fluctuation struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Type string `json:"type"` + Info string `json:"info"` + } `json:"error"` + Fluctuation bool `json:"fluctuation"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Base string `json:"base"` + Rates map[string]Flux `json:"rates"` +} + +// Flux is a sub type holding fluctation data +type Flux struct { + StartRate float64 `json:"start_rate"` + EndRate float64 `json:"end_rate"` + Change float64 `json:"change"` + ChangePCT float64 `json:"change_pct"` +} diff --git a/currency/forexprovider/forexprovider.go b/currency/forexprovider/forexprovider.go new file mode 100644 index 00000000..425444ef --- /dev/null +++ b/currency/forexprovider/forexprovider.go @@ -0,0 +1,67 @@ +// Package forexprovider utilises foreign exchange API services to manage +// relational FIAT currencies +package forexprovider + +import ( + "log" + + "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" + currencyconverter "github.com/thrasher-/gocryptotrader/currency/forexprovider/currencyconverterapi" + "github.com/thrasher-/gocryptotrader/currency/forexprovider/currencylayer" + fixer "github.com/thrasher-/gocryptotrader/currency/forexprovider/fixer.io" + "github.com/thrasher-/gocryptotrader/currency/forexprovider/openexchangerates" +) + +// ForexProviders is an array of foreign exchange interfaces +type ForexProviders struct { + base.IFXProviders +} + +// GetAvailableForexProviders returns a list of supported forex providers +func GetAvailableForexProviders() []string { + return []string{"CurrencyConverter", "CurrencyLayer", "Fixer", "OpenExchangeRates"} +} + +// NewDefaultFXProvider returns the default forex provider (currencyconverterAPI) +func NewDefaultFXProvider() *ForexProviders { + fxp := new(ForexProviders) + currencyC := new(currencyconverter.CurrencyConverter) + currencyC.PrimaryProvider = true + currencyC.Enabled = true + currencyC.Name = "CurrencyConverter" + currencyC.APIKeyLvl = 0 + currencyC.Verbose = false + fxp.IFXProviders = append(fxp.IFXProviders, currencyC) + return fxp +} + +// StartFXService starts the forex provider service and returns a pointer to it +func StartFXService(fxProviders []base.Settings) *ForexProviders { + fxp := new(ForexProviders) + for i := range fxProviders { + if fxProviders[i].Name == "CurrencyConverter" && fxProviders[i].Enabled { + currencyC := new(currencyconverter.CurrencyConverter) + currencyC.Setup(fxProviders[i]) + fxp.IFXProviders = append(fxp.IFXProviders, currencyC) + } + if fxProviders[i].Name == "CurrencyLayer" && fxProviders[i].Enabled { + currencyLayerP := new(currencylayer.CurrencyLayer) + currencyLayerP.Setup(fxProviders[i]) + fxp.IFXProviders = append(fxp.IFXProviders, currencyLayerP) + } + if fxProviders[i].Name == "Fixer" && fxProviders[i].Enabled { + fixerP := new(fixer.Fixer) + fixerP.Setup(fxProviders[i]) + fxp.IFXProviders = append(fxp.IFXProviders, fixerP) + } + if fxProviders[i].Name == "OpenExchangeRates" && fxProviders[i].Enabled { + OpenExchangeRatesP := new(openexchangerates.OXR) + OpenExchangeRatesP.Setup(fxProviders[i]) + fxp.IFXProviders = append(fxp.IFXProviders, OpenExchangeRatesP) + } + } + if len(fxp.IFXProviders) == 0 { + log.Fatal("No foreign exchange providers enabled") + } + return fxp +} diff --git a/currency/forexprovider/forexprovider_test.go b/currency/forexprovider/forexprovider_test.go new file mode 100644 index 00000000..1b49d811 --- /dev/null +++ b/currency/forexprovider/forexprovider_test.go @@ -0,0 +1 @@ +package forexprovider diff --git a/currency/forexprovider/openexchangerates/openexchangerates.go b/currency/forexprovider/openexchangerates/openexchangerates.go new file mode 100644 index 00000000..34ecb601 --- /dev/null +++ b/currency/forexprovider/openexchangerates/openexchangerates.go @@ -0,0 +1,230 @@ +// Open Exchange Rates provides a simple, lightweight and portable JSON API with +// live and historical foreign exchange (forex) rates, via a simple and +// easy-to-integrate API, in JSON format. Data are tracked and blended +// algorithmically from multiple reliable sources, ensuring fair and unbiased +// consistency. +// End-of-day rates are available historically for all days going back to +// 1st January, 1999. + +package openexchangerates + +import ( + "errors" + "fmt" + "net/url" + "strconv" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/forexprovider/base" +) + +// These consts contain endpoint information +const ( + APIDeveloperAccess = iota + APIEnterpriseAccess + APIUnlimitedAccess + + APIURL = "https://openexchangerates.org/api/" + APIEndpointLatest = "latest.json" + APIEndpointHistorical = "historical/%s.json" + APIEndpointCurrencies = "currencies.json" + APIEndpointTimeSeries = "time-series.json" + APIEndpointConvert = "convert/%s/%s/%s" + APIEndpointOHLC = "ohlc.json" + APIEndpointUsage = "usage.json" +) + +// OXR is a foreign exchange rate provider at https://openexchangerates.org/ +// this is the overarching type across this package +// DOCs : https://docs.openexchangerates.org/docs +type OXR struct { + base.Base +} + +// Setup sets values for the OXR object +func (o *OXR) Setup(config base.Settings) { + o.APIKey = config.APIKey + o.APIKeyLvl = config.APIKeyLvl + o.Enabled = config.Enabled + o.Name = config.Name + o.RESTPollingDelay = config.RESTPollingDelay + o.Verbose = config.Verbose + o.PrimaryProvider = config.PrimaryProvider +} + +// GetRates is a wrapper function to return rates +func (o *OXR) GetRates(baseCurrency, symbols string) (map[string]float64, error) { + rates, err := o.GetLatest(baseCurrency, symbols, false, false) + if err != nil { + return nil, err + } + + standardisedRates := make(map[string]float64) + for k, v := range rates { + curr := baseCurrency + k + standardisedRates[curr] = v + } + + return standardisedRates, nil +} + +// GetLatest returns the latest exchange rates available from the Open Exchange +// Rates +func (o *OXR) GetLatest(base, symbols string, prettyPrint, showAlternative bool) (map[string]float64, error) { + var resp Latest + + v := url.Values{} + v.Set("base", base) + v.Set("symbols", symbols) + v.Set("prettyprint", strconv.FormatBool(prettyPrint)) + v.Set("show_alternative", strconv.FormatBool(showAlternative)) + + if err := o.SendHTTPRequest(APIEndpointLatest, v, &resp); err != nil { + return nil, err + } + + if resp.Error { + return nil, errors.New(resp.Message) + } + return resp.Rates, nil +} + +// GetHistoricalRates returns historical exchange rates for any date available +// from the Open Exchange Rates API. +func (o *OXR) GetHistoricalRates(date, base string, symbols []string, prettyPrint, showAlternative bool) (map[string]float64, error) { + var resp Latest + + v := url.Values{} + v.Set("base", base) + v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("prettyprint", strconv.FormatBool(prettyPrint)) + v.Set("show_alternative", strconv.FormatBool(showAlternative)) + endpoint := fmt.Sprintf(APIEndpointHistorical, date) + + if err := o.SendHTTPRequest(endpoint, v, &resp); err != nil { + return nil, err + } + + if resp.Error { + return nil, errors.New(resp.Message) + } + return resp.Rates, nil +} + +// GetCurrencies returns a list of all currency symbols available from the Open +// Exchange Rates API, +func (o *OXR) GetCurrencies(showInactive, prettyPrint, showAlternative bool) (map[string]string, error) { + resp := make(map[string]string) + + v := url.Values{} + v.Set("show_inactive", strconv.FormatBool(showInactive)) + v.Set("prettyprint", strconv.FormatBool(prettyPrint)) + v.Set("show_alternative", strconv.FormatBool(showAlternative)) + + return resp, o.SendHTTPRequest(APIEndpointCurrencies, v, &resp) +} + +// GetTimeSeries returns historical exchange rates for a given time period, +// where available. +func (o *OXR) GetTimeSeries(base, startDate, endDate string, symbols []string, prettyPrint, showAlternative bool) (map[string]interface{}, error) { + if o.APIKeyLvl < APIEnterpriseAccess { + return nil, errors.New("upgrade account, insufficient access") + } + + var resp TimeSeries + + v := url.Values{} + v.Set("base", base) + v.Set("start", startDate) + v.Set("end", endDate) + v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("prettyprint", strconv.FormatBool(prettyPrint)) + v.Set("show_alternative", strconv.FormatBool(showAlternative)) + + if err := o.SendHTTPRequest(APIEndpointTimeSeries, v, &resp); err != nil { + return nil, err + } + + if resp.Error { + return nil, errors.New(resp.Message) + } + return resp.Rates, nil +} + +// ConvertCurrency converts any money value from one currency to another at the +// latest API rates +func (o *OXR) ConvertCurrency(amount float64, from, to string) (float64, error) { + if o.APIKeyLvl < APIUnlimitedAccess { + return 0, errors.New("upgrade account, insufficient access") + } + + var resp Convert + + endPoint := fmt.Sprintf(APIEndpointConvert, strconv.FormatFloat(amount, 'f', -1, 64), from, to) + if err := o.SendHTTPRequest(endPoint, url.Values{}, &resp); err != nil { + return 0, err + } + + if resp.Error { + return 0, errors.New(resp.Message) + } + return resp.Response, nil +} + +// GetOHLC returns historical Open, High Low, Close (OHLC) and Average exchange +// rates for a given time period, ranging from 1 month to 1 minute, where +// available. +func (o *OXR) GetOHLC(startTime, period, base string, symbols []string, prettyPrint bool) (map[string]interface{}, error) { + if o.APIKeyLvl < APIUnlimitedAccess { + return nil, errors.New("upgrade account, insufficient access") + } + + var resp OHLC + + v := url.Values{} + v.Set("start_time", startTime) + v.Set("period", period) + v.Set("base", base) + v.Set("symbols", common.JoinStrings(symbols, ",")) + v.Set("prettyprint", strconv.FormatBool(prettyPrint)) + + if err := o.SendHTTPRequest(APIEndpointOHLC, v, &resp); err != nil { + return nil, err + } + + if resp.Error { + return nil, errors.New(resp.Message) + } + return resp.Rates, nil +} + +// GetUsageStats returns basic plan information and usage statistics for an Open +// Exchange Rates App ID +func (o *OXR) GetUsageStats(prettyPrint bool) (Usage, error) { + var resp Usage + + v := url.Values{} + v.Set("prettyprint", strconv.FormatBool(prettyPrint)) + + if err := o.SendHTTPRequest(APIEndpointUsage, v, &resp); err != nil { + return resp, err + } + + if resp.Error { + return resp, errors.New(resp.Message) + } + return resp, nil +} + +// SendHTTPRequest sends a HTTP request +func (o *OXR) SendHTTPRequest(endpoint string, values url.Values, result interface{}) error { + headers := make(map[string]string) + headers["Authorization"] = "Token " + o.APIKey + path := APIURL + endpoint + "?" + values.Encode() + + resp, err := common.SendHTTPRequest("GET", path, headers, nil) + if err != nil { + return err + } + return common.JSONDecode([]byte(resp), result) +} diff --git a/currency/forexprovider/openexchangerates/openexchangerates_test.go b/currency/forexprovider/openexchangerates/openexchangerates_test.go new file mode 100644 index 00000000..c3ead75a --- /dev/null +++ b/currency/forexprovider/openexchangerates/openexchangerates_test.go @@ -0,0 +1,70 @@ +package openexchangerates + +import ( + "testing" +) + +// please set apikey for due diligence testing NOTE testing uses your allocated +// API request quota +const ( + apikey = "" + apilvl = 2 +) + +var o OXR + +func TestGetRates(t *testing.T) { + _, err := o.GetRates("USD", "AUD") + if err == nil { + t.Error("test failed - GetRates() error", err) + } +} + +func TestGetLatest(t *testing.T) { + _, err := o.GetLatest("USD", "AUD", false, false) + if err == nil { + t.Error("test failed - GetLatest() error", err) + } +} + +func TestGetHistoricalRates(t *testing.T) { + _, err := o.GetHistoricalRates("2017-12-01", "USD", []string{"CNH", "AUD", "ANG"}, false, false) + if err == nil { + t.Error("test failed - GetRates() error", err) + } +} + +func TestGetCurrencies(t *testing.T) { + _, err := o.GetCurrencies(true, true, true) + if err != nil { + t.Error("test failed - GetCurrencies() error", err) + } +} + +func TestGetTimeSeries(t *testing.T) { + _, err := o.GetTimeSeries("USD", "2017-12-01", "2017-12-02", []string{"CNH", "AUD", "ANG"}, false, false) + if err == nil { + t.Error("test failed - GetTimeSeries() error", err) + } +} + +func TestConvertCurrency(t *testing.T) { + _, err := o.ConvertCurrency(1337, "USD", "AUD") + if err == nil { + t.Error("test failed - ConvertCurrency() error", err) + } +} + +func TestGetOHLC(t *testing.T) { + _, err := o.GetOHLC("2017-07-17T08:30:00Z", "1m", "USD", []string{"AUD"}, false) + if err == nil { + t.Error("test failed - GetOHLC() error", err) + } +} + +func TestGetUsageStats(t *testing.T) { + _, err := o.GetUsageStats(false) + if err == nil { + t.Error("test failed - GetUsageStats() error", err) + } +} diff --git a/currency/forexprovider/openexchangerates/openexchangerates_types.go b/currency/forexprovider/openexchangerates/openexchangerates_types.go new file mode 100644 index 00000000..8c2d6e18 --- /dev/null +++ b/currency/forexprovider/openexchangerates/openexchangerates_types.go @@ -0,0 +1,108 @@ +package openexchangerates + +// Latest holds latest rate data +type Latest struct { + Disclaimer string `json:"disclaimer"` + License string `json:"license"` + Timestamp int64 `json:"timestamp"` + Base string `json:"base"` + Rates map[string]float64 `json:"rates"` + Error bool `json:"error"` + Status int `json:"status"` + Message string `json:"message"` + Description string `json:"description"` +} + +// Historical holds historic rate data +type Historical struct { + Disclaimer string `json:"disclaimer"` + License string `json:"license"` + Timestamp int64 `json:"timestamp"` + Base string `json:"base"` + Rates map[string]float64 `json:"rates"` + Error bool `json:"error"` + Status int `json:"status"` + Message string `json:"message"` + Description string `json:"description"` +} + +// TimeSeries holds historic rate data +type TimeSeries struct { + Disclaimer string `json:"disclaimer"` + License string `json:"license"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Base string `json:"base"` + Rates map[string]interface{} `json:"rates"` + Error bool `json:"error"` + Status int `json:"status"` + Message string `json:"message"` + Description string `json:"description"` +} + +// Convert holds historic rate data +type Convert struct { + Disclaimer string `json:"disclaimer"` + License string `json:"license"` + Request struct { + Query string `json:"query"` + Amount float64 `json:"amount"` + From string `json:"from"` + To string `json:"to"` + } `json:"request"` + Meta struct { + Timestamp int64 `json:"timestamp"` + Rate float64 `json:"rate"` + } + Response float64 `json:"response"` + Error bool `json:"error"` + Status int `json:"status"` + Message string `json:"message"` + Description string `json:"description"` +} + +// OHLC holds open high low close values +type OHLC struct { + Disclaimer string `json:"disclaimer"` + License string `json:"license"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Base string `json:"base"` + Rates map[string]interface{} `json:"rates"` + Error bool `json:"error"` + Status int `json:"status"` + Message string `json:"message"` + Description string `json:"description"` +} + +// Usage holds usage statistical data +type Usage struct { + Status int `json:"status"` + Data struct { + AppID string `json:"app_id"` + Status string `json:"status"` + Plan struct { + Name string `json:"name"` + Quota string `json:"quota"` + UpdateFrequency string `json:"update_frequency"` + Features struct { + Base bool `json:"base"` + Symbols bool `json:"symbols"` + Experimental bool `json:"experimental"` + Timeseries bool `json:"time-series"` + Convert bool `json:"convert"` + } `json:"features"` + } `json:"plaab"` + } `json:"data"` + Usages struct { + Requests int64 `json:"requests"` + RequestQuota int `json:"requests_quota"` + RequestsRemaining int `json:"requests_remaining"` + DaysElapsed int `json:"days_elapsed"` + DaysRemaining int `json:"days_remaining"` + DailyAverage int `json:"daily_average"` + } + Error bool `json:"error"` + Message string `json:"message"` + Description string `json:"description"` +} diff --git a/exchanges/exchange.go b/exchanges/exchange.go index ba049730..e953bfee 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -365,8 +365,8 @@ func FormatExchangeCurrency(exchName string, p pair.CurrencyPair) pair.CurrencyI // based on the user currency display preferences func FormatCurrency(p pair.CurrencyPair) pair.CurrencyItem { cfg := config.GetConfig() - return p.Display(cfg.CurrencyPairFormat.Delimiter, - cfg.CurrencyPairFormat.Uppercase) + return p.Display(cfg.Currency.CurrencyPairFormat.Delimiter, + cfg.Currency.CurrencyPairFormat.Uppercase) } // SetEnabled is a method that sets if the exchange is enabled diff --git a/helpers.go b/helpers.go index da7fc633..a35f3b5e 100644 --- a/helpers.go +++ b/helpers.go @@ -48,7 +48,9 @@ func GetSpecificAvailablePairs(enabledExchangesOnly, fiatPairs, includeUSDT, cry for x := range supportedPairs { if fiatPairs { - if currency.IsCryptoFiatPair(supportedPairs[x]) && !pair.ContainsCurrency(supportedPairs[x], "USDT") || (includeUSDT && pair.ContainsCurrency(supportedPairs[x], "USDT") && currency.IsCryptoPair(supportedPairs[x])) { + if currency.IsCryptoFiatPair(supportedPairs[x]) && + !pair.ContainsCurrency(supportedPairs[x], "USDT") || + (includeUSDT && pair.ContainsCurrency(supportedPairs[x], "USDT") && currency.IsCryptoPair(supportedPairs[x])) { if pair.Contains(pairList, supportedPairs[x], false) { continue } @@ -160,7 +162,7 @@ func GetRelatableCryptocurrencies(p pair.CurrencyPair) []pair.CurrencyPair { // incOrig includes the supplied pair if desired func GetRelatableFiatCurrencies(p pair.CurrencyPair) []pair.CurrencyPair { var pairs []pair.CurrencyPair - fiatCurrencies := currency.BaseCurrencies + fiatCurrencies := currency.FiatCurrencies for x := range fiatCurrencies { newPair := pair.NewCurrencyPair(p.FirstCurrency.String(), fiatCurrencies[x]) diff --git a/helpers_test.go b/helpers_test.go index a45bee61..074c7193 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -18,7 +18,9 @@ const ( TestConfig = "./testdata/configtest.json" ) -var helperTestLoaded = false +var ( + helperTestLoaded = false +) func SetupTestHelpers(t *testing.T) { if !helperTestLoaded { @@ -30,8 +32,7 @@ func SetupTestHelpers(t *testing.T) { } testSetup = true } - - err := bot.config.RetrieveConfigCurrencyPairs(false) + err := bot.config.RetrieveConfigCurrencyPairs(true) if err != nil { t.Fatalf("Failed to retrieve config currency pairs. %s", err) } @@ -43,7 +44,6 @@ func TestGetSpecificAvailablePairs(t *testing.T) { SetupTestHelpers(t) result := GetSpecificAvailablePairs(true, true, true, false) - log.Println(result) if !pair.Contains(result, pair.NewCurrencyPair("BTC", "USD"), true) { t.Fatal("Unexpected result") } @@ -177,15 +177,15 @@ func TestGetRelatableFiatCurrencies(t *testing.T) { t.Fatal("Unexpected result") } - backup := currency.BaseCurrencies - currency.BaseCurrencies = append(currency.BaseCurrencies, "USD") + backup := currency.FiatCurrencies + currency.FiatCurrencies = append(currency.FiatCurrencies, "USD") p = GetRelatableFiatCurrencies(pair.NewCurrencyPair("BTC", "USD")) if !pair.Contains(p, pair.NewCurrencyPair("BTC", "ZAR"), true) { t.Fatal("Unexpected result") } - currency.BaseCurrencies = backup + currency.FiatCurrencies = backup } func TestMapCurrenciesByExchange(t *testing.T) { diff --git a/main.go b/main.go index 120365f8..6472d1b5 100644 --- a/main.go +++ b/main.go @@ -11,9 +11,11 @@ import ( "strconv" "syscall" + "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/currency/forexprovider" + "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/portfolio" "github.com/thrasher-/gocryptotrader/smsglobal" @@ -32,12 +34,12 @@ type Bot struct { } const banner = ` - ______ ______ __ ______ __ + ______ ______ __ ______ __ / ____/____ / ____/_____ __ __ ____ / /_ ____ /_ __/_____ ______ ____/ /___ _____ / / __ / __ \ / / / ___// / / // __ \ / __// __ \ / / / ___// __ // __ // _ \ / ___/ -/ /_/ // /_/ // /___ / / / /_/ // /_/ // /_ / /_/ // / / / / /_/ // /_/ // __// / -\____/ \____/ \____//_/ \__, // .___/ \__/ \____//_/ /_/ \__,_/ \__,_/ \___//_/ - /____//_/ +/ /_/ // /_/ // /___ / / / /_/ // /_/ // /_ / /_/ // / / / / /_/ // /_/ // __// / +\____/ \____/ \____//_/ \__, // .___/ \__/ \____//_/ /_/ \__,_/ \__,_/ \___//_/ + /____//_/ ` var bot Bot @@ -73,12 +75,11 @@ func main() { err = bot.config.LoadConfig(bot.configFile) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to load config. Err: %s", err) } AdjustGoMaxProcs() log.Printf("Bot '%s' started.\n", bot.config.Name) - log.Printf("Fiat display currency: %s.", bot.config.FiatDisplayCurrency) log.Printf("Bot dry run mode: %v.\n", common.IsEnabled(bot.dryRun)) if bot.config.SMS.Enabled { @@ -105,25 +106,20 @@ func main() { log.Fatalf("No exchanges were able to be loaded. Exiting") } - if bot.config.CurrencyExchangeProvider == "yahoo" { - currency.SetProvider(true) - } else { - currency.SetProvider(false) - } - log.Printf("Currency exchange provider: %s.", bot.config.CurrencyExchangeProvider) - - bot.config.RetrieveConfigCurrencyPairs(true) - err = currency.SeedCurrencyData(common.JoinStrings(currency.BaseCurrencies, ",")) + log.Printf("Fiat display currency: %s.", bot.config.Currency.FiatDisplayCurrency) + currency.BaseCurrency = bot.config.Currency.FiatDisplayCurrency + currency.FXProviders = forexprovider.StartFXService(bot.config.GetCurrencyConfig().ForexProviders) + log.Printf("Primary forex conversion provider: %s.\n", bot.config.GetPrimaryForexProvider()) + err = bot.config.RetrieveConfigCurrencyPairs(true) if err != nil { - currency.SwapProvider() - log.Printf("'%s' currency exchange provider failed, swapping to %s and testing..", - bot.config.CurrencyExchangeProvider, currency.GetProvider()) - err = currency.SeedCurrencyData(common.JoinStrings(currency.BaseCurrencies, ",")) - if err != nil { - log.Fatalf("Fatal error retrieving config currencies. Error: %s", err) - } + log.Fatalf("Failed to retrieve config currency pairs. Error: %s", err) } log.Println("Successfully retrieved config currencies.") + log.Println("Fetching currency data from forex provider..") + err = currency.SeedCurrencyData(common.JoinStrings(currency.FiatCurrencies, ",")) + if err != nil { + log.Fatalf("Unable to fetch forex data. Error: %s", err) + } bot.portfolio = &portfolio.Portfolio bot.portfolio.SeedPortfolio(bot.config.Portfolio) diff --git a/routines.go b/routines.go index cd3fc700..6ce25270 100644 --- a/routines.go +++ b/routines.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/symbol" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" @@ -16,7 +16,7 @@ import ( ) func printCurrencyFormat(price float64) string { - displaySymbol, err := symbol.GetSymbolByCurrencyName(bot.config.FiatDisplayCurrency) + displaySymbol, err := symbol.GetSymbolByCurrencyName(bot.config.Currency.FiatDisplayCurrency) if err != nil { log.Printf("Failed to get display symbol: %s", err) } @@ -25,7 +25,7 @@ func printCurrencyFormat(price float64) string { } func printConvertCurrencyFormat(origCurrency string, origPrice float64) string { - displayCurrency := bot.config.FiatDisplayCurrency + displayCurrency := bot.config.Currency.FiatDisplayCurrency conv, err := currency.ConvertCurrency(origPrice, origCurrency, displayCurrency) if err != nil { log.Printf("Failed to convert currency: %s", err) @@ -61,7 +61,7 @@ func printTickerSummary(result ticker.Price, p pair.CurrencyPair, assetType, exc } stats.Add(exchangeName, p, assetType, result.Last, result.Volume) - if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.String() != bot.config.FiatDisplayCurrency { + if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.String() != bot.config.Currency.FiatDisplayCurrency { origCurrency := p.SecondCurrency.Upper().String() log.Printf("%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", exchangeName, @@ -74,7 +74,7 @@ func printTickerSummary(result ticker.Price, p pair.CurrencyPair, assetType, exc printConvertCurrencyFormat(origCurrency, result.Low), result.Volume) } else { - if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.Upper().String() == bot.config.FiatDisplayCurrency { + if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.Upper().String() == bot.config.Currency.FiatDisplayCurrency { log.Printf("%s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", exchangeName, exchange.FormatCurrency(p).String(), @@ -111,7 +111,7 @@ func printOrderbookSummary(result orderbook.Base, p pair.CurrencyPair, assetType bidsAmount, bidsValue := result.CalculateTotalBids() asksAmount, asksValue := result.CalculateTotalAsks() - if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.String() != bot.config.FiatDisplayCurrency { + if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.String() != bot.config.Currency.FiatDisplayCurrency { origCurrency := p.SecondCurrency.Upper().String() log.Printf("%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s", exchangeName, @@ -127,7 +127,7 @@ func printOrderbookSummary(result orderbook.Base, p pair.CurrencyPair, assetType printConvertCurrencyFormat(origCurrency, asksValue), ) } else { - if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.Upper().String() == bot.config.FiatDisplayCurrency { + if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.Upper().String() == bot.config.Currency.FiatDisplayCurrency { log.Printf("%s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s", exchangeName, exchange.FormatCurrency(p).String(), diff --git a/testdata/configtest.json b/testdata/configtest.json index edbe18c1..411a1bc5 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -1,13 +1,52 @@ { "Name": "Skynet", "EncryptConfig": 0, - "Cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH", - "CurrencyExchangeProvider": "fixer", - "CurrencyPairFormat": { - "Uppercase": true, - "Delimiter": "-" + "CurrencyConfig": { + "ForexProviders": [ + { + "Name": "CurrencyConverter", + "Enabled": true, + "Verbose": false, + "RESTPollingDelay": 600, + "APIKey": "", + "APIKeyLvl": 0, + "PrimaryProvider": true + }, + { + "Name": "CurrencyLayer", + "Enabled": false, + "Verbose": false, + "RESTPollingDelay": 600, + "APIKey": "Key", + "APIKeyLvl": -1, + "PrimaryProvider": false + }, + { + "Name": "Fixer", + "Enabled": false, + "Verbose": false, + "RESTPollingDelay": 600, + "APIKey": "Key", + "APIKeyLvl": -1, + "PrimaryProvider": false + }, + { + "Name": "OpenExchangeRates", + "Enabled": false, + "Verbose": false, + "RESTPollingDelay": 600, + "APIKey": "Key", + "APIKeyLvl": -1, + "PrimaryProvider": false + } + ], + "Cryptocurrencies": "BTC,LTC,ETH,DOGE,DASH,XRP,XMR", + "CurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + }, + "FiatDisplayCurrency": "USD" }, - "FiatDisplayCurrency": "USD", "GlobalHTTPTimeout": 15000000000, "PortfolioAddresses": { "Addresses": [ @@ -55,6 +94,7 @@ "AdminPassword": "Password", "ListenAddress": ":9050", "WebsocketConnectionLimit": 1, + "WebsocketMaxAuthFailures": 3, "WebsocketAllowInsecureOrigin": false }, "Exchanges": [ diff --git a/websocket.go b/websocket.go index 5e010003..59d224eb 100644 --- a/websocket.go +++ b/websocket.go @@ -431,11 +431,7 @@ func wsGetExchangeRates(client *WebsocketClient, data interface{}) error { wsResp := WebsocketEventResponse{ Event: "GetExchangeRates", } - if currency.YahooEnabled { - wsResp.Data = currency.CurrencyStore - } else { - wsResp.Data = currency.CurrencyStoreFixer - } + wsResp.Data = currency.GetExchangeRates() return client.SendWebsocketMessage(wsResp) }