mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-19 15:10:05 +00:00
Currency: Add new forex provider exchangerate.host (#682)
* Add new forex provider ExchangeRateHost.io * Fix linter paramTypeComine * Add templates and README files * Convert all times to UTC * Fix cosmetic issue and address nits * Add support for fx exchangerate.host engine override * Address nit plus use remove plural
This commit is contained in:
@@ -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"}}
|
||||
|
||||
@@ -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}}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ const (
|
||||
DefaultUnsetAPIKey = "Key"
|
||||
DefaultUnsetAPISecret = "Secret"
|
||||
DefaultUnsetAccountPlan = "accountPlan"
|
||||
DefaultForexProviderExchangeRatesAPI = "ExchangeRates"
|
||||
DefaultForexProviderExchangeRatesAPI = "ExchangeRateHost"
|
||||
)
|
||||
|
||||
// Variables here are used for configuration
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -25,6 +25,7 @@ type BotOverrides struct {
|
||||
FxCurrencyLayer bool
|
||||
FxFixer bool
|
||||
FxOpenExchangeRates bool
|
||||
FxExchangeRateHost bool
|
||||
}
|
||||
|
||||
// CoinmarketcapSettings refers to settings
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
69
currency/forexprovider/exchangerate.host/README.md
Normal file
69
currency/forexprovider/exchangerate.host/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# GoCryptoTrader package Exchangerate.Host
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-corp/gocryptotrader)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/currency/forexprovider/exchangerate.host)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](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
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
262
currency/forexprovider/exchangerate.host/exchangerate.go
Normal file
262
currency/forexprovider/exchangerate.host/exchangerate.go
Normal file
@@ -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,
|
||||
})
|
||||
}
|
||||
107
currency/forexprovider/exchangerate.host/exchangerate_test.go
Normal file
107
currency/forexprovider/exchangerate.host/exchangerate_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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(symbols) > 0 {
|
||||
s := cleanCurrencies(baseCurrency, strings.Join(symbols, ","))
|
||||
v.Set("symbols", s)
|
||||
}
|
||||
|
||||
if len(baseCurrency) > 0 {
|
||||
if len(baseCurrency) > 0 && e.APIKeyLvl <= apiKeyFree && !strings.EqualFold("EUR", baseCurrency) {
|
||||
return nil, errCannotSetBaseCurrencyOnFreePlan
|
||||
} else if len(baseCurrency) > 0 {
|
||||
v.Set("base", baseCurrency)
|
||||
}
|
||||
|
||||
return resp, e.SendHTTPRequest(date, v, &resp)
|
||||
if len(symbols) > 0 {
|
||||
s := e.cleanCurrencies(baseCurrency, strings.Join(symbols, ","))
|
||||
v.Set("symbols", s)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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 TestGetHistoricalRates(t *testing.T) {
|
||||
if !isAPIKeySet() {
|
||||
t.Skip("API key not set, skipping test")
|
||||
}
|
||||
|
||||
_, err := e.GetHistoricalRates(time.Time{}, "EUR", []string{"AUD"})
|
||||
if err == nil {
|
||||
t.Fatalf("invalid date should throw an error")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = e.GetHistoricalRates(time.Now(), "EUR", []string{"AUD,USD"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
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.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.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
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.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.Fatal("future startTime should throw an error")
|
||||
}
|
||||
|
||||
_, err = e.GetTimeSeriesRates(tmNow.AddDate(0, -1, 0), tmNow, "EUR", []string{"AUD,USD"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFluctuation(t *testing.T) {
|
||||
if !isAPIKeySet() {
|
||||
t.Skip("API key not set, skipping test")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
tmNow := time.Now()
|
||||
_, err := e.GetFluctuations(tmNow.AddDate(0, -1, 0), tmNow, "EUR", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanCurrencies(t *testing.T) {
|
||||
if !initialSetup {
|
||||
setup()
|
||||
if !isAPIKeySet() {
|
||||
t.Skip("API key not set, skipping test")
|
||||
}
|
||||
result := cleanCurrencies("USD", "USD,AUD")
|
||||
|
||||
result := e.cleanCurrencies("EUR", "EUR,AUD")
|
||||
if result != "AUD" {
|
||||
t.Fatalf("unexpected result. AUD should be the only symbol")
|
||||
t.Fatalf("AUD should be the only symbol")
|
||||
}
|
||||
|
||||
result = cleanCurrencies("", "EUR,USD")
|
||||
if result != "USD" {
|
||||
t.Fatalf("unexpected result. USD should be the only symbol")
|
||||
}
|
||||
|
||||
if cleanCurrencies("EUR", "RUR") != "RUB" {
|
||||
if e.cleanCurrencies("EUR", "RUR") != "RUB" {
|
||||
t.Fatalf("unexpected result. RUB should be the only symbol")
|
||||
}
|
||||
|
||||
if cleanCurrencies("EUR", "AUD,BLA") != "AUD" {
|
||||
t.Fatalf("unexpected result. AUD 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 !initialSetup {
|
||||
setup()
|
||||
if !isAPIKeySet() {
|
||||
t.Skip("API key not set, skipping test")
|
||||
}
|
||||
_, err := e.GetRates("USD", "AUD")
|
||||
|
||||
_, err := e.GetRates("EUR", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to GetRates. Err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricalRates(t *testing.T) {
|
||||
if !initialSetup {
|
||||
setup()
|
||||
}
|
||||
_, err := e.GetHistoricalRates("-1", "USD", []string{"AUD"})
|
||||
if err == nil {
|
||||
t.Fatalf("unexpected result. Invalid date should throw an error")
|
||||
func TestGetSupportedCurrencies(t *testing.T) {
|
||||
if !isAPIKeySet() {
|
||||
t.Skip("API key not set, skipping test")
|
||||
}
|
||||
|
||||
_, err = e.GetHistoricalRates("2010-01-12", "USD", []string{"EUR,USD"})
|
||||
r, err := e.GetSupportedCurrencies()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to GetHistoricalRates. Err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTimeSeriesRates(t *testing.T) {
|
||||
if !initialSetup {
|
||||
setup()
|
||||
}
|
||||
_, err := e.GetTimeSeriesRates("", "", "USD", []string{"EUR", "USD"})
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result. Empty startDate endDate params should throw an error")
|
||||
}
|
||||
|
||||
_, err = e.GetTimeSeriesRates("2018-01-01", "2018-09-01", "USD", []string{"EUR,USD"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to TestGetTimeSeriesRates. Err: %s", err)
|
||||
}
|
||||
|
||||
_, err = e.GetTimeSeriesRates("-1", "-1", "USD", []string{"EUR,USD"})
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result. Invalid date params should throw an error")
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(r) == 0 {
|
||||
t.Error("expected greater than zero supported symbols")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ type Settings struct {
|
||||
EnableCurrencyLayer bool
|
||||
EnableFixer bool
|
||||
EnableOpenExchangeRates bool
|
||||
EnableExchangeRateHost bool
|
||||
|
||||
// Exchange tuning settings
|
||||
EnableExchangeHTTPRateLimiter bool
|
||||
|
||||
1
main.go
1
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")
|
||||
|
||||
Reference in New Issue
Block a user