mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* build(deps): Bump golangci/golangci-lint-action from 6 to 7 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6 to 7. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6...v7) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * golangci-lint: Bump to v2 and fix issues * refactor: remove no longer need ifshort nolint directives, fix test grammar and improve for loop logic * nits: update order pair handling in tests and improve string replacement logic --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
283 lines
8.5 KiB
Go
283 lines
8.5 KiB
Go
package exchangerates
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
)
|
|
|
|
var errAPIKeyNotSet = errors.New("API key must be set")
|
|
|
|
// Setup sets appropriate values for CurrencyLayer
|
|
func (e *ExchangeRates) Setup(config base.Settings) error {
|
|
if config.APIKey == "" {
|
|
return errAPIKeyNotSet
|
|
}
|
|
e.Name = config.Name
|
|
e.Enabled = config.Enabled
|
|
e.Verbose = config.Verbose
|
|
e.PrimaryProvider = config.PrimaryProvider
|
|
e.APIKey = config.APIKey
|
|
e.APIKeyLvl = config.APIKeyLvl
|
|
var err error
|
|
e.Requester, err = request.New(e.Name,
|
|
common.NewHTTPClientWithTimeout(base.DefaultTimeOut),
|
|
request.WithLimiter(request.NewBasicRateLimit(rateLimitInterval, requestRate, 1)))
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
symbols = strings.ReplaceAll(symbols, "RUR", "RUB")
|
|
s := strings.Split(symbols, ",")
|
|
cleanedCurrencies := make([]string, 0, len(s))
|
|
for _, x := range s {
|
|
// first make sure that the baseCurrency is not in the symbols list
|
|
// if it is set
|
|
if baseCurrency != "" {
|
|
if x == baseCurrency {
|
|
continue
|
|
}
|
|
} else {
|
|
// otherwise since the baseCurrency is empty, make sure that it
|
|
// does not exist in the symbols list
|
|
if x == "EUR" {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// remove and warn about any unsupported currencies
|
|
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) {
|
|
vals := url.Values{}
|
|
if baseCurrency != "" && e.APIKeyLvl <= apiKeyFree && !strings.EqualFold("EUR", baseCurrency) {
|
|
return nil, errCannotSetBaseCurrencyOnFreePlan
|
|
} else if baseCurrency != "" {
|
|
vals.Set("base", baseCurrency)
|
|
}
|
|
|
|
if symbols != "" {
|
|
symbols = e.cleanCurrencies(baseCurrency, symbols)
|
|
vals.Set("symbols", symbols)
|
|
}
|
|
|
|
var result Rates
|
|
return &result, e.SendHTTPRequest(exchangeRatesLatest, vals, &result)
|
|
}
|
|
|
|
// GetHistoricalRates returns historical exchange rate data for all available or
|
|
// a specific set of currencies.
|
|
// date - YYYY-MM-DD [required] A date in the past
|
|
// 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 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 baseCurrency != "" && e.APIKeyLvl <= apiKeyFree && !strings.EqualFold("EUR", baseCurrency) {
|
|
return nil, errCannotSetBaseCurrencyOnFreePlan
|
|
} else if baseCurrency != "" {
|
|
v.Set("base", baseCurrency)
|
|
}
|
|
|
|
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
|
|
// specified dates for all available or a specific set of currencies.
|
|
// startDate - YYYY-MM-DD [required] A date in the past
|
|
// endDate - YYYY-MM-DD [required] A date in the past but greater than the startDate
|
|
// 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 time.Time, baseCurrency string, symbols []string) (*TimeSeriesRates, error) {
|
|
if e.APIKeyLvl <= apiKeyFree {
|
|
return nil, errAPIKeyLevelRestrictedAccess
|
|
}
|
|
|
|
if startDate.IsZero() || endDate.IsZero() {
|
|
return nil, errStartEndDatesInvalid
|
|
}
|
|
|
|
if startDate.After(endDate) {
|
|
return nil, errStartAfterEnd
|
|
}
|
|
|
|
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 len(symbols) > 0 {
|
|
s := e.cleanCurrencies(baseCurrency, strings.Join(symbols, ","))
|
|
v.Set("symbols", s)
|
|
}
|
|
|
|
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, errStartEndDatesInvalid
|
|
}
|
|
|
|
if startDate.After(endDate) {
|
|
return nil, errStartAfterEnd
|
|
}
|
|
|
|
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
|
|
func (e *ExchangeRates) GetRates(baseCurrency, symbols string) (map[string]float64, error) {
|
|
result, err := e.GetLatestRates(baseCurrency, symbols)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
standardisedRates := make(map[string]float64)
|
|
for k, v := range result.Rates {
|
|
curr := baseCurrency + k
|
|
standardisedRates[curr] = v
|
|
}
|
|
|
|
return standardisedRates, nil
|
|
}
|
|
|
|
// GetSupportedCurrencies returns the supported currency list
|
|
func (e *ExchangeRates) GetSupportedCurrencies() ([]string, error) {
|
|
symbols, err := e.GetSymbols()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
supportedCurrencies := make([]string, 0, len(symbols))
|
|
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 any) error {
|
|
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)
|
|
item := &request.Item{
|
|
Method: http.MethodGet,
|
|
Path: path,
|
|
Result: result,
|
|
Verbose: e.Verbose,
|
|
}
|
|
err := e.Requester.SendPayload(context.TODO(), request.Unset, func() (*request.Item, error) {
|
|
return item, nil
|
|
}, request.AuthenticatedRequest)
|
|
if err != nil {
|
|
return fmt.Errorf("exchangeRatesAPI: SendHTTPRequest error %s with path %s",
|
|
err,
|
|
path)
|
|
}
|
|
return nil
|
|
}
|