diff --git a/cmd/documentation/currency_templates/fx.tmpl b/cmd/documentation/currency_templates/fx.tmpl index 36b4df964dd..964f9786d53 100644 --- a/cmd/documentation/currency_templates/fx.tmpl +++ b/cmd/documentation/currency_templates/fx.tmpl @@ -6,6 +6,7 @@ + Currency Layer support + Fixer.io support + Open Exchange Rates support ++ ExchangeRate.host support ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} diff --git a/cmd/documentation/currency_templates/fx_exchangeratehost.tmpl b/cmd/documentation/currency_templates/fx_exchangeratehost.tmpl new file mode 100644 index 00000000000..02beb65c46e --- /dev/null +++ b/cmd/documentation/currency_templates/fx_exchangeratehost.tmpl @@ -0,0 +1,35 @@ +{{define "currency forexprovider exchangerate.host" -}} +{{template "header" .}} +## Current Features for {{.Name}} + ++ Fetches up to date curency data from [ExchangeRate.host API]("https://exchangerate.host") + +### How to enable + ++ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-currency-via-config-example) + ++ Individual package example below: +```go +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangerate.host" +) + +var c exchangeratehost.ExchangeRateHost + +// Define configuration +newSettings := base.Settings{ + Name: "ExchangeRateHost", + // ... +} + +c.Setup(newSettings) + +rates, err := c.GetRates("USD", "EUR,AUD") +// Handle error +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package +{{template "contributions"}} +{{template "donations" .}} +{{- end}} \ No newline at end of file diff --git a/config/config.go b/config/config.go index 21b7ebdba2c..d3c9e18ad4f 100644 --- a/config/config.go +++ b/config/config.go @@ -1086,11 +1086,12 @@ func (c *Config) CheckCurrencyConfigValues() error { count := 0 for i := range c.Currency.ForexProviders { if c.Currency.ForexProviders[i].Enabled { - if c.Currency.ForexProviders[i].Name == "CurrencyConverter" && + if (c.Currency.ForexProviders[i].Name == "CurrencyConverter" || c.Currency.ForexProviders[i].Name == "ExchangeRates") && c.Currency.ForexProviders[i].PrimaryProvider && (c.Currency.ForexProviders[i].APIKey == "" || c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey) { - log.Warnln(log.Global, "CurrencyConverter forex provider no longer supports unset API key requests. Switching to ExchangeRates FX provider..") + log.Warnf(log.Global, "%s forex provider no longer supports unset API key requests. Switching to %s FX provider..", + c.Currency.ForexProviders[i].Name, DefaultForexProviderExchangeRatesAPI) c.Currency.ForexProviders[i].Enabled = false c.Currency.ForexProviders[i].PrimaryProvider = false c.Currency.ForexProviders[i].APIKey = DefaultUnsetAPIKey @@ -1118,7 +1119,8 @@ func (c *Config) CheckCurrencyConfigValues() error { if c.Currency.ForexProviders[x].Name == DefaultForexProviderExchangeRatesAPI { c.Currency.ForexProviders[x].Enabled = true c.Currency.ForexProviders[x].PrimaryProvider = true - log.Warnln(log.ConfigMgr, "Using ExchangeRatesAPI for default forex provider.") + log.Warnf(log.ConfigMgr, "No valid forex providers configured. Defaulting to %s.", + DefaultForexProviderExchangeRatesAPI) } } } diff --git a/config/config_test.go b/config/config_test.go index 83d9b73be73..c1cc71f682b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1209,7 +1209,7 @@ func TestGetForexProviders(t *testing.T) { t.Error(err) } - if r := cfg.GetForexProviders(); len(r) != 5 { + if r := cfg.GetForexProviders(); len(r) != 6 { t.Error("unexpected length of forex providers") } } diff --git a/config/config_types.go b/config/config_types.go index ebe28d17d2e..87e4b999510 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -62,7 +62,7 @@ const ( DefaultUnsetAPIKey = "Key" DefaultUnsetAPISecret = "Secret" DefaultUnsetAccountPlan = "accountPlan" - DefaultForexProviderExchangeRatesAPI = "ExchangeRates" + DefaultForexProviderExchangeRatesAPI = "ExchangeRateHost" ) // Variables here are used for configuration diff --git a/currency/conversion_test.go b/currency/conversion_test.go index 83b859af7ad..6cf9a151e5e 100644 --- a/currency/conversion_test.go +++ b/currency/conversion_test.go @@ -182,7 +182,7 @@ func TestGetRate(t *testing.T) { c, err := NewConversion(from, to) if err != nil { - t.Error(err) + t.Fatal(err) } rate, err := c.GetRate() if err != nil { diff --git a/currency/currency_types.go b/currency/currency_types.go index 83b5b07731a..e3494d5c4cc 100644 --- a/currency/currency_types.go +++ b/currency/currency_types.go @@ -25,6 +25,7 @@ type BotOverrides struct { FxCurrencyLayer bool FxFixer bool FxOpenExchangeRates bool + FxExchangeRateHost bool } // CoinmarketcapSettings refers to settings diff --git a/currency/forexprovider/README.md b/currency/forexprovider/README.md index 4b8d0de51c8..47d001437a2 100644 --- a/currency/forexprovider/README.md +++ b/currency/forexprovider/README.md @@ -24,6 +24,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader + Currency Layer support + Fixer.io support + Open Exchange Rates support ++ ExchangeRate.host support ### Please click GoDocs chevron above to view current GoDoc information for this package diff --git a/currency/forexprovider/exchangerate.host/README.md b/currency/forexprovider/exchangerate.host/README.md new file mode 100644 index 00000000000..8e5e4fe3dc1 --- /dev/null +++ b/currency/forexprovider/exchangerate.host/README.md @@ -0,0 +1,69 @@ +# GoCryptoTrader package Exchangerate.Host + + + + +[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangerate.host) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + + +This exchangerate.host package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) + +## Current Features for exchangerate.host + ++ Fetches up to date curency data from [ExchangeRate.host API]("https://exchangerate.host") + +### How to enable + ++ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-currency-via-config-example) + ++ Individual package example below: +```go +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangerate.host" +) + +var c exchangeratehost.ExchangeRateHost + +// Define configuration +newSettings := base.Settings{ + Name: "ExchangeRateHost", + // ... +} + +c.Setup(newSettings) + +rates, err := c.GetRates("USD", "EUR,AUD") +// Handle error +``` + +### Please click GoDocs chevron above to view current GoDoc information for this package + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + +***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc*** \ No newline at end of file diff --git a/currency/forexprovider/exchangerate.host/exchangerate.go b/currency/forexprovider/exchangerate.host/exchangerate.go new file mode 100644 index 00000000000..24b91e95408 --- /dev/null +++ b/currency/forexprovider/exchangerate.host/exchangerate.go @@ -0,0 +1,262 @@ +package exchangeratehost + +import ( + "context" + "errors" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +// A client for the exchangerate.host API. NOTE: The format and callback +// parameters aren't supported as they're not needed for this implementation. +// Furthermore, the source is set to "ECB" as default + +const ( + timeLayout = "2006-01-02" + exchangeRateHostURL = "https://api.exchangerate.host" +) + +var ( + // DefaultSource uses the ecb for forex rates + DefaultSource = "ecb" +) + +// Setup sets up the ExchangeRateHost config +func (e *ExchangeRateHost) Setup(config base.Settings) error { + e.Name = config.Name + e.Enabled = config.Enabled + e.RESTPollingDelay = config.RESTPollingDelay + e.Verbose = config.Verbose + e.PrimaryProvider = config.PrimaryProvider + e.Requester = request.New(e.Name, + common.NewHTTPClientWithTimeout(base.DefaultTimeOut)) + return nil +} + +// GetLatestRates returns a list of forex rates based on the supplied params +func (e *ExchangeRateHost) GetLatestRates(baseCurrency, symbols string, amount float64, places int64, source string) (*LatestRates, error) { + v := url.Values{} + if baseCurrency != "" { + v.Set("base", baseCurrency) + } + + if symbols != "" { + v.Set("symbols", symbols) + } + + if amount != 0 { + v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + } + + if places != 0 { + v.Set("places", strconv.FormatInt(places, 10)) + } + + targetSource := DefaultSource + if source != "" { + targetSource = source + } + v.Set("source", targetSource) + + var l LatestRates + return &l, e.SendHTTPRequest("latest", v, &l) +} + +// ConvertCurrency converts a currency based on the supplied params +func (e *ExchangeRateHost) ConvertCurrency(from, to, baseCurrency, symbols, source string, date time.Time, amount float64, places int64) (*ConvertCurrency, error) { + v := url.Values{} + if from != "" { + v.Set("from", from) + } + if to != "" { + v.Set("to", to) + } + if !date.IsZero() { + v.Set("date", date.UTC().Format(timeLayout)) + } + if baseCurrency != "" { + v.Set("base", baseCurrency) + } + if symbols != "" { + v.Set("symbols", symbols) + } + if amount != 0 { + v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + } + if places != 0 { + v.Set("places", strconv.FormatInt(places, 10)) + } + targetSource := DefaultSource + if source != "" { + targetSource = source + } + v.Set("source", targetSource) + + var c ConvertCurrency + return &c, e.SendHTTPRequest("convert", v, &c) +} + +// GetHistoricalRates returns a list of historical rates based on the supplied params +func (e *ExchangeRateHost) GetHistoricalRates(date time.Time, baseCurrency, symbols string, amount float64, places int64, source string) (*HistoricRates, error) { + v := url.Values{} + if date.IsZero() { + date = time.Now() + } + fmtDate := date.UTC().Format(timeLayout) + v.Set("date", fmtDate) + if baseCurrency != "" { + v.Set("base", baseCurrency) + } + + if symbols != "" { + v.Set("symbols", symbols) + } + + if amount != 0 { + v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + } + + if places != 0 { + v.Set("places", strconv.FormatInt(places, 10)) + } + + targetSource := DefaultSource + if source != "" { + targetSource = source + } + v.Set("source", targetSource) + + var h HistoricRates + return &h, e.SendHTTPRequest(fmtDate, v, &h) +} + +// GetTimeSeries returns time series forex data based on the supplied params +func (e *ExchangeRateHost) GetTimeSeries(startDate, endDate time.Time, baseCurrency, symbols string, amount float64, places int64, source string) (*TimeSeries, error) { + if startDate.IsZero() || endDate.IsZero() { + return nil, errors.New("startDate and endDate must be set") + } + + if startDate.After(endDate) || startDate.Equal(endDate) { + return nil, errors.New("startDate and endDate must be set correctly") + } + + v := url.Values{} + v.Set("start_date", startDate.UTC().Format(timeLayout)) + v.Set("end_date", endDate.UTC().Format(timeLayout)) + + if baseCurrency != "" { + v.Set("base", baseCurrency) + } + + if symbols != "" { + v.Set("symbols", symbols) + } + + if amount != 0 { + v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + } + + if places != 0 { + v.Set("places", strconv.FormatInt(places, 10)) + } + + targetSource := DefaultSource + if source != "" { + targetSource = source + } + v.Set("source", targetSource) + + var t TimeSeries + return &t, e.SendHTTPRequest("timeseries", v, &t) +} + +// GetFluctuations returns a list of forex price fluctuations based on the supplied params +func (e *ExchangeRateHost) GetFluctuations(startDate, endDate time.Time, baseCurrency, symbols string, amount float64, places int64, source string) (*Fluctuations, error) { + if startDate.IsZero() || endDate.IsZero() { + return nil, errors.New("startDate and endDate must be set") + } + + if startDate.After(endDate) || startDate.Equal(endDate) { + return nil, errors.New("startDate and endDate must be set correctly") + } + + v := url.Values{} + v.Set("start_date", startDate.UTC().Format(timeLayout)) + v.Set("end_date", endDate.UTC().Format(timeLayout)) + + if baseCurrency != "" { + v.Set("base", baseCurrency) + } + + if symbols != "" { + v.Set("symbols", symbols) + } + + if amount != 0 { + v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + } + + if places != 0 { + v.Set("places", strconv.FormatInt(places, 10)) + } + + targetSource := DefaultSource + if source != "" { + targetSource = source + } + v.Set("source", targetSource) + + var f Fluctuations + return &f, e.SendHTTPRequest("fluctuation", v, &f) +} + +// GetSupportedSymbols returns a list of supported symbols +func (e *ExchangeRateHost) GetSupportedSymbols() (*SupportedSymbols, error) { + var s SupportedSymbols + return &s, e.SendHTTPRequest("symbols", url.Values{}, &s) +} + +// GetSupportedCurrencies returns a list of supported currencies +func (e *ExchangeRateHost) GetSupportedCurrencies() ([]string, error) { + s, err := e.GetSupportedSymbols() + if err != nil { + return nil, err + } + + var symbols []string + for x := range s.Symbols { + symbols = append(symbols, x) + } + return symbols, nil +} + +// GetRates returns the forex rates based on the supplied base currency and symbols +func (e *ExchangeRateHost) GetRates(baseCurrency, symbols string) (map[string]float64, error) { + l, err := e.GetLatestRates(baseCurrency, symbols, 0, 0, "") + if err != nil { + return nil, err + } + + rates := make(map[string]float64) + for k, v := range l.Rates { + rates[baseCurrency+k] = v + } + return rates, nil +} + +// SendHTTPRequest sends a typical get request +func (e *ExchangeRateHost) SendHTTPRequest(endpoint string, v url.Values, result interface{}) error { + path := common.EncodeURLValues(exchangeRateHostURL+"/"+endpoint, v) + return e.Requester.SendPayload(context.Background(), &request.Item{ + Method: http.MethodGet, + Path: path, + Result: &result, + Verbose: e.Verbose, + }) +} diff --git a/currency/forexprovider/exchangerate.host/exchangerate_test.go b/currency/forexprovider/exchangerate.host/exchangerate_test.go new file mode 100644 index 00000000000..5fc8c97a037 --- /dev/null +++ b/currency/forexprovider/exchangerate.host/exchangerate_test.go @@ -0,0 +1,107 @@ +package exchangeratehost + +import ( + "os" + "testing" + "time" + + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" +) + +var ( + e ExchangeRateHost + testCurrencies = "USD,EUR,CZK" +) + +func TestMain(t *testing.M) { + e.Setup(base.Settings{ + Name: "ExchangeRateHost", + }) + os.Exit(t.Run()) +} + +func TestGetLatestRates(t *testing.T) { + _, err := e.GetLatestRates("USD", testCurrencies, 1200, 2, "") + if err != nil { + t.Error(err) + } +} + +func TestConvertCurrency(t *testing.T) { + _, err := e.ConvertCurrency("USD", "EUR", "", testCurrencies, "", time.Now(), 1200, 2) + if err != nil { + t.Error(err) + } +} + +func TestGetHistoricRates(t *testing.T) { + _, err := e.GetHistoricalRates(time.Time{}, "AUD", testCurrencies, 1200, 2, "") + if err != nil { + t.Error(err) + } +} + +func TestGetTimeSeriesRates(t *testing.T) { + _, err := e.GetTimeSeries(time.Time{}, time.Now(), "USD", testCurrencies, 1200, 2, "") + if err == nil { + t.Error("empty start time show throw an error") + } + tmNow := time.Now() + _, err = e.GetTimeSeries(tmNow, tmNow, "USD", testCurrencies, 1200, 2, "") + if err == nil { + t.Error("equal times show throw an error") + } + tmStart := tmNow.AddDate(0, -3, 0) + _, err = e.GetTimeSeries(tmStart, tmNow, "USD", testCurrencies, 1200, 2, "") + if err != nil { + t.Error(err) + } +} + +func TestGetFluctuationData(t *testing.T) { + _, err := e.GetFluctuations(time.Time{}, time.Now(), "USD", testCurrencies, 1200, 2, "") + if err == nil { + t.Error("empty start time show throw an error") + } + tmNow := time.Now() + _, err = e.GetFluctuations(tmNow, tmNow, "USD", testCurrencies, 1200, 2, "") + if err == nil { + t.Error("equal times show throw an error") + } + tmStart := tmNow.AddDate(0, -3, 0) + _, err = e.GetFluctuations(tmStart, tmNow, "USD", testCurrencies, 1200, 2, "") + if err != nil { + t.Error(err) + } +} + +func TestGetSupportedSymbols(t *testing.T) { + r, err := e.GetSupportedSymbols() + if err != nil { + t.Fatal(err) + } + _, ok := r.Symbols["AUD"] + if !ok { + t.Error("should contain AUD") + } +} + +func TestGetGetSupportedCurrencies(t *testing.T) { + s, err := e.GetSupportedCurrencies() + if err != nil { + t.Fatal(err) + } + if len(s) == 0 { + t.Error("supported currencies should be greater than 0") + } +} + +func TestGetRates(t *testing.T) { + r, err := e.GetRates("USD", "") + if err != nil { + t.Fatal(err) + } + if rate := r["USDAUD"]; rate == 0 { + t.Error("rate of USDAUD should be set") + } +} diff --git a/currency/forexprovider/exchangerate.host/exchangerate_types.go b/currency/forexprovider/exchangerate.host/exchangerate_types.go new file mode 100644 index 00000000000..0a56ac9838c --- /dev/null +++ b/currency/forexprovider/exchangerate.host/exchangerate_types.go @@ -0,0 +1,91 @@ +package exchangeratehost + +import ( + "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +// ExchangeRateHost stores the struct for the exchangerate.host API +type ExchangeRateHost struct { + base.Base + Requester *request.Requester +} + +// MessageOfTheDay stores the message of the day +type MessageOfTheDay struct { + Message string `json:"msg"` + DonationURL string `json:"url"` +} + +// LatestRates stores the latest forex rates +type LatestRates struct { + MessageOfTheDay MessageOfTheDay `json:"motd"` + Success bool `json:"success"` + Base string `json:"base"` + Date string `json:"date"` + Rates map[string]float64 `json:"rates"` +} + +// ConvertCurrency stores currency conversion data +type ConvertCurrency struct { + MessageOfTheDay MessageOfTheDay `json:"motd"` + Query struct { + From string `json:"from"` + To string `json:"to"` + Amount float64 `json:"amount"` + } `json:"query"` + Info struct { + Rate float64 `json:"rate"` + } `json:"info"` + Historical bool `json:"historical"` + Date string `json:"date"` + Result float64 `json:"result"` +} + +// HistoricRates stores the hostoric rates +type HistoricRates struct { + LatestRates + Historical bool `json:"historical"` +} + +// TimeSeries stores time series data +type TimeSeries struct { + MessageOfTheDay MessageOfTheDay `json:"motd"` + Success bool `json:"success"` + TimeSeries bool `json:"timeseries"` + Base string `json:"base"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Rates map[string]map[string]float64 `json:"rates"` +} + +// Fluctuation stores an individual rate flucutation +type Fluctuation struct { + StartRate float64 `json:"start_rate"` + EndRate float64 `json:"end_rate"` + Change float64 `json:"change"` + ChangePercentage float64 `json:"change_pct"` +} + +// Fluctuations stores a collection of rate fluctuations +type Fluctuations struct { + MessageOfTheDay MessageOfTheDay `json:"motd"` + Success bool `json:"success"` + Flucutation bool `json:"fluctuation"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Rates map[string]Fluctuation `json:"rate"` +} + +// Symbol stores an individual symbol +type Symbol struct { + Description string `json:"description"` + Code string `json:"code"` +} + +// SupportedSymbols store a collection of supported symbols +type SupportedSymbols struct { + MessageOfTheDay MessageOfTheDay `json:"motd"` + Success bool `json:"success"` + Symbols map[string]Symbol `json:"symbols"` +} diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index 717ae691c40..b461198ac25 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -6,7 +6,9 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" + "time" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" @@ -16,18 +18,31 @@ import ( // Setup sets appropriate values for CurrencyLayer func (e *ExchangeRates) Setup(config base.Settings) error { + if config.APIKey == "" { + return errors.New("API key must be set") + } e.Name = config.Name e.Enabled = config.Enabled e.RESTPollingDelay = config.RESTPollingDelay e.Verbose = config.Verbose e.PrimaryProvider = config.PrimaryProvider + e.APIKey = config.APIKey + e.APIKeyLvl = config.APIKeyLvl e.Requester = request.New(e.Name, common.NewHTTPClientWithTimeout(base.DefaultTimeOut), request.WithLimiter(request.NewBasicRateLimit(rateLimitInterval, requestRate))) return nil } -func cleanCurrencies(baseCurrency, symbols string) string { +func (e *ExchangeRates) cleanCurrencies(baseCurrency, symbols string) string { + if len(e.supportedCurrencies) == 0 { + supportedCurrencies, err := e.GetSupportedCurrencies() + if err != nil { + log.Warnf(log.Global, "ExchangeRatesAPI unable to fetch supported currencies: %s", err) + } else { + e.supportedCurrencies = supportedCurrencies + } + } var cleanedCurrencies []string symbols = strings.Replace(symbols, "RUR", "RUB", -1) var s = strings.Split(symbols, ",") @@ -47,34 +62,45 @@ func cleanCurrencies(baseCurrency, symbols string) string { } // remove and warn about any unsupported currencies - if !strings.Contains(exchangeRatesSupportedCurrencies, x) { // nolint:gocritic - log.Warnf(log.Global, - "Forex provider ExchangeRatesAPI does not support currency %s, removing from forex rates query.\n", x) - continue + if len(e.supportedCurrencies) > 0 { + if !strings.Contains(strings.Join(e.supportedCurrencies, ","), x) { + log.Warnf(log.Global, + "Forex provider ExchangeRatesAPI does not support currency %s, removing from forex rates query.\n", x) + continue + } } cleanedCurrencies = append(cleanedCurrencies, x) } return strings.Join(cleanedCurrencies, ",") } +// GetSymbols returns a list of supported symbols +func (e *ExchangeRates) GetSymbols() (map[string]string, error) { + resp := struct { + Symbols map[string]string `json:"symbols"` + }{} + return resp.Symbols, e.SendHTTPRequest("symbols", url.Values{}, &resp) +} + // GetLatestRates returns a map of forex rates based on the supplied params // baseCurrency - USD [optional] The base currency to use for forex rates, defaults to EUR // symbols - AUD,USD [optional] The symbols to query the forex rates for, default is // all supported currencies -func (e *ExchangeRates) GetLatestRates(baseCurrency, symbols string) (Rates, error) { +func (e *ExchangeRates) GetLatestRates(baseCurrency, symbols string) (*Rates, error) { vals := url.Values{} - - if len(baseCurrency) > 0 { + if len(baseCurrency) > 0 && e.APIKeyLvl <= apiKeyFree && !strings.EqualFold("EUR", baseCurrency) { + return nil, errCannotSetBaseCurrencyOnFreePlan + } else if len(baseCurrency) > 0 { vals.Set("base", baseCurrency) } if len(symbols) > 0 { - symbols = cleanCurrencies(baseCurrency, symbols) + symbols = e.cleanCurrencies(baseCurrency, symbols) vals.Set("symbols", symbols) } var result Rates - return result, e.SendHTTPRequest(exchangeRatesLatest, vals, &result) + return &result, e.SendHTTPRequest(exchangeRatesLatest, vals, &result) } // GetHistoricalRates returns historical exchange rate data for all available or @@ -83,20 +109,48 @@ func (e *ExchangeRates) GetLatestRates(baseCurrency, symbols string) (Rates, err // baseCurrency - USD [optional] The base currency to use for forex rates, defaults to EUR // symbols - AUD,USD [optional] The symbols to query the forex rates for, default is // all supported currencies -func (e *ExchangeRates) GetHistoricalRates(date, baseCurrency string, symbols []string) (HistoricalRates, error) { +func (e *ExchangeRates) GetHistoricalRates(date time.Time, baseCurrency string, symbols []string) (*HistoricalRates, error) { + if date.IsZero() { + return nil, errors.New("a date must be specified") + } + var resp HistoricalRates v := url.Values{} + if len(baseCurrency) > 0 && e.APIKeyLvl <= apiKeyFree && !strings.EqualFold("EUR", baseCurrency) { + return nil, errCannotSetBaseCurrencyOnFreePlan + } else if len(baseCurrency) > 0 { + v.Set("base", baseCurrency) + } + if len(symbols) > 0 { - s := cleanCurrencies(baseCurrency, strings.Join(symbols, ",")) + s := e.cleanCurrencies(baseCurrency, strings.Join(symbols, ",")) v.Set("symbols", s) } - if len(baseCurrency) > 0 { - v.Set("base", baseCurrency) + return &resp, e.SendHTTPRequest(date.UTC().Format(timeLayout), v, &resp) +} + +// ConvertCurrency converts a currency based on the supplied params +func (e *ExchangeRates) ConvertCurrency(from, to string, amount float64, date time.Time) (*ConvertCurrency, error) { + if e.APIKeyLvl <= apiKeyFree { + return nil, errAPIKeyLevelRestrictedAccess + } + vals := url.Values{} + if from == "" || to == "" || amount == 0 { + return nil, errors.New("from, to and amount must be set") } - return resp, e.SendHTTPRequest(date, v, &resp) + vals.Set("from", from) + vals.Set("to", to) + vals.Set("amount", strconv.FormatFloat(amount, 'e', -1, 64)) + + if !date.IsZero() { + vals.Set("date", date.UTC().Format(timeLayout)) + } + + var cc ConvertCurrency + return &cc, e.SendHTTPRequest(exchangeRatesConvert, vals, &cc) } // GetTimeSeriesRates returns daily historical exchange rate data between two @@ -106,26 +160,63 @@ func (e *ExchangeRates) GetHistoricalRates(date, baseCurrency string, symbols [] // baseCurrency - USD [optional] The base currency to use for forex rates, defaults to EUR // symbols - AUD,USD [optional] The symbols to query the forex rates for, default is // all supported currencies -func (e *ExchangeRates) GetTimeSeriesRates(startDate, endDate, baseCurrency string, symbols []string) (TimeSeriesRates, error) { - var resp TimeSeriesRates - if startDate == "" || endDate == "" { - return resp, errors.New("startDate and endDate params must be set") +func (e *ExchangeRates) GetTimeSeriesRates(startDate, endDate time.Time, baseCurrency string, symbols []string) (*TimeSeriesRates, error) { + if e.APIKeyLvl <= apiKeyFree { + return nil, errAPIKeyLevelRestrictedAccess + } + + if startDate.IsZero() || endDate.IsZero() { + return nil, errors.New("startDate and endDate params must be set") + } + + if startDate.After(endDate) { + return nil, errors.New("startDate must be before endDate") } v := url.Values{} - v.Set("start_at", startDate) - v.Set("end_at", endDate) + v.Set("start_date", startDate.UTC().Format(timeLayout)) + v.Set("end_date", endDate.UTC().Format(timeLayout)) - if len(baseCurrency) > 0 { + if baseCurrency != "" { v.Set("base", baseCurrency) } if len(symbols) > 0 { - s := cleanCurrencies(baseCurrency, strings.Join(symbols, ",")) + s := e.cleanCurrencies(baseCurrency, strings.Join(symbols, ",")) v.Set("symbols", s) } - return resp, e.SendHTTPRequest(exchangeRatesHistory, v, &resp) + var resp TimeSeriesRates + return &resp, e.SendHTTPRequest(exchangeRatesTimeSeries, v, &resp) +} + +// GetFluctuations returns rate fluctuations based on the supplied params +func (e *ExchangeRates) GetFluctuations(startDate, endDate time.Time, baseCurrency, symbols string) (*Fluctuations, error) { + if e.APIKeyLvl <= apiKeyFree { + return nil, errAPIKeyLevelRestrictedAccess + } + + if startDate.IsZero() || endDate.IsZero() { + return nil, errors.New("startDate and endDate must be set") + } + + if startDate.After(endDate) { + return nil, errors.New("startDate must be before endDate") + } + + v := url.Values{} + v.Set("start_date", startDate.UTC().Format(timeLayout)) + v.Set("end_date", endDate.UTC().Format(timeLayout)) + + if baseCurrency != "" { + v.Set("base", baseCurrency) + } + if symbols != "" { + v.Set("symbols", symbols) + } + + var f Fluctuations + return &f, e.SendHTTPRequest(exchangeRatesFluctuation, v, &f) } // GetRates is a wrapper function to return forex rates @@ -146,19 +237,38 @@ func (e *ExchangeRates) GetRates(baseCurrency, symbols string) (map[string]float // GetSupportedCurrencies returns the supported currency list func (e *ExchangeRates) GetSupportedCurrencies() ([]string, error) { - return strings.Split(exchangeRatesSupportedCurrencies, ","), nil + symbols, err := e.GetSymbols() + if err != nil { + return nil, err + } + + var supportedCurrencies []string + for x := range symbols { + supportedCurrencies = append(supportedCurrencies, x) + } + e.supportedCurrencies = supportedCurrencies + return supportedCurrencies, nil } // SendHTTPRequest sends a HTTPS request to the desired endpoint and returns the result func (e *ExchangeRates) SendHTTPRequest(endPoint string, values url.Values, result interface{}) error { - path := common.EncodeURLValues(exchangeRatesAPI+"/"+endPoint, values) + if e.APIKey == "" { + return errors.New("api key must be set") + } + values.Set("access_key", e.APIKey) + protocolScheme := "https://" + if e.APIKeyLvl == apiKeyFree { + protocolScheme = "http://" + } + path := common.EncodeURLValues(protocolScheme+exchangeRatesAPI+"/v1/"+endPoint, values) err := e.Requester.SendPayload(context.Background(), &request.Item{ Method: http.MethodGet, Path: path, - Result: &result, - Verbose: e.Verbose}) + Result: result, + Verbose: e.Verbose, + }) if err != nil { - return fmt.Errorf("exchangeRatesAPI SendHTTPRequest error %s with path %s", + return fmt.Errorf("exchangeRatesAPI: SendHTTPRequest error %s with path %s", err, path) } diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_test.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_test.go index fd41e1d0f6e..7ab87081487 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_test.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_test.go @@ -1,45 +1,78 @@ package exchangerates import ( + "errors" + "os" "testing" + "time" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" ) var e ExchangeRates -var initialSetup bool +const ( + apiKey = "" + apiKeyLevel = apiKeyFree // Adjust this if your API key level is different +) -func setup() { +func TestMain(t *testing.M) { e.Setup(base.Settings{ - Name: "ExchangeRates", - Enabled: true, + Name: "ExchangeRates", + APIKey: apiKey, + APIKeyLvl: apiKeyLevel, }) - initialSetup = true + os.Exit(t.Run()) +} + +func isAPIKeySet() bool { + return e.APIKey != "" +} + +func TestGetSymbols(t *testing.T) { + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") + } + + r, err := e.GetSymbols() + if err != nil { + t.Fatal(err) + } + if len(r) == 0 { + t.Error("expected rates map greater than 0") + } } func TestGetLatestRates(t *testing.T) { - if !initialSetup { - setup() + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") } - result, err := e.GetLatestRates("USD", "") + + result, err := e.GetLatestRates("", "") if err != nil { - t.Fatalf("failed to GetLatestRates. Err: %s", err) + t.Fatal(err) } - if result.Base != "USD" { - t.Fatalf("unexepcted result. Base currency should be USD") + if result.Base != "EUR" { + t.Fatalf("unexepcted result. Base currency should be EUR") } - if result.Rates["USD"] != 1 { - t.Fatalf("unexepcted result. USD value should be 1") + if result.Rates["EUR"] != 1 { + t.Fatalf("unexepcted result. EUR value should be 1") } if len(result.Rates) <= 1 { t.Fatalf("unexepcted result. Rates map should be 1") } - result, err = e.GetLatestRates("", "AUD") + if e.APIKeyLvl <= apiKeyFree { + _, err = e.GetLatestRates("USD", "") + if !errors.Is(err, errCannotSetBaseCurrencyOnFreePlan) { + t.Errorf("expected: %s, got %s", errCannotSetBaseCurrencyOnFreePlan, err) + } + } + + result, err = e.GetLatestRates("EUR", "AUD") if err != nil { t.Fatalf("failed to GetLatestRates. Err: %s", err) } @@ -53,70 +86,143 @@ func TestGetLatestRates(t *testing.T) { } } -func TestCleanCurrencies(t *testing.T) { - if !initialSetup { - setup() - } - result := cleanCurrencies("USD", "USD,AUD") - if result != "AUD" { - t.Fatalf("unexpected result. AUD should be the only symbol") +func TestGetHistoricalRates(t *testing.T) { + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") } - result = cleanCurrencies("", "EUR,USD") - if result != "USD" { - t.Fatalf("unexpected result. USD should be the only symbol") + _, err := e.GetHistoricalRates(time.Time{}, "EUR", []string{"AUD"}) + if err == nil { + t.Fatalf("invalid date should throw an error") } - if cleanCurrencies("EUR", "RUR") != "RUB" { - t.Fatalf("unexpected result. RUB should be the only symbol") + if e.APIKeyLvl <= apiKeyFree { + _, err = e.GetHistoricalRates(time.Now(), "USD", []string{"AUD"}) + if !errors.Is(err, errCannotSetBaseCurrencyOnFreePlan) { + t.Errorf("expected: %s, got %s", errCannotSetBaseCurrencyOnFreePlan, err) + } } - if cleanCurrencies("EUR", "AUD,BLA") != "AUD" { - t.Fatalf("unexpected result. AUD should be the only symbol") + _, err = e.GetHistoricalRates(time.Now(), "EUR", []string{"AUD,USD"}) + if err != nil { + t.Error(err) } } -func TestGetRates(t *testing.T) { - if !initialSetup { - setup() +func TestConvertCurrency(t *testing.T) { + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") + } + + if e.APIKeyLvl <= apiKeyFree { + _, err := e.ConvertCurrency("USD", "AUD", 1000, time.Time{}) + if !errors.Is(err, errAPIKeyLevelRestrictedAccess) { + t.Errorf("expected: %s, got %s", errAPIKeyLevelRestrictedAccess, err) + } + return } - _, err := e.GetRates("USD", "AUD") + + _, err := e.ConvertCurrency("", "AUD", 1000, time.Time{}) + if err == nil { + t.Errorf("no from currency should throw an error") + } + + _, err = e.ConvertCurrency("USD", "AUD", 1000, time.Now()) if err != nil { - t.Fatalf("failed to GetRates. Err: %s", err) + t.Error(err) } } -func TestGetHistoricalRates(t *testing.T) { - if !initialSetup { - setup() +func TestGetTimeSeriesRates(t *testing.T) { + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") + } + + if e.APIKeyLvl <= apiKeyFree { + _, err := e.GetTimeSeriesRates(time.Time{}, time.Time{}, "EUR", []string{"EUR,USD"}) + if !errors.Is(err, errAPIKeyLevelRestrictedAccess) { + t.Errorf("expected %s, got %s", errAPIKeyLevelRestrictedAccess, err) + } + return } - _, err := e.GetHistoricalRates("-1", "USD", []string{"AUD"}) + + _, err := e.GetTimeSeriesRates(time.Time{}, time.Time{}, "USD", []string{"EUR", "USD"}) + if err == nil { + t.Fatal("empty startDate endDate params should throw an error") + } + + tmNow := time.Now() + _, err = e.GetTimeSeriesRates(tmNow.AddDate(0, 1, 0), tmNow, "USD", []string{"EUR", "USD"}) if err == nil { - t.Fatalf("unexpected result. Invalid date should throw an error") + t.Fatal("future startTime should throw an error") } - _, err = e.GetHistoricalRates("2010-01-12", "USD", []string{"EUR,USD"}) + _, err = e.GetTimeSeriesRates(tmNow.AddDate(0, -1, 0), tmNow, "EUR", []string{"AUD,USD"}) if err != nil { - t.Fatalf("failed to GetHistoricalRates. Err: %s", err) + t.Error(err) } } -func TestGetTimeSeriesRates(t *testing.T) { - if !initialSetup { - setup() +func TestGetFluctuation(t *testing.T) { + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") } - _, err := e.GetTimeSeriesRates("", "", "USD", []string{"EUR", "USD"}) - if err == nil { - t.Fatal("unexpected result. Empty startDate endDate params should throw an error") + + if e.APIKeyLvl <= apiKeyFree { + _, err := e.GetFluctuations(time.Time{}, time.Time{}, "EUR", "") + if !errors.Is(err, errAPIKeyLevelRestrictedAccess) { + t.Errorf("expected: %s, got %s", errAPIKeyLevelRestrictedAccess, err) + } + return } - _, err = e.GetTimeSeriesRates("2018-01-01", "2018-09-01", "USD", []string{"EUR,USD"}) + tmNow := time.Now() + _, err := e.GetFluctuations(tmNow.AddDate(0, -1, 0), tmNow, "EUR", "") if err != nil { - t.Fatalf("failed to TestGetTimeSeriesRates. Err: %s", err) + t.Fatal(err) } +} - _, err = e.GetTimeSeriesRates("-1", "-1", "USD", []string{"EUR,USD"}) - if err == nil { - t.Fatal("unexpected result. Invalid date params should throw an error") +func TestCleanCurrencies(t *testing.T) { + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") + } + + result := e.cleanCurrencies("EUR", "EUR,AUD") + if result != "AUD" { + t.Fatalf("AUD should be the only symbol") + } + + if e.cleanCurrencies("EUR", "RUR") != "RUB" { + t.Fatalf("unexpected result. RUB should be the only symbol") + } + + if e.cleanCurrencies("EUR", "AUD,BLA") != "AUD" { + t.Fatalf("AUD should be the only symbol") + } +} + +func TestGetRates(t *testing.T) { + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") + } + + _, err := e.GetRates("EUR", "") + if err != nil { + t.Fatalf("failed to GetRates. Err: %s", err) + } +} + +func TestGetSupportedCurrencies(t *testing.T) { + if !isAPIKeySet() { + t.Skip("API key not set, skipping test") + } + + r, err := e.GetSupportedCurrencies() + if err != nil { + t.Fatal(err) + } + if len(r) == 0 { + t.Error("expected greater than zero supported symbols") } } diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_types.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_types.go index da8260edf3a..83a784fec0e 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_types.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi_types.go @@ -1,6 +1,7 @@ package exchangerates import ( + "errors" "time" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" @@ -8,37 +9,85 @@ import ( ) const ( - exchangeRatesAPI = "https://api.exchangeratesapi.io" - exchangeRatesLatest = "latest" - exchangeRatesHistory = "history" - exchangeRatesSupportedCurrencies = "EUR,CHF,USD,BRL,ISK,PHP,KRW,BGN,MXN," + - "RON,CAD,SGD,NZD,THB,HKD,JPY,NOK,HRK,ILS,GBP,DKK,HUF,MYR,RUB,TRY,IDR," + - "ZAR,INR,AUD,CZK,SEK,CNY,PLN" + exchangeRatesAPI = "api.exchangeratesapi.io" + exchangeRatesLatest = "latest" + exchangeRatesTimeSeries = "timeseries" + exchangeRatesConvert = "convert" + exchangeRatesFluctuation = "fluctuation" rateLimitInterval = time.Second * 10 requestRate = 10 + timeLayout = "2006-01-02" + + apiKeyFree = iota + apiKeyBasic + apiKeyProfessional + apiKeyBusiness +) + +var ( + errCannotSetBaseCurrencyOnFreePlan = errors.New("base currency cannot be set on the free plan") + errAPIKeyLevelRestrictedAccess = errors.New("apiKey level function access denied") ) // ExchangeRates stores the struct for the ExchangeRatesAPI API type ExchangeRates struct { base.Base - Requester *request.Requester + supportedCurrencies []string + Requester *request.Requester } // Rates holds the latest forex rates info type Rates struct { - Base string `json:"base"` - Date string `json:"date"` - Rates map[string]float64 `json:"rates"` + Base string `json:"base"` + Timestamp int64 `json:"timestamp"` + Date string `json:"date"` + Rates map[string]float64 `json:"rates"` } // HistoricalRates stores the historical rate info -type HistoricalRates Rates +type HistoricalRates struct { + Historical bool `json:"historical"` + Rates +} + +// ConvertCurrency stores the converted currency info +type ConvertCurrency struct { + 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"` + Result float64 `json:"result"` +} // TimeSeriesRates stores time series rate info type TimeSeriesRates struct { - Base string `json:"base"` - StartAt string `json:"start_at"` - EndAt string `json:"end_at"` - Rates map[string]interface{} `json:"rates"` + Timeseries bool `json:"timeseries"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Base string `json:"base"` + Rates map[string]map[string]float64 `json:"rates"` +} + +// FlucutationItem stores an individual rate fluctuation +type FlucutationItem struct { + StartRate float64 `json:"start_rate"` + EndRate float64 `json:"end_rate"` + Change float64 `json:"change"` + ChangePercentage float64 `json:"change_pct"` +} + +// Fluctuations stores a collection of rate fluctuations +type Fluctuations struct { + Fluctuation bool `json:"fluctuation"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Base string `json:"base"` + Rates map[string]FlucutationItem `json:"rates"` } diff --git a/currency/forexprovider/forexprovider.go b/currency/forexprovider/forexprovider.go index ab415bbc722..b52809a965e 100644 --- a/currency/forexprovider/forexprovider.go +++ b/currency/forexprovider/forexprovider.go @@ -8,6 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" currencyconverter "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencyconverterapi" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencylayer" + exchangeratehost "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangerate.host" exchangerates "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangeratesapi.io" fixer "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/fixer.io" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/openexchangerates" @@ -15,21 +16,24 @@ import ( // GetSupportedForexProviders returns a list of supported forex providers func GetSupportedForexProviders() []string { - return []string{"CurrencyConverter", + return []string{ + "CurrencyConverter", "CurrencyLayer", "ExchangeRates", "Fixer", - "OpenExchangeRates"} + "OpenExchangeRates", + "ExchangeRateHost", + } } // NewDefaultFXProvider returns the default forex provider (currencyconverterAPI) func NewDefaultFXProvider() *ForexProviders { handler := new(ForexProviders) - provider := new(exchangerates.ExchangeRates) + provider := new(exchangeratehost.ExchangeRateHost) err := provider.Setup(base.Settings{ PrimaryProvider: true, Enabled: true, - Name: "ExchangeRates", + Name: "ExchangeRateHost", }) if err != nil { panic(err) diff --git a/currency/storage.go b/currency/storage.go index 1556885f19b..8819446c93f 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -139,6 +139,13 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration fxSettings = append(fxSettings, base.Settings(settings.ForexProviders[i])) } + + case "ExchangeRateHost": + if overrides.FxExchangeRateHost || settings.ForexProviders[i].Enabled { + settings.ForexProviders[i].Enabled = true + fxSettings = append(fxSettings, + base.Settings(settings.ForexProviders[i])) + } } } diff --git a/engine/engine.go b/engine/engine.go index 468c1d4a514..e32935ccb1c 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -313,6 +313,7 @@ func PrintSettings(s *Settings) { gctlog.Debugf(gctlog.Global, "\t Enable currency layer: %v", s.EnableCurrencyLayer) gctlog.Debugf(gctlog.Global, "\t Enable fixer: %v", s.EnableFixer) gctlog.Debugf(gctlog.Global, "\t Enable OpenExchangeRates: %v", s.EnableOpenExchangeRates) + gctlog.Debugf(gctlog.Global, "\t Enable ExchangeRateHost: %v", s.EnableExchangeRateHost) gctlog.Debugf(gctlog.Global, "- EXCHANGE SETTINGS:") gctlog.Debugf(gctlog.Global, "\t Enable exchange auto pair updates: %v", s.EnableExchangeAutoPairUpdates) gctlog.Debugf(gctlog.Global, "\t Disable all exchange auto pair updates: %v", s.DisableExchangeAutoPairUpdates) @@ -412,13 +413,15 @@ func (bot *Engine) Start() error { bot.Settings.EnableCurrencyConverter || bot.Settings.EnableCurrencyLayer || bot.Settings.EnableFixer || - bot.Settings.EnableOpenExchangeRates { + bot.Settings.EnableOpenExchangeRates || + bot.Settings.EnableExchangeRateHost { err = currency.RunStorageUpdater(currency.BotOverrides{ Coinmarketcap: bot.Settings.EnableCoinmarketcapAnalysis, FxCurrencyConverter: bot.Settings.EnableCurrencyConverter, FxCurrencyLayer: bot.Settings.EnableCurrencyLayer, FxFixer: bot.Settings.EnableFixer, FxOpenExchangeRates: bot.Settings.EnableOpenExchangeRates, + FxExchangeRateHost: bot.Settings.EnableExchangeRateHost, }, ¤cy.MainConfiguration{ ForexProviders: bot.Config.GetForexProviders(), @@ -563,7 +566,8 @@ func (bot *Engine) Stop() { bot.Settings.EnableCurrencyConverter || bot.Settings.EnableCurrencyLayer || bot.Settings.EnableFixer || - bot.Settings.EnableOpenExchangeRates { + bot.Settings.EnableOpenExchangeRates || + bot.Settings.EnableExchangeRateHost { if err := currency.ShutdownStorageUpdater(); err != nil { gctlog.Errorf(gctlog.Global, "ExchangeSettings storage system. Error: %v", err) } diff --git a/engine/engine_types.go b/engine/engine_types.go index d9bbf5d2560..e9b258ffab7 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -52,6 +52,7 @@ type Settings struct { EnableCurrencyLayer bool EnableFixer bool EnableOpenExchangeRates bool + EnableExchangeRateHost bool // Exchange tuning settings EnableExchangeHTTPRateLimiter bool diff --git a/main.go b/main.go index 4a1abce978d..eb8362cc789 100644 --- a/main.go +++ b/main.go @@ -73,6 +73,7 @@ func main() { flag.BoolVar(&settings.EnableCurrencyLayer, "currencylayer", false, "overrides config and sets up foreign exchange Currency Layer") flag.BoolVar(&settings.EnableFixer, "fixer", false, "overrides config and sets up foreign exchange Fixer.io") flag.BoolVar(&settings.EnableOpenExchangeRates, "openexchangerates", false, "overrides config and sets up foreign exchange Open Exchange Rates") + flag.BoolVar(&settings.EnableExchangeRateHost, "exchangeratehost", false, "overrides config and sets up foreign exchange ExchangeRate.host") // Exchange tuning settings flag.BoolVar(&settings.EnableExchangeAutoPairUpdates, "exchangeautopairupdates", false, "enables automatic available currency pair updates for supported exchanges")