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")