mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-24 15:10:19 +00:00
* currency: Add method to derive pair * currency: Add method to lower entire charset but used the slice copy and returned that. This will change the original, just gotta see if this is an issue, but the slice usually goes out of scope anyway. * currency/pairs: add filter method * currency: add function to derive select currencies from currency pairs * currency/engine: slight adjustments * currency: fix linter issue also shift burden of proof to caller instead of repair, more performant. * currency: more linter * pairs: optimize; reduce allocs/op and B/op * currency: Add in function 'NewPairsFromString' for testing purposes * currency: don't suppress error * currency: stop panic on empty currency code * currency: Add helper method to match currencies between exchanges * currency: fixed my bad spelling * currency: Implement stable coin checks, refactored base code methods, optimized upper and lower case strings for currency code/pairs * currency: add pairs method to derive stable coins from internal list. * Currency: Cleanup, fix tests. * engine/exchanges/currency: fix whoops * Currency: force govet no copy on Item datatype * Currency: fix naughty linter issues * exchange: revert change * currency/config: fix config upgrade mistake * currency: re-implement currency sub-systems * *RetrieveConfigCurrencyPairs removed *CheckCurrencyConfigValues to only provide warnings, add additional support when, disable when support is lost or not available and set default values. *Drop Cryptocurrencies from configuration as this is not needed. *Drop REST Poll delay field as this was unused. *Update default values for currencyFileUpdateDuration & foreignExchangeUpdateDuration. *Allow Role to be marshalled for file type. *Refactor RunUpdater to verify and check config values and set default running foreign exchange provider. * currency: cleanup * currency: change match -> equal for comparison which is more of a standard and little easier to find * currency: address nits * currency: fix whoops * currency: Add some more pairs methods * currency: linter issues * currency: RM unused field * currency: rm verbose * currency: fix word * currency: gocritic * currency: fix another whoopsie * example_config: default to show log system name * Currency: Force all support packages to use Equal method for comparison as there is a small comparison bug when checking upper and lower casing, this has a more of a pronounced impact between exchanges and client instances of currency generation * currency: fix log name * ordermanager: fix potential panic * currency: small optim. * engine: display correct bool and force shutdown * currency: add function and fix regression * Change ConvertCurrency -> ConvertFiat to be more precise * ADD GetForeignExchangeRate to get specific exchange rate for fiat pair * Fix currency display and formatting regression and tied in with config.Currency fields * engine: fix tests * currency: return the amount when no conversion needs to take place * currency: reduce method name * currency: Address nits glorious nits * currency: fix linter * currency: addr nits * currency: check underlying role in test * gct: change to EMPTYCODE and EMPTYPAIR across codebase * currency: fix nits * currency: this fixes test race but this issue has not been resolved. Please see: https://trello.com/c/54eizOIo/143-currency-package-upgrades * currency: Add temp dir for testing * Update engine/engine.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * documentation: update and regen * currency: Address niterinos * currency: Add test case for config upgrade when falling over to exchange rate host as default from exchangeRates provider * currency: addr nits * currency: fix whoops Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
763 lines
22 KiB
Go
763 lines
22 KiB
Go
package currency
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/common/file"
|
|
"github.com/thrasher-corp/gocryptotrader/currency/coinmarketcap"
|
|
"github.com/thrasher-corp/gocryptotrader/currency/forexprovider"
|
|
"github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
)
|
|
|
|
// storage is an overarching type that keeps track of and updates currency,
|
|
// currency exchange rates and pairs
|
|
var storage Storage
|
|
|
|
func init() {
|
|
storage.SetDefaults()
|
|
}
|
|
|
|
// CurrencyFileUpdateDelay defines the rate at which the currency.json file is
|
|
// updated
|
|
const (
|
|
DefaultCurrencyFileDelay = 168 * time.Hour
|
|
DefaultForeignExchangeDelay = 1 * time.Minute
|
|
DefaultStorageFile = "currency.json"
|
|
DefaultForexProviderExchangeRatesAPI = "ExchangeRateHost"
|
|
)
|
|
|
|
var (
|
|
// ErrFiatDisplayCurrencyIsNotFiat defines an error for when the fiat
|
|
// display currency is not set as a fiat currency.
|
|
ErrFiatDisplayCurrencyIsNotFiat = errors.New("fiat display currency is not a fiat currency")
|
|
|
|
errUnexpectedRole = errors.New("unexpected currency role")
|
|
errFiatDisplayCurrencyUnset = errors.New("fiat display currency is unset")
|
|
errNoFilePathSet = errors.New("no file path set")
|
|
errInvalidCurrencyFileUpdateDuration = errors.New("invalid currency file update duration")
|
|
errInvalidForeignExchangeUpdateDuration = errors.New("invalid foreign exchange update duration")
|
|
errNoForeignExchangeProvidersEnabled = errors.New("no foreign exchange providers enabled")
|
|
errNotFiatCurrency = errors.New("not a fiat currency")
|
|
errInvalidAmount = errors.New("invalid amount")
|
|
)
|
|
|
|
// SetDefaults sets storage defaults for basic package functionality
|
|
func (s *Storage) SetDefaults() {
|
|
s.defaultBaseCurrency = USD
|
|
s.baseCurrency = s.defaultBaseCurrency
|
|
var fiatCurrencies []Code
|
|
for item := range symbols {
|
|
if item == USDT.Item {
|
|
continue
|
|
}
|
|
fiatCurrencies = append(fiatCurrencies, Code{Item: item, UpperCase: true})
|
|
}
|
|
|
|
err := s.SetDefaultFiatCurrencies(fiatCurrencies)
|
|
if err != nil {
|
|
log.Errorf(log.Currency, "Currency Storage: Setting default fiat currencies error: %s", err)
|
|
}
|
|
|
|
err = s.SetStableCoins(stables)
|
|
if err != nil {
|
|
log.Errorf(log.Currency, "Currency Storage: Setting default stable currencies error: %s", err)
|
|
}
|
|
|
|
err = s.SetDefaultCryptocurrencies(Currencies{BTC, LTC, ETH, DOGE, DASH, XRP, XMR, USDT, UST})
|
|
if err != nil {
|
|
log.Errorf(log.Currency, "Currency Storage: Setting default cryptocurrencies error: %s", err)
|
|
}
|
|
s.SetupConversionRates()
|
|
s.fiatExchangeMarkets = forexprovider.NewDefaultFXProvider()
|
|
}
|
|
|
|
// RunUpdater runs the foreign exchange updater service. This will set up a JSON
|
|
// dump file and keep foreign exchange rates updated as fast as possible without
|
|
// triggering rate limiters, it will also run a full cryptocurrency check
|
|
// through coin market cap and expose analytics for exchange services
|
|
func (s *Storage) RunUpdater(overrides BotOverrides, settings *Config, filePath string) error {
|
|
if settings.FiatDisplayCurrency.IsEmpty() {
|
|
return errFiatDisplayCurrencyUnset
|
|
}
|
|
|
|
if !settings.FiatDisplayCurrency.IsFiatCurrency() {
|
|
return fmt.Errorf("%s: %w", settings.FiatDisplayCurrency, ErrFiatDisplayCurrencyIsNotFiat)
|
|
}
|
|
|
|
if filePath == "" {
|
|
return errNoFilePathSet
|
|
}
|
|
|
|
if settings.CurrencyFileUpdateDuration <= 0 {
|
|
return errInvalidCurrencyFileUpdateDuration
|
|
}
|
|
|
|
if settings.ForeignExchangeUpdateDuration <= 0 {
|
|
return errInvalidForeignExchangeUpdateDuration
|
|
}
|
|
|
|
s.mtx.Lock()
|
|
s.shutdown = make(chan struct{})
|
|
s.baseCurrency = settings.FiatDisplayCurrency
|
|
s.path = filepath.Join(filePath, DefaultStorageFile)
|
|
s.currencyFileUpdateDelay = settings.CurrencyFileUpdateDuration
|
|
s.foreignExchangeUpdateDelay = settings.ForeignExchangeUpdateDuration
|
|
|
|
log.Debugf(log.Currency, "Fiat display currency: %s.\n", s.baseCurrency)
|
|
var err error
|
|
if overrides.Coinmarketcap {
|
|
if settings.CryptocurrencyProvider.APIKey != "" &&
|
|
settings.CryptocurrencyProvider.APIKey != "Key" {
|
|
log.Debugln(log.Currency, "Setting up currency analysis system with Coinmarketcap...")
|
|
s.currencyAnalysis, err = coinmarketcap.NewFromSettings(coinmarketcap.Settings(settings.CryptocurrencyProvider))
|
|
if err != nil {
|
|
log.Errorf(log.Currency, "Unable to setup CoinMarketCap analysis. Error: %s", err)
|
|
}
|
|
} else {
|
|
log.Warnf(log.Currency, "%s API key not set, disabling. Please set this in your config.json file\n",
|
|
settings.CryptocurrencyProvider.Name)
|
|
}
|
|
}
|
|
|
|
var fxSettings []base.Settings
|
|
var primaryProvider bool
|
|
for i := range settings.ForexProviders {
|
|
enabled := (settings.ForexProviders[i].Name == "CurrencyConverter" && overrides.CurrencyConverter) ||
|
|
(settings.ForexProviders[i].Name == "CurrencyLayer" && overrides.CurrencyLayer) ||
|
|
(settings.ForexProviders[i].Name == "Fixer" && overrides.Fixer) ||
|
|
(settings.ForexProviders[i].Name == "OpenExchangeRates" && overrides.OpenExchangeRates) ||
|
|
(settings.ForexProviders[i].Name == "ExchangeRates" && overrides.ExchangeRates) ||
|
|
(settings.ForexProviders[i].Name == "ExchangeRateHost" && overrides.ExchangeRateHost)
|
|
|
|
if !enabled {
|
|
continue
|
|
}
|
|
|
|
if settings.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI {
|
|
if settings.ForexProviders[i].APIKey == "" || settings.ForexProviders[i].APIKey == "Key" {
|
|
log.Warnf(log.Currency, "%s forex provider API key not set, disabling. Please set this in your config.json file\n",
|
|
settings.ForexProviders[i].Name)
|
|
settings.ForexProviders[i].Enabled = false
|
|
settings.ForexProviders[i].PrimaryProvider = false
|
|
continue
|
|
}
|
|
|
|
if settings.ForexProviders[i].APIKeyLvl == -1 && settings.ForexProviders[i].Name != "ExchangeRates" {
|
|
log.Warnf(log.Currency, "%s APIKey level not set, functionality is limited. Please review this in your config.json file\n",
|
|
settings.ForexProviders[i].Name)
|
|
}
|
|
}
|
|
|
|
if settings.ForexProviders[i].PrimaryProvider {
|
|
if primaryProvider {
|
|
log.Warnf(log.Currency, "%s disabling primary provider, multiple primarys found. Please review providers in your config.json file\n",
|
|
settings.ForexProviders[i].Name)
|
|
settings.ForexProviders[i].PrimaryProvider = false
|
|
} else {
|
|
primaryProvider = true
|
|
}
|
|
}
|
|
fxSettings = append(fxSettings, base.Settings(settings.ForexProviders[i]))
|
|
}
|
|
|
|
if len(fxSettings) == 0 {
|
|
log.Warnln(log.Currency, "No foreign exchange providers enabled, setting default provider...")
|
|
for x := range settings.ForexProviders {
|
|
if settings.ForexProviders[x].Name != DefaultForexProviderExchangeRatesAPI {
|
|
continue
|
|
}
|
|
settings.ForexProviders[x].Enabled = true
|
|
settings.ForexProviders[x].PrimaryProvider = true
|
|
primaryProvider = true
|
|
log.Warnf(log.Currency, "No valid foreign exchange providers configured. Defaulting to %s.", DefaultForexProviderExchangeRatesAPI)
|
|
fxSettings = append(fxSettings, base.Settings(settings.ForexProviders[x]))
|
|
}
|
|
}
|
|
|
|
if len(fxSettings) == 0 {
|
|
s.mtx.Unlock()
|
|
return errNoForeignExchangeProvidersEnabled
|
|
}
|
|
|
|
if !primaryProvider {
|
|
for x := range settings.ForexProviders {
|
|
if settings.ForexProviders[x].Name == fxSettings[0].Name {
|
|
settings.ForexProviders[x].PrimaryProvider = true
|
|
fxSettings[0].PrimaryProvider = true
|
|
log.Warnf(log.Currency, "No primary foreign exchange provider set. Defaulting to %s.", fxSettings[0].Name)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
s.fiatExchangeMarkets, err = forexprovider.StartFXService(fxSettings)
|
|
if err != nil {
|
|
s.mtx.Unlock()
|
|
return err
|
|
}
|
|
|
|
log.Debugf(log.Currency, "Using primary foreign exchange provider %s\n",
|
|
s.fiatExchangeMarkets.Primary.Provider.GetName())
|
|
|
|
for i := range s.fiatExchangeMarkets.Support {
|
|
log.Debugf(log.Currency, "Supporting foreign exchange provider %s\n",
|
|
s.fiatExchangeMarkets.Support[i].Provider.GetName())
|
|
}
|
|
|
|
// Mutex present in this go routine to lock down retrieving rate data
|
|
// until this system initially updates
|
|
s.wg.Add(1)
|
|
go s.ForeignExchangeUpdater()
|
|
return nil
|
|
}
|
|
|
|
// SetupConversionRates sets default conversion rate values
|
|
func (s *Storage) SetupConversionRates() {
|
|
s.fxRates = ConversionRates{
|
|
m: make(map[*Item]map[*Item]*float64),
|
|
}
|
|
}
|
|
|
|
// SetDefaultFiatCurrencies assigns the default fiat currency list and adds it
|
|
// to the running list
|
|
func (s *Storage) SetDefaultFiatCurrencies(c Currencies) error {
|
|
for i := range c {
|
|
err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Fiat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
s.defaultFiatCurrencies = append(s.defaultFiatCurrencies, c...)
|
|
s.fiatCurrencies = append(s.fiatCurrencies, c...)
|
|
return nil
|
|
}
|
|
|
|
// SetStableCoins assigns the stable currency list and adds it to the running
|
|
// list
|
|
func (s *Storage) SetStableCoins(c Currencies) error {
|
|
for i := range c {
|
|
err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Stable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
s.stableCurrencies = append(s.stableCurrencies, c...)
|
|
return nil
|
|
}
|
|
|
|
// SetDefaultCryptocurrencies assigns the default cryptocurrency list and adds
|
|
// it to the running list
|
|
func (s *Storage) SetDefaultCryptocurrencies(c Currencies) error {
|
|
for i := range c {
|
|
err := s.currencyCodes.UpdateCurrency("",
|
|
c[i].String(),
|
|
"",
|
|
0,
|
|
Cryptocurrency)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
s.defaultCryptoCurrencies = append(s.defaultCryptoCurrencies, c...)
|
|
s.cryptocurrencies = append(s.cryptocurrencies, c...)
|
|
return nil
|
|
}
|
|
|
|
// SetupForexProviders sets up a new instance of the forex providers
|
|
func (s *Storage) SetupForexProviders(setting ...base.Settings) error {
|
|
addr, err := forexprovider.StartFXService(setting)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.fiatExchangeMarkets = addr
|
|
return nil
|
|
}
|
|
|
|
// ForeignExchangeUpdater is a routine that seeds foreign exchange rate and keeps
|
|
// updated as fast as possible
|
|
func (s *Storage) ForeignExchangeUpdater() {
|
|
defer s.wg.Done()
|
|
log.Debugln(log.Currency, "Foreign exchange updater started, seeding FX rate list...")
|
|
|
|
err := s.SeedCurrencyAnalysisData()
|
|
if err != nil {
|
|
log.Errorln(log.Currency, err)
|
|
}
|
|
|
|
err = s.SeedForeignExchangeRates()
|
|
if err != nil {
|
|
log.Errorln(log.Currency, err)
|
|
}
|
|
|
|
// Unlock main rate retrieval mutex so all routines waiting can get access
|
|
// to data
|
|
s.mtx.Unlock()
|
|
|
|
// Set tickers to client defined rates or defaults
|
|
SeedForeignExchangeTick := time.NewTicker(s.foreignExchangeUpdateDelay)
|
|
SeedCurrencyAnalysisTick := time.NewTicker(s.currencyFileUpdateDelay)
|
|
defer SeedForeignExchangeTick.Stop()
|
|
defer SeedCurrencyAnalysisTick.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-s.shutdown:
|
|
return
|
|
case <-SeedForeignExchangeTick.C:
|
|
go func() {
|
|
err := s.SeedForeignExchangeRates()
|
|
if err != nil {
|
|
log.Errorln(log.Currency, err)
|
|
}
|
|
}()
|
|
case <-SeedCurrencyAnalysisTick.C:
|
|
go func() {
|
|
err := s.SeedCurrencyAnalysisData()
|
|
if err != nil {
|
|
log.Errorln(log.Currency, err)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
}
|
|
|
|
// SeedCurrencyAnalysisData sets a new instance of a coinmarketcap data.
|
|
func (s *Storage) SeedCurrencyAnalysisData() error {
|
|
if s.currencyCodes.LastMainUpdate.IsZero() {
|
|
b, err := ioutil.ReadFile(s.path)
|
|
if err != nil {
|
|
return s.FetchCurrencyAnalysisData()
|
|
}
|
|
var f *File
|
|
err = json.Unmarshal(b, &f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = s.LoadFileCurrencyData(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Based on update delay update the file
|
|
if time.Now().After(s.currencyCodes.LastMainUpdate.Add(s.currencyFileUpdateDelay)) ||
|
|
s.currencyCodes.LastMainUpdate.IsZero() {
|
|
err := s.FetchCurrencyAnalysisData()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FetchCurrencyAnalysisData fetches a new fresh batch of currency data and
|
|
// loads it into memory
|
|
func (s *Storage) FetchCurrencyAnalysisData() error {
|
|
if s.currencyAnalysis == nil {
|
|
log.Warnln(log.Currency,
|
|
"Currency analysis system offline, please set api keys for coinmarketcap if you wish to use this feature.")
|
|
return errors.New("currency analysis system offline")
|
|
}
|
|
|
|
return s.UpdateCurrencies()
|
|
}
|
|
|
|
// WriteCurrencyDataToFile writes the full currency data to a designated file
|
|
func (s *Storage) WriteCurrencyDataToFile(path string, mainUpdate bool) error {
|
|
data, err := s.currencyCodes.GetFullCurrencyData()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mainUpdate {
|
|
t := time.Now()
|
|
data.LastMainUpdate = t.Unix()
|
|
s.currencyCodes.LastMainUpdate = t
|
|
}
|
|
|
|
var encoded []byte
|
|
encoded, err = json.MarshalIndent(data, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return file.Write(path, encoded)
|
|
}
|
|
|
|
func (s *Storage) checkFileCurrencyData(item *Item, role Role) error {
|
|
if item.Role == Unset {
|
|
item.Role = role
|
|
}
|
|
if item.Role != role {
|
|
return fmt.Errorf("%w %s expecting: %s", errUnexpectedRole, item.Role, role)
|
|
}
|
|
return s.currencyCodes.LoadItem(item)
|
|
}
|
|
|
|
// LoadFileCurrencyData loads currencies into the currency codes
|
|
func (s *Storage) LoadFileCurrencyData(f *File) error {
|
|
for i := range f.Contracts {
|
|
err := s.checkFileCurrencyData(f.Contracts[i], Contract)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := range f.Cryptocurrency {
|
|
err := s.checkFileCurrencyData(f.Cryptocurrency[i], Cryptocurrency)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := range f.Token {
|
|
err := s.checkFileCurrencyData(f.Token[i], Token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := range f.FiatCurrency {
|
|
err := s.checkFileCurrencyData(f.FiatCurrency[i], Fiat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := range f.UnsetCurrency {
|
|
err := s.checkFileCurrencyData(f.UnsetCurrency[i], Unset)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := range f.Stable {
|
|
err := s.checkFileCurrencyData(f.Stable[i], Stable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
switch t := f.LastMainUpdate.(type) {
|
|
case string:
|
|
parseT, err := time.Parse(time.RFC3339Nano, t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.currencyCodes.LastMainUpdate = parseT
|
|
case float64:
|
|
s.currencyCodes.LastMainUpdate = time.Unix(int64(t), 0)
|
|
default:
|
|
return errors.New("unhandled type conversion for LastMainUpdate time")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateCurrencies updates currency role and information using coin market cap
|
|
func (s *Storage) UpdateCurrencies() error {
|
|
m, err := s.currencyAnalysis.GetCryptocurrencyIDMap()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for x := range m {
|
|
if m[x].IsActive != 1 {
|
|
continue
|
|
}
|
|
|
|
if m[x].Platform.Symbol != "" {
|
|
err = s.currencyCodes.UpdateCurrency(m[x].Name,
|
|
m[x].Symbol,
|
|
m[x].Platform.Symbol,
|
|
m[x].ID,
|
|
Token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
err = s.currencyCodes.UpdateCurrency(m[x].Name,
|
|
m[x].Symbol,
|
|
"",
|
|
m[x].ID,
|
|
Cryptocurrency)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SeedForeignExchangeRatesByCurrencies seeds the foreign exchange rates by
|
|
// currencies supplied
|
|
func (s *Storage) SeedForeignExchangeRatesByCurrencies(c Currencies) error {
|
|
s.fxRates.mtx.Lock()
|
|
defer s.fxRates.mtx.Unlock()
|
|
rates, err := s.fiatExchangeMarkets.GetCurrencyData(s.baseCurrency.String(),
|
|
c.Strings())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.updateExchangeRates(rates)
|
|
}
|
|
|
|
// SeedForeignExchangeRate returns a singular exchange rate
|
|
func (s *Storage) SeedForeignExchangeRate(from, to Code) (map[string]float64, error) {
|
|
return s.fiatExchangeMarkets.GetCurrencyData(from.String(),
|
|
[]string{to.String()})
|
|
}
|
|
|
|
// GetDefaultForeignExchangeRates returns foreign exchange rates based off
|
|
// default fiat currencies.
|
|
func (s *Storage) GetDefaultForeignExchangeRates() (Conversions, error) {
|
|
if !s.updaterRunning {
|
|
err := s.SeedDefaultForeignExchangeRates()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return s.fxRates.GetFullRates(), nil
|
|
}
|
|
|
|
// SeedDefaultForeignExchangeRates seeds the default foreign exchange rates
|
|
func (s *Storage) SeedDefaultForeignExchangeRates() error {
|
|
s.fxRates.mtx.Lock()
|
|
defer s.fxRates.mtx.Unlock()
|
|
rates, err := s.fiatExchangeMarkets.GetCurrencyData(
|
|
s.defaultBaseCurrency.String(),
|
|
s.defaultFiatCurrencies.Strings())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.updateExchangeRates(rates)
|
|
}
|
|
|
|
// GetExchangeRates returns storage seeded exchange rates
|
|
func (s *Storage) GetExchangeRates() (Conversions, error) {
|
|
if !s.updaterRunning {
|
|
err := s.SeedForeignExchangeRates()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return s.fxRates.GetFullRates(), nil
|
|
}
|
|
|
|
// SeedForeignExchangeRates seeds the foreign exchange rates from storage config
|
|
// currencies
|
|
func (s *Storage) SeedForeignExchangeRates() error {
|
|
s.fxRates.mtx.Lock()
|
|
defer s.fxRates.mtx.Unlock()
|
|
rates, err := s.fiatExchangeMarkets.GetCurrencyData(s.baseCurrency.String(),
|
|
s.fiatCurrencies.Strings())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.updateExchangeRates(rates)
|
|
}
|
|
|
|
// UpdateForeignExchangeRates sets exchange rates on the FX map
|
|
func (s *Storage) updateExchangeRates(m map[string]float64) error {
|
|
return s.fxRates.Update(m)
|
|
}
|
|
|
|
// GetTotalMarketCryptocurrencies returns the total seeded market
|
|
// cryptocurrencies
|
|
func (s *Storage) GetTotalMarketCryptocurrencies() (Currencies, error) {
|
|
if !s.currencyCodes.HasData() {
|
|
return nil, errors.New("market currency codes not populated")
|
|
}
|
|
return s.currencyCodes.GetCurrencies(), nil
|
|
}
|
|
|
|
// IsDefaultCurrency returns if a currency is a default currency
|
|
func (s *Storage) IsDefaultCurrency(c Code) bool {
|
|
for i := range s.defaultFiatCurrencies {
|
|
if s.defaultFiatCurrencies[i].Equal(c) ||
|
|
s.defaultFiatCurrencies[i].Equal(GetTranslation(c)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsDefaultCryptocurrency returns if a cryptocurrency is a default
|
|
// cryptocurrency
|
|
func (s *Storage) IsDefaultCryptocurrency(c Code) bool {
|
|
for i := range s.defaultCryptoCurrencies {
|
|
if s.defaultCryptoCurrencies[i].Equal(c) ||
|
|
s.defaultCryptoCurrencies[i].Equal(GetTranslation(c)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidateCode validates string against currency list and returns a currency
|
|
// code
|
|
func (s *Storage) ValidateCode(newCode string) Code {
|
|
return s.currencyCodes.Register(newCode, Unset)
|
|
}
|
|
|
|
// ValidateFiatCode validates a fiat currency string and returns a currency
|
|
// code
|
|
func (s *Storage) ValidateFiatCode(newCode string) Code {
|
|
c := s.currencyCodes.Register(newCode, Fiat)
|
|
if !s.fiatCurrencies.Contains(c) {
|
|
s.fiatCurrencies = append(s.fiatCurrencies, c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// UpdateBaseCurrency changes base currency
|
|
func (s *Storage) UpdateBaseCurrency(c Code) error {
|
|
if c.IsFiatCurrency() {
|
|
s.baseCurrency = c
|
|
return nil
|
|
}
|
|
return fmt.Errorf("currency %s not fiat failed to set currency", c)
|
|
}
|
|
|
|
// GetCryptocurrencies returns the cryptocurrency list
|
|
func (s *Storage) GetCryptocurrencies() Currencies {
|
|
return s.cryptocurrencies
|
|
}
|
|
|
|
// GetDefaultCryptocurrencies returns a list of default cryptocurrencies
|
|
func (s *Storage) GetDefaultCryptocurrencies() Currencies {
|
|
return s.defaultCryptoCurrencies
|
|
}
|
|
|
|
// GetFiatCurrencies returns the fiat currencies list
|
|
func (s *Storage) GetFiatCurrencies() Currencies {
|
|
return s.fiatCurrencies
|
|
}
|
|
|
|
// GetDefaultFiatCurrencies returns the default fiat currencies list
|
|
func (s *Storage) GetDefaultFiatCurrencies() Currencies {
|
|
return s.defaultFiatCurrencies
|
|
}
|
|
|
|
// GetDefaultBaseCurrency returns the default base currency
|
|
func (s *Storage) GetDefaultBaseCurrency() Code {
|
|
return s.defaultBaseCurrency
|
|
}
|
|
|
|
// GetBaseCurrency returns the current storage base currency
|
|
func (s *Storage) GetBaseCurrency() Code {
|
|
return s.baseCurrency
|
|
}
|
|
|
|
// UpdateEnabledCryptoCurrencies appends new cryptocurrencies to the enabled
|
|
// currency list
|
|
func (s *Storage) UpdateEnabledCryptoCurrencies(c Currencies) {
|
|
for i := range c {
|
|
if !s.cryptocurrencies.Contains(c[i]) {
|
|
s.cryptocurrencies = append(s.cryptocurrencies, c[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// UpdateEnabledFiatCurrencies appends new fiat currencies to the enabled
|
|
// currency list
|
|
func (s *Storage) UpdateEnabledFiatCurrencies(c Currencies) {
|
|
for i := range c {
|
|
if !s.fiatCurrencies.Contains(c[i]) &&
|
|
!s.cryptocurrencies.Contains(c[i]) {
|
|
s.fiatCurrencies = append(s.fiatCurrencies, c[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// ConvertCurrency for example converts $1 USD to the equivalent Japanese Yen
|
|
// or vice versa.
|
|
func (s *Storage) ConvertCurrency(amount float64, from, to Code) (float64, error) {
|
|
if amount <= 0 {
|
|
return 0, fmt.Errorf("%f %w", amount, errInvalidAmount)
|
|
}
|
|
if !from.IsFiatCurrency() {
|
|
return 0, fmt.Errorf("%s %w", from, errNotFiatCurrency)
|
|
}
|
|
if !to.IsFiatCurrency() {
|
|
return 0, fmt.Errorf("%s %w", to, errNotFiatCurrency)
|
|
}
|
|
|
|
if from.Equal(to) { // No need to lock down storage for this rate.
|
|
return amount, nil
|
|
}
|
|
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
if !s.fxRates.HasData() {
|
|
err := s.SeedDefaultForeignExchangeRates()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
r, err := s.fxRates.GetRate(from, to)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return r * amount, nil
|
|
}
|
|
|
|
// GetStorageRate returns the rate of the conversion value
|
|
func (s *Storage) GetStorageRate(from, to Code) (float64, error) {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
|
|
if !s.fxRates.HasData() {
|
|
err := s.SeedDefaultForeignExchangeRates()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
return s.fxRates.GetRate(from, to)
|
|
}
|
|
|
|
// NewConversion returns a new conversion object that has a pointer to a related
|
|
// rate with its inversion.
|
|
func (s *Storage) NewConversion(from, to Code) (Conversion, error) {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
|
|
if !s.fxRates.HasData() {
|
|
err := storage.SeedDefaultForeignExchangeRates()
|
|
if err != nil {
|
|
return Conversion{}, err
|
|
}
|
|
}
|
|
return s.fxRates.Register(from, to)
|
|
}
|
|
|
|
// IsVerbose returns if the storage is in verbose mode
|
|
func (s *Storage) IsVerbose() bool {
|
|
return s.Verbose
|
|
}
|
|
|
|
// Shutdown shuts down the currency storage system and saves to currency.json
|
|
func (s *Storage) Shutdown() error {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
if s.shutdown == nil {
|
|
return nil
|
|
}
|
|
close(s.shutdown)
|
|
s.wg.Wait()
|
|
s.shutdown = nil
|
|
return s.WriteCurrencyDataToFile(s.path, true)
|
|
}
|