mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
New package provider for foreign exchange rates.
Fixes issue: https://github.com/thrasher-/gocryptotrader/issues/131 Supersedes: https://github.com/thrasher-/gocryptotrader/pull/123
This commit is contained in:
committed by
Adrian Gallagher
parent
443d378f92
commit
58051b89c7
184
config/config.go
184
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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
36
currency/forexprovider/base/base.go
Normal file
36
currency/forexprovider/base/base.go
Normal file
@@ -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
|
||||
}
|
||||
44
currency/forexprovider/base/base_interface.go
Normal file
44
currency/forexprovider/base/base_interface.go
Normal file
@@ -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")
|
||||
}
|
||||
1
currency/forexprovider/base/base_test.go
Normal file
1
currency/forexprovider/base/base_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package base
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
209
currency/forexprovider/currencylayer/currencylayer.go
Normal file
209
currency/forexprovider/currencylayer/currencylayer.go
Normal file
@@ -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)
|
||||
}
|
||||
63
currency/forexprovider/currencylayer/currencylayer_test.go
Normal file
63
currency/forexprovider/currencylayer/currencylayer_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
106
currency/forexprovider/currencylayer/currencylayer_types.go
Normal file
106
currency/forexprovider/currencylayer/currencylayer_types.go
Normal file
@@ -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"`
|
||||
}
|
||||
212
currency/forexprovider/fixer.io/fixer.go
Normal file
212
currency/forexprovider/fixer.io/fixer.go
Normal file
@@ -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)
|
||||
}
|
||||
57
currency/forexprovider/fixer.io/fixer_test.go
Normal file
57
currency/forexprovider/fixer.io/fixer_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
76
currency/forexprovider/fixer.io/fixer_types.go
Normal file
76
currency/forexprovider/fixer.io/fixer_types.go
Normal file
@@ -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"`
|
||||
}
|
||||
67
currency/forexprovider/forexprovider.go
Normal file
67
currency/forexprovider/forexprovider.go
Normal file
@@ -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
|
||||
}
|
||||
1
currency/forexprovider/forexprovider_test.go
Normal file
1
currency/forexprovider/forexprovider_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package forexprovider
|
||||
230
currency/forexprovider/openexchangerates/openexchangerates.go
Normal file
230
currency/forexprovider/openexchangerates/openexchangerates.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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) {
|
||||
|
||||
42
main.go
42
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)
|
||||
|
||||
14
routines.go
14
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(),
|
||||
|
||||
52
testdata/configtest.json
vendored
52
testdata/configtest.json
vendored
@@ -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": [
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user