diff --git a/README.md b/README.md index 8cebce64..42c441cf 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ However, we welcome pull requests for any exchange which does not match this cri + Connection monitor package. + gRPC service and JSON RPC proxy. See [gRPC service](/gctrpc/README.md). + gRPC client. See [gctcli](/cmd/gctcli/README.md). -+ Forex currency converter packages (CurrencyConverterAPI, CurrencyLayer, Fixer.io, OpenExchangeRates). ++ Forex currency converter packages (CurrencyConverterAPI, CurrencyLayer, Exchange Rates, Fixer.io, OpenExchangeRates, Exchange Rate Host). + Packages for handling currency pairs, tickers and orderbooks. + Portfolio management tool; fetches balances from supported exchanges and allows for custom address tracking. + Basic event trigger system. @@ -143,13 +143,13 @@ Binaries will be published once the codebase reaches a stable condition. |User|Contribution Amount| |--|--| -| [thrasher-](https://github.com/thrasher-) | 662 | -| [shazbert](https://github.com/shazbert) | 231 | +| [thrasher-](https://github.com/thrasher-) | 664 | +| [shazbert](https://github.com/shazbert) | 232 | | [gloriousCode](https://github.com/gloriousCode) | 194 | | [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 88 | -| [dependabot[bot]](https://github.com/apps/dependabot) | 50 | +| [dependabot[bot]](https://github.com/apps/dependabot) | 57 | | [xtda](https://github.com/xtda) | 47 | -| [lrascao](https://github.com/lrascao) | 21 | +| [lrascao](https://github.com/lrascao) | 27 | | [Rots](https://github.com/Rots) | 15 | | [vazha](https://github.com/vazha) | 15 | | [ydm](https://github.com/ydm) | 15 | diff --git a/backtester/backtest/backtest.go b/backtester/backtest/backtest.go index a099194e..eb3ed167 100644 --- a/backtester/backtest/backtest.go +++ b/backtester/backtest/backtest.go @@ -525,19 +525,19 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange func (bt *BackTest) loadExchangePairAssetBase(exch, base, quote, ass string) (gctexchange.IBotExchange, currency.Pair, asset.Item, error) { e, err := bt.exchangeManager.GetExchangeByName(exch) if err != nil { - return nil, currency.Pair{}, "", err + return nil, currency.EMPTYPAIR, "", err } var cp, fPair currency.Pair cp, err = currency.NewPairFromStrings(base, quote) if err != nil { - return nil, currency.Pair{}, "", err + return nil, currency.EMPTYPAIR, "", err } var a asset.Item a, err = asset.New(ass) if err != nil { - return nil, currency.Pair{}, "", err + return nil, currency.EMPTYPAIR, "", err } exchangeBase := e.GetBase() @@ -547,7 +547,7 @@ func (bt *BackTest) loadExchangePairAssetBase(exch, base, quote, ass string) (gc fPair, err = exchangeBase.FormatExchangeCurrency(cp, a) if err != nil { - return nil, currency.Pair{}, "", err + return nil, currency.EMPTYPAIR, "", err } return e, fPair, a, nil } diff --git a/backtester/eventhandlers/exchange/exchange.go b/backtester/eventhandlers/exchange/exchange.go index 36b18bd4..877c7c32 100644 --- a/backtester/eventhandlers/exchange/exchange.go +++ b/backtester/eventhandlers/exchange/exchange.go @@ -320,7 +320,7 @@ func (e *Exchange) SetExchangeAssetCurrencySettings(exch string, a asset.Item, c } for i := range e.CurrencySettings { - if e.CurrencySettings[i].Pair == cp && + if e.CurrencySettings[i].Pair.Equal(cp) && e.CurrencySettings[i].Asset == a && exch == e.CurrencySettings[i].Exchange { e.CurrencySettings[i] = *c diff --git a/backtester/eventhandlers/exchange/exchange_test.go b/backtester/eventhandlers/exchange/exchange_test.go index 5c1bc6f0..9655bc00 100644 --- a/backtester/eventhandlers/exchange/exchange_test.go +++ b/backtester/eventhandlers/exchange/exchange_test.go @@ -44,7 +44,7 @@ func TestReset(t *testing.T) { func TestSetCurrency(t *testing.T) { t.Parallel() e := Exchange{} - e.SetExchangeAssetCurrencySettings("", "", currency.Pair{}, &Settings{}) + e.SetExchangeAssetCurrencySettings("", "", currency.EMPTYPAIR, &Settings{}) if len(e.CurrencySettings) != 0 { t.Error("expected 0") } @@ -264,7 +264,7 @@ func TestExecuteOrder(t *testing.T) { d := &kline.DataFromKline{ Item: gctkline.Item{ Exchange: "", - Pair: currency.Pair{}, + Pair: currency.EMPTYPAIR, Asset: "", Interval: 0, Candles: []gctkline.Candle{ @@ -385,7 +385,7 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) { d := &kline.DataFromKline{ Item: gctkline.Item{ Exchange: "", - Pair: currency.Pair{}, + Pair: currency.EMPTYPAIR, Asset: "", Interval: 0, Candles: []gctkline.Candle{ diff --git a/backtester/eventhandlers/portfolio/portfolio_test.go b/backtester/eventhandlers/portfolio/portfolio_test.go index e7132c86..fd0765c4 100644 --- a/backtester/eventhandlers/portfolio/portfolio_test.go +++ b/backtester/eventhandlers/portfolio/portfolio_test.go @@ -303,7 +303,7 @@ func TestUpdate(t *testing.T) { func TestGetFee(t *testing.T) { t.Parallel() p := Portfolio{} - f := p.GetFee("", "", currency.Pair{}) + f := p.GetFee("", "", currency.EMPTYPAIR) if !f.IsZero() { t.Error("expected 0") } @@ -323,7 +323,7 @@ func TestGetFee(t *testing.T) { func TestGetComplianceManager(t *testing.T) { t.Parallel() p := Portfolio{} - _, err := p.GetComplianceManager("", "", currency.Pair{}) + _, err := p.GetComplianceManager("", "", currency.EMPTYPAIR) if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } diff --git a/backtester/eventhandlers/statistics/fundingstatistics.go b/backtester/eventhandlers/statistics/fundingstatistics.go index f936e36c..28db2106 100644 --- a/backtester/eventhandlers/statistics/fundingstatistics.go +++ b/backtester/eventhandlers/statistics/fundingstatistics.go @@ -35,11 +35,11 @@ func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[str } var relevantStats []relatedCurrencyPairStatistics for k, v := range exchangeAssetStats { - if k.Base == report.Items[i].Currency { + if k.Base.Equal(report.Items[i].Currency) { relevantStats = append(relevantStats, relatedCurrencyPairStatistics{isBaseCurrency: true, stat: v}) continue } - if k.Quote == report.Items[i].Currency { + if k.Quote.Equal(report.Items[i].Currency) { relevantStats = append(relevantStats, relatedCurrencyPairStatistics{stat: v}) } } diff --git a/backtester/funding/funding.go b/backtester/funding/funding.go index 09827411..5aa25d5a 100644 --- a/backtester/funding/funding.go +++ b/backtester/funding/funding.go @@ -112,7 +112,7 @@ func (f *FundManager) AddUSDTrackingData(k *kline.DataFromKline) error { } if strings.EqualFold(f.items[i].exchange, k.Item.Exchange) && f.items[i].asset == k.Item.Asset { - if f.items[i].currency == k.Item.Pair.Base { + if f.items[i].currency.Equal(k.Item.Pair.Base) { if f.items[i].usdTrackingCandles == nil && trackingcurrencies.CurrencyIsUSDTracked(k.Item.Pair.Quote) { f.items[i].usdTrackingCandles = k @@ -123,7 +123,7 @@ func (f *FundManager) AddUSDTrackingData(k *kline.DataFromKline) error { baseSet = true } if trackingcurrencies.CurrencyIsUSDTracked(f.items[i].currency) { - if f.items[i].pairedWith != nil && f.items[i].currency != basePairedWith { + if f.items[i].pairedWith != nil && !f.items[i].currency.Equal(basePairedWith) { continue } if f.items[i].usdTrackingCandles == nil { @@ -267,10 +267,10 @@ func (f *FundManager) Transfer(amount decimal.Decimal, sender, receiver *Item, i } } - if sender.currency != receiver.currency { + if !sender.currency.Equal(receiver.currency) { return errTransferMustBeSameCurrency } - if sender.currency == receiver.currency && + if sender.currency.Equal(receiver.currency) && sender.exchange == receiver.exchange && sender.asset == receiver.asset { return fmt.Errorf("%v %v %v %w", sender.exchange, sender.asset, sender.currency, errCannotTransferToSameFunds) @@ -336,7 +336,7 @@ func (f *FundManager) GetFundingForEvent(ev common.EventHandler) (*Pair, error) // GetFundingForEAC This will construct a funding based on the exchange, asset, currency code func (f *FundManager) GetFundingForEAC(exch string, a asset.Item, c currency.Code) (*Item, error) { for i := range f.items { - if f.items[i].BasicEqual(exch, a, c, currency.Code{}) { + if f.items[i].BasicEqual(exch, a, c, currency.EMPTYCODE) { return f.items[i], nil } } @@ -514,7 +514,7 @@ func (i *Item) Equal(item *Item) bool { if item == nil || i == nil { return false } - if i.currency == item.currency && + if i.currency.Equal(item.currency) && i.asset == item.asset && i.exchange == item.exchange { if i.pairedWith == nil && item.pairedWith == nil { @@ -523,7 +523,7 @@ func (i *Item) Equal(item *Item) bool { if i.pairedWith == nil || item.pairedWith == nil { return false } - if i.pairedWith.currency == item.pairedWith.currency && + if i.pairedWith.currency.Equal(item.pairedWith.currency) && i.pairedWith.asset == item.pairedWith.asset && i.pairedWith.exchange == item.pairedWith.exchange { return true @@ -537,19 +537,19 @@ func (i *Item) BasicEqual(exch string, a asset.Item, currency, pairedCurrency cu return i != nil && i.exchange == exch && i.asset == a && - i.currency == currency && + i.currency.Equal(currency) && (i.pairedWith == nil || - (i.pairedWith != nil && i.pairedWith.currency == pairedCurrency)) + (i.pairedWith != nil && i.pairedWith.currency.Equal(pairedCurrency))) } // MatchesCurrency checks that an item's currency is equal func (i *Item) MatchesCurrency(c currency.Code) bool { - return i != nil && i.currency == c + return i != nil && i.currency.Equal(c) } // MatchesItemCurrency checks that an item's currency is equal func (i *Item) MatchesItemCurrency(item *Item) bool { - return i != nil && item != nil && i.currency == item.currency + return i != nil && item != nil && i.currency.Equal(item.currency) } // MatchesExchange checks that an item's exchange is equal diff --git a/backtester/funding/funding_test.go b/backtester/funding/funding_test.go index 38089b65..2e2cf238 100644 --- a/backtester/funding/funding_test.go +++ b/backtester/funding/funding_test.go @@ -247,12 +247,12 @@ func TestAddPair(t *testing.T) { } if resp.Base.exchange != exch || resp.Base.asset != a || - resp.Base.currency != pair.Base { + !resp.Base.currency.Equal(pair.Base) { t.Error("woah nelly") } if resp.Quote.exchange != exch || resp.Quote.asset != a || - resp.Quote.currency != pair.Quote { + !resp.Quote.currency.Equal(pair.Quote) { t.Error("woah nelly") } if resp.Quote.pairedWith != resp.Base { @@ -841,7 +841,7 @@ func TestMatchesCurrency(t *testing.T) { if !i.MatchesCurrency(currency.BTC) { t.Error("expected true") } - if i.MatchesCurrency(currency.Code{}) { + if i.MatchesCurrency(currency.EMPTYCODE) { t.Error("expected false") } if i.MatchesCurrency(currency.NewCode("")) { @@ -855,7 +855,7 @@ func TestCreateSnapshot(t *testing.T) { f.items = append(f.items, &Item{ exchange: "", asset: "", - currency: currency.Code{}, + currency: currency.EMPTYCODE, initialFunds: decimal.Decimal{}, available: decimal.Decimal{}, reserved: decimal.Decimal{}, diff --git a/backtester/funding/trackingcurrencies/trackingcurrencies.go b/backtester/funding/trackingcurrencies/trackingcurrencies.go index 67460c4c..dca46171 100644 --- a/backtester/funding/trackingcurrencies/trackingcurrencies.go +++ b/backtester/funding/trackingcurrencies/trackingcurrencies.go @@ -121,13 +121,13 @@ func pairContainsUSD(pair currency.Pair) bool { // this will allow for data retrieval and total tracking on backtesting runs func findMatchingUSDPairs(pair currency.Pair, pairs *currency.PairStore) (basePair, quotePair currency.Pair, err error) { if pairs == nil { - return currency.Pair{}, currency.Pair{}, errNilPairs + return currency.EMPTYPAIR, currency.EMPTYPAIR, errNilPairs } if pairContainsUSD(pair) { - return currency.Pair{}, currency.Pair{}, ErrCurrencyContainsUSD + return currency.EMPTYPAIR, currency.EMPTYPAIR, ErrCurrencyContainsUSD } if !pairs.Available.Contains(pair, true) { - return currency.Pair{}, currency.Pair{}, fmt.Errorf("%v %w", pair, errCurrencyNotFoundInPairs) + return currency.EMPTYPAIR, currency.EMPTYPAIR, fmt.Errorf("%v %w", pair, errCurrencyNotFoundInPairs) } var baseFound, quoteFound bool diff --git a/backtester/funding/trackingcurrencies/trackingcurrencies_test.go b/backtester/funding/trackingcurrencies/trackingcurrencies_test.go index 7c3ee386..72447f39 100644 --- a/backtester/funding/trackingcurrencies/trackingcurrencies_test.go +++ b/backtester/funding/trackingcurrencies/trackingcurrencies_test.go @@ -82,8 +82,8 @@ func TestFindMatchingUSDPairs(t *testing.T) { description: "already has USD", initialPair: currency.NewPair(currency.BTC, currency.USDT), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.USDT)}}, - basePair: currency.Pair{}, - quotePair: currency.Pair{}, + basePair: currency.EMPTYPAIR, + quotePair: currency.EMPTYPAIR, expectedErr: ErrCurrencyContainsUSD, }, { @@ -99,14 +99,14 @@ func TestFindMatchingUSDPairs(t *testing.T) { initialPair: currency.NewPair(currency.BTC, currency.LTC), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.LTC), currency.NewPair(currency.BTC, currency.DAI)}}, basePair: currency.NewPair(currency.BTC, currency.DAI), - quotePair: currency.Pair{}, + quotePair: currency.EMPTYPAIR, expectedErr: errNoMatchingQuoteUSDFound, }, { description: "base currency has no matching USD pair", initialPair: currency.NewPair(currency.BTC, currency.LTC), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.LTC), currency.NewPair(currency.LTC, currency.USDT)}}, - basePair: currency.Pair{}, + basePair: currency.EMPTYPAIR, quotePair: currency.NewPair(currency.LTC, currency.USDT), expectedErr: errNoMatchingBaseUSDFound, }, @@ -114,16 +114,16 @@ func TestFindMatchingUSDPairs(t *testing.T) { description: "both base and quote don't have USD pairs", initialPair: currency.NewPair(currency.BTC, currency.LTC), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.LTC)}}, - basePair: currency.Pair{}, - quotePair: currency.Pair{}, + basePair: currency.EMPTYPAIR, + quotePair: currency.EMPTYPAIR, expectedErr: errNoMatchingPairUSDFound, }, { description: "currency doesnt exist in available pairs", initialPair: currency.NewPair(currency.BTC, currency.LTC), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.DOGE)}}, - basePair: currency.Pair{}, - quotePair: currency.Pair{}, + basePair: currency.EMPTYPAIR, + quotePair: currency.EMPTYPAIR, expectedErr: errCurrencyNotFoundInPairs, }, } diff --git a/cmd/documentation/currency_templates/fx.tmpl b/cmd/documentation/currency_templates/fx.tmpl index 964f9786..14a0fba9 100644 --- a/cmd/documentation/currency_templates/fx.tmpl +++ b/cmd/documentation/currency_templates/fx.tmpl @@ -4,6 +4,7 @@ + Currency Converter API support + Currency Layer support ++ Exchange Rates support + Fixer.io support + Open Exchange Rates support + ExchangeRate.host support diff --git a/cmd/documentation/root_templates/root_readme.tmpl b/cmd/documentation/root_templates/root_readme.tmpl index 6cce76df..8a1b47e2 100644 --- a/cmd/documentation/root_templates/root_readme.tmpl +++ b/cmd/documentation/root_templates/root_readme.tmpl @@ -69,7 +69,7 @@ However, we welcome pull requests for any exchange which does not match this cri + Connection monitor package. + gRPC service and JSON RPC proxy. See [gRPC service](/gctrpc/README.md). + gRPC client. See [gctcli](/cmd/gctcli/README.md). -+ Forex currency converter packages (CurrencyConverterAPI, CurrencyLayer, Fixer.io, OpenExchangeRates). ++ Forex currency converter packages (CurrencyConverterAPI, CurrencyLayer, Exchange Rates, Fixer.io, OpenExchangeRates, Exchange Rate Host). + Packages for handling currency pairs, tickers and orderbooks. + Portfolio management tool; fetches balances from supported exchanges and allows for custom address tracking. + Basic event trigger system. diff --git a/cmd/exchange_wrapper_issues/exchange_wrapper_issues_test.go b/cmd/exchange_wrapper_issues/exchange_wrapper_issues_test.go index 5fd81bb0..fbbbd1a1 100644 --- a/cmd/exchange_wrapper_issues/exchange_wrapper_issues_test.go +++ b/cmd/exchange_wrapper_issues/exchange_wrapper_issues_test.go @@ -7,7 +7,7 @@ import ( ) func TestDisruptFormatting(t *testing.T) { - _, err := disruptFormatting(currency.Pair{}) + _, err := disruptFormatting(currency.EMPTYPAIR) if err == nil { t.Fatal("error cannot be nil") } diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index 122b346f..dbbcd9a4 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -953,10 +953,10 @@ func outputToConsole(exchangeResponses []ExchangeResponses) { // ensure format currency pair is used throughout the code base. func disruptFormatting(p currency.Pair) (currency.Pair, error) { if p.Base.IsEmpty() { - return currency.Pair{}, errors.New("cannot disrupt formatting as base is not populated") + return currency.EMPTYPAIR, errors.New("cannot disrupt formatting as base is not populated") } if p.Quote.IsEmpty() { - return currency.Pair{}, errors.New("cannot disrupt formatting as quote is not populated") + return currency.EMPTYPAIR, errors.New("cannot disrupt formatting as quote is not populated") } return currency.Pair{ diff --git a/cmd/portfolio/portfolio.go b/cmd/portfolio/portfolio.go index f6536a38..5b187530 100644 --- a/cmd/portfolio/portfolio.go +++ b/cmd/portfolio/portfolio.go @@ -9,13 +9,12 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/bitfinex" "github.com/thrasher-corp/gocryptotrader/portfolio" ) var ( - priceMap map[currency.Code]float64 + priceMap map[*currency.Item]float64 displayCurrency currency.Code ) @@ -23,10 +22,8 @@ func printSummary(msg string, amount float64) { log.Println() log.Println(fmt.Sprintf("%s in USD: $%.2f", msg, amount)) - if displayCurrency != currency.USD { - conv, err := currency.ConvertCurrency(amount, - currency.USD, - displayCurrency) + if !displayCurrency.Equal(currency.USD) { + conv, err := currency.ConvertFiat(amount, currency.USD, displayCurrency) if err != nil { log.Println(err) } else { @@ -50,7 +47,7 @@ func printSummary(msg string, amount float64) { func getOnlineOfflinePortfolio(coins []portfolio.Coin, online bool) { var totals float64 for _, x := range coins { - value := priceMap[x.Coin] * x.Balance + value := priceMap[x.Coin.Item] * x.Balance totals += value log.Printf("\t%v %v Subtotal: $%.2f Coin percentage: %.2f%%\n", x.Coin, x.Balance, value, x.Percentage) @@ -90,13 +87,7 @@ func main() { Subtotal float64 } - err = cfg.RetrieveConfigCurrencyPairs(true, asset.Spot) - if err != nil { - log.Printf("Failed to retrieve config currency pairs %v\n", err) - os.Exit(1) - } - - portfolioMap := make(map[currency.Code]PortfolioTemp) + portfolioMap := make(map[*currency.Item]PortfolioTemp) total := float64(0) log.Println("Fetching currency data..") @@ -114,21 +105,21 @@ func main() { log.Println("Fetched currency data.") log.Println("Fetching ticker data and calculating totals..") - priceMap = make(map[currency.Code]float64) - priceMap[currency.USD] = 1 + priceMap = make(map[*currency.Item]float64) + priceMap[currency.USD.Item] = 1 for _, y := range result.Totals { pf := PortfolioTemp{} pf.Balance = y.Balance pf.Subtotal = 0 - if y.Coin.IsDefaultFiatCurrency() { - if y.Coin != currency.USD { - conv, err := currency.ConvertCurrency(y.Balance, y.Coin, currency.USD) + if y.Coin.IsFiatCurrency() { + if !y.Coin.Equal(currency.USD) { + conv, err := currency.ConvertFiat(y.Balance, y.Coin, currency.USD) if err != nil { log.Println(err) } else { - priceMap[y.Coin] = conv / y.Balance + priceMap[y.Coin.Item] = conv / y.Balance pf.Subtotal = conv } } else { @@ -143,18 +134,25 @@ func main() { if errf != nil { log.Println(errf) } else { - priceMap[y.Coin] = ticker.Last + priceMap[y.Coin.Item] = ticker.Last pf.Subtotal = ticker.Last * y.Balance } } - portfolioMap[y.Coin] = pf + portfolioMap[y.Coin.Item] = pf total += pf.Subtotal } log.Println("Done.") log.Println() log.Println("PORTFOLIO TOTALS:") for x, y := range portfolioMap { - log.Printf("\t%s Amount: %f Subtotal: $%.2f USD (1 %s = $%.2f USD). Percentage of portfolio %.3f%%", x, y.Balance, y.Subtotal, x, y.Subtotal/y.Balance, y.Subtotal/total*100/1) + code := currency.Code{Item: x} + log.Printf("\t%s Amount: %f Subtotal: $%.2f USD (1 %s = $%.2f USD). Percentage of portfolio %.3f%%", + code, + y.Balance, + y.Subtotal, + code, + y.Subtotal/y.Balance, + y.Subtotal/total*100/1) } printSummary("\tTotal balance", total) @@ -170,7 +168,7 @@ func main() { log.Printf("\t%s:", x) totals = 0 for z := range y { - value := priceMap[x] * y[z].Balance + value := priceMap[x.Item] * y[z].Balance totals += value log.Printf("\t %s Amount: %f Subtotal: $%.2f Coin percentage: %.2f%%\n", y[z].Address, y[z].Balance, value, y[z].Percentage) @@ -183,7 +181,7 @@ func main() { log.Printf("\t%s:", x) totals = 0 for z, w := range y { - value := priceMap[z] * w.Balance + value := priceMap[z.Item] * w.Balance totals += value log.Printf("\t %s Amount: %f Subtotal $%.2f Coin percentage: %.2f%%", z, w.Balance, value, w.Percentage) diff --git a/cmd/portfolio/portfolio_test.go b/cmd/portfolio/portfolio_test.go deleted file mode 100644 index e11a9212..00000000 --- a/cmd/portfolio/portfolio_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "testing" - -func TestMain(t *testing.T) { - -} diff --git a/config/config.go b/config/config.go index 6ce2f10c..1c3035dd 100644 --- a/config/config.go +++ b/config/config.go @@ -33,7 +33,7 @@ import ( var errExchangeConfigIsNil = errors.New("exchange config is nil") // GetCurrencyConfig returns currency configurations -func (c *Config) GetCurrencyConfig() CurrencyConfig { +func (c *Config) GetCurrencyConfig() currency.Config { return c.Currency } @@ -191,7 +191,7 @@ func (c *Config) UpdateCommunicationsConfig(config *base.CommunicationsConfig) { } // GetCryptocurrencyProviderConfig returns the communications configuration -func (c *Config) GetCryptocurrencyProviderConfig() CryptocurrencyProvider { +func (c *Config) GetCryptocurrencyProviderConfig() currency.Provider { m.Lock() provider := c.Currency.CryptocurrencyProvider m.Unlock() @@ -199,7 +199,7 @@ func (c *Config) GetCryptocurrencyProviderConfig() CryptocurrencyProvider { } // UpdateCryptocurrencyProviderConfig returns the communications configuration -func (c *Config) UpdateCryptocurrencyProviderConfig(config CryptocurrencyProvider) { +func (c *Config) UpdateCryptocurrencyProviderConfig(config currency.Provider) { m.Lock() c.Currency.CryptocurrencyProvider = config m.Unlock() @@ -732,7 +732,7 @@ func (c *Config) CountEnabledExchanges() int { } // GetCurrencyPairDisplayConfig retrieves the currency pair display preference -func (c *Config) GetCurrencyPairDisplayConfig() *CurrencyPairFormatConfig { +func (c *Config) GetCurrencyPairDisplayConfig() *currency.PairFormat { return c.Currency.CurrencyPairFormat } @@ -756,38 +756,6 @@ func (c *Config) GetExchangeConfig(name string) (*Exchange, error) { return nil, fmt.Errorf("%s %w", name, ErrExchangeNotFound) } -// GetForexProvider returns a forex provider configuration by its name -func (c *Config) GetForexProvider(name string) (currency.FXSettings, error) { - m.Lock() - defer m.Unlock() - for i := range c.Currency.ForexProviders { - if strings.EqualFold(c.Currency.ForexProviders[i].Name, name) { - return c.Currency.ForexProviders[i], nil - } - } - return currency.FXSettings{}, errors.New("provider not found") -} - -// GetForexProviders returns a list of available forex providers -func (c *Config) GetForexProviders() []currency.FXSettings { - m.Lock() - fxProviders := c.Currency.ForexProviders - m.Unlock() - return fxProviders -} - -// GetPrimaryForexProvider returns the primary forex provider -func (c *Config) GetPrimaryForexProvider() string { - m.Lock() - defer m.Unlock() - for i := range c.Currency.ForexProviders { - if c.Currency.ForexProviders[i].PrimaryProvider { - return c.Currency.ForexProviders[i].Name - } - } - return "" -} - // UpdateExchangeConfig updates exchange configurations func (c *Config) UpdateExchangeConfig(e *Exchange) error { m.Lock() @@ -903,13 +871,19 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].EnabledPairs = nil } else { assets := c.Exchanges[i].CurrencyPairs.GetAssetTypes(false) + if len(assets) == 0 { + c.Exchanges[i].Enabled = false + log.Warnf(log.ConfigMgr, "%s no assets found, disabling...", c.Exchanges[i].Name) + continue + } + var atLeastOne bool for index := range assets { err := c.Exchanges[i].CurrencyPairs.IsAssetEnabled(assets[index]) if err != nil { - // Checks if we have an old config without the ability to - // enable disable the entire asset - if err.Error() == "cannot ascertain if asset is enabled, variable is nil" { + if errors.Is(err, currency.ErrAssetIsNil) { + // Checks if we have an old config without the ability to + // enable disable the entire asset log.Warnf(log.ConfigMgr, "Exchange %s: upgrading config for asset type %s and setting enabled.\n", c.Exchanges[i].Name, @@ -926,14 +900,6 @@ func (c *Config) CheckExchangeConfigValues() error { } if !atLeastOne { - if len(assets) == 0 { - c.Exchanges[i].Enabled = false - log.Warnf(log.ConfigMgr, - "%s no assets found, disabling...", - c.Exchanges[i].Name) - continue - } - // turn on an asset if all disabled log.Warnf(log.ConfigMgr, "%s assets disabled, turning on asset %s", @@ -1076,100 +1042,74 @@ func (c *Config) CheckBankAccountConfig() { banking.SetAccounts(c.BankAccounts...) } -// CheckCurrencyConfigValues checks to see if the currency config values are correct or not -func (c *Config) CheckCurrencyConfigValues() error { - fxProviders := forexprovider.GetSupportedForexProviders() +// GetForexProviders returns a list of available forex providers +func (c *Config) GetForexProviders() []currency.FXSettings { + m.Lock() + fxProviders := c.Currency.ForexProviders + m.Unlock() + return fxProviders +} - if len(fxProviders) != len(c.Currency.ForexProviders) { - for x := range fxProviders { - _, err := c.GetForexProvider(fxProviders[x]) - if err != nil { - log.Warnf(log.Global, "%s forex provider not found, adding to config..\n", fxProviders[x]) - c.Currency.ForexProviders = append(c.Currency.ForexProviders, currency.FXSettings{ - Name: fxProviders[x], - RESTPollingDelay: 600, - APIKey: DefaultUnsetAPIKey, - APIKeyLvl: -1, - }) - } - } - } - - count := 0 +// GetPrimaryForexProvider returns the primary forex provider +func (c *Config) GetPrimaryForexProvider() string { + m.Lock() + defer m.Unlock() for i := range c.Currency.ForexProviders { - if c.Currency.ForexProviders[i].Enabled { - 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.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 - c.Currency.ForexProviders[i].APIKeyLvl = -1 - continue - } - if c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey && - c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { - log.Warnf(log.Global, "%s enabled forex provider API key not set. Please set this in your config.json file\n", c.Currency.ForexProviders[i].Name) - c.Currency.ForexProviders[i].Enabled = false - c.Currency.ForexProviders[i].PrimaryProvider = false - continue - } + if c.Currency.ForexProviders[i].PrimaryProvider { + return c.Currency.ForexProviders[i].Name + } + } + return "" +} - if c.Currency.ForexProviders[i].APIKeyLvl == -1 && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { - log.Warnf(log.Global, "%s APIKey Level not set, functions limited. Please set this in your config.json file\n", - c.Currency.ForexProviders[i].Name) - } - count++ +// forexProviderExists checks to see if the provider exist. +func (c *Config) forexProviderExists(name string) bool { + for i := range c.Currency.ForexProviders { + if strings.EqualFold(c.Currency.ForexProviders[i].Name, name) { + return true + } + } + return false +} + +// CheckCurrencyConfigValues checks to see if the currency config values are +// correct or not +func (c *Config) CheckCurrencyConfigValues() error { + supported := forexprovider.GetSupportedForexProviders() + for x := range supported { + if !c.forexProviderExists(supported[x]) { + log.Warnf(log.ConfigMgr, "%s forex provider not found, adding to config...\n", supported[x]) + c.Currency.ForexProviders = append(c.Currency.ForexProviders, + currency.FXSettings{ + Name: supported[x], + APIKey: DefaultUnsetAPIKey, + APIKeyLvl: -1, + }) } } - if count == 0 { - for x := range c.Currency.ForexProviders { - if c.Currency.ForexProviders[x].Name == DefaultForexProviderExchangeRatesAPI { - c.Currency.ForexProviders[x].Enabled = true - c.Currency.ForexProviders[x].PrimaryProvider = true - log.Warnf(log.ConfigMgr, "No valid forex providers configured. Defaulting to %s.", - DefaultForexProviderExchangeRatesAPI) - } + for i := range c.Currency.ForexProviders { + if !common.StringDataContainsInsensitive(supported, c.Currency.ForexProviders[i].Name) { + log.Warnf(log.ConfigMgr, + "%s forex provider not supported, please remove from config.\n", + c.Currency.ForexProviders[i].Name) + c.Currency.ForexProviders[i].Enabled = false } } - if c.Currency.CryptocurrencyProvider == (CryptocurrencyProvider{}) { + if c.Currency.CryptocurrencyProvider == (currency.Provider{}) { c.Currency.CryptocurrencyProvider.Name = "CoinMarketCap" c.Currency.CryptocurrencyProvider.Enabled = false c.Currency.CryptocurrencyProvider.Verbose = false c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan - c.Currency.CryptocurrencyProvider.APIkey = DefaultUnsetAPIKey + c.Currency.CryptocurrencyProvider.APIKey = DefaultUnsetAPIKey } - if c.Currency.CryptocurrencyProvider.Enabled { - if c.Currency.CryptocurrencyProvider.APIkey == "" || - c.Currency.CryptocurrencyProvider.APIkey == DefaultUnsetAPIKey { - log.Warnln(log.ConfigMgr, "CryptocurrencyProvider enabled but api key is unset please set this in your config.json file") - } - if c.Currency.CryptocurrencyProvider.AccountPlan == "" || - c.Currency.CryptocurrencyProvider.AccountPlan == DefaultUnsetAccountPlan { - log.Warnln(log.ConfigMgr, "CryptocurrencyProvider enabled but account plan is unset please set this in your config.json file") - } - } else { - if c.Currency.CryptocurrencyProvider.APIkey == "" { - c.Currency.CryptocurrencyProvider.APIkey = DefaultUnsetAPIKey - } - if c.Currency.CryptocurrencyProvider.AccountPlan == "" { - c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan - } + if c.Currency.CryptocurrencyProvider.APIKey == "" { + c.Currency.CryptocurrencyProvider.APIKey = DefaultUnsetAPIKey } - - if c.Currency.Cryptocurrencies.Join() == "" { - if c.Cryptocurrencies != nil { - c.Currency.Cryptocurrencies = *c.Cryptocurrencies - c.Cryptocurrencies = nil - } else { - c.Currency.Cryptocurrencies = currency.GetDefaultCryptocurrencies() - } + if c.Currency.CryptocurrencyProvider.AccountPlan == "" { + c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan } if c.Currency.CurrencyPairFormat == nil { @@ -1177,7 +1117,7 @@ func (c *Config) CheckCurrencyConfigValues() error { c.Currency.CurrencyPairFormat = c.CurrencyPairFormat c.CurrencyPairFormat = nil } else { - c.Currency.CurrencyPairFormat = &CurrencyPairFormatConfig{ + c.Currency.CurrencyPairFormat = ¤cy.PairFormat{ Delimiter: "-", Uppercase: true, } @@ -1198,65 +1138,16 @@ func (c *Config) CheckCurrencyConfigValues() error { c.FiatDisplayCurrency = nil } - return nil -} - -// RetrieveConfigCurrencyPairs splits, assigns and verifies enabled currency -// pairs either cryptoCurrencies or fiatCurrencies -func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool, assetType asset.Item) error { - cryptoCurrencies := c.Currency.Cryptocurrencies - fiatCurrencies := currency.GetFiatCurrencies() - - for x := range c.Exchanges { - if !c.Exchanges[x].Enabled && enabledOnly { - continue - } - - err := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) - if err != nil { - continue - } - - baseCurrencies := c.Exchanges[x].BaseCurrencies - for y := range baseCurrencies { - if !fiatCurrencies.Contains(baseCurrencies[y]) { - fiatCurrencies = append(fiatCurrencies, baseCurrencies[y]) - } - } + if c.Currency.CurrencyFileUpdateDuration <= 0 { + log.Warnf(log.ConfigMgr, "Currency file update duration invalid, defaulting to %s", currency.DefaultCurrencyFileDelay) + c.Currency.CurrencyFileUpdateDuration = currency.DefaultCurrencyFileDelay } - for x := range c.Exchanges { - err := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) - if err != nil { - continue - } - - var pairs []currency.Pair - if !c.Exchanges[x].Enabled && enabledOnly { - pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, assetType) - } else { - pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name, assetType) - } - - if err != nil { - return err - } - - for y := range pairs { - if !fiatCurrencies.Contains(pairs[y].Base) && - !cryptoCurrencies.Contains(pairs[y].Base) { - cryptoCurrencies = append(cryptoCurrencies, pairs[y].Base) - } - - if !fiatCurrencies.Contains(pairs[y].Quote) && - !cryptoCurrencies.Contains(pairs[y].Quote) { - cryptoCurrencies = append(cryptoCurrencies, pairs[y].Quote) - } - } + if c.Currency.ForeignExchangeUpdateDuration <= 0 { + log.Warnf(log.ConfigMgr, "Currency foreign exchange update duration invalid, defaulting to %s", currency.DefaultForeignExchangeDelay) + c.Currency.ForeignExchangeUpdateDuration = currency.DefaultForeignExchangeDelay } - currency.UpdateCurrencies(fiatCurrencies, false) - currency.UpdateCurrencies(cryptoCurrencies, true) return nil } @@ -1282,7 +1173,7 @@ func (c *Config) CheckLoggerConfig() error { c.Logging.LoggerFileConfig.Rotate = convert.BoolPtr(false) } if c.Logging.LoggerFileConfig.MaxSize <= 0 { - log.Warnf(log.Global, "Logger rotation size invalid, defaulting to %v", log.DefaultMaxFileSize) + log.Warnf(log.ConfigMgr, "Logger rotation size invalid, defaulting to %v", log.DefaultMaxFileSize) c.Logging.LoggerFileConfig.MaxSize = log.DefaultMaxFileSize } log.FileLoggingConfiguredCorrectly = true @@ -1676,7 +1567,7 @@ func (c *Config) SaveConfigToFile(configPath string) error { if writer != nil { err = writer.Close() if err != nil { - log.Error(log.Global, err) + log.Error(log.ConfigMgr, err) } } }() @@ -1786,7 +1677,7 @@ func (c *Config) CheckConfig() error { err = c.checkGCTScriptConfig() if err != nil { - log.Errorf(log.Global, + log.Errorf(log.ConfigMgr, "Failed to configure gctscript, feature has been disabled: %s\n", err) } diff --git a/config/config_test.go b/config/config_test.go index 52e71002..a3ac049e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -46,7 +46,7 @@ func TestGetNonExistentDefaultFilePathDoesNotCreateDefaultDir(t *testing.T) { func TestGetCurrencyConfig(t *testing.T) { t.Parallel() cfg := &Config{ - Currency: CurrencyConfig{ + Currency: currency.Config{ ForeignExchangeUpdateDuration: time.Second, }, } @@ -361,8 +361,8 @@ func TestUpdateCommunicationsConfig(t *testing.T) { func TestGetCryptocurrencyProviderConfig(t *testing.T) { t.Parallel() cfg := &Config{ - Currency: CurrencyConfig{ - CryptocurrencyProvider: CryptocurrencyProvider{ + Currency: currency.Config{ + CryptocurrencyProvider: currency.Provider{ Name: "hellomoto", }, }, @@ -376,13 +376,13 @@ func TestGetCryptocurrencyProviderConfig(t *testing.T) { func TestUpdateCryptocurrencyProviderConfig(t *testing.T) { t.Parallel() cfg := &Config{ - Currency: CurrencyConfig{ - CryptocurrencyProvider: CryptocurrencyProvider{ + Currency: currency.Config{ + CryptocurrencyProvider: currency.Provider{ Name: "hellomoto", }, }, } - cfg.UpdateCryptocurrencyProviderConfig(CryptocurrencyProvider{Name: "SERIOUS TESTING PROCEDURE!"}) + cfg.UpdateCryptocurrencyProviderConfig(currency.Provider{Name: "SERIOUS TESTING PROCEDURE!"}) if cfg.Currency.CryptocurrencyProvider.Name != "SERIOUS TESTING PROCEDURE!" { t.Error("UpdateCurrencyProviderConfig LoadConfig error") } @@ -1194,8 +1194,8 @@ func TestCountEnabledExchanges(t *testing.T) { func TestGetCurrencyPairDisplayConfig(t *testing.T) { t.Parallel() cfg := &Config{ - Currency: CurrencyConfig{ - CurrencyPairFormat: &CurrencyPairFormatConfig{ + Currency: currency.Config{ + CurrencyPairFormat: ¤cy.PairFormat{ Delimiter: "-", Uppercase: true, }, @@ -1241,34 +1241,11 @@ func TestGetExchangeConfig(t *testing.T) { } } -func TestGetForexProviderConfig(t *testing.T) { - t.Parallel() - fxr := "Fixer" - cfg := &Config{ - Currency: CurrencyConfig{ - ForexProviders: []currency.FXSettings{ - { - Name: fxr, - }, - }, - }, - } - _, err := cfg.GetForexProvider(fxr) - if err != nil { - t.Error("GetForexProviderConfig error", err) - } - - _, err = cfg.GetForexProvider("this is not a forex provider") - if err == nil { - t.Error("GetForexProviderConfig no error for invalid provider") - } -} - func TestGetForexProviders(t *testing.T) { t.Parallel() fxr := "Fixer" cfg := &Config{ - Currency: CurrencyConfig{ + Currency: currency.Config{ ForexProviders: []currency.FXSettings{ { Name: fxr, @@ -1285,7 +1262,7 @@ func TestGetPrimaryForexProvider(t *testing.T) { t.Parallel() fxr := "Fixer" // nolint:ifshort,nolintlint // false positive and triggers only on Windows cfg := &Config{ - Currency: CurrencyConfig{ + Currency: currency.Config{ ForexProviders: []currency.FXSettings{ { Name: fxr, @@ -1700,52 +1677,6 @@ func TestCheckExchangeConfigValues(t *testing.T) { } } -func TestRetrieveConfigCurrencyPairs(t *testing.T) { - t.Parallel() - cp1 := currency.NewPair(currency.DOGE, currency.XRP) - cp2 := currency.NewPair(currency.DOGE, currency.USD) - cfg := &Config{ - Exchanges: []Exchange{ - { - Enabled: true, - BaseCurrencies: currency.Currencies{ - currency.USD, - }, - CurrencyPairs: ¤cy.PairsManager{ - RequestFormat: nil, - ConfigFormat: nil, - UseGlobalFormat: false, - LastUpdated: 0, - Pairs: map[asset.Item]*currency.PairStore{ - asset.Spot: { - AssetEnabled: convert.BoolPtr(true), - Available: currency.Pairs{cp1, cp2}, - Enabled: currency.Pairs{cp1}, - ConfigFormat: ¤cy.PairFormat{}, - RequestFormat: ¤cy.PairFormat{}, - }, - }, - }, - }, - }, - } - err := cfg.RetrieveConfigCurrencyPairs(true, asset.Spot) - if err != nil { - t.Errorf( - "TestRetrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs: %s", - err.Error(), - ) - } - - err = cfg.RetrieveConfigCurrencyPairs(false, asset.Spot) - if err != nil { - t.Errorf( - "TestRetrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs: %s", - err.Error(), - ) - } -} - func TestReadConfigFromFile(t *testing.T) { cfg := &Config{} err := cfg.ReadConfigFromFile(TestFile, true) @@ -1959,14 +1890,10 @@ func TestUpdateConfig(t *testing.T) { t.Fatalf("Error should have been thrown for invalid path") } - newCfg.Currency.Cryptocurrencies = currency.NewCurrenciesFromStringArray([]string{""}) err = c.UpdateConfig(TestFile, &newCfg, true) if err != nil { t.Errorf("%s", err) } - if c.Currency.Cryptocurrencies.Join() == "" { - t.Fatalf("Cryptocurrencies should have been repopulated") - } } func BenchmarkUpdateConfig(b *testing.B) { @@ -2112,10 +2039,10 @@ func TestCheckNTPConfig(t *testing.T) { func TestCheckCurrencyConfigValues(t *testing.T) { t.Parallel() cfg := &Config{ - Currency: CurrencyConfig{}, + Currency: currency.Config{}, } cfg.Currency.ForexProviders = nil - cfg.Currency.CryptocurrencyProvider = CryptocurrencyProvider{} + cfg.Currency.CryptocurrencyProvider = currency.Provider{} err := cfg.CheckCurrencyConfigValues() if err != nil { t.Error(err) @@ -2123,7 +2050,7 @@ func TestCheckCurrencyConfigValues(t *testing.T) { if cfg.Currency.ForexProviders == nil { t.Error("Failed to populate c.Currency.ForexProviders") } - if cfg.Currency.CryptocurrencyProvider.APIkey != DefaultUnsetAPIKey { + if cfg.Currency.CryptocurrencyProvider.APIKey != DefaultUnsetAPIKey { t.Error("Failed to set the api key to the default key") } if cfg.Currency.CryptocurrencyProvider.Name != "CoinMarketCap" { @@ -2133,34 +2060,29 @@ func TestCheckCurrencyConfigValues(t *testing.T) { cfg.Currency.ForexProviders[0].Enabled = true cfg.Currency.ForexProviders[0].Name = "CurrencyConverter" cfg.Currency.ForexProviders[0].PrimaryProvider = true - cfg.Currency.Cryptocurrencies = nil cfg.Cryptocurrencies = nil cfg.Currency.CurrencyPairFormat = nil - cfg.CurrencyPairFormat = &CurrencyPairFormatConfig{ + cfg.CurrencyPairFormat = ¤cy.PairFormat{ Uppercase: true, } - cfg.Currency.FiatDisplayCurrency = currency.Code{} + cfg.Currency.FiatDisplayCurrency = currency.EMPTYCODE cfg.FiatDisplayCurrency = ¤cy.BTC cfg.Currency.CryptocurrencyProvider.Enabled = true err = cfg.CheckCurrencyConfigValues() if err != nil { t.Error(err) } - if cfg.Currency.ForexProviders[0].Enabled { - t.Error("Failed to disable invalid forex provider") - } if !cfg.Currency.CurrencyPairFormat.Uppercase { t.Error("Failed to apply c.CurrencyPairFormat format to c.Currency.CurrencyPairFormat") } cfg.Currency.CryptocurrencyProvider.Enabled = false - cfg.Currency.CryptocurrencyProvider.APIkey = "" + cfg.Currency.CryptocurrencyProvider.APIKey = "" cfg.Currency.CryptocurrencyProvider.AccountPlan = "" cfg.FiatDisplayCurrency = ¤cy.BTC cfg.Currency.ForexProviders[0].Enabled = true cfg.Currency.ForexProviders[0].Name = "Name" cfg.Currency.ForexProviders[0].PrimaryProvider = true - cfg.Currency.Cryptocurrencies = currency.Currencies{} cfg.Cryptocurrencies = ¤cy.Currencies{} err = cfg.CheckCurrencyConfigValues() if err != nil { @@ -2169,7 +2091,7 @@ func TestCheckCurrencyConfigValues(t *testing.T) { if cfg.FiatDisplayCurrency != nil { t.Error("Failed to clear c.FiatDisplayCurrency") } - if cfg.Currency.CryptocurrencyProvider.APIkey != DefaultUnsetAPIKey || + if cfg.Currency.CryptocurrencyProvider.APIKey != DefaultUnsetAPIKey || cfg.Currency.CryptocurrencyProvider.AccountPlan != DefaultUnsetAccountPlan { t.Error("Failed to set CryptocurrencyProvider.APIkey and AccountPlan") } diff --git a/config/config_types.go b/config/config_types.go index 38810f3f..47f9be66 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -86,7 +86,7 @@ type Config struct { Profiler Profiler `json:"profiler"` NTPClient NTPClientConfig `json:"ntpclient"` GCTScript gctscript.Config `json:"gctscript"` - Currency CurrencyConfig `json:"currencyConfig"` + Currency currency.Config `json:"currencyConfig"` Communications base.CommunicationsConfig `json:"communications"` RemoteControl RemoteControlConfig `json:"remoteControl"` Portfolio portfolio.Base `json:"portfolioAddresses"` @@ -94,11 +94,11 @@ type Config struct { BankAccounts []banking.Account `json:"bankAccounts"` // Deprecated config settings, will be removed at a future date - Webserver *WebserverConfig `json:"webserver,omitempty"` - CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat,omitempty"` - FiatDisplayCurrency *currency.Code `json:"fiatDispayCurrency,omitempty"` - Cryptocurrencies *currency.Currencies `json:"cryptocurrencies,omitempty"` - SMS *base.SMSGlobalConfig `json:"smsGlobal,omitempty"` + Webserver *WebserverConfig `json:"webserver,omitempty"` + CurrencyPairFormat *currency.PairFormat `json:"currencyPairFormat,omitempty"` + FiatDisplayCurrency *currency.Code `json:"fiatDispayCurrency,omitempty"` + Cryptocurrencies *currency.Currencies `json:"cryptocurrencies,omitempty"` + SMS *base.SMSGlobalConfig `json:"smsGlobal,omitempty"` // encryption session values storedSalt []byte sessionDK []byte @@ -249,26 +249,6 @@ type BankTransaction struct { PaymentInstructions string `json:"paymentInstructions"` } -// CurrencyConfig holds all the information needed for currency related manipulation -type CurrencyConfig struct { - ForexProviders []currency.FXSettings `json:"forexProviders"` - CryptocurrencyProvider CryptocurrencyProvider `json:"cryptocurrencyProvider"` - Cryptocurrencies currency.Currencies `json:"cryptocurrencies"` - CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat"` - FiatDisplayCurrency currency.Code `json:"fiatDisplayCurrency"` - CurrencyFileUpdateDuration time.Duration `json:"currencyFileUpdateDuration"` - ForeignExchangeUpdateDuration time.Duration `json:"foreignExchangeUpdateDuration"` -} - -// CryptocurrencyProvider defines coinmarketcap tools -type CryptocurrencyProvider struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - APIkey string `json:"apiKey"` - AccountPlan string `json:"accountPlan"` -} - // FeaturesSupportedConfig stores the exchanges supported features type FeaturesSupportedConfig struct { REST bool `json:"restAPI"` diff --git a/config_example.json b/config_example.json index 93b12ed4..44a9c091 100644 --- a/config_example.json +++ b/config_example.json @@ -26,7 +26,7 @@ "maxsize": 250 }, "advancedSettings": { - "showLogSystemName": false, + "showLogSystemName": true, "spacer": " | ", "timeStampFormat": "02/01/2006 15:04:05", "headers": { diff --git a/currency/code.go b/currency/code.go index 6a209155..6cc151fa 100644 --- a/currency/code.go +++ b/currency/code.go @@ -8,10 +8,21 @@ import ( "unicode" ) +var ( + // ErrCurrencyCodeEmpty defines an error if the currency code is empty + ErrCurrencyCodeEmpty = errors.New("currency code is empty") + errItemIsNil = errors.New("item is nil") + errItemIsEmpty = errors.New("item is empty") + errRoleUnset = errors.New("role unset") + + // EMPTYCODE is an empty currency code + EMPTYCODE = Code{} + // EMPTYPAIR is an empty currency pair + EMPTYPAIR = Pair{} +) + func (r Role) String() string { switch r { - case Unset: - return UnsetRoleString case Fiat: return FiatCurrencyString case Cryptocurrency: @@ -20,8 +31,10 @@ func (r Role) String() string { return TokenString case Contract: return ContractString + case Stable: + return StableString default: - return "UNKNOWN" + return UnsetRoleString } } @@ -49,6 +62,8 @@ func (r *Role) UnmarshalJSON(d []byte) error { *r = Token case ContractString: *r = Contract + case StableString: + *r = Stable default: return fmt.Errorf("unmarshal error role type %s unsupported for currency", incoming) @@ -69,20 +84,21 @@ func (b *BaseCodes) GetFullCurrencyData() (File, error) { for i := range b.Items { switch b.Items[i].Role { case Unset: - file.UnsetCurrency = append(file.UnsetCurrency, *b.Items[i]) + file.UnsetCurrency = append(file.UnsetCurrency, b.Items[i]) case Fiat: - file.FiatCurrency = append(file.FiatCurrency, *b.Items[i]) + file.FiatCurrency = append(file.FiatCurrency, b.Items[i]) case Cryptocurrency: - file.Cryptocurrency = append(file.Cryptocurrency, *b.Items[i]) + file.Cryptocurrency = append(file.Cryptocurrency, b.Items[i]) case Token: - file.Token = append(file.Token, *b.Items[i]) + file.Token = append(file.Token, b.Items[i]) case Contract: - file.Contracts = append(file.Contracts, *b.Items[i]) + file.Contracts = append(file.Contracts, b.Items[i]) + case Stable: + file.Stable = append(file.Stable, b.Items[i]) default: return file, errors.New("role undefined") } } - file.LastMainUpdate = b.LastMainUpdate.Unix() return file, nil } @@ -90,12 +106,10 @@ func (b *BaseCodes) GetFullCurrencyData() (File, error) { // GetCurrencies gets the full currency list from the base code type available // from the currency system func (b *BaseCodes) GetCurrencies() Currencies { - var currencies Currencies b.mtx.Lock() + currencies := make(Currencies, len(b.Items)) for i := range b.Items { - currencies = append(currencies, Code{ - Item: b.Items[i], - }) + currencies[i] = Code{Item: b.Items[i]} } b.mtx.Unlock() return currencies @@ -104,31 +118,27 @@ func (b *BaseCodes) GetCurrencies() Currencies { // UpdateCurrency updates or registers a currency/contract func (b *BaseCodes) UpdateCurrency(fullName, symbol, blockchain string, id int, r Role) error { if r == Unset { - return fmt.Errorf("role cannot be unset in update currency for %s", symbol) + return fmt.Errorf("cannot update currency %w for %s", errRoleUnset, symbol) } b.mtx.Lock() defer b.mtx.Unlock() for i := range b.Items { - if b.Items[i].Symbol != symbol { + if b.Items[i].Symbol != symbol || (b.Items[i].Role != Unset && b.Items[i].Role != r) { continue } - if b.Items[i].Role == Unset { + if fullName != "" { b.Items[i].FullName = fullName + } + if r != Unset { b.Items[i].Role = r + } + if blockchain != "" { b.Items[i].AssocChain = blockchain + } + if id != 0 { b.Items[i].ID = id - return nil } - - if b.Items[i].Role != r { - // Captures same name currencies and duplicates to different roles - break - } - - b.Items[i].FullName = fullName - b.Items[i].AssocChain = blockchain - b.Items[i].ID = id return nil } @@ -142,75 +152,70 @@ func (b *BaseCodes) UpdateCurrency(fullName, symbol, blockchain string, id int, return nil } -// Register registers a currency from a string and returns a currency code -func (b *BaseCodes) Register(c string) Code { - var format bool - if c != "" { - format = unicode.IsUpper(rune(c[0])) +// Register registers a currency from a string and returns a currency code, this +// can optionally include a role when it is known. +func (b *BaseCodes) Register(c string, newRole Role) Code { + if c == "" { + return EMPTYCODE } + + var format bool + // Digits fool upper and lower casing. So find first letter and check case. + for x := range c { + if !unicode.IsDigit(rune(c[x])) { + format = unicode.IsUpper(rune(c[x])) + break + } + } + // Force upper string storage and matching c = strings.ToUpper(c) b.mtx.Lock() defer b.mtx.Unlock() for i := range b.Items { - if b.Items[i].Symbol == c { - return Code{ - Item: b.Items[i], - UpperCase: format, - } + if b.Items[i].Symbol != c { + continue } - } - newItem := &Item{Symbol: c} - b.Items = append(b.Items, newItem) - - return Code{ - Item: newItem, - UpperCase: format, - } -} - -// RegisterFiat registers a fiat currency from a string and returns a currency -// code -func (b *BaseCodes) RegisterFiat(c string) Code { - c = strings.ToUpper(c) - - b.mtx.Lock() - defer b.mtx.Unlock() - for i := range b.Items { - if b.Items[i].Symbol == c { + if newRole != Unset { if b.Items[i].Role == Unset { - b.Items[i].Role = Fiat - } - - if b.Items[i].Role != Fiat { + b.Items[i].Role = newRole + } else if b.Items[i].Role != newRole { + // This will duplicate item with same name but different role. + // TODO: This will need a specific update to NewCode to add in + // a specific param to find the exact name and role. continue } - return Code{Item: b.Items[i], UpperCase: true} } - } - item := &Item{Symbol: c, Role: Fiat} - b.Items = append(b.Items, item) - return Code{Item: item, UpperCase: true} + return Code{Item: b.Items[i], UpperCase: format} + } + newItem := &Item{Symbol: c, Lower: strings.ToLower(c), Role: newRole} + b.Items = append(b.Items, newItem) + return Code{Item: newItem, UpperCase: format} } // LoadItem sets item data func (b *BaseCodes) LoadItem(item *Item) error { + if item == nil { + return errItemIsNil + } + + if *item == (Item{}) { + return errItemIsEmpty + } + + item.Symbol = strings.ToUpper(item.Symbol) + item.Lower = strings.ToLower(item.Symbol) + b.mtx.Lock() defer b.mtx.Unlock() for i := range b.Items { if b.Items[i].Symbol != item.Symbol || - (b.Items[i].Role != Unset && - item.Role != Unset && - b.Items[i].Role != item.Role) { + (b.Items[i].Role != Unset && item.Role != Unset && b.Items[i].Role != item.Role) { continue } - b.Items[i].AssocChain = item.AssocChain - b.Items[i].ID = item.ID - b.Items[i].Role = item.Role - b.Items[i].FullName = item.FullName return nil } b.Items = append(b.Items, item) @@ -224,7 +229,7 @@ func NewCode(c string) Code { // String conforms to the stringer interface func (i *Item) String() string { - return i.FullName + return i.Symbol } // String converts the code to string @@ -232,11 +237,10 @@ func (c Code) String() string { if c.Item == nil { return "" } - if c.UpperCase { - return strings.ToUpper(c.Item.Symbol) + return c.Item.Symbol } - return strings.ToLower(c.Item.Symbol) + return c.Item.Lower } // Lower converts the code to lowercase formatting @@ -272,35 +276,27 @@ func (c Code) MarshalJSON() ([]byte, error) { // IsEmpty returns true if the code is empty func (c Code) IsEmpty() bool { - if c.Item == nil { - return true - } - return c.Item.Symbol == "" + return c.Item == nil || c.Item.Symbol == "" } -// Match returns if the code supplied is the same as the corresponding code -func (c Code) Match(check Code) bool { +// Equal returns if the code supplied is the same as the corresponding code +func (c Code) Equal(check Code) bool { return c.Item == check.Item } -// IsDefaultFiatCurrency checks if the currency passed in matches the default -// fiat currency -func (c Code) IsDefaultFiatCurrency() bool { - return storage.IsDefaultCurrency(c) -} - -// IsDefaultCryptocurrency checks if the currency passed in matches the default -// cryptocurrency -func (c Code) IsDefaultCryptocurrency() bool { - return storage.IsDefaultCryptocurrency(c) -} - // IsFiatCurrency checks if the currency passed is an enabled fiat currency func (c Code) IsFiatCurrency() bool { - return storage.IsFiatCurrency(c) + return c.Item != nil && c.Item.Role == Fiat } // IsCryptocurrency checks if the currency passed is an enabled CRYPTO currency. +// NOTE: All unset currencies will default to cryptocurrencies and stable coins +// are cryptocurrencies as well. func (c Code) IsCryptocurrency() bool { - return storage.IsCryptocurrency(c) + return c.Item != nil && c.Item.Role&(Cryptocurrency|Stable) == c.Item.Role +} + +// IsStableCurrency checks if the currency is a stable currency. +func (c Code) IsStableCurrency() bool { + return c.Item != nil && c.Item.Role == Stable } diff --git a/currency/code_test.go b/currency/code_test.go index 779f69d3..af49d49a 100644 --- a/currency/code_test.go +++ b/currency/code_test.go @@ -2,6 +2,7 @@ package currency import ( "encoding/json" + "errors" "testing" ) @@ -38,7 +39,7 @@ func TestRoleString(t *testing.T) { var random Role = 1 << 7 - if random.String() != "UNKNOWN" { + if random.String() != UnsetRoleString { t.Errorf("Role String() error expected %s but received %s", "UNKNOWN", random) @@ -51,7 +52,7 @@ func TestRoleMarshalJSON(t *testing.T) { t.Error("Role MarshalJSON() error", err) } - if expected := `"fiatCurrency"`; string(d) != expected { + if expected := `"fiatcurrency"`; string(d) != expected { t.Errorf("Role MarshalJSON() error expected %s but received %s", expected, string(d)) @@ -66,6 +67,7 @@ func TestRoleUnmarshalJSON(t *testing.T) { RoleThree Role `json:"RoleThree"` RoleFour Role `json:"RoleFour"` RoleFive Role `json:"RoleFive"` + RoleSix Role `json:"RoleSix"` RoleUnknown Role `json:"RoleUnknown"` } @@ -75,6 +77,7 @@ func TestRoleUnmarshalJSON(t *testing.T) { RoleThree: Fiat, RoleFour: Token, RoleFive: Contract, + RoleSix: Stable, } e, err := json.Marshal(1337) @@ -138,6 +141,27 @@ func TestRoleUnmarshalJSON(t *testing.T) { if err == nil { t.Error("Expected unmarshall error") } + + err = unhandled.UnmarshalJSON([]byte(`1336`)) + if err == nil { + t.Error("Expected unmarshall error") + } +} + +func (b *BaseCodes) assertRole(t *testing.T, c Code, r Role) { + t.Helper() + b.mtx.Lock() + defer b.mtx.Unlock() + for x := range b.Items { + if b.Items[x] != c.Item { + continue + } + if b.Items[x].Role != r { + t.Fatal("unexpected role") + } + return + } + t.Fatal("code pointer not found") } func TestBaseCode(t *testing.T) { @@ -147,35 +171,66 @@ func TestBaseCode(t *testing.T) { main.HasData()) } - catsCode := main.Register("CATS") + catsUnset := main.Register("CATS", Unset) + main.assertRole(t, catsUnset, Unset) if !main.HasData() { t.Errorf("BaseCode HasData() error expected true but received %v", main.HasData()) } - if !main.Register("CATS").Match(catsCode) { + // Changes unset to fiat + catsFiat := main.Register("CATS", Fiat) + main.assertRole(t, catsUnset, Fiat) + + // Register as unset, will return first match. + otherFiatCat := main.Register("CATS", Unset) + main.assertRole(t, otherFiatCat, Fiat) + if !otherFiatCat.Equal(catsFiat) { t.Errorf("BaseCode Match() error expected true but received %v", false) } - if main.Register("DOGS").Match(catsCode) { + // Register as fiat, will return fiat match. + thatOtherFiatCat := main.Register("CATS", Fiat) + main.assertRole(t, otherFiatCat, Fiat) + if !thatOtherFiatCat.Equal(catsFiat) { + t.Errorf("BaseCode Match() error expected true but received %v", + false) + } + + // Register as stable, will return a different currency with the same + // currency code. + superStableCatNoShakes := main.Register("CATS", Stable) + main.assertRole(t, superStableCatNoShakes, Stable) + if superStableCatNoShakes.Equal(catsFiat) { + t.Errorf("BaseCode Match() error expected true but received %v", + true) + } + + // Due to the role being unset originally, this will be set to Fiat when + // explicitly set. + if !catsUnset.Equal(catsFiat) { + t.Fatal("both should be the same") + } + + if main.Register("DOGS", Unset).Equal(catsUnset) { t.Errorf("BaseCode Match() error expected false but received %v", true) } loadedCurrencies := main.GetCurrencies() - if loadedCurrencies.Contains(main.Register("OWLS")) { + if loadedCurrencies.Contains(main.Register("OWLS", Unset)) { t.Errorf("BaseCode Contains() error expected false but received %v", true) } - if !loadedCurrencies.Contains(catsCode) { + if !loadedCurrencies.Contains(catsFiat) { t.Errorf("BaseCode Contains() error expected true but received %v", false) } - main.Register("XBTUSD") + main.Register("XBTUSD", Unset) err := main.UpdateCurrency("Bitcoin Perpetual", "XBTUSD", @@ -186,19 +241,24 @@ func TestBaseCode(t *testing.T) { t.Fatal(err) } - main.Register("BTC") + main.Register("BTC", Unset) + err = main.UpdateCurrency("Bitcoin", "BTC", "", 1337, Unset) + if !errors.Is(err, errRoleUnset) { + t.Fatalf("received: '%v' but expected: '%v'", err, errRoleUnset) + } + err = main.UpdateCurrency("Bitcoin", "BTC", "", 1337, Cryptocurrency) if err != nil { t.Fatal(err) } - main.Register("AUD") + aud := main.Register("AUD", Unset) err = main.UpdateCurrency("Unreal Dollar", "AUD", "", 1111, Fiat) if err != nil { t.Fatal(err) } - if main.Items[5].FullName != "Unreal Dollar" { + if aud.Item.FullName != "Unreal Dollar" { t.Error("Expected fullname to update for AUD") } @@ -207,22 +267,22 @@ func TestBaseCode(t *testing.T) { t.Fatal(err) } - main.Items[5].Role = Unset + aud.Item.Role = Unset err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat) if err != nil { t.Fatal(err) } - if main.Items[5].Role != Fiat { + if aud.Item.Role != Fiat { t.Error("Expected role to change to Fiat") } - main.Register("PPT") + main.Register("PPT", Unset) err = main.UpdateCurrency("Populous", "PPT", "ETH", 1335, Token) if err != nil { t.Fatal(err) } - contract := main.Register("XBTUSD") + contract := main.Register("XBTUSD", Unset) if contract.IsFiatCurrency() { t.Errorf("BaseCode IsFiatCurrency() error expected false but received %v", @@ -230,18 +290,28 @@ func TestBaseCode(t *testing.T) { } if contract.IsCryptocurrency() { - t.Errorf("BaseCode IsFiatCurrency() error expected false but received %v", + t.Errorf("BaseCode IsCryptocurrency() error expected false but received %v", true) } - if contract.IsDefaultFiatCurrency() { - t.Errorf("BaseCode IsDefaultFiatCurrency() error expected false but received %v", - true) + err = main.LoadItem(nil) + if !errors.Is(err, errItemIsNil) { + t.Fatalf("received: '%v' but expected: '%v'", err, errItemIsNil) } - if contract.IsDefaultFiatCurrency() { - t.Errorf("BaseCode IsFiatCurrency() error expected false but received %v", - true) + err = main.LoadItem(&Item{}) + if !errors.Is(err, errItemIsEmpty) { + t.Fatalf("received: '%v' but expected: '%v'", err, errItemIsEmpty) + } + + err = main.LoadItem(&Item{ + ID: 0, + FullName: "Cardano", + Role: Cryptocurrency, + Symbol: "ADA", + }) + if err != nil { + t.Fatal(err) } err = main.LoadItem(&Item{ @@ -269,7 +339,7 @@ func TestBaseCode(t *testing.T) { len(full.Cryptocurrency)) } - if len(full.FiatCurrency) != 1 { + if len(full.FiatCurrency) != 2 { t.Errorf("BaseCode GetFullCurrencyData() error expected 1 but received %v", len(full.FiatCurrency)) } @@ -279,7 +349,7 @@ func TestBaseCode(t *testing.T) { len(full.Token)) } - if len(full.UnsetCurrency) != 3 { + if len(full.UnsetCurrency) != 2 { t.Errorf("BaseCode GetFullCurrencyData() error expected 3 but received %v", len(full.UnsetCurrency)) } @@ -304,24 +374,25 @@ func TestBaseCode(t *testing.T) { } main.Items[0].FullName = "Hello" - err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency) + err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Fiat) if err != nil { t.Fatal(err) } + if main.Items[0].FullName != "MEWOW" { t.Error("Fullname not updated") } - err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency) + err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Fiat) if err != nil { t.Fatal(err) } - err = main.UpdateCurrency("WOWCATS", "CATS", "", 3, Token) + err = main.UpdateCurrency("WOWCATS", "CATS", "", 3, Fiat) if err != nil { t.Fatal(err) } // Creates a new item under a different currency role - if main.Items[9].ID != 3 { + if main.Items[0].ID != 3 { t.Error("ID not updated") } @@ -381,6 +452,15 @@ func TestCodeUnmarshalJSON(t *testing.T) { expected, unmarshalHere) } + + encoded, err = json.Marshal(1336) // :'( + if err != nil { + t.Fatal(err) + } + err = json.Unmarshal(encoded, &unmarshalHere) + if err == nil { + t.Fatal("expected error") + } } func TestCodeMarshalJSON(t *testing.T) { @@ -406,7 +486,7 @@ func TestCodeMarshalJSON(t *testing.T) { quickstruct = struct { Codey Code `json:"sweetCodes"` }{ - Codey: Code{}, // nil code + Codey: EMPTYCODE, // nil code } encoded, err = json.Marshal(quickstruct) @@ -421,37 +501,11 @@ func TestCodeMarshalJSON(t *testing.T) { } } -func TestIsDefaultCurrency(t *testing.T) { - if !USD.IsDefaultFiatCurrency() { - t.Errorf("TestIsDefaultCurrency Cannot match currency %s.", - USD) - } - if !AUD.IsDefaultFiatCurrency() { - t.Errorf("TestIsDefaultCurrency Cannot match currency, %s.", - AUD) - } - if LTC.IsDefaultFiatCurrency() { - t.Errorf("TestIsDefaultCurrency Function return is incorrect with, %s.", - LTC) - } -} - -func TestIsDefaultCryptocurrency(t *testing.T) { - if !BTC.IsDefaultCryptocurrency() { - t.Errorf("TestIsDefaultCryptocurrency cannot match currency, %s.", - BTC) - } - if !LTC.IsDefaultCryptocurrency() { - t.Errorf("TestIsDefaultCryptocurrency cannot match currency, %s.", - LTC) - } - if AUD.IsDefaultCryptocurrency() { - t.Errorf("TestIsDefaultCryptocurrency function return is incorrect with, %s.", - AUD) - } -} - func TestIsFiatCurrency(t *testing.T) { + if EMPTYCODE.IsFiatCurrency() { + t.Errorf("TestIsFiatCurrency cannot match currency, %s.", + EMPTYCODE) + } if !USD.IsFiatCurrency() { t.Errorf( "TestIsFiatCurrency cannot match currency, %s.", USD) @@ -465,31 +519,74 @@ func TestIsFiatCurrency(t *testing.T) { "TestIsFiatCurrency cannot match currency, %s.", LINO, ) } + if USDT.IsFiatCurrency() { + t.Errorf( + "TestIsFiatCurrency cannot match currency, %s.", USD) + } + if DAI.IsFiatCurrency() { + t.Errorf( + "TestIsFiatCurrency cannot match currency, %s.", USD) + } } func TestIsCryptocurrency(t *testing.T) { + if EMPTYCODE.IsCryptocurrency() { + t.Errorf("TestIsCryptocurrency cannot match currency, %s.", + EMPTYCODE) + } if !BTC.IsCryptocurrency() { - t.Errorf("TestIsFiatCurrency cannot match currency, %s.", + t.Errorf("TestIsCryptocurrency cannot match currency, %s.", BTC) } if !LTC.IsCryptocurrency() { - t.Errorf("TestIsFiatCurrency cannot match currency, %s.", + t.Errorf("TestIsCryptocurrency cannot match currency, %s.", LTC) } if AUD.IsCryptocurrency() { - t.Errorf("TestIsFiatCurrency cannot match currency, %s.", + t.Errorf("TestIsCryptocurrency cannot match currency, %s.", AUD) } + if !USDT.IsCryptocurrency() { + t.Errorf( + "TestIsCryptocurrency cannot match currency, %s.", USD) + } + if !DAI.IsCryptocurrency() { + t.Errorf( + "TestIsCryptocurrency cannot match currency, %s.", USD) + } +} + +func TestIsStableCurrency(t *testing.T) { + if EMPTYCODE.IsStableCurrency() { + t.Errorf("TestIsStableCurrency cannot match currency, %s.", EMPTYCODE) + } + if BTC.IsStableCurrency() { + t.Errorf("TestIsStableCurrency cannot match currency, %s.", BTC) + } + if LTC.IsStableCurrency() { + t.Errorf("TestIsStableCurrency cannot match currency, %s.", LTC) + } + if AUD.IsStableCurrency() { + t.Errorf("TestIsStableCurrency cannot match currency, %s.", AUD) + } + if !USDT.IsStableCurrency() { + t.Errorf("TestIsStableCurrency cannot match currency, %s.", USDT) + } + if !DAI.IsStableCurrency() { + t.Errorf("TestIsStableCurrency cannot match currency, %s.", DAI) + } } func TestItemString(t *testing.T) { - expected := "Hello,World" newItem := Item{ - FullName: expected, + ID: 1337, + FullName: "Hello,World", + Symbol: "HWORLD", + AssocChain: "Silly", } - if newItem.String() != expected { - t.Errorf("Currency String() error expected %s but received %s", + if expected := "HWORLD"; newItem.String() != expected { + t.Errorf("Currency String() error expected '%s' but received '%s'", expected, &newItem) } diff --git a/currency/code_types.go b/currency/code_types.go index 0d46e78f..d4997952 100644 --- a/currency/code_types.go +++ b/currency/code_types.go @@ -12,12 +12,14 @@ const ( Cryptocurrency Token Contract + Stable UnsetRoleString = "roleUnset" - FiatCurrencyString = "fiatCurrency" + FiatCurrencyString = "fiatcurrency" CryptocurrencyString = "cryptocurrency" TokenString = "token" ContractString = "contract" + StableString = "stablecurrency" ) // Role defines a bitmask for the full currency roles either; fiat, @@ -34,1636 +36,3044 @@ type BaseCodes struct { // Code defines an ISO 4217 fiat currency or unofficial cryptocurrency code // string type Code struct { - Item *Item + Item *Item + // TODO: Below will force the use of the Equal method for comparison. Big + // job to update all maps and instances through the code base. + // _ []struct{} UpperCase bool } // Item defines a sub type containing the main attributes of a designated // currency code pointer type Item struct { - ID int `json:"id,omitempty"` - FullName string `json:"fullName,omitempty"` - Symbol string `json:"symbol"` - Role Role `json:"-"` + ID int `json:"id,omitempty"` + FullName string `json:"fullName,omitempty"` + Symbol string `json:"symbol"` + // Lower is the lower case symbol for optimization purposes so no need to + // rely on the strings package to upper and lower strings when it is not + // needed + Lower string `json:"-"` + Role Role `json:"role"` AssocChain string `json:"associatedBlockchain,omitempty"` } +// Lock implements the sync.Locker interface and forces a govet check nocopy +func (*Item) Lock() {} + +// Unlock implements the sync.Locker interface and forces a govet check nocopy +func (*Item) Unlock() {} + // Const declarations for individual currencies/tokens/fiat // An ever growing list. Cares not for equivalence, just is var ( - BTC = NewCode("BTC") - LTC = NewCode("LTC") - ETH = NewCode("ETH") - XRP = NewCode("XRP") - BCH = NewCode("BCH") - EOS = NewCode("EOS") - XLM = NewCode("XLM") - USDT = NewCode("USDT") - USDC = NewCode("USDC") - ADA = NewCode("ADA") - XMR = NewCode("XMR") - TRX = NewCode("TRX") - MIOTA = NewCode("MIOTA") - DASH = NewCode("DASH") - BNB = NewCode("BNB") - NEO = NewCode("NEO") - ETC = NewCode("ETC") - XEM = NewCode("XEM") - XTZ = NewCode("XTZ") - VET = NewCode("VET") - DOGE = NewCode("DOGE") - ZEC = NewCode("ZEC") - OMG = NewCode("OMG") - BTG = NewCode("BTG") - MKR = NewCode("MKR") - BCN = NewCode("BCN") - ONT = NewCode("ONT") - ZRX = NewCode("ZRX") - LSK = NewCode("LSK") - DCR = NewCode("DCR") - QTUM = NewCode("QTUM") - BCD = NewCode("BCD") - BTS = NewCode("BTS") - NANO = NewCode("NANO") - ZIL = NewCode("ZIL") - SC = NewCode("SC") - DGB = NewCode("DGB") - ICX = NewCode("ICX") - STEEM = NewCode("STEEM") - AE = NewCode("AE") - XVG = NewCode("XVG") - WAVES = NewCode("WAVES") - NPXS = NewCode("NPXS") - ETN = NewCode("ETN") - BTM = NewCode("BTM") - BAT = NewCode("BAT") - ETP = NewCode("ETP") - HOT = NewCode("HOT") - STRAT = NewCode("STRAT") // nolint // Cryptocurrency code - GNT = NewCode("GNT") - REP = NewCode("REP") - SNT = NewCode("SNT") - PPT = NewCode("PPT") - KMD = NewCode("KMD") - TUSD = NewCode("TUSD") - CNX = NewCode("CNX") - LINK = NewCode("LINK") - WTC = NewCode("WTC") - ARDR = NewCode("ARDR") - WAN = NewCode("WAN") - MITH = NewCode("MITH") - RDD = NewCode("RDD") - IOST = NewCode("IOST") - IOT = NewCode("IOT") - KCS = NewCode("KCS") - MAID = NewCode("MAID") - XET = NewCode("XET") - MOAC = NewCode("MOAC") - HC = NewCode("HC") - AION = NewCode("AION") - AOA = NewCode("AOA") - HT = NewCode("HT") - ELF = NewCode("ELF") - LRC = NewCode("LRC") - BNT = NewCode("BNT") - CMT = NewCode("CMT") - DGD = NewCode("DGD") - DCN = NewCode("DCN") - FUN = NewCode("FUN") - GXS = NewCode("GXS") - DROP = NewCode("DROP") - MANA = NewCode("MANA") - PAY = NewCode("PAY") - MCO = NewCode("MCO") - THETA = NewCode("THETA") - NXT = NewCode("NXT") - NOAH = NewCode("NOAH") - LOOM = NewCode("LOOM") - POWR = NewCode("POWR") - WAX = NewCode("WAX") - ELA = NewCode("ELA") - PIVX = NewCode("PIVX") - XIN = NewCode("XIN") - DAI = NewCode("DAI") - BTCP = NewCode("BTCP") - NEXO = NewCode("NEXO") - XBT = NewCode("XBT") - SAN = NewCode("SAN") - GAS = NewCode("GAS") - BCC = NewCode("BCC") - HCC = NewCode("HCC") - OAX = NewCode("OAX") - DNT = NewCode("DNT") - ICN = NewCode("ICN") - LLT = NewCode("LLT") - YOYO = NewCode("YOYO") - SNGLS = NewCode("SNGLS") - BQX = NewCode("BQX") - KNC = NewCode("KNC") - SNM = NewCode("SNM") - CTR = NewCode("CTR") - SALT = NewCode("SALT") - MDA = NewCode("MDA") - IOTA = NewCode("IOTA") - SUB = NewCode("SUB") - MTL = NewCode("MTL") - MTH = NewCode("MTH") - ENG = NewCode("ENG") - AST = NewCode("AST") - CLN = NewCode("CLN") - EDG = NewCode("EDG") - FIRST = NewCode("1ST") - GOLOS = NewCode("GOLOS") - ANT = NewCode("ANT") - GBG = NewCode("GBG") - HMQ = NewCode("HMQ") - INCNT = NewCode("INCNT") - ACE = NewCode("ACE") - ACT = NewCode("ACT") - AAC = NewCode("AAC") - AIDOC = NewCode("AIDOC") - SOC = NewCode("SOC") - ATL = NewCode("ATL") - AVT = NewCode("AVT") - BKX = NewCode("BKX") - BEC = NewCode("BEC") - VEE = NewCode("VEE") - PTOY = NewCode("PTOY") - CAG = NewCode("CAG") - CIC = NewCode("CIC") - CBT = NewCode("CBT") - CAN = NewCode("CAN") - DAT = NewCode("DAT") - DNA = NewCode("DNA") - INT = NewCode("INT") - IPC = NewCode("IPC") - ILA = NewCode("ILA") - LIGHT = NewCode("LIGHT") - MAG = NewCode("MAG") - AMM = NewCode("AMM") - MOF = NewCode("MOF") - MGC = NewCode("MGC") - OF = NewCode("OF") - LA = NewCode("LA") - LEV = NewCode("LEV") - NGC = NewCode("NGC") - OKB = NewCode("OKB") - MOT = NewCode("MOT") - PRA = NewCode("PRA") - R = NewCode("R") - SSC = NewCode("SSC") - SHOW = NewCode("SHOW") - SPF = NewCode("SPF") - SNC = NewCode("SNC") - SWFTC = NewCode("SWFTC") - TRA = NewCode("TRA") - TOPC = NewCode("TOPC") - TRIO = NewCode("TRIO") - QVT = NewCode("QVT") - UCT = NewCode("UCT") - UKG = NewCode("UKG") - UTK = NewCode("UTK") - VIU = NewCode("VIU") - WFEE = NewCode("WFEE") - WRC = NewCode("WRC") - UGC = NewCode("UGC") - YEE = NewCode("YEE") - YOYOW = NewCode("YOYOW") - ZIP = NewCode("ZIP") - READ = NewCode("READ") - RCT = NewCode("RCT") - REF = NewCode("REF") - XUC = NewCode("XUC") - FAIR = NewCode("FAIR") - GSC = NewCode("GSC") - HMC = NewCode("HMC") - PLU = NewCode("PLU") - PRO = NewCode("PRO") - QRL = NewCode("QRL") - REN = NewCode("REN") - ROUND = NewCode("ROUND") - SRN = NewCode("SRN") - XID = NewCode("XID") - SBD = NewCode("SBD") - TAAS = NewCode("TAAS") - TKN = NewCode("TKN") - VEN = NewCode("VEN") - VSL = NewCode("VSL") - TRST = NewCode("TRST") - XXX = NewCode("XXX") - IND = NewCode("IND") - LDC = NewCode("LDC") - GUP = NewCode("GUP") - MGO = NewCode("MGO") - MYST = NewCode("MYST") - NEU = NewCode("NEU") - NET = NewCode("NET") - BMC = NewCode("BMC") - BCAP = NewCode("BCAP") - TIME = NewCode("TIME") - CFI = NewCode("CFI") - EVX = NewCode("EVX") - REQ = NewCode("REQ") - VIB = NewCode("VIB") - ARK = NewCode("ARK") - MOD = NewCode("MOD") - ENJ = NewCode("ENJ") - STORJ = NewCode("STORJ") - RCN = NewCode("RCN") - NULS = NewCode("NULS") - RDN = NewCode("RDN") - DLT = NewCode("DLT") - AMB = NewCode("AMB") - BCPT = NewCode("BCPT") - ARN = NewCode("ARN") - GVT = NewCode("GVT") - CDT = NewCode("CDT") - POE = NewCode("POE") - QSP = NewCode("QSP") - XZC = NewCode("XZC") - TNT = NewCode("TNT") - FUEL = NewCode("FUEL") - ADX = NewCode("ADX") - CND = NewCode("CND") - LEND = NewCode("LEND") - WABI = NewCode("WABI") - SBTC = NewCode("SBTC") - BCX = NewCode("BCX") - TNB = NewCode("TNB") - GTO = NewCode("GTO") - OST = NewCode("OST") - CVC = NewCode("CVC") - DATA = NewCode("DATA") - ETF = NewCode("ETF") - BRD = NewCode("BRD") - NEBL = NewCode("NEBL") - VIBE = NewCode("VIBE") - LUN = NewCode("LUN") - CHAT = NewCode("CHAT") - RLC = NewCode("RLC") - INS = NewCode("INS") - VIA = NewCode("VIA") - BLZ = NewCode("BLZ") - SYS = NewCode("SYS") - NCASH = NewCode("NCASH") - POA = NewCode("POA") - STORM = NewCode("STORM") - WPR = NewCode("WPR") - QLC = NewCode("QLC") - GRS = NewCode("GRS") - CLOAK = NewCode("CLOAK") - ZEN = NewCode("ZEN") - SKY = NewCode("SKY") - IOTX = NewCode("IOTX") - QKC = NewCode("QKC") - AGI = NewCode("AGI") - NXS = NewCode("NXS") - EON = NewCode("EON") - KEY = NewCode("KEY") - NAS = NewCode("NAS") - ADD = NewCode("ADD") - MEETONE = NewCode("MEETONE") - ATD = NewCode("ATD") - MFT = NewCode("MFT") - EOP = NewCode("EOP") - DENT = NewCode("DENT") - IQ = NewCode("IQ") - DOCK = NewCode("DOCK") - POLY = NewCode("POLY") - VTHO = NewCode("VTHO") - ONG = NewCode("ONG") - PHX = NewCode("PHX") - GO = NewCode("GO") - PAX = NewCode("PAX") - PAXG = NewCode("PAXG") - EDO = NewCode("EDO") - WINGS = NewCode("WINGS") - NAV = NewCode("NAV") - TRIG = NewCode("TRIG") - APPC = NewCode("APPC") - KRW = NewCode("KRW") - HSR = NewCode("HSR") - ETHOS = NewCode("ETHOS") - CTXC = NewCode("CTXC") - ITC = NewCode("ITC") - TRUE = NewCode("TRUE") - ABT = NewCode("ABT") - RNT = NewCode("RNT") - PLY = NewCode("PLY") - PST = NewCode("PST") - KICK = NewCode("KICK") - BTCZ = NewCode("BTCZ") - DXT = NewCode("DXT") - STQ = NewCode("STQ") - INK = NewCode("INK") - HBZ = NewCode("HBZ") - USDT_ETH = NewCode("USDT_ETH") // nolint // Cryptocurrency code - QTUM_ETH = NewCode("QTUM_ETH") // nolint // Cryptocurrency code - BTM_ETH = NewCode("BTM_ETH") // nolint // Cryptocurrency code - FIL = NewCode("FIL") - STX = NewCode("STX") - BOT = NewCode("BOT") - VERI = NewCode("VERI") - ZSC = NewCode("ZSC") - QBT = NewCode("QBT") - MED = NewCode("MED") - QASH = NewCode("QASH") - MDS = NewCode("MDS") - GOD = NewCode("GOD") - SMT = NewCode("SMT") - BTF = NewCode("BTF") - NAS_ETH = NewCode("NAS_ETH") // nolint // Cryptocurrency code - TSL = NewCode("TSL") - BIFI = NewCode("BIFI") - BNTY = NewCode("BNTY") - DRGN = NewCode("DRGN") - GTC = NewCode("GTC") - MDT = NewCode("MDT") - QUN = NewCode("QUN") - GNX = NewCode("GNX") - DDD = NewCode("DDD") - BTO = NewCode("BTO") - TIO = NewCode("TIO") - OCN = NewCode("OCN") - RUFF = NewCode("RUFF") - TNC = NewCode("TNC") - SNET = NewCode("SNET") - COFI = NewCode("COFI") - ZPT = NewCode("ZPT") - JNT = NewCode("JNT") - MTN = NewCode("MTN") - GEM = NewCode("GEM") - DADI = NewCode("DADI") - RFR = NewCode("RFR") - MOBI = NewCode("MOBI") - LEDU = NewCode("LEDU") - DBC = NewCode("DBC") - MKR_OLD = NewCode("MKR_OLD") // nolint // Cryptocurrency code - DPY = NewCode("DPY") - BCDN = NewCode("BCDN") - EOSDAC = NewCode("EOSDAC") - TIPS = NewCode("TIPS") - XMC = NewCode("XMC") - PPS = NewCode("PPS") - BOE = NewCode("BOE") - MEDX = NewCode("MEDX") - SMT_ETH = NewCode("SMT_ETH") // nolint // Cryptocurrency code - CS = NewCode("CS") - MAN = NewCode("MAN") - REM = NewCode("REM") - LYM = NewCode("LYM") - INSTAR = NewCode("INSTAR") - BFT = NewCode("BFT") - IHT = NewCode("IHT") - SENC = NewCode("SENC") - TOMO = NewCode("TOMO") - ELEC = NewCode("ELEC") - SHIP = NewCode("SHIP") - TFD = NewCode("TFD") - HAV = NewCode("HAV") - HUR = NewCode("HUR") - LST = NewCode("LST") - LINO = NewCode("LINO") - SWTH = NewCode("SWTH") - NKN = NewCode("NKN") - SOUL = NewCode("SOUL") - GALA_NEO = NewCode("GALA_NEO") // nolint // Cryptocurrency code - LRN = NewCode("LRN") - GSE = NewCode("GSE") - RATING = NewCode("RATING") - HSC = NewCode("HSC") - HIT = NewCode("HIT") - DX = NewCode("DX") - BXC = NewCode("BXC") - GARD = NewCode("GARD") - FTI = NewCode("FTI") - SOP = NewCode("SOP") - LEMO = NewCode("LEMO") - RED = NewCode("RED") - LBA = NewCode("LBA") - KAN = NewCode("KAN") - OPEN = NewCode("OPEN") - SKM = NewCode("SKM") - NBAI = NewCode("NBAI") - UPP = NewCode("UPP") - ATMI = NewCode("ATMI") - TMT = NewCode("TMT") - BBK = NewCode("BBK") - EDR = NewCode("EDR") - MET = NewCode("MET") - TCT = NewCode("TCT") - EXC = NewCode("EXC") - CNC = NewCode("CNC") - TIX = NewCode("TIX") - XTC = NewCode("XTC") - BU = NewCode("BU") - GNO = NewCode("GNO") - MLN = NewCode("MLN") - XBC = NewCode("XBC") - BTCD = NewCode("BTCD") - BURST = NewCode("BURST") - CLAM = NewCode("CLAM") - XCP = NewCode("XCP") - EMC2 = NewCode("EMC2") - EXP = NewCode("EXP") - FCT = NewCode("FCT") - GAME = NewCode("GAME") - GRC = NewCode("GRC") - HUC = NewCode("HUC") - LBC = NewCode("LBC") - NMC = NewCode("NMC") - NEOS = NewCode("NEOS") - OMNI = NewCode("OMNI") - PASC = NewCode("PASC") - PPC = NewCode("PPC") - DSH = NewCode("DSH") - GML = NewCode("GML") - GSY = NewCode("GSY") - POT = NewCode("POT") - XPM = NewCode("XPM") - AMP = NewCode("AMP") - VRC = NewCode("VRC") - VTC = NewCode("VTC") - ZERO07 = NewCode("007") - BIT16 = NewCode("BIT16") - TWO015 = NewCode("2015") - TWO56 = NewCode("256") - TWOBACCO = NewCode("2BACCO") - TWOGIVE = NewCode("2GIVE") - THIRTY2BIT = NewCode("32BIT") - THREE65 = NewCode("365") - FOUR04 = NewCode("404") - SEVEN00 = NewCode("700") - EIGHTBIT = NewCode("8BIT") - ACLR = NewCode("ACLR") - ACES = NewCode("ACES") - ACPR = NewCode("ACPR") - ACID = NewCode("ACID") - ACOIN = NewCode("ACOIN") - ACRN = NewCode("ACRN") - ADAM = NewCode("ADAM") - ADT = NewCode("ADT") - AIB = NewCode("AIB") - ADZ = NewCode("ADZ") - AECC = NewCode("AECC") - AM = NewCode("AM") - AGRI = NewCode("AGRI") - AGT = NewCode("AGT") - AIR = NewCode("AIR") - ALEX = NewCode("ALEX") - AUM = NewCode("AUM") - ALIEN = NewCode("ALIEN") - ALIS = NewCode("ALIS") - ALL = NewCode("ALL") - ASAFE = NewCode("ASAFE") - AMBER = NewCode("AMBER") - AMS = NewCode("AMS") - ANAL = NewCode("ANAL") - ACP = NewCode("ACP") - ANI = NewCode("ANI") - ANTI = NewCode("ANTI") - ALTC = NewCode("ALTC") - APT = NewCode("APT") - ARCO = NewCode("ARCO") - ALC = NewCode("ALC") - ARB = NewCode("ARB") - ARCT = NewCode("ARCT") - ARCX = NewCode("ARCX") - ARGUS = NewCode("ARGUS") - ARH = NewCode("ARH") - ARM = NewCode("ARM") - ARNA = NewCode("ARNA") - ARPA = NewCode("ARPA") - ARTA = NewCode("ARTA") - ABY = NewCode("ABY") - ARTC = NewCode("ARTC") - AL = NewCode("AL") - ASN = NewCode("ASN") - ADCN = NewCode("ADCN") - ATB = NewCode("ATB") - ATM = NewCode("ATM") - ATMCHA = NewCode("ATMCHA") - ATOM = NewCode("ATOM") - ADC = NewCode("ADC") - ARE = NewCode("ARE") - AUR = NewCode("AUR") - AV = NewCode("AV") - AXIOM = NewCode("AXIOM") - B2B = NewCode("B2B") - B2 = NewCode("B2") - B3 = NewCode("B3") - BAB = NewCode("BAB") - BAN = NewCode("BAN") - BamitCoin = NewCode("BamitCoin") - NANAS = NewCode("NANAS") - BBCC = NewCode("BBCC") - BTA = NewCode("BTA") - BSTK = NewCode("BSTK") - BATL = NewCode("BATL") - BBH = NewCode("BBH") - BITB = NewCode("BITB") - BRDD = NewCode("BRDD") - XBTS = NewCode("XBTS") - BVC = NewCode("BVC") - CHATX = NewCode("CHATX") - BEEP = NewCode("BEEP") - BEEZ = NewCode("BEEZ") - BENJI = NewCode("BENJI") - BERN = NewCode("BERN") - PROFIT = NewCode("PROFIT") - BEST = NewCode("BEST") - BGF = NewCode("BGF") - BIGUP = NewCode("BIGUP") - BLRY = NewCode("BLRY") - BILL = NewCode("BILL") - BIOB = NewCode("BIOB") - BIO = NewCode("BIO") - BIOS = NewCode("BIOS") - BPTN = NewCode("BPTN") - BTCA = NewCode("BTCA") - BA = NewCode("BA") - BAC = NewCode("BAC") - BBT = NewCode("BBT") - BOSS = NewCode("BOSS") - BRONZ = NewCode("BRONZ") - CAT = NewCode("CAT") - BTD = NewCode("BTD") - XBTC21 = NewCode("XBTC21") - BCA = NewCode("BCA") - BCP = NewCode("BCP") - BTDOLL = NewCode("BTDOLL") - LIZA = NewCode("LIZA") - BTCRED = NewCode("BTCRED") - BTCS = NewCode("BTCS") - BTU = NewCode("BTU") - BUM = NewCode("BUM") - LITE = NewCode("LITE") - BCM = NewCode("BCM") - BCS = NewCode("BCS") - BTCU = NewCode("BTCU") - BM = NewCode("BM") - BTCRY = NewCode("BTCRY") - BTCR = NewCode("BTCR") - HIRE = NewCode("HIRE") - STU = NewCode("STU") - BITOK = NewCode("BITOK") - BITON = NewCode("BITON") - BPC = NewCode("BPC") - BPOK = NewCode("BPOK") - BTP = NewCode("BTP") - BITCNY = NewCode("bitCNY") - RNTB = NewCode("RNTB") - BSH = NewCode("BSH") - XBS = NewCode("XBS") - BITS = NewCode("BITS") - BST = NewCode("BST") - BXT = NewCode("BXT") - VEG = NewCode("VEG") - VOLT = NewCode("VOLT") - BTV = NewCode("BTV") - BITZ = NewCode("BITZ") - BTZ = NewCode("BTZ") - BHC = NewCode("BHC") - BDC = NewCode("BDC") - JACK = NewCode("JACK") - BS = NewCode("BS") - BSTAR = NewCode("BSTAR") - BLAZR = NewCode("BLAZR") - BOD = NewCode("BOD") - BLUE = NewCode("BLUE") - BLU = NewCode("BLU") - BLUS = NewCode("BLUS") - BMT = NewCode("BMT") - BOLI = NewCode("BOLI") - BOMB = NewCode("BOMB") - BON = NewCode("BON") - BOOM = NewCode("BOOM") - BOSON = NewCode("BOSON") - BSC = NewCode("BSC") - BRH = NewCode("BRH") - BRAIN = NewCode("BRAIN") - BRE = NewCode("BRE") - BTCM = NewCode("BTCM") - BTCO = NewCode("BTCO") - TALK = NewCode("TALK") - BUB = NewCode("BUB") - BUY = NewCode("BUY") - BUZZ = NewCode("BUZZ") - BTH = NewCode("BTH") - C0C0 = NewCode("C0C0") - CAB = NewCode("CAB") - CF = NewCode("CF") - CLO = NewCode("CLO") - CAM = NewCode("CAM") - CD = NewCode("CD") - CANN = NewCode("CANN") - CNNC = NewCode("CNNC") - CPC = NewCode("CPC") - CST = NewCode("CST") - CAPT = NewCode("CAPT") - CARBON = NewCode("CARBON") - CME = NewCode("CME") - CTK = NewCode("CTK") - CBD = NewCode("CBD") - CCC = NewCode("CCC") - CNT = NewCode("CNT") - XCE = NewCode("XCE") - CHRG = NewCode("CHRG") - CHEMX = NewCode("CHEMX") - CHESS = NewCode("CHESS") - CKS = NewCode("CKS") - CHILL = NewCode("CHILL") - CHIP = NewCode("CHIP") - CHOOF = NewCode("CHOOF") - CRX = NewCode("CRX") - CIN = NewCode("CIN") - POLL = NewCode("POLL") - CLICK = NewCode("CLICK") - CLINT = NewCode("CLINT") - CLUB = NewCode("CLUB") - CLUD = NewCode("CLUD") - COX = NewCode("COX") - COXST = NewCode("COXST") - CFC = NewCode("CFC") - CTIC2 = NewCode("CTIC2") - COIN = NewCode("COIN") - BTTF = NewCode("BTTF") - C2 = NewCode("C2") - CAID = NewCode("CAID") - CL = NewCode("CL") - CTIC = NewCode("CTIC") - CXT = NewCode("CXT") - CHP = NewCode("CHP") - CV2 = NewCode("CV2") - COC = NewCode("COC") - COMP = NewCode("COMP") - CMS = NewCode("CMS") - CONX = NewCode("CONX") - CCX = NewCode("CCX") - CLR = NewCode("CLR") - CORAL = NewCode("CORAL") - CORG = NewCode("CORG") - CSMIC = NewCode("CSMIC") - CMC = NewCode("CMC") - COV = NewCode("COV") - COVX = NewCode("COVX") - CRAB = NewCode("CRAB") - CRAFT = NewCode("CRAFT") - CRNK = NewCode("CRNK") - CRAVE = NewCode("CRAVE") - CRM = NewCode("CRM") - XCRE = NewCode("XCRE") - CREDIT = NewCode("CREDIT") - CREVA = NewCode("CREVA") - CRIME = NewCode("CRIME") - CROC = NewCode("CROC") - CRC = NewCode("CRC") - CRW = NewCode("CRW") - CRY = NewCode("CRY") - CBX = NewCode("CBX") - TKTX = NewCode("TKTX") - CB = NewCode("CB") - CIRC = NewCode("CIRC") - CCB = NewCode("CCB") - CDO = NewCode("CDO") - CG = NewCode("CG") - CJ = NewCode("CJ") - CJC = NewCode("CJC") - CYT = NewCode("CYT") - CRPS = NewCode("CRPS") - PING = NewCode("PING") - CWXT = NewCode("CWXT") - CCT = NewCode("CCT") - CTL = NewCode("CTL") - CURVES = NewCode("CURVES") - CC = NewCode("CC") - CYC = NewCode("CYC") - CYG = NewCode("CYG") - CYP = NewCode("CYP") - FUNK = NewCode("FUNK") - CZECO = NewCode("CZECO") - DALC = NewCode("DALC") - DLISK = NewCode("DLISK") - MOOND = NewCode("MOOND") - DB = NewCode("DB") - DCC = NewCode("DCC") - DCYP = NewCode("DCYP") - DETH = NewCode("DETH") - DKC = NewCode("DKC") - DISK = NewCode("DISK") - DRKT = NewCode("DRKT") - DTT = NewCode("DTT") - DASHS = NewCode("DASHS") - DBTC = NewCode("DBTC") - DCT = NewCode("DCT") - DBET = NewCode("DBET") - DEC = NewCode("DEC") - DECR = NewCode("DECR") - DEA = NewCode("DEA") - DPAY = NewCode("DPAY") - DCRE = NewCode("DCRE") - DC = NewCode("DC") - DES = NewCode("DES") - DEM = NewCode("DEM") - DXC = NewCode("DXC") - DCK = NewCode("DCK") - CUBE = NewCode("CUBE") - DGMS = NewCode("DGMS") - DBG = NewCode("DBG") - DGCS = NewCode("DGCS") - DBLK = NewCode("DBLK") - DIME = NewCode("DIME") - DIRT = NewCode("DIRT") - DVD = NewCode("DVD") - DMT = NewCode("DMT") - NOTE = NewCode("NOTE") - DGORE = NewCode("DGORE") - DLC = NewCode("DLC") - DRT = NewCode("DRT") - DOTA = NewCode("DOTA") - DOX = NewCode("DOX") - DRA = NewCode("DRA") - DFT = NewCode("DFT") - XDB = NewCode("XDB") - DRM = NewCode("DRM") - DRZ = NewCode("DRZ") - DRACO = NewCode("DRACO") - DBIC = NewCode("DBIC") - DUB = NewCode("DUB") - GUM = NewCode("GUM") - DUR = NewCode("DUR") - DUST = NewCode("DUST") - DUX = NewCode("DUX") - DXO = NewCode("DXO") - ECN = NewCode("ECN") - EDR2 = NewCode("EDR2") - EA = NewCode("EA") - EAGS = NewCode("EAGS") - EMT = NewCode("EMT") - EBONUS = NewCode("EBONUS") - ECCHI = NewCode("ECCHI") - EKO = NewCode("EKO") - ECLI = NewCode("ECLI") - ECOB = NewCode("ECOB") - ECO = NewCode("ECO") - EDIT = NewCode("EDIT") - EDRC = NewCode("EDRC") - EDC = NewCode("EDC") - EGAME = NewCode("EGAME") - EGG = NewCode("EGG") - EGO = NewCode("EGO") - ELC = NewCode("ELC") - ELCO = NewCode("ELCO") - ECA = NewCode("ECA") - EPC = NewCode("EPC") - ELE = NewCode("ELE") - ONE337 = NewCode("1337") - EMB = NewCode("EMB") - EMC = NewCode("EMC") - EPY = NewCode("EPY") - EMPC = NewCode("EMPC") - EMP = NewCode("EMP") - ENE = NewCode("ENE") - EET = NewCode("EET") - XNG = NewCode("XNG") - EGMA = NewCode("EGMA") - ENTER = NewCode("ENTER") - ETRUST = NewCode("ETRUST") - EQL = NewCode("EQL") - EQM = NewCode("EQM") - EQT = NewCode("EQT") - ERR = NewCode("ERR") - ESC = NewCode("ESC") - ESP = NewCode("ESP") - ENT = NewCode("ENT") - ETCO = NewCode("ETCO") - DOGETH = NewCode("DOGETH") - ECASH = NewCode("ECASH") - ELITE = NewCode("ELITE") - ETHS = NewCode("ETHS") - ETL = NewCode("ETL") - ETZ = NewCode("ETZ") - EUC = NewCode("EUC") - EURC = NewCode("EURC") - EUROPE = NewCode("EUROPE") - EVA = NewCode("EVA") - EGC = NewCode("EGC") - EOC = NewCode("EOC") - EVIL = NewCode("EVIL") - EVO = NewCode("EVO") - EXB = NewCode("EXB") - EXIT = NewCode("EXIT") - XT = NewCode("XT") - F16 = NewCode("F16") - FADE = NewCode("FADE") - FAZZ = NewCode("FAZZ") - FX = NewCode("FX") - FIDEL = NewCode("FIDEL") - FIDGT = NewCode("FIDGT") - FIND = NewCode("FIND") - FPC = NewCode("FPC") - FIRE = NewCode("FIRE") - FFC = NewCode("FFC") - FRST = NewCode("FRST") - FIST = NewCode("FIST") - FIT = NewCode("FIT") - FLX = NewCode("FLX") - FLVR = NewCode("FLVR") - FLY = NewCode("FLY") - FONZ = NewCode("FONZ") - XFCX = NewCode("XFCX") - FOREX = NewCode("FOREX") - FRN = NewCode("FRN") - FRK = NewCode("FRK") - FRWC = NewCode("FRWC") - FGZ = NewCode("FGZ") - FRE = NewCode("FRE") - FRDC = NewCode("FRDC") - FJC = NewCode("FJC") - FURY = NewCode("FURY") - FSN = NewCode("FSN") - FCASH = NewCode("FCASH") - FTO = NewCode("FTO") - FUZZ = NewCode("FUZZ") - GAKH = NewCode("GAKH") - GBT = NewCode("GBT") - UNITS = NewCode("UNITS") - FOUR20G = NewCode("420G") - GENIUS = NewCode("GENIUS") - GEN = NewCode("GEN") - GEO = NewCode("GEO") - GER = NewCode("GER") - GSR = NewCode("GSR") - SPKTR = NewCode("SPKTR") - GIFT = NewCode("GIFT") - WTT = NewCode("WTT") - GHS = NewCode("GHS") - GIG = NewCode("GIG") - GOT = NewCode("GOT") - XGTC = NewCode("XGTC") - GIZ = NewCode("GIZ") - GLO = NewCode("GLO") - GCR = NewCode("GCR") - BSTY = NewCode("BSTY") - GLC = NewCode("GLC") - GSX = NewCode("GSX") - GOAT = NewCode("GOAT") - GB = NewCode("GB") - GFL = NewCode("GFL") - MNTP = NewCode("MNTP") - GP = NewCode("GP") - GLUCK = NewCode("GLUCK") - GOON = NewCode("GOON") - GTFO = NewCode("GTFO") - GOTX = NewCode("GOTX") - GPU = NewCode("GPU") - GRF = NewCode("GRF") - GRAM = NewCode("GRAM") - GRAV = NewCode("GRAV") - GBIT = NewCode("GBIT") - GREED = NewCode("GREED") - GE = NewCode("GE") - GREENF = NewCode("GREENF") - GRE = NewCode("GRE") - GREXIT = NewCode("GREXIT") - GMCX = NewCode("GMCX") - GROW = NewCode("GROW") - GSM = NewCode("GSM") - GT = NewCode("GT") - NLG = NewCode("NLG") - HKN = NewCode("HKN") - HAC = NewCode("HAC") - HALLO = NewCode("HALLO") - HAMS = NewCode("HAMS") - HPC = NewCode("HPC") - HAWK = NewCode("HAWK") - HAZE = NewCode("HAZE") - HZT = NewCode("HZT") - HDG = NewCode("HDG") - HEDG = NewCode("HEDG") - HEEL = NewCode("HEEL") - HMP = NewCode("HMP") - PLAY = NewCode("PLAY") - HXX = NewCode("HXX") - XHI = NewCode("XHI") - HVCO = NewCode("HVCO") - HTC = NewCode("HTC") - MINH = NewCode("MINH") - HODL = NewCode("HODL") - HON = NewCode("HON") - HOPE = NewCode("HOPE") - HQX = NewCode("HQX") - HSP = NewCode("HSP") - HTML5 = NewCode("HTML5") - HYPERX = NewCode("HYPERX") - HPS = NewCode("HPS") - IOC = NewCode("IOC") - IBANK = NewCode("IBANK") - IBITS = NewCode("IBITS") - ICASH = NewCode("ICASH") - ICOB = NewCode("ICOB") - ICON = NewCode("ICON") - IETH = NewCode("IETH") - ILM = NewCode("ILM") - IMPS = NewCode("IMPS") - NKA = NewCode("NKA") - INCP = NewCode("INCP") - IN = NewCode("IN") - INC = NewCode("INC") - IMS = NewCode("IMS") - IFLT = NewCode("IFLT") - INFX = NewCode("INFX") - INGT = NewCode("INGT") - INPAY = NewCode("INPAY") - INSANE = NewCode("INSANE") - INXT = NewCode("INXT") - IFT = NewCode("IFT") - INV = NewCode("INV") - IVZ = NewCode("IVZ") - ILT = NewCode("ILT") - IONX = NewCode("IONX") - ISL = NewCode("ISL") - ITI = NewCode("ITI") - ING = NewCode("ING") - IEC = NewCode("IEC") - IW = NewCode("IW") - IXC = NewCode("IXC") - IXT = NewCode("IXT") - JPC = NewCode("JPC") - JANE = NewCode("JANE") - JWL = NewCode("JWL") - JIF = NewCode("JIF") - JOBS = NewCode("JOBS") - JOCKER = NewCode("JOCKER") - JW = NewCode("JW") - JOK = NewCode("JOK") - XJO = NewCode("XJO") - KGB = NewCode("KGB") - KARMC = NewCode("KARMC") - KARMA = NewCode("KARMA") - KASHH = NewCode("KASHH") - KAT = NewCode("KAT") - KC = NewCode("KC") - KIDS = NewCode("KIDS") - KIN = NewCode("KIN") - KISS = NewCode("KISS") - KOBO = NewCode("KOBO") - TP1 = NewCode("TP1") - KRAK = NewCode("KRAK") - KGC = NewCode("KGC") - KTK = NewCode("KTK") - KR = NewCode("KR") - KUBO = NewCode("KUBO") - KURT = NewCode("KURT") - KUSH = NewCode("KUSH") - LANA = NewCode("LANA") - LTH = NewCode("LTH") - LAZ = NewCode("LAZ") - LEA = NewCode("LEA") - LEAF = NewCode("LEAF") - LENIN = NewCode("LENIN") - LEPEN = NewCode("LEPEN") - LIR = NewCode("LIR") - LVG = NewCode("LVG") - LGBTQ = NewCode("LGBTQ") - LHC = NewCode("LHC") - EXT = NewCode("EXT") - LBTC = NewCode("LBTC") - LSD = NewCode("LSD") - LIMX = NewCode("LIMX") - LTD = NewCode("LTD") - LINDA = NewCode("LINDA") - LKC = NewCode("LKC") - LBTCX = NewCode("LBTCX") - LCC = NewCode("LCC") - LTCU = NewCode("LTCU") - LTCR = NewCode("LTCR") - LDOGE = NewCode("LDOGE") - LTS = NewCode("LTS") - LIV = NewCode("LIV") - LIZI = NewCode("LIZI") - LOC = NewCode("LOC") - LOCX = NewCode("LOCX") - LOOK = NewCode("LOOK") - LOOT = NewCode("LOOT") - XLTCG = NewCode("XLTCG") - BASH = NewCode("BASH") - LUCKY = NewCode("LUCKY") - L7S = NewCode("L7S") - LDM = NewCode("LDM") - LUMI = NewCode("LUMI") - LUNA = NewCode("LUNA") - LC = NewCode("LC") - LUX = NewCode("LUX") - MCRN = NewCode("MCRN") - XMG = NewCode("XMG") - MMXIV = NewCode("MMXIV") - MAT = NewCode("MAT") - MAO = NewCode("MAO") - MAPC = NewCode("MAPC") - MRB = NewCode("MRB") - MXT = NewCode("MXT") - MARV = NewCode("MARV") - MARX = NewCode("MARX") - MCAR = NewCode("MCAR") - MM = NewCode("MM") - MVC = NewCode("MVC") - MAVRO = NewCode("MAVRO") - MAX = NewCode("MAX") - MAZE = NewCode("MAZE") - MBIT = NewCode("MBIT") - MCOIN = NewCode("MCOIN") - MPRO = NewCode("MPRO") - XMS = NewCode("XMS") - MLITE = NewCode("MLITE") - MLNC = NewCode("MLNC") - MENTAL = NewCode("MENTAL") - MERGEC = NewCode("MERGEC") - MTLMC3 = NewCode("MTLMC3") - METAL = NewCode("METAL") - MUU = NewCode("MUU") - MILO = NewCode("MILO") - MND = NewCode("MND") - XMINE = NewCode("XMINE") - MNM = NewCode("MNM") - XNM = NewCode("XNM") - MIRO = NewCode("MIRO") - MIS = NewCode("MIS") - MMXVI = NewCode("MMXVI") - MOIN = NewCode("MOIN") - MOJO = NewCode("MOJO") - TAB = NewCode("TAB") - MONETA = NewCode("MONETA") - MUE = NewCode("MUE") - MONEY = NewCode("MONEY") - MRP = NewCode("MRP") - MOTO = NewCode("MOTO") - MULTI = NewCode("MULTI") - MST = NewCode("MST") - MVR = NewCode("MVR") - MYSTIC = NewCode("MYSTIC") - WISH = NewCode("WISH") - NKT = NewCode("NKT") - NAT = NewCode("NAT") - ENAU = NewCode("ENAU") - NEBU = NewCode("NEBU") - NEF = NewCode("NEF") - NBIT = NewCode("NBIT") - NETKO = NewCode("NETKO") - NTM = NewCode("NTM") - NETC = NewCode("NETC") - NRC = NewCode("NRC") - NTK = NewCode("NTK") - NTRN = NewCode("NTRN") - NEVA = NewCode("NEVA") - NIC = NewCode("NIC") - NKC = NewCode("NKC") - NYC = NewCode("NYC") - NZC = NewCode("NZC") - NICE = NewCode("NICE") - NDOGE = NewCode("NDOGE") - XTR = NewCode("XTR") - N2O = NewCode("N2O") - NIXON = NewCode("NIXON") - NOC = NewCode("NOC") - NODC = NewCode("NODC") - NODES = NewCode("NODES") - NODX = NewCode("NODX") - NLC = NewCode("NLC") - NLC2 = NewCode("NLC2") - NOO = NewCode("NOO") - NVC = NewCode("NVC") - NPC = NewCode("NPC") - NUBIS = NewCode("NUBIS") - NUKE = NewCode("NUKE") - N7 = NewCode("N7") - NUM = NewCode("NUM") - NMR = NewCode("NMR") - NXE = NewCode("NXE") - OBS = NewCode("OBS") - OCEAN = NewCode("OCEAN") - OCOW = NewCode("OCOW") - EIGHT88 = NewCode("888") - OCC = NewCode("OCC") - OK = NewCode("OK") - ODNT = NewCode("ODNT") - FLAV = NewCode("FLAV") - OLIT = NewCode("OLIT") - OLYMP = NewCode("OLYMP") - OMA = NewCode("OMA") - OMC = NewCode("OMC") - ONEK = NewCode("ONEK") - ONX = NewCode("ONX") - XPO = NewCode("XPO") - OPAL = NewCode("OPAL") - OTN = NewCode("OTN") - OP = NewCode("OP") - OPES = NewCode("OPES") - OPTION = NewCode("OPTION") - ORLY = NewCode("ORLY") - OS76 = NewCode("OS76") - OZC = NewCode("OZC") - P7C = NewCode("P7C") - PAC = NewCode("PAC") - PAK = NewCode("PAK") - PAL = NewCode("PAL") - PND = NewCode("PND") - PINKX = NewCode("PINKX") - POPPY = NewCode("POPPY") - DUO = NewCode("DUO") - PARA = NewCode("PARA") - PKB = NewCode("PKB") - GENE = NewCode("GENE") - PARTY = NewCode("PARTY") - PYN = NewCode("PYN") - XPY = NewCode("XPY") - CON = NewCode("CON") - PAYP = NewCode("PAYP") - GUESS = NewCode("GUESS") - PEN = NewCode("PEN") - PTA = NewCode("PTA") - PEO = NewCode("PEO") - PSB = NewCode("PSB") - XPD = NewCode("XPD") - PXL = NewCode("PXL") - PHR = NewCode("PHR") - PIE = NewCode("PIE") - PIO = NewCode("PIO") - PIPR = NewCode("PIPR") - SKULL = NewCode("SKULL") - PLANET = NewCode("PLANET") - PNC = NewCode("PNC") - XPTX = NewCode("XPTX") - PLNC = NewCode("PLNC") - XPS = NewCode("XPS") - POKE = NewCode("POKE") - PLBT = NewCode("PLBT") - POM = NewCode("POM") - PONZ2 = NewCode("PONZ2") - PONZI = NewCode("PONZI") - XSP = NewCode("XSP") - XPC = NewCode("XPC") - PEX = NewCode("PEX") - TRON = NewCode("TRON") - POST = NewCode("POST") - POSW = NewCode("POSW") - PWR = NewCode("PWR") - POWER = NewCode("POWER") - PRE = NewCode("PRE") - PRS = NewCode("PRS") - PXI = NewCode("PXI") - PEXT = NewCode("PEXT") - PRIMU = NewCode("PRIMU") - PRX = NewCode("PRX") - PRM = NewCode("PRM") - PRIX = NewCode("PRIX") - XPRO = NewCode("XPRO") - PCM = NewCode("PCM") - PROC = NewCode("PROC") - NANOX = NewCode("NANOX") - VRP = NewCode("VRP") - PTY = NewCode("PTY") - PSI = NewCode("PSI") - PSY = NewCode("PSY") - PULSE = NewCode("PULSE") - PUPA = NewCode("PUPA") - PURE = NewCode("PURE") - VIDZ = NewCode("VIDZ") - PUTIN = NewCode("PUTIN") - PX = NewCode("PX") - QTM = NewCode("QTM") - QTZ = NewCode("QTZ") - QBC = NewCode("QBC") - XQN = NewCode("XQN") - RBBT = NewCode("RBBT") - RAC = NewCode("RAC") - RADI = NewCode("RADI") - RAD = NewCode("RAD") - RAI = NewCode("RAI") - XRA = NewCode("XRA") - RATIO = NewCode("RATIO") - REA = NewCode("REA") - RCX = NewCode("RCX") - REE = NewCode("REE") - REC = NewCode("REC") - RMS = NewCode("RMS") - RBIT = NewCode("RBIT") - RNC = NewCode("RNC") - REV = NewCode("REV") - RH = NewCode("RH") - XRL = NewCode("XRL") - RICE = NewCode("RICE") - RICHX = NewCode("RICHX") - RID = NewCode("RID") - RIDE = NewCode("RIDE") - RBT = NewCode("RBT") - RING = NewCode("RING") - RIO = NewCode("RIO") - RISE = NewCode("RISE") - ROCKET = NewCode("ROCKET") - RPC = NewCode("RPC") - ROS = NewCode("ROS") - ROYAL = NewCode("ROYAL") - RSGP = NewCode("RSGP") - RBIES = NewCode("RBIES") - RUBIT = NewCode("RUBIT") - RBY = NewCode("RBY") - RUC = NewCode("RUC") - RUPX = NewCode("RUPX") - RUP = NewCode("RUP") - RUST = NewCode("RUST") - SFE = NewCode("SFE") - SLS = NewCode("SLS") - SMSR = NewCode("SMSR") - RONIN = NewCode("RONIN") - STV = NewCode("STV") - HIFUN = NewCode("HIFUN") - MAD = NewCode("MAD") - SANDG = NewCode("SANDG") - STO = NewCode("STO") - SCAN = NewCode("SCAN") - SCITW = NewCode("SCITW") - SCRPT = NewCode("SCRPT") - SCRT = NewCode("SCRT") - SED = NewCode("SED") - SEEDS = NewCode("SEEDS") - B2X = NewCode("B2X") - SEL = NewCode("SEL") - SLFI = NewCode("SLFI") - SMBR = NewCode("SMBR") - SEN = NewCode("SEN") - SENT = NewCode("SENT") - SRNT = NewCode("SRNT") - SEV = NewCode("SEV") - SP = NewCode("SP") - SXC = NewCode("SXC") - GELD = NewCode("GELD") - SHDW = NewCode("SHDW") - SDC = NewCode("SDC") - SAK = NewCode("SAK") - SHRP = NewCode("SHRP") - SHELL = NewCode("SHELL") - SH = NewCode("SH") - SHORTY = NewCode("SHORTY") - SHREK = NewCode("SHREK") - SHRM = NewCode("SHRM") - SIB = NewCode("SIB") - SIGT = NewCode("SIGT") - SLCO = NewCode("SLCO") - SIGU = NewCode("SIGU") - SIX = NewCode("SIX") - SJW = NewCode("SJW") - SKB = NewCode("SKB") - SW = NewCode("SW") - SLEEP = NewCode("SLEEP") - SLING = NewCode("SLING") - SMART = NewCode("SMART") - SMC = NewCode("SMC") - SMF = NewCode("SMF") - SOCC = NewCode("SOCC") - SCL = NewCode("SCL") - SDAO = NewCode("SDAO") - SOLAR = NewCode("SOLAR") - SOLO = NewCode("SOLO") - SCT = NewCode("SCT") - SONG = NewCode("SONG") - ALTCOM = NewCode("ALTCOM") - SPHTX = NewCode("SPHTX") - SPC = NewCode("SPC") - SPACE = NewCode("SPACE") - SBT = NewCode("SBT") - SPEC = NewCode("SPEC") - SPX = NewCode("SPX") - SCS = NewCode("SCS") - SPORT = NewCode("SPORT") - SPT = NewCode("SPT") - SPR = NewCode("SPR") - SPEX = NewCode("SPEX") - SQL = NewCode("SQL") - SBIT = NewCode("SBIT") - STHR = NewCode("STHR") - STALIN = NewCode("STALIN") - STAR = NewCode("STAR") - STA = NewCode("STA") - START = NewCode("START") - STP = NewCode("STP") - PNK = NewCode("PNK") - STEPS = NewCode("STEPS") - STK = NewCode("STK") - STONK = NewCode("STONK") - STS = NewCode("STS") - STRP = NewCode("STRP") - STY = NewCode("STY") - XMT = NewCode("XMT") - SSTC = NewCode("SSTC") - SUPER = NewCode("SUPER") - SRND = NewCode("SRND") - STRB = NewCode("STRB") - M1 = NewCode("M1") - SPM = NewCode("SPM") - BUCKS = NewCode("BUCKS") - TOKEN = NewCode("TOKEN") - SWT = NewCode("SWT") - SWEET = NewCode("SWEET") - SWING = NewCode("SWING") - CHSB = NewCode("CHSB") - SIC = NewCode("SIC") - SDP = NewCode("SDP") - XSY = NewCode("XSY") - SYNX = NewCode("SYNX") - SNRG = NewCode("SNRG") - TAG = NewCode("TAG") - TAGR = NewCode("TAGR") - TAJ = NewCode("TAJ") - TAK = NewCode("TAK") - TAKE = NewCode("TAKE") - TAM = NewCode("TAM") - XTO = NewCode("XTO") - TAP = NewCode("TAP") - TLE = NewCode("TLE") - TSE = NewCode("TSE") - TLEX = NewCode("TLEX") - TAXI = NewCode("TAXI") - TCN = NewCode("TCN") - TDFB = NewCode("TDFB") - TEAM = NewCode("TEAM") - TECH = NewCode("TECH") - TEC = NewCode("TEC") - TEK = NewCode("TEK") - TB = NewCode("TB") - TLX = NewCode("TLX") - TELL = NewCode("TELL") - TENNET = NewCode("TENNET") - TES = NewCode("TES") - TGS = NewCode("TGS") - XVE = NewCode("XVE") - TCR = NewCode("TCR") - GCC = NewCode("GCC") - MAY = NewCode("MAY") - THOM = NewCode("THOM") - TIA = NewCode("TIA") - TIDE = NewCode("TIDE") - TIE = NewCode("TIE") - TIT = NewCode("TIT") - TTC = NewCode("TTC") - TODAY = NewCode("TODAY") - TBX = NewCode("TBX") - TDS = NewCode("TDS") - TLOSH = NewCode("TLOSH") - TOKC = NewCode("TOKC") - TMRW = NewCode("TMRW") - TOOL = NewCode("TOOL") - TCX = NewCode("TCX") - TOT = NewCode("TOT") - TX = NewCode("TX") - TRANSF = NewCode("TRANSF") - TRAP = NewCode("TRAP") - TBCX = NewCode("TBCX") - TRICK = NewCode("TRICK") - TPG = NewCode("TPG") - TFL = NewCode("TFL") - TRUMP = NewCode("TRUMP") - TNG = NewCode("TNG") - TUR = NewCode("TUR") - TWERK = NewCode("TWERK") - TWIST = NewCode("TWIST") - TWO = NewCode("TWO") - UCASH = NewCode("UCASH") - UAE = NewCode("UAE") - XBU = NewCode("XBU") - UBQ = NewCode("UBQ") - U = NewCode("U") - UDOWN = NewCode("UDOWN") - GAIN = NewCode("GAIN") - USC = NewCode("USC") - UMC = NewCode("UMC") - UNF = NewCode("UNF") - UNIFY = NewCode("UNIFY") - USDE = NewCode("USDE") - UBTC = NewCode("UBTC") - UIS = NewCode("UIS") - UNIT = NewCode("UNIT") - UNI = NewCode("UNI") - UXC = NewCode("UXC") - URC = NewCode("URC") - XUP = NewCode("XUP") - UFR = NewCode("UFR") - URO = NewCode("URO") - UTLE = NewCode("UTLE") - VAL = NewCode("VAL") - VPRC = NewCode("VPRC") - VAPOR = NewCode("VAPOR") - VCOIN = NewCode("VCOIN") - VEC = NewCode("VEC") - VEC2 = NewCode("VEC2") - VLT = NewCode("VLT") - VENE = NewCode("VENE") - VNTX = NewCode("VNTX") - VTN = NewCode("VTN") - CRED = NewCode("CRED") - VERS = NewCode("VERS") - VTX = NewCode("VTX") - VTY = NewCode("VTY") - VIP = NewCode("VIP") - VISIO = NewCode("VISIO") - VK = NewCode("VK") - VOL = NewCode("VOL") - VOYA = NewCode("VOYA") - VPN = NewCode("VPN") - XVS = NewCode("XVS") - VTL = NewCode("VTL") - VULC = NewCode("VULC") - VVI = NewCode("VVI") - WGR = NewCode("WGR") - WAM = NewCode("WAM") - WARP = NewCode("WARP") - WASH = NewCode("WASH") - WGO = NewCode("WGO") - WAY = NewCode("WAY") - WCASH = NewCode("WCASH") - WEALTH = NewCode("WEALTH") - WEEK = NewCode("WEEK") - WHO = NewCode("WHO") - WIC = NewCode("WIC") - WBB = NewCode("WBB") - WINE = NewCode("WINE") - WINK = NewCode("WINK") - WISC = NewCode("WISC") - WITCH = NewCode("WITCH") - WMC = NewCode("WMC") - WOMEN = NewCode("WOMEN") - WOK = NewCode("WOK") - WRT = NewCode("WRT") - XCO = NewCode("XCO") - X2 = NewCode("X2") - XNX = NewCode("XNX") - XAU = NewCode("XAU") - XAV = NewCode("XAV") - XDE2 = NewCode("XDE2") - XDE = NewCode("XDE") - XIOS = NewCode("XIOS") - XOC = NewCode("XOC") - XSSX = NewCode("XSSX") - XBY = NewCode("XBY") - YAC = NewCode("YAC") - YMC = NewCode("YMC") - YAY = NewCode("YAY") - YBC = NewCode("YBC") - YES = NewCode("YES") - YOB2X = NewCode("YOB2X") - YOVI = NewCode("YOVI") - ZYD = NewCode("ZYD") - ZECD = NewCode("ZECD") - ZEIT = NewCode("ZEIT") - ZENI = NewCode("ZENI") - ZET2 = NewCode("ZET2") - ZET = NewCode("ZET") - ZMC = NewCode("ZMC") - ZIRK = NewCode("ZIRK") - ZLQ = NewCode("ZLQ") - ZNE = NewCode("ZNE") - ZONTO = NewCode("ZONTO") - ZOOM = NewCode("ZOOM") - ZRC = NewCode("ZRC") - ZUR = NewCode("ZUR") - ZB = NewCode("ZB") - QC = NewCode("QC") - HLC = NewCode("HLC") - SAFE = NewCode("SAFE") - BTN = NewCode("BTN") - CDC = NewCode("CDC") - DDM = NewCode("DDM") - HOTC = NewCode("HOTC") - BDS = NewCode("BDS") - AAA = NewCode("AAA") - XWC = NewCode("XWC") - PDX = NewCode("PDX") - SLT = NewCode("SLT") - HPY = NewCode("HPY") - XXRP = NewCode("XXRP") // XRP - XXBT = NewCode("XXBT") // BTC, but XXBT instead - XXDG = NewCode("XXDG") // DOGE - XDG = NewCode("XDG") // DOGE - HKD = NewCode("HKD") // Hong Kong Dollar - AUD = NewCode("AUD") // Australian Dollar - USD = NewCode("USD") // United States Dollar - ZUSD = NewCode("ZUSD") // United States Dollar, but with a Z in front of it - EUR = NewCode("EUR") // Euro - ZEUR = NewCode("ZEUR") // Euro, but with a Z in front of it - CAD = NewCode("CAD") // Canadaian Dollar - ZCAD = NewCode("ZCAD") // Canadaian Dollar, but with a Z in front of it - SGD = NewCode("SGD") // Singapore Dollar - RUB = NewCode("RUB") // RUssian ruBle - RUR = NewCode("RUR") // RUssian Ruble - PLN = NewCode("PLN") // Polish zÅ‚oty - TRY = NewCode("TRY") // Turkish lira - UAH = NewCode("UAH") // Ukrainian hryvnia - JPY = NewCode("JPY") // Japanese yen - ZJPY = NewCode("ZJPY") // Japanese yen, but with a Z in front of it - LCH = NewCode("LCH") - MYR = NewCode("MYR") - AFN = NewCode("AFN") - ARS = NewCode("ARS") - AWG = NewCode("AWG") - AZN = NewCode("AZN") - BSD = NewCode("BSD") - BBD = NewCode("BBD") - BYN = NewCode("BYN") - BZD = NewCode("BZD") - BMD = NewCode("BMD") - BOB = NewCode("BOB") - BAM = NewCode("BAM") - BWP = NewCode("BWP") - BGN = NewCode("BGN") - BRL = NewCode("BRL") - BND = NewCode("BND") - KHR = NewCode("KHR") - KYD = NewCode("KYD") - CLP = NewCode("CLP") - CNY = NewCode("CNY") - COP = NewCode("COP") - HRK = NewCode("HRK") - CUP = NewCode("CUP") - CZK = NewCode("CZK") - DKK = NewCode("DKK") - DOP = NewCode("DOP") - XCD = NewCode("XCD") - EGP = NewCode("EGP") - SVC = NewCode("SVC") - FKP = NewCode("FKP") - FJD = NewCode("FJD") - GIP = NewCode("GIP") - GTQ = NewCode("GTQ") - GGP = NewCode("GGP") - GYD = NewCode("GYD") - HNL = NewCode("HNL") - HUF = NewCode("HUF") - ISK = NewCode("ISK") - INR = NewCode("INR") - IDR = NewCode("IDR") - IRR = NewCode("IRR") - IMP = NewCode("IMP") - ILS = NewCode("ILS") - JMD = NewCode("JMD") - JEP = NewCode("JEP") - KZT = NewCode("KZT") - KPW = NewCode("KPW") - KGS = NewCode("KGS") - LAK = NewCode("LAK") - LBP = NewCode("LBP") - LRD = NewCode("LRD") - MKD = NewCode("MKD") - MUR = NewCode("MUR") - MXN = NewCode("MXN") - MNT = NewCode("MNT") - MZN = NewCode("MZN") - NAD = NewCode("NAD") - NPR = NewCode("NPR") - ANG = NewCode("ANG") - NZD = NewCode("NZD") - NIO = NewCode("NIO") - NGN = NewCode("NGN") - NOK = NewCode("NOK") - OMR = NewCode("OMR") - PKR = NewCode("PKR") - PAB = NewCode("PAB") - PYG = NewCode("PYG") - PHP = NewCode("PHP") - QAR = NewCode("QAR") - RON = NewCode("RON") - SHP = NewCode("SHP") - SAR = NewCode("SAR") - RSD = NewCode("RSD") - SCR = NewCode("SCR") - SOS = NewCode("SOS") - ZAR = NewCode("ZAR") - LKR = NewCode("LKR") - SEK = NewCode("SEK") - CHF = NewCode("CHF") - SRD = NewCode("SRD") - SYP = NewCode("SYP") - TWD = NewCode("TWD") - THB = NewCode("THB") - TTD = NewCode("TTD") - TVD = NewCode("TVD") - GBP = NewCode("GBP") - UYU = NewCode("UYU") - UZS = NewCode("UZS") - VEF = NewCode("VEF") - VND = NewCode("VND") - YER = NewCode("YER") - ZWD = NewCode("ZWD") - XETH = NewCode("XETH") - FX_BTC = NewCode("FX_BTC") // nolint // Cryptocurrency code - AAVE = NewCode("AAVE") - YFI = NewCode("YFI") - BAL = NewCode("BAL") - UMA = NewCode("UMA") - SNX = NewCode("SNX") - CRV = NewCode("CRV") - OXT = NewCode("OXT") - BUSD = NewCode("BUSD") - SRM = NewCode("SRM") - FTT = NewCode("FTT") - UST = NewCode("UST") + BTC = NewCode("BTC") + LTC = NewCode("LTC") + ETH = NewCode("ETH") + XRP = NewCode("XRP") + BCH = NewCode("BCH") + EOS = NewCode("EOS") + XLM = NewCode("XLM") + USDT = NewCode("USDT") + USDC = NewCode("USDC") + ADA = NewCode("ADA") + XMR = NewCode("XMR") + TRX = NewCode("TRX") + MIOTA = NewCode("MIOTA") + DASH = NewCode("DASH") + BNB = NewCode("BNB") + NEO = NewCode("NEO") + ETC = NewCode("ETC") + XEM = NewCode("XEM") + XTZ = NewCode("XTZ") + VET = NewCode("VET") + DOGE = NewCode("DOGE") + ZEC = NewCode("ZEC") + OMG = NewCode("OMG") + BTG = NewCode("BTG") + MKR = NewCode("MKR") + BCN = NewCode("BCN") + ONT = NewCode("ONT") + ZRX = NewCode("ZRX") + LSK = NewCode("LSK") + DCR = NewCode("DCR") + QTUM = NewCode("QTUM") + BCD = NewCode("BCD") + BTS = NewCode("BTS") + NANO = NewCode("NANO") + ZIL = NewCode("ZIL") + SC = NewCode("SC") + DGB = NewCode("DGB") + ICX = NewCode("ICX") + STEEM = NewCode("STEEM") + AE = NewCode("AE") + XVG = NewCode("XVG") + WAVES = NewCode("WAVES") + NPXS = NewCode("NPXS") + ETN = NewCode("ETN") + BTM = NewCode("BTM") + BAT = NewCode("BAT") + ETP = NewCode("ETP") + HOT = NewCode("HOT") + STRAT = NewCode("STRAT") // nolint // Cryptocurrency code + GNT = NewCode("GNT") + REP = NewCode("REP") + SNT = NewCode("SNT") + PPT = NewCode("PPT") + KMD = NewCode("KMD") + TUSD = NewCode("TUSD") + CNX = NewCode("CNX") + LINK = NewCode("LINK") + WTC = NewCode("WTC") + ARDR = NewCode("ARDR") + WAN = NewCode("WAN") + MITH = NewCode("MITH") + RDD = NewCode("RDD") + IOST = NewCode("IOST") + IOT = NewCode("IOT") + KCS = NewCode("KCS") + MAID = NewCode("MAID") + XET = NewCode("XET") + MOAC = NewCode("MOAC") + HC = NewCode("HC") + AION = NewCode("AION") + AOA = NewCode("AOA") + HT = NewCode("HT") + ELF = NewCode("ELF") + LRC = NewCode("LRC") + BNT = NewCode("BNT") + CMT = NewCode("CMT") + DGD = NewCode("DGD") + DCN = NewCode("DCN") + FUN = NewCode("FUN") + GXS = NewCode("GXS") + DROP = NewCode("DROP") + MANA = NewCode("MANA") + PAY = NewCode("PAY") + MCO = NewCode("MCO") + THETA = NewCode("THETA") + NXT = NewCode("NXT") + NOAH = NewCode("NOAH") + LOOM = NewCode("LOOM") + POWR = NewCode("POWR") + WAX = NewCode("WAX") + ELA = NewCode("ELA") + PIVX = NewCode("PIVX") + XIN = NewCode("XIN") + DAI = NewCode("DAI") + BTCP = NewCode("BTCP") + NEXO = NewCode("NEXO") + XBT = NewCode("XBT") + SAN = NewCode("SAN") + GAS = NewCode("GAS") + BCC = NewCode("BCC") + HCC = NewCode("HCC") + OAX = NewCode("OAX") + DNT = NewCode("DNT") + ICN = NewCode("ICN") + LLT = NewCode("LLT") + YOYO = NewCode("YOYO") + SNGLS = NewCode("SNGLS") + BQX = NewCode("BQX") + KNC = NewCode("KNC") + SNM = NewCode("SNM") + CTR = NewCode("CTR") + SALT = NewCode("SALT") + MDA = NewCode("MDA") + IOTA = NewCode("IOTA") + SUB = NewCode("SUB") + MTL = NewCode("MTL") + MTH = NewCode("MTH") + ENG = NewCode("ENG") + AST = NewCode("AST") + CLN = NewCode("CLN") + EDG = NewCode("EDG") + FIRST = NewCode("1ST") + GOLOS = NewCode("GOLOS") + ANT = NewCode("ANT") + GBG = NewCode("GBG") + HMQ = NewCode("HMQ") + INCNT = NewCode("INCNT") + ACE = NewCode("ACE") + ACT = NewCode("ACT") + AAC = NewCode("AAC") + AIDOC = NewCode("AIDOC") + SOC = NewCode("SOC") + ATL = NewCode("ATL") + AVT = NewCode("AVT") + BKX = NewCode("BKX") + BEC = NewCode("BEC") + VEE = NewCode("VEE") + PTOY = NewCode("PTOY") + CAG = NewCode("CAG") + CIC = NewCode("CIC") + CBT = NewCode("CBT") + CAN = NewCode("CAN") + DAT = NewCode("DAT") + DNA = NewCode("DNA") + INT = NewCode("INT") + IPC = NewCode("IPC") + ILA = NewCode("ILA") + LIGHT = NewCode("LIGHT") + MAG = NewCode("MAG") + AMM = NewCode("AMM") + MOF = NewCode("MOF") + MGC = NewCode("MGC") + OF = NewCode("OF") + LA = NewCode("LA") + LEV = NewCode("LEV") + NGC = NewCode("NGC") + OKB = NewCode("OKB") + MOT = NewCode("MOT") + PRA = NewCode("PRA") + R = NewCode("R") + SSC = NewCode("SSC") + SHOW = NewCode("SHOW") + SPF = NewCode("SPF") + SNC = NewCode("SNC") + SWFTC = NewCode("SWFTC") + TRA = NewCode("TRA") + TOPC = NewCode("TOPC") + TRIO = NewCode("TRIO") + QVT = NewCode("QVT") + UCT = NewCode("UCT") + UKG = NewCode("UKG") + UTK = NewCode("UTK") + VIU = NewCode("VIU") + WFEE = NewCode("WFEE") + WRC = NewCode("WRC") + UGC = NewCode("UGC") + YEE = NewCode("YEE") + YOYOW = NewCode("YOYOW") + ZIP = NewCode("ZIP") + READ = NewCode("READ") + RCT = NewCode("RCT") + REF = NewCode("REF") + XUC = NewCode("XUC") + FAIR = NewCode("FAIR") + GSC = NewCode("GSC") + HMC = NewCode("HMC") + PLU = NewCode("PLU") + PRO = NewCode("PRO") + QRL = NewCode("QRL") + REN = NewCode("REN") + ROUND = NewCode("ROUND") + SRN = NewCode("SRN") + XID = NewCode("XID") + SBD = NewCode("SBD") + TAAS = NewCode("TAAS") + TKN = NewCode("TKN") + VEN = NewCode("VEN") + VSL = NewCode("VSL") + TRST = NewCode("TRST") + XXX = NewCode("XXX") + IND = NewCode("IND") + LDC = NewCode("LDC") + GUP = NewCode("GUP") + MGO = NewCode("MGO") + MYST = NewCode("MYST") + NEU = NewCode("NEU") + NET = NewCode("NET") + BMC = NewCode("BMC") + BCAP = NewCode("BCAP") + TIME = NewCode("TIME") + CFI = NewCode("CFI") + EVX = NewCode("EVX") + REQ = NewCode("REQ") + VIB = NewCode("VIB") + ARK = NewCode("ARK") + MOD = NewCode("MOD") + ENJ = NewCode("ENJ") + STORJ = NewCode("STORJ") + RCN = NewCode("RCN") + NULS = NewCode("NULS") + RDN = NewCode("RDN") + DLT = NewCode("DLT") + AMB = NewCode("AMB") + BCPT = NewCode("BCPT") + ARN = NewCode("ARN") + GVT = NewCode("GVT") + CDT = NewCode("CDT") + POE = NewCode("POE") + QSP = NewCode("QSP") + XZC = NewCode("XZC") + TNT = NewCode("TNT") + FUEL = NewCode("FUEL") + ADX = NewCode("ADX") + CND = NewCode("CND") + LEND = NewCode("LEND") + WABI = NewCode("WABI") + SBTC = NewCode("SBTC") + BCX = NewCode("BCX") + TNB = NewCode("TNB") + GTO = NewCode("GTO") + OST = NewCode("OST") + CVC = NewCode("CVC") + DATA = NewCode("DATA") + ETF = NewCode("ETF") + BRD = NewCode("BRD") + NEBL = NewCode("NEBL") + VIBE = NewCode("VIBE") + LUN = NewCode("LUN") + CHAT = NewCode("CHAT") + RLC = NewCode("RLC") + INS = NewCode("INS") + VIA = NewCode("VIA") + BLZ = NewCode("BLZ") + SYS = NewCode("SYS") + NCASH = NewCode("NCASH") + POA = NewCode("POA") + STORM = NewCode("STORM") + WPR = NewCode("WPR") + QLC = NewCode("QLC") + GRS = NewCode("GRS") + CLOAK = NewCode("CLOAK") + ZEN = NewCode("ZEN") + SKY = NewCode("SKY") + IOTX = NewCode("IOTX") + QKC = NewCode("QKC") + AGI = NewCode("AGI") + NXS = NewCode("NXS") + EON = NewCode("EON") + KEY = NewCode("KEY") + NAS = NewCode("NAS") + ADD = NewCode("ADD") + MEETONE = NewCode("MEETONE") + ATD = NewCode("ATD") + MFT = NewCode("MFT") + EOP = NewCode("EOP") + DENT = NewCode("DENT") + IQ = NewCode("IQ") + DOCK = NewCode("DOCK") + POLY = NewCode("POLY") + VTHO = NewCode("VTHO") + ONG = NewCode("ONG") + PHX = NewCode("PHX") + GO = NewCode("GO") + PAX = NewCode("PAX") + PAXG = NewCode("PAXG") + EDO = NewCode("EDO") + WINGS = NewCode("WINGS") + NAV = NewCode("NAV") + TRIG = NewCode("TRIG") + APPC = NewCode("APPC") + KRW = NewCode("KRW") + HSR = NewCode("HSR") + ETHOS = NewCode("ETHOS") + CTXC = NewCode("CTXC") + ITC = NewCode("ITC") + TRUE = NewCode("TRUE") + ABT = NewCode("ABT") + RNT = NewCode("RNT") + PLY = NewCode("PLY") + PST = NewCode("PST") + KICK = NewCode("KICK") + BTCZ = NewCode("BTCZ") + DXT = NewCode("DXT") + STQ = NewCode("STQ") + INK = NewCode("INK") + HBZ = NewCode("HBZ") + USDT_ETH = NewCode("USDT_ETH") // nolint // Cryptocurrency code + QTUM_ETH = NewCode("QTUM_ETH") // nolint // Cryptocurrency code + BTM_ETH = NewCode("BTM_ETH") // nolint // Cryptocurrency code + FIL = NewCode("FIL") + STX = NewCode("STX") + BOT = NewCode("BOT") + VERI = NewCode("VERI") + ZSC = NewCode("ZSC") + QBT = NewCode("QBT") + MED = NewCode("MED") + QASH = NewCode("QASH") + MDS = NewCode("MDS") + GOD = NewCode("GOD") + SMT = NewCode("SMT") + BTF = NewCode("BTF") + NAS_ETH = NewCode("NAS_ETH") // nolint // Cryptocurrency code + TSL = NewCode("TSL") + BIFI = NewCode("BIFI") + BNTY = NewCode("BNTY") + DRGN = NewCode("DRGN") + GTC = NewCode("GTC") + MDT = NewCode("MDT") + QUN = NewCode("QUN") + GNX = NewCode("GNX") + DDD = NewCode("DDD") + BTO = NewCode("BTO") + TIO = NewCode("TIO") + OCN = NewCode("OCN") + RUFF = NewCode("RUFF") + TNC = NewCode("TNC") + SNET = NewCode("SNET") + COFI = NewCode("COFI") + ZPT = NewCode("ZPT") + JNT = NewCode("JNT") + MTN = NewCode("MTN") + GEM = NewCode("GEM") + DADI = NewCode("DADI") + RFR = NewCode("RFR") + MOBI = NewCode("MOBI") + LEDU = NewCode("LEDU") + DBC = NewCode("DBC") + MKR_OLD = NewCode("MKR_OLD") // nolint // Cryptocurrency code + DPY = NewCode("DPY") + BCDN = NewCode("BCDN") + EOSDAC = NewCode("EOSDAC") + TIPS = NewCode("TIPS") + XMC = NewCode("XMC") + PPS = NewCode("PPS") + BOE = NewCode("BOE") + MEDX = NewCode("MEDX") + SMT_ETH = NewCode("SMT_ETH") // nolint // Cryptocurrency code + CS = NewCode("CS") + MAN = NewCode("MAN") + REM = NewCode("REM") + LYM = NewCode("LYM") + INSTAR = NewCode("INSTAR") + BFT = NewCode("BFT") + IHT = NewCode("IHT") + SENC = NewCode("SENC") + TOMO = NewCode("TOMO") + ELEC = NewCode("ELEC") + SHIP = NewCode("SHIP") + TFD = NewCode("TFD") + HAV = NewCode("HAV") + HUR = NewCode("HUR") + LST = NewCode("LST") + LINO = NewCode("LINO") + SWTH = NewCode("SWTH") + NKN = NewCode("NKN") + SOUL = NewCode("SOUL") + GALA_NEO = NewCode("GALA_NEO") // nolint // Cryptocurrency code + LRN = NewCode("LRN") + GSE = NewCode("GSE") + RATING = NewCode("RATING") + HSC = NewCode("HSC") + HIT = NewCode("HIT") + DX = NewCode("DX") + BXC = NewCode("BXC") + GARD = NewCode("GARD") + FTI = NewCode("FTI") + SOP = NewCode("SOP") + LEMO = NewCode("LEMO") + RED = NewCode("RED") + LBA = NewCode("LBA") + KAN = NewCode("KAN") + OPEN = NewCode("OPEN") + SKM = NewCode("SKM") + NBAI = NewCode("NBAI") + UPP = NewCode("UPP") + ATMI = NewCode("ATMI") + TMT = NewCode("TMT") + BBK = NewCode("BBK") + EDR = NewCode("EDR") + MET = NewCode("MET") + TCT = NewCode("TCT") + EXC = NewCode("EXC") + CNC = NewCode("CNC") + TIX = NewCode("TIX") + XTC = NewCode("XTC") + BU = NewCode("BU") + GNO = NewCode("GNO") + MLN = NewCode("MLN") + XBC = NewCode("XBC") + BTCD = NewCode("BTCD") + BURST = NewCode("BURST") + CLAM = NewCode("CLAM") + XCP = NewCode("XCP") + EMC2 = NewCode("EMC2") + EXP = NewCode("EXP") + FCT = NewCode("FCT") + GAME = NewCode("GAME") + GRC = NewCode("GRC") + HUC = NewCode("HUC") + LBC = NewCode("LBC") + NMC = NewCode("NMC") + NEOS = NewCode("NEOS") + OMNI = NewCode("OMNI") + PASC = NewCode("PASC") + PPC = NewCode("PPC") + DSH = NewCode("DSH") + GML = NewCode("GML") + GSY = NewCode("GSY") + POT = NewCode("POT") + XPM = NewCode("XPM") + AMP = NewCode("AMP") + VRC = NewCode("VRC") + VTC = NewCode("VTC") + ZERO07 = NewCode("007") + BIT16 = NewCode("BIT16") + TWO015 = NewCode("2015") + TWO56 = NewCode("256") + TWOBACCO = NewCode("2BACCO") + TWOGIVE = NewCode("2GIVE") + THIRTY2BIT = NewCode("32BIT") + THREE65 = NewCode("365") + FOUR04 = NewCode("404") + SEVEN00 = NewCode("700") + EIGHTBIT = NewCode("8BIT") + ACLR = NewCode("ACLR") + ACES = NewCode("ACES") + ACPR = NewCode("ACPR") + ACID = NewCode("ACID") + ACOIN = NewCode("ACOIN") + ACRN = NewCode("ACRN") + ADAM = NewCode("ADAM") + ADT = NewCode("ADT") + AIB = NewCode("AIB") + ADZ = NewCode("ADZ") + AECC = NewCode("AECC") + AM = NewCode("AM") + AGRI = NewCode("AGRI") + AGT = NewCode("AGT") + AIR = NewCode("AIR") + ALEX = NewCode("ALEX") + AUM = NewCode("AUM") + ALIEN = NewCode("ALIEN") + ALIS = NewCode("ALIS") + ALL = NewCode("ALL") + ASAFE = NewCode("ASAFE") + AMBER = NewCode("AMBER") + AMS = NewCode("AMS") + ANAL = NewCode("ANAL") + ACP = NewCode("ACP") + ANI = NewCode("ANI") + ANTI = NewCode("ANTI") + ALTC = NewCode("ALTC") + APT = NewCode("APT") + ARCO = NewCode("ARCO") + ALC = NewCode("ALC") + ARB = NewCode("ARB") + ARCT = NewCode("ARCT") + ARCX = NewCode("ARCX") + ARGUS = NewCode("ARGUS") + ARH = NewCode("ARH") + ARM = NewCode("ARM") + ARNA = NewCode("ARNA") + ARPA = NewCode("ARPA") + ARTA = NewCode("ARTA") + ABY = NewCode("ABY") + ARTC = NewCode("ARTC") + AL = NewCode("AL") + ASN = NewCode("ASN") + ADCN = NewCode("ADCN") + ATB = NewCode("ATB") + ATM = NewCode("ATM") + ATMCHA = NewCode("ATMCHA") + ATOM = NewCode("ATOM") + ADC = NewCode("ADC") + ARE = NewCode("ARE") + AUR = NewCode("AUR") + AV = NewCode("AV") + AXIOM = NewCode("AXIOM") + B2B = NewCode("B2B") + B2 = NewCode("B2") + B3 = NewCode("B3") + BAB = NewCode("BAB") + BAN = NewCode("BAN") + BamitCoin = NewCode("BamitCoin") + NANAS = NewCode("NANAS") + BBCC = NewCode("BBCC") + BTA = NewCode("BTA") + BSTK = NewCode("BSTK") + BATL = NewCode("BATL") + BBH = NewCode("BBH") + BITB = NewCode("BITB") + BRDD = NewCode("BRDD") + XBTS = NewCode("XBTS") + BVC = NewCode("BVC") + CHATX = NewCode("CHATX") + BEEP = NewCode("BEEP") + BEEZ = NewCode("BEEZ") + BENJI = NewCode("BENJI") + BERN = NewCode("BERN") + PROFIT = NewCode("PROFIT") + BEST = NewCode("BEST") + BGF = NewCode("BGF") + BIGUP = NewCode("BIGUP") + BLRY = NewCode("BLRY") + BILL = NewCode("BILL") + BIOB = NewCode("BIOB") + BIO = NewCode("BIO") + BIOS = NewCode("BIOS") + BPTN = NewCode("BPTN") + BTCA = NewCode("BTCA") + BA = NewCode("BA") + BAC = NewCode("BAC") + BBT = NewCode("BBT") + BOSS = NewCode("BOSS") + BRONZ = NewCode("BRONZ") + CAT = NewCode("CAT") + BTD = NewCode("BTD") + XBTC21 = NewCode("XBTC21") + BCA = NewCode("BCA") + BCP = NewCode("BCP") + BTDOLL = NewCode("BTDOLL") + LIZA = NewCode("LIZA") + BTCRED = NewCode("BTCRED") + BTCS = NewCode("BTCS") + BTU = NewCode("BTU") + BUM = NewCode("BUM") + LITE = NewCode("LITE") + BCM = NewCode("BCM") + BCS = NewCode("BCS") + BTCU = NewCode("BTCU") + BM = NewCode("BM") + BTCRY = NewCode("BTCRY") + BTCR = NewCode("BTCR") + HIRE = NewCode("HIRE") + STU = NewCode("STU") + BITOK = NewCode("BITOK") + BITON = NewCode("BITON") + BPC = NewCode("BPC") + BPOK = NewCode("BPOK") + BTP = NewCode("BTP") + BITCNY = NewCode("bitCNY") + RNTB = NewCode("RNTB") + BSH = NewCode("BSH") + XBS = NewCode("XBS") + BITS = NewCode("BITS") + BST = NewCode("BST") + BXT = NewCode("BXT") + VEG = NewCode("VEG") + VOLT = NewCode("VOLT") + BTV = NewCode("BTV") + BITZ = NewCode("BITZ") + BTZ = NewCode("BTZ") + BHC = NewCode("BHC") + BDC = NewCode("BDC") + JACK = NewCode("JACK") + BS = NewCode("BS") + BSTAR = NewCode("BSTAR") + BLAZR = NewCode("BLAZR") + BOD = NewCode("BOD") + BLUE = NewCode("BLUE") + BLU = NewCode("BLU") + BLUS = NewCode("BLUS") + BMT = NewCode("BMT") + BOLI = NewCode("BOLI") + BOMB = NewCode("BOMB") + BON = NewCode("BON") + BOOM = NewCode("BOOM") + BOSON = NewCode("BOSON") + BSC = NewCode("BSC") + BRH = NewCode("BRH") + BRAIN = NewCode("BRAIN") + BRE = NewCode("BRE") + BTCM = NewCode("BTCM") + BTCO = NewCode("BTCO") + TALK = NewCode("TALK") + BUB = NewCode("BUB") + BUY = NewCode("BUY") + BUZZ = NewCode("BUZZ") + BTH = NewCode("BTH") + C0C0 = NewCode("C0C0") + CAB = NewCode("CAB") + CF = NewCode("CF") + CLO = NewCode("CLO") + CAM = NewCode("CAM") + CD = NewCode("CD") + CANN = NewCode("CANN") + CNNC = NewCode("CNNC") + CPC = NewCode("CPC") + CST = NewCode("CST") + CAPT = NewCode("CAPT") + CARBON = NewCode("CARBON") + CME = NewCode("CME") + CTK = NewCode("CTK") + CBD = NewCode("CBD") + CCC = NewCode("CCC") + CNT = NewCode("CNT") + XCE = NewCode("XCE") + CHRG = NewCode("CHRG") + CHEMX = NewCode("CHEMX") + CHESS = NewCode("CHESS") + CKS = NewCode("CKS") + CHILL = NewCode("CHILL") + CHIP = NewCode("CHIP") + CHOOF = NewCode("CHOOF") + CRX = NewCode("CRX") + CIN = NewCode("CIN") + POLL = NewCode("POLL") + CLICK = NewCode("CLICK") + CLINT = NewCode("CLINT") + CLUB = NewCode("CLUB") + CLUD = NewCode("CLUD") + COX = NewCode("COX") + COXST = NewCode("COXST") + CFC = NewCode("CFC") + CTIC2 = NewCode("CTIC2") + COIN = NewCode("COIN") + BTTF = NewCode("BTTF") + C2 = NewCode("C2") + CAID = NewCode("CAID") + CL = NewCode("CL") + CTIC = NewCode("CTIC") + CXT = NewCode("CXT") + CHP = NewCode("CHP") + CV2 = NewCode("CV2") + COC = NewCode("COC") + COMP = NewCode("COMP") + CMS = NewCode("CMS") + CONX = NewCode("CONX") + CCX = NewCode("CCX") + CLR = NewCode("CLR") + CORAL = NewCode("CORAL") + CORG = NewCode("CORG") + CSMIC = NewCode("CSMIC") + CMC = NewCode("CMC") + COV = NewCode("COV") + COVX = NewCode("COVX") + CRAB = NewCode("CRAB") + CRAFT = NewCode("CRAFT") + CRNK = NewCode("CRNK") + CRAVE = NewCode("CRAVE") + CRM = NewCode("CRM") + XCRE = NewCode("XCRE") + CREDIT = NewCode("CREDIT") + CREVA = NewCode("CREVA") + CRIME = NewCode("CRIME") + CROC = NewCode("CROC") + CRC = NewCode("CRC") + CRW = NewCode("CRW") + CRY = NewCode("CRY") + CBX = NewCode("CBX") + TKTX = NewCode("TKTX") + CB = NewCode("CB") + CIRC = NewCode("CIRC") + CCB = NewCode("CCB") + CDO = NewCode("CDO") + CG = NewCode("CG") + CJ = NewCode("CJ") + CJC = NewCode("CJC") + CYT = NewCode("CYT") + CRPS = NewCode("CRPS") + PING = NewCode("PING") + CWXT = NewCode("CWXT") + CCT = NewCode("CCT") + CTL = NewCode("CTL") + CURVES = NewCode("CURVES") + CC = NewCode("CC") + CYC = NewCode("CYC") + CYG = NewCode("CYG") + CYP = NewCode("CYP") + FUNK = NewCode("FUNK") + CZECO = NewCode("CZECO") + DALC = NewCode("DALC") + DLISK = NewCode("DLISK") + MOOND = NewCode("MOOND") + DB = NewCode("DB") + DCC = NewCode("DCC") + DCYP = NewCode("DCYP") + DETH = NewCode("DETH") + DKC = NewCode("DKC") + DISK = NewCode("DISK") + DRKT = NewCode("DRKT") + DTT = NewCode("DTT") + DASHS = NewCode("DASHS") + DBTC = NewCode("DBTC") + DCT = NewCode("DCT") + DBET = NewCode("DBET") + DEC = NewCode("DEC") + DECR = NewCode("DECR") + DEA = NewCode("DEA") + DPAY = NewCode("DPAY") + DCRE = NewCode("DCRE") + DC = NewCode("DC") + DES = NewCode("DES") + DEM = NewCode("DEM") + DXC = NewCode("DXC") + DCK = NewCode("DCK") + CUBE = NewCode("CUBE") + DGMS = NewCode("DGMS") + DBG = NewCode("DBG") + DGCS = NewCode("DGCS") + DBLK = NewCode("DBLK") + DIME = NewCode("DIME") + DIRT = NewCode("DIRT") + DVD = NewCode("DVD") + DMT = NewCode("DMT") + NOTE = NewCode("NOTE") + DGORE = NewCode("DGORE") + DLC = NewCode("DLC") + DRT = NewCode("DRT") + DOTA = NewCode("DOTA") + DOX = NewCode("DOX") + DRA = NewCode("DRA") + DFT = NewCode("DFT") + XDB = NewCode("XDB") + DRM = NewCode("DRM") + DRZ = NewCode("DRZ") + DRACO = NewCode("DRACO") + DBIC = NewCode("DBIC") + DUB = NewCode("DUB") + GUM = NewCode("GUM") + DUR = NewCode("DUR") + DUST = NewCode("DUST") + DUX = NewCode("DUX") + DXO = NewCode("DXO") + ECN = NewCode("ECN") + EDR2 = NewCode("EDR2") + EA = NewCode("EA") + EAGS = NewCode("EAGS") + EMT = NewCode("EMT") + EBONUS = NewCode("EBONUS") + ECCHI = NewCode("ECCHI") + EKO = NewCode("EKO") + ECLI = NewCode("ECLI") + ECOB = NewCode("ECOB") + ECO = NewCode("ECO") + EDIT = NewCode("EDIT") + EDRC = NewCode("EDRC") + EDC = NewCode("EDC") + EGAME = NewCode("EGAME") + EGG = NewCode("EGG") + EGO = NewCode("EGO") + ELC = NewCode("ELC") + ELCO = NewCode("ELCO") + ECA = NewCode("ECA") + EPC = NewCode("EPC") + ELE = NewCode("ELE") + ONE337 = NewCode("1337") + EMB = NewCode("EMB") + EMC = NewCode("EMC") + EPY = NewCode("EPY") + EMPC = NewCode("EMPC") + EMP = NewCode("EMP") + ENE = NewCode("ENE") + EET = NewCode("EET") + XNG = NewCode("XNG") + EGMA = NewCode("EGMA") + ENTER = NewCode("ENTER") + ETRUST = NewCode("ETRUST") + EQL = NewCode("EQL") + EQM = NewCode("EQM") + EQT = NewCode("EQT") + ERR = NewCode("ERR") + ESC = NewCode("ESC") + ESP = NewCode("ESP") + ENT = NewCode("ENT") + ETCO = NewCode("ETCO") + DOGETH = NewCode("DOGETH") + ECASH = NewCode("ECASH") + ELITE = NewCode("ELITE") + ETHS = NewCode("ETHS") + ETL = NewCode("ETL") + ETZ = NewCode("ETZ") + EUC = NewCode("EUC") + EURC = NewCode("EURC") + EUROPE = NewCode("EUROPE") + EVA = NewCode("EVA") + EGC = NewCode("EGC") + EOC = NewCode("EOC") + EVIL = NewCode("EVIL") + EVO = NewCode("EVO") + EXB = NewCode("EXB") + EXIT = NewCode("EXIT") + XT = NewCode("XT") + F16 = NewCode("F16") + FADE = NewCode("FADE") + FAZZ = NewCode("FAZZ") + FX = NewCode("FX") + FIDEL = NewCode("FIDEL") + FIDGT = NewCode("FIDGT") + FIND = NewCode("FIND") + FPC = NewCode("FPC") + FIRE = NewCode("FIRE") + FFC = NewCode("FFC") + FRST = NewCode("FRST") + FIST = NewCode("FIST") + FIT = NewCode("FIT") + FLX = NewCode("FLX") + FLVR = NewCode("FLVR") + FLY = NewCode("FLY") + FONZ = NewCode("FONZ") + XFCX = NewCode("XFCX") + FOREX = NewCode("FOREX") + FRN = NewCode("FRN") + FRK = NewCode("FRK") + FRWC = NewCode("FRWC") + FGZ = NewCode("FGZ") + FRE = NewCode("FRE") + FRDC = NewCode("FRDC") + FJC = NewCode("FJC") + FURY = NewCode("FURY") + FSN = NewCode("FSN") + FCASH = NewCode("FCASH") + FTO = NewCode("FTO") + FUZZ = NewCode("FUZZ") + GAKH = NewCode("GAKH") + GBT = NewCode("GBT") + UNITS = NewCode("UNITS") + FOUR20G = NewCode("420G") + GENIUS = NewCode("GENIUS") + GEN = NewCode("GEN") + GEO = NewCode("GEO") + GER = NewCode("GER") + GSR = NewCode("GSR") + SPKTR = NewCode("SPKTR") + GIFT = NewCode("GIFT") + WTT = NewCode("WTT") + GHS = NewCode("GHS") + GIG = NewCode("GIG") + GOT = NewCode("GOT") + XGTC = NewCode("XGTC") + GIZ = NewCode("GIZ") + GLO = NewCode("GLO") + GCR = NewCode("GCR") + BSTY = NewCode("BSTY") + GLC = NewCode("GLC") + GSX = NewCode("GSX") + GOAT = NewCode("GOAT") + GB = NewCode("GB") + GFL = NewCode("GFL") + MNTP = NewCode("MNTP") + GP = NewCode("GP") + GLUCK = NewCode("GLUCK") + GOON = NewCode("GOON") + GTFO = NewCode("GTFO") + GOTX = NewCode("GOTX") + GPU = NewCode("GPU") + GRF = NewCode("GRF") + GRAM = NewCode("GRAM") + GRAV = NewCode("GRAV") + GBIT = NewCode("GBIT") + GREED = NewCode("GREED") + GE = NewCode("GE") + GREENF = NewCode("GREENF") + GRE = NewCode("GRE") + GREXIT = NewCode("GREXIT") + GMCX = NewCode("GMCX") + GROW = NewCode("GROW") + GSM = NewCode("GSM") + GT = NewCode("GT") + NLG = NewCode("NLG") + HKN = NewCode("HKN") + HAC = NewCode("HAC") + HALLO = NewCode("HALLO") + HAMS = NewCode("HAMS") + HPC = NewCode("HPC") + HAWK = NewCode("HAWK") + HAZE = NewCode("HAZE") + HZT = NewCode("HZT") + HDG = NewCode("HDG") + HEDG = NewCode("HEDG") + HEEL = NewCode("HEEL") + HMP = NewCode("HMP") + PLAY = NewCode("PLAY") + HXX = NewCode("HXX") + XHI = NewCode("XHI") + HVCO = NewCode("HVCO") + HTC = NewCode("HTC") + MINH = NewCode("MINH") + HODL = NewCode("HODL") + HON = NewCode("HON") + HOPE = NewCode("HOPE") + HQX = NewCode("HQX") + HSP = NewCode("HSP") + HTML5 = NewCode("HTML5") + HYPERX = NewCode("HYPERX") + HPS = NewCode("HPS") + IOC = NewCode("IOC") + IBANK = NewCode("IBANK") + IBITS = NewCode("IBITS") + ICASH = NewCode("ICASH") + ICOB = NewCode("ICOB") + ICON = NewCode("ICON") + IETH = NewCode("IETH") + ILM = NewCode("ILM") + IMPS = NewCode("IMPS") + NKA = NewCode("NKA") + INCP = NewCode("INCP") + IN = NewCode("IN") + INC = NewCode("INC") + IMS = NewCode("IMS") + IFLT = NewCode("IFLT") + INFX = NewCode("INFX") + INGT = NewCode("INGT") + INPAY = NewCode("INPAY") + INSANE = NewCode("INSANE") + INXT = NewCode("INXT") + IFT = NewCode("IFT") + INV = NewCode("INV") + IVZ = NewCode("IVZ") + ILT = NewCode("ILT") + IONX = NewCode("IONX") + ISL = NewCode("ISL") + ITI = NewCode("ITI") + ING = NewCode("ING") + IEC = NewCode("IEC") + IW = NewCode("IW") + IXC = NewCode("IXC") + IXT = NewCode("IXT") + JPC = NewCode("JPC") + JANE = NewCode("JANE") + JWL = NewCode("JWL") + JIF = NewCode("JIF") + JOBS = NewCode("JOBS") + JOCKER = NewCode("JOCKER") + JW = NewCode("JW") + JOK = NewCode("JOK") + XJO = NewCode("XJO") + KGB = NewCode("KGB") + KARMC = NewCode("KARMC") + KARMA = NewCode("KARMA") + KASHH = NewCode("KASHH") + KAT = NewCode("KAT") + KC = NewCode("KC") + KIDS = NewCode("KIDS") + KIN = NewCode("KIN") + KISS = NewCode("KISS") + KOBO = NewCode("KOBO") + TP1 = NewCode("TP1") + KRAK = NewCode("KRAK") + KGC = NewCode("KGC") + KTK = NewCode("KTK") + KR = NewCode("KR") + KUBO = NewCode("KUBO") + KURT = NewCode("KURT") + KUSH = NewCode("KUSH") + LANA = NewCode("LANA") + LTH = NewCode("LTH") + LAZ = NewCode("LAZ") + LEA = NewCode("LEA") + LEAF = NewCode("LEAF") + LENIN = NewCode("LENIN") + LEPEN = NewCode("LEPEN") + LIR = NewCode("LIR") + LVG = NewCode("LVG") + LGBTQ = NewCode("LGBTQ") + LHC = NewCode("LHC") + EXT = NewCode("EXT") + LBTC = NewCode("LBTC") + LSD = NewCode("LSD") + LIMX = NewCode("LIMX") + LTD = NewCode("LTD") + LINDA = NewCode("LINDA") + LKC = NewCode("LKC") + LBTCX = NewCode("LBTCX") + LCC = NewCode("LCC") + LTCU = NewCode("LTCU") + LTCR = NewCode("LTCR") + LDOGE = NewCode("LDOGE") + LTS = NewCode("LTS") + LIV = NewCode("LIV") + LIZI = NewCode("LIZI") + LOC = NewCode("LOC") + LOCX = NewCode("LOCX") + LOOK = NewCode("LOOK") + LOOT = NewCode("LOOT") + XLTCG = NewCode("XLTCG") + BASH = NewCode("BASH") + LUCKY = NewCode("LUCKY") + L7S = NewCode("L7S") + LDM = NewCode("LDM") + LUMI = NewCode("LUMI") + LUNA = NewCode("LUNA") + LC = NewCode("LC") + LUX = NewCode("LUX") + MCRN = NewCode("MCRN") + XMG = NewCode("XMG") + MMXIV = NewCode("MMXIV") + MAT = NewCode("MAT") + MAO = NewCode("MAO") + MAPC = NewCode("MAPC") + MRB = NewCode("MRB") + MXT = NewCode("MXT") + MARV = NewCode("MARV") + MARX = NewCode("MARX") + MCAR = NewCode("MCAR") + MM = NewCode("MM") + MVC = NewCode("MVC") + MAVRO = NewCode("MAVRO") + MAX = NewCode("MAX") + MAZE = NewCode("MAZE") + MBIT = NewCode("MBIT") + MCOIN = NewCode("MCOIN") + MPRO = NewCode("MPRO") + XMS = NewCode("XMS") + MLITE = NewCode("MLITE") + MLNC = NewCode("MLNC") + MENTAL = NewCode("MENTAL") + MERGEC = NewCode("MERGEC") + MTLMC3 = NewCode("MTLMC3") + METAL = NewCode("METAL") + MUU = NewCode("MUU") + MILO = NewCode("MILO") + MND = NewCode("MND") + XMINE = NewCode("XMINE") + MNM = NewCode("MNM") + XNM = NewCode("XNM") + MIRO = NewCode("MIRO") + MIS = NewCode("MIS") + MMXVI = NewCode("MMXVI") + MOIN = NewCode("MOIN") + MOJO = NewCode("MOJO") + TAB = NewCode("TAB") + MONETA = NewCode("MONETA") + MUE = NewCode("MUE") + MONEY = NewCode("MONEY") + MRP = NewCode("MRP") + MOTO = NewCode("MOTO") + MULTI = NewCode("MULTI") + MST = NewCode("MST") + MVR = NewCode("MVR") + MYSTIC = NewCode("MYSTIC") + WISH = NewCode("WISH") + NKT = NewCode("NKT") + NAT = NewCode("NAT") + ENAU = NewCode("ENAU") + NEBU = NewCode("NEBU") + NEF = NewCode("NEF") + NBIT = NewCode("NBIT") + NETKO = NewCode("NETKO") + NTM = NewCode("NTM") + NETC = NewCode("NETC") + NRC = NewCode("NRC") + NTK = NewCode("NTK") + NTRN = NewCode("NTRN") + NEVA = NewCode("NEVA") + NIC = NewCode("NIC") + NKC = NewCode("NKC") + NYC = NewCode("NYC") + NZC = NewCode("NZC") + NICE = NewCode("NICE") + NDOGE = NewCode("NDOGE") + XTR = NewCode("XTR") + N2O = NewCode("N2O") + NIXON = NewCode("NIXON") + NOC = NewCode("NOC") + NODC = NewCode("NODC") + NODES = NewCode("NODES") + NODX = NewCode("NODX") + NLC = NewCode("NLC") + NLC2 = NewCode("NLC2") + NOO = NewCode("NOO") + NVC = NewCode("NVC") + NPC = NewCode("NPC") + NUBIS = NewCode("NUBIS") + NUKE = NewCode("NUKE") + N7 = NewCode("N7") + NUM = NewCode("NUM") + NMR = NewCode("NMR") + NXE = NewCode("NXE") + OBS = NewCode("OBS") + OCEAN = NewCode("OCEAN") + OCOW = NewCode("OCOW") + EIGHT88 = NewCode("888") + OCC = NewCode("OCC") + OK = NewCode("OK") + ODNT = NewCode("ODNT") + FLAV = NewCode("FLAV") + OLIT = NewCode("OLIT") + OLYMP = NewCode("OLYMP") + OMA = NewCode("OMA") + OMC = NewCode("OMC") + ONEK = NewCode("ONEK") + ONX = NewCode("ONX") + XPO = NewCode("XPO") + OPAL = NewCode("OPAL") + OTN = NewCode("OTN") + OP = NewCode("OP") + OPES = NewCode("OPES") + OPTION = NewCode("OPTION") + ORLY = NewCode("ORLY") + OS76 = NewCode("OS76") + OZC = NewCode("OZC") + P7C = NewCode("P7C") + PAC = NewCode("PAC") + PAK = NewCode("PAK") + PAL = NewCode("PAL") + PND = NewCode("PND") + PINKX = NewCode("PINKX") + POPPY = NewCode("POPPY") + DUO = NewCode("DUO") + PARA = NewCode("PARA") + PKB = NewCode("PKB") + GENE = NewCode("GENE") + PARTY = NewCode("PARTY") + PYN = NewCode("PYN") + XPY = NewCode("XPY") + CON = NewCode("CON") + PAYP = NewCode("PAYP") + GUESS = NewCode("GUESS") + PEN = NewCode("PEN") + PTA = NewCode("PTA") + PEO = NewCode("PEO") + PSB = NewCode("PSB") + XPD = NewCode("XPD") + PXL = NewCode("PXL") + PHR = NewCode("PHR") + PIE = NewCode("PIE") + PIO = NewCode("PIO") + PIPR = NewCode("PIPR") + SKULL = NewCode("SKULL") + PLANET = NewCode("PLANET") + PNC = NewCode("PNC") + XPTX = NewCode("XPTX") + PLNC = NewCode("PLNC") + XPS = NewCode("XPS") + POKE = NewCode("POKE") + PLBT = NewCode("PLBT") + POM = NewCode("POM") + PONZ2 = NewCode("PONZ2") + PONZI = NewCode("PONZI") + XSP = NewCode("XSP") + XPC = NewCode("XPC") + PEX = NewCode("PEX") + TRON = NewCode("TRON") + POST = NewCode("POST") + POSW = NewCode("POSW") + PWR = NewCode("PWR") + POWER = NewCode("POWER") + PRE = NewCode("PRE") + PRS = NewCode("PRS") + PXI = NewCode("PXI") + PEXT = NewCode("PEXT") + PRIMU = NewCode("PRIMU") + PRX = NewCode("PRX") + PRM = NewCode("PRM") + PRIX = NewCode("PRIX") + XPRO = NewCode("XPRO") + PCM = NewCode("PCM") + PROC = NewCode("PROC") + NANOX = NewCode("NANOX") + VRP = NewCode("VRP") + PTY = NewCode("PTY") + PSI = NewCode("PSI") + PSY = NewCode("PSY") + PULSE = NewCode("PULSE") + PUPA = NewCode("PUPA") + PURE = NewCode("PURE") + VIDZ = NewCode("VIDZ") + PUTIN = NewCode("PUTIN") + PX = NewCode("PX") + QTM = NewCode("QTM") + QTZ = NewCode("QTZ") + QBC = NewCode("QBC") + XQN = NewCode("XQN") + RBBT = NewCode("RBBT") + RAC = NewCode("RAC") + RADI = NewCode("RADI") + RAD = NewCode("RAD") + RAI = NewCode("RAI") + XRA = NewCode("XRA") + RATIO = NewCode("RATIO") + REA = NewCode("REA") + RCX = NewCode("RCX") + REE = NewCode("REE") + REC = NewCode("REC") + RMS = NewCode("RMS") + RBIT = NewCode("RBIT") + RNC = NewCode("RNC") + REV = NewCode("REV") + RH = NewCode("RH") + XRL = NewCode("XRL") + RICE = NewCode("RICE") + RICHX = NewCode("RICHX") + RID = NewCode("RID") + RIDE = NewCode("RIDE") + RBT = NewCode("RBT") + RING = NewCode("RING") + RIO = NewCode("RIO") + RISE = NewCode("RISE") + ROCKET = NewCode("ROCKET") + RPC = NewCode("RPC") + ROS = NewCode("ROS") + ROYAL = NewCode("ROYAL") + RSGP = NewCode("RSGP") + RBIES = NewCode("RBIES") + RUBIT = NewCode("RUBIT") + RBY = NewCode("RBY") + RUC = NewCode("RUC") + RUPX = NewCode("RUPX") + RUP = NewCode("RUP") + RUST = NewCode("RUST") + SFE = NewCode("SFE") + SLS = NewCode("SLS") + SMSR = NewCode("SMSR") + RONIN = NewCode("RONIN") + STV = NewCode("STV") + HIFUN = NewCode("HIFUN") + MAD = NewCode("MAD") + SANDG = NewCode("SANDG") + STO = NewCode("STO") + SCAN = NewCode("SCAN") + SCITW = NewCode("SCITW") + SCRPT = NewCode("SCRPT") + SCRT = NewCode("SCRT") + SED = NewCode("SED") + SEEDS = NewCode("SEEDS") + B2X = NewCode("B2X") + SEL = NewCode("SEL") + SLFI = NewCode("SLFI") + SMBR = NewCode("SMBR") + SEN = NewCode("SEN") + SENT = NewCode("SENT") + SRNT = NewCode("SRNT") + SEV = NewCode("SEV") + SP = NewCode("SP") + SXC = NewCode("SXC") + GELD = NewCode("GELD") + SHDW = NewCode("SHDW") + SDC = NewCode("SDC") + SAK = NewCode("SAK") + SHRP = NewCode("SHRP") + SHELL = NewCode("SHELL") + SH = NewCode("SH") + SHORTY = NewCode("SHORTY") + SHREK = NewCode("SHREK") + SHRM = NewCode("SHRM") + SIB = NewCode("SIB") + SIGT = NewCode("SIGT") + SLCO = NewCode("SLCO") + SIGU = NewCode("SIGU") + SIX = NewCode("SIX") + SJW = NewCode("SJW") + SKB = NewCode("SKB") + SW = NewCode("SW") + SLEEP = NewCode("SLEEP") + SLING = NewCode("SLING") + SMART = NewCode("SMART") + SMC = NewCode("SMC") + SMF = NewCode("SMF") + SOCC = NewCode("SOCC") + SCL = NewCode("SCL") + SDAO = NewCode("SDAO") + SOLAR = NewCode("SOLAR") + SOLO = NewCode("SOLO") + SCT = NewCode("SCT") + SONG = NewCode("SONG") + ALTCOM = NewCode("ALTCOM") + SPHTX = NewCode("SPHTX") + SPC = NewCode("SPC") + SPACE = NewCode("SPACE") + SBT = NewCode("SBT") + SPEC = NewCode("SPEC") + SPX = NewCode("SPX") + SCS = NewCode("SCS") + SPORT = NewCode("SPORT") + SPT = NewCode("SPT") + SPR = NewCode("SPR") + SPEX = NewCode("SPEX") + SQL = NewCode("SQL") + SBIT = NewCode("SBIT") + STHR = NewCode("STHR") + STALIN = NewCode("STALIN") + STAR = NewCode("STAR") + STA = NewCode("STA") + START = NewCode("START") + STP = NewCode("STP") + PNK = NewCode("PNK") + STEPS = NewCode("STEPS") + STK = NewCode("STK") + STONK = NewCode("STONK") + STS = NewCode("STS") + STRP = NewCode("STRP") + STY = NewCode("STY") + XMT = NewCode("XMT") + SSTC = NewCode("SSTC") + SUPER = NewCode("SUPER") + SRND = NewCode("SRND") + STRB = NewCode("STRB") + M1 = NewCode("M1") + SPM = NewCode("SPM") + BUCKS = NewCode("BUCKS") + TOKEN = NewCode("TOKEN") + SWT = NewCode("SWT") + SWEET = NewCode("SWEET") + SWING = NewCode("SWING") + CHSB = NewCode("CHSB") + SIC = NewCode("SIC") + SDP = NewCode("SDP") + XSY = NewCode("XSY") + SYNX = NewCode("SYNX") + SNRG = NewCode("SNRG") + TAG = NewCode("TAG") + TAGR = NewCode("TAGR") + TAJ = NewCode("TAJ") + TAK = NewCode("TAK") + TAKE = NewCode("TAKE") + TAM = NewCode("TAM") + XTO = NewCode("XTO") + TAP = NewCode("TAP") + TLE = NewCode("TLE") + TSE = NewCode("TSE") + TLEX = NewCode("TLEX") + TAXI = NewCode("TAXI") + TCN = NewCode("TCN") + TDFB = NewCode("TDFB") + TEAM = NewCode("TEAM") + TECH = NewCode("TECH") + TEC = NewCode("TEC") + TEK = NewCode("TEK") + TB = NewCode("TB") + TLX = NewCode("TLX") + TELL = NewCode("TELL") + TENNET = NewCode("TENNET") + TES = NewCode("TES") + TGS = NewCode("TGS") + XVE = NewCode("XVE") + TCR = NewCode("TCR") + GCC = NewCode("GCC") + MAY = NewCode("MAY") + THOM = NewCode("THOM") + TIA = NewCode("TIA") + TIDE = NewCode("TIDE") + TIE = NewCode("TIE") + TIT = NewCode("TIT") + TTC = NewCode("TTC") + TODAY = NewCode("TODAY") + TBX = NewCode("TBX") + TDS = NewCode("TDS") + TLOSH = NewCode("TLOSH") + TOKC = NewCode("TOKC") + TMRW = NewCode("TMRW") + TOOL = NewCode("TOOL") + TCX = NewCode("TCX") + TOT = NewCode("TOT") + TX = NewCode("TX") + TRANSF = NewCode("TRANSF") + TRAP = NewCode("TRAP") + TBCX = NewCode("TBCX") + TRICK = NewCode("TRICK") + TPG = NewCode("TPG") + TFL = NewCode("TFL") + TRUMP = NewCode("TRUMP") + TNG = NewCode("TNG") + TUR = NewCode("TUR") + TWERK = NewCode("TWERK") + TWIST = NewCode("TWIST") + TWO = NewCode("TWO") + UCASH = NewCode("UCASH") + UAE = NewCode("UAE") + XBU = NewCode("XBU") + UBQ = NewCode("UBQ") + U = NewCode("U") + UDOWN = NewCode("UDOWN") + GAIN = NewCode("GAIN") + USC = NewCode("USC") + UMC = NewCode("UMC") + UNF = NewCode("UNF") + UNIFY = NewCode("UNIFY") + USDE = NewCode("USDE") + UBTC = NewCode("UBTC") + UIS = NewCode("UIS") + UNIT = NewCode("UNIT") + UNI = NewCode("UNI") + UXC = NewCode("UXC") + URC = NewCode("URC") + XUP = NewCode("XUP") + UFR = NewCode("UFR") + URO = NewCode("URO") + UTLE = NewCode("UTLE") + VAL = NewCode("VAL") + VPRC = NewCode("VPRC") + VAPOR = NewCode("VAPOR") + VCOIN = NewCode("VCOIN") + VEC = NewCode("VEC") + VEC2 = NewCode("VEC2") + VLT = NewCode("VLT") + VENE = NewCode("VENE") + VNTX = NewCode("VNTX") + VTN = NewCode("VTN") + CRED = NewCode("CRED") + VERS = NewCode("VERS") + VTX = NewCode("VTX") + VTY = NewCode("VTY") + VIP = NewCode("VIP") + VISIO = NewCode("VISIO") + VK = NewCode("VK") + VOL = NewCode("VOL") + VOYA = NewCode("VOYA") + VPN = NewCode("VPN") + XVS = NewCode("XVS") + VTL = NewCode("VTL") + VULC = NewCode("VULC") + VVI = NewCode("VVI") + WGR = NewCode("WGR") + WAM = NewCode("WAM") + WARP = NewCode("WARP") + WASH = NewCode("WASH") + WGO = NewCode("WGO") + WAY = NewCode("WAY") + WCASH = NewCode("WCASH") + WEALTH = NewCode("WEALTH") + WEEK = NewCode("WEEK") + WHO = NewCode("WHO") + WIC = NewCode("WIC") + WBB = NewCode("WBB") + WINE = NewCode("WINE") + WINK = NewCode("WINK") + WISC = NewCode("WISC") + WITCH = NewCode("WITCH") + WMC = NewCode("WMC") + WOMEN = NewCode("WOMEN") + WOK = NewCode("WOK") + WRT = NewCode("WRT") + XCO = NewCode("XCO") + X2 = NewCode("X2") + XNX = NewCode("XNX") + XAU = NewCode("XAU") + XAV = NewCode("XAV") + XDE2 = NewCode("XDE2") + XDE = NewCode("XDE") + XIOS = NewCode("XIOS") + XOC = NewCode("XOC") + XSSX = NewCode("XSSX") + XBY = NewCode("XBY") + YAC = NewCode("YAC") + YMC = NewCode("YMC") + YAY = NewCode("YAY") + YBC = NewCode("YBC") + YES = NewCode("YES") + YOB2X = NewCode("YOB2X") + YOVI = NewCode("YOVI") + ZYD = NewCode("ZYD") + ZECD = NewCode("ZECD") + ZEIT = NewCode("ZEIT") + ZENI = NewCode("ZENI") + ZET2 = NewCode("ZET2") + ZET = NewCode("ZET") + ZMC = NewCode("ZMC") + ZIRK = NewCode("ZIRK") + ZLQ = NewCode("ZLQ") + ZNE = NewCode("ZNE") + ZONTO = NewCode("ZONTO") + ZOOM = NewCode("ZOOM") + ZRC = NewCode("ZRC") + ZUR = NewCode("ZUR") + ZB = NewCode("ZB") + QC = NewCode("QC") + HLC = NewCode("HLC") + SAFE = NewCode("SAFE") + BTN = NewCode("BTN") + CDC = NewCode("CDC") + DDM = NewCode("DDM") + HOTC = NewCode("HOTC") + BDS = NewCode("BDS") + AAA = NewCode("AAA") + XWC = NewCode("XWC") + PDX = NewCode("PDX") + SLT = NewCode("SLT") + HPY = NewCode("HPY") + XXRP = NewCode("XXRP") // XRP + XXBT = NewCode("XXBT") // BTC, but XXBT instead + XXDG = NewCode("XXDG") // DOGE + XDG = NewCode("XDG") // DOGE + HKD = NewCode("HKD") // Hong Kong Dollar + AUD = NewCode("AUD") // Australian Dollar + USD = NewCode("USD") // United States Dollar + ZUSD = NewCode("ZUSD") // United States Dollar, but with a Z in front of it + EUR = NewCode("EUR") // Euro + ZEUR = NewCode("ZEUR") // Euro, but with a Z in front of it + CAD = NewCode("CAD") // Canadaian Dollar + ZCAD = NewCode("ZCAD") // Canadaian Dollar, but with a Z in front of it + SGD = NewCode("SGD") // Singapore Dollar + RUB = NewCode("RUB") // RUssian ruBle + RUR = NewCode("RUR") // RUssian Ruble + PLN = NewCode("PLN") // Polish zÅ‚oty + TRY = NewCode("TRY") // Turkish lira + UAH = NewCode("UAH") // Ukrainian hryvnia + JPY = NewCode("JPY") // Japanese yen + ZJPY = NewCode("ZJPY") // Japanese yen, but with a Z in front of it + LCH = NewCode("LCH") + MYR = NewCode("MYR") + AFN = NewCode("AFN") + ARS = NewCode("ARS") + AWG = NewCode("AWG") + AZN = NewCode("AZN") + BSD = NewCode("BSD") + BBD = NewCode("BBD") + BYN = NewCode("BYN") + BZD = NewCode("BZD") + BMD = NewCode("BMD") + BOB = NewCode("BOB") + BAM = NewCode("BAM") + BWP = NewCode("BWP") + BGN = NewCode("BGN") + BRL = NewCode("BRL") + BND = NewCode("BND") + KHR = NewCode("KHR") + KYD = NewCode("KYD") + CLP = NewCode("CLP") + CNY = NewCode("CNY") + COP = NewCode("COP") + HRK = NewCode("HRK") + CUP = NewCode("CUP") + CZK = NewCode("CZK") + DKK = NewCode("DKK") + DOP = NewCode("DOP") + XCD = NewCode("XCD") + EGP = NewCode("EGP") + SVC = NewCode("SVC") + FKP = NewCode("FKP") + FJD = NewCode("FJD") + GIP = NewCode("GIP") + GTQ = NewCode("GTQ") + GGP = NewCode("GGP") + GYD = NewCode("GYD") + HNL = NewCode("HNL") + HUF = NewCode("HUF") + ISK = NewCode("ISK") + INR = NewCode("INR") + IDR = NewCode("IDR") + IRR = NewCode("IRR") + IMP = NewCode("IMP") + ILS = NewCode("ILS") + JMD = NewCode("JMD") + JEP = NewCode("JEP") + KZT = NewCode("KZT") + KPW = NewCode("KPW") + KGS = NewCode("KGS") + LAK = NewCode("LAK") + LBP = NewCode("LBP") + LRD = NewCode("LRD") + MKD = NewCode("MKD") + MUR = NewCode("MUR") + MXN = NewCode("MXN") + MNT = NewCode("MNT") + MZN = NewCode("MZN") + NAD = NewCode("NAD") + NPR = NewCode("NPR") + ANG = NewCode("ANG") + NZD = NewCode("NZD") + NIO = NewCode("NIO") + NGN = NewCode("NGN") + NOK = NewCode("NOK") + OMR = NewCode("OMR") + PKR = NewCode("PKR") + PAB = NewCode("PAB") + PYG = NewCode("PYG") + PHP = NewCode("PHP") + QAR = NewCode("QAR") + RON = NewCode("RON") + SHP = NewCode("SHP") + SAR = NewCode("SAR") + RSD = NewCode("RSD") + SCR = NewCode("SCR") + SOS = NewCode("SOS") + ZAR = NewCode("ZAR") + LKR = NewCode("LKR") + SEK = NewCode("SEK") + CHF = NewCode("CHF") + SRD = NewCode("SRD") + SYP = NewCode("SYP") + TWD = NewCode("TWD") + THB = NewCode("THB") + TTD = NewCode("TTD") + TVD = NewCode("TVD") + GBP = NewCode("GBP") + UYU = NewCode("UYU") + UZS = NewCode("UZS") + VEF = NewCode("VEF") + VND = NewCode("VND") + YER = NewCode("YER") + ZWD = NewCode("ZWD") + XETH = NewCode("XETH") + FX_BTC = NewCode("FX_BTC") // nolint // Cryptocurrency code + AAVE = NewCode("AAVE") + YFI = NewCode("YFI") + BAL = NewCode("BAL") + UMA = NewCode("UMA") + SNX = NewCode("SNX") + CRV = NewCode("CRV") + OXT = NewCode("OXT") + BUSD = NewCode("BUSD") + SRM = NewCode("SRM") + FTT = NewCode("FTT") + UGX = NewCode("UGX") // Uganda Shilling + GLM = NewCode("GLM") // Golem + WAXP = NewCode("WAXP") + STRAX = NewCode("STRAX") // Stratis + TMTG = NewCode("TMTG") // The Midas Touch Gold + HDAC = NewCode("HDAC") + AMO = NewCode("AMO") + BSV = NewCode("BSV") + ORBS = NewCode("ORBS") + TFUEL = NewCode("TFUEL") + VALOR = NewCode("VALOR") + ANKR = NewCode("ANKR") + MIX = NewCode("MIX") + CRO = NewCode("CRO") + CHR = NewCode("CHR") + MBL = NewCode("MBL") + MXC = NewCode("MXC") + TRV = NewCode("TRV") + DAD = NewCode("DAD") + WOM = NewCode("WOM") + EM = NewCode("EM") + BOA = NewCode("BOA") + FLETA = NewCode("FLETA") + SXP = NewCode("SXP") + COS = NewCode("COS") + APIX = NewCode("APIX") + EL = NewCode("EL") + BASIC = NewCode("BASIC") + HIV = NewCode("HIV") + XPR = NewCode("XPR") + VRA = NewCode("VRA") + BORA = NewCode("BORA") + APM = NewCode("APM") + CKB = NewCode("CKB") + AERGO = NewCode("AERGO") + ANW = NewCode("ANW") + CENNZ = NewCode("CENNZ") + EVZ = NewCode("EVZ") + CYCLUB = NewCode("CYCLUB") + QTCON = NewCode("QTCON") + RSR = NewCode("RSR") + UOS = NewCode("UOS") + SAND = NewCode("SAND") + STPT = NewCode("STPT") + GOM2 = NewCode("GOM2") + RINGX = NewCode("RINGX") + BEL = NewCode("BEL") + OBSR = NewCode("OBSR") + ORC = NewCode("ORC") + POLA = NewCode("POLA") + AWO = NewCode("AWO") + ADP = NewCode("ADP") + DVI = NewCode("DVI") + IBP = NewCode("IBP") + MIR = NewCode("MIR") + GHX = NewCode("GHX") + BLY = NewCode("BLY") + WOZX = NewCode("WOZX") + ANV = NewCode("ANV") + GRT = NewCode("GRT") + BIOT = NewCode("BIOT") + XNO = NewCode("XNO") + COLA = NewCode("COLA") + NU = NewCode("NU") + LINA = NewCode("LINA") + ASTA = NewCode("ASTA") + MAP = NewCode("MAP") + AQT = NewCode("AQT") + WIKEN = NewCode("WIKEN") + CTSI = NewCode("CTSI") + LPT = NewCode("LPT") + SUSHI = NewCode("SUSHI") + ASM = NewCode("ASM") + CELR = NewCode("CELR") + PUNDIX = NewCode("PUNDIX") + LF = NewCode("LF") + ARW = NewCode("ARW") + MSB = NewCode("MSB") + RLY = NewCode("RLY") + BFC = NewCode("BFC") + ALICE = NewCode("ALICE") + CAKE = NewCode("CAKE") + CHZ = NewCode("CHZ") + AXS = NewCode("AXS") + MATIC = NewCode("MATIC") + BAKE = NewCode("BAKE") + VELO = NewCode("VELO") + GXC = NewCode("GXC") + BTT = NewCode("BTT") + VSYS = NewCode("VSYS") + IPX = NewCode("IPX") + WICC = NewCode("WICC") + META = NewCode("META") + KLAY = NewCode("KLAY") + ALGO = NewCode("ALGO") + JST = NewCode("JST") + MLK = NewCode("MLK") + WEMIX = NewCode("WEMIX") + DOT = NewCode("DOT") + SSX = NewCode("SSX") + TEMCO = NewCode("TEMCO") + HIBS = NewCode("HIBS") + BURGER = NewCode("BURGER") + KSM = NewCode("KSM") + XYM = NewCode("XYM") + SUN = NewCode("SUN") + XEC = NewCode("XEC") + PCI = NewCode("PCI") + SOL = NewCode("SOL") + LN = NewCode("LN") + GUSD = NewCode("GUSD") + AUDIO = NewCode("AUDIO") + EURT = NewCode("EURT") + ALPHA = NewCode("ALPHA") + MCAU = NewCode("MCAU") + AED = NewCode("AED") + BAND = NewCode("BAND") + BCB = NewCode("BCB") + BRZ = NewCode("BRZ") + BTSE = NewCode("BTSE") + FRM = NewCode("FRM") + HXRO = NewCode("HXRO") + LEO = NewCode("LEO") + MBM = NewCode("MBM") + PHNX = NewCode("PHNX") + SFI = NewCode("SFI") + SHIB = NewCode("SHIB") + STAKE = NewCode("STAKE") + SWRV = NewCode("SWRV") + TRYB = NewCode("TRYB") + USDP = NewCode("USDP") + WAUD = NewCode("WAUD") + WCAD = NewCode("WCAD") + WCHF = NewCode("WCHF") + WEUR = NewCode("WEUR") + WGBP = NewCode("WGBP") + WHKD = NewCode("WHKD") + WINR = NewCode("WINR") + WJPY = NewCode("WJPY") + WMYR = NewCode("WMYR") + WOO = NewCode("WOO") + WSGD = NewCode("WSGD") + WUSD = NewCode("WUSD") + WXMR = NewCode("WXMR") + XAUT = NewCode("XAUT") + XSGD = NewCode("XSGD") + EXM = NewCode("EXM") + BTCV = NewCode("BTCV") + CRON = NewCode("CRON") + GNY = NewCode("GNY") + HAI = NewCode("HAI") + HB = NewCode("HB") + HP = NewCode("HP") + IQN = NewCode("IQN") + MNC = NewCode("MNC") + ONE = NewCode("ONE") + PRQ = NewCode("PRQ") + ROOBEE = NewCode("ROOBEE") + TONCOIN = NewCode("TONCOIN") + VLX = NewCode("VLX") + WXT = NewCode("WXT") + UST = NewCode("UST") + USDG = NewCode("USDG") + NYZO = NewCode("NYZO") + ETH2 = NewCode("ETH2") + KAVA = NewCode("KAVA") + RSV = NewCode("RSV") + MTRG = NewCode("MTRG") + COTI = NewCode("COTI") + DIGG = NewCode("DIGG") + YAMV1 = NewCode("YAMV1") + BZRX = NewCode("BZRX") + YAMV2 = NewCode("YAMV2") + BOX = NewCode("BOX") + ERG = NewCode("ERG") + KPHA = NewCode("KPHA") + KAR = NewCode("KAR") + RMRK = NewCode("RMRK") + CRING = NewCode("CRING") + PICA = NewCode("PICA") + XRT = NewCode("XRT") + TEER = NewCode("TEER") + SGB = NewCode("SGB") + KPN = NewCode("KPN") + CSM = NewCode("CSM") + KAZE = NewCode("KAZE") + SASHIMI = NewCode("SASHIMI") + AUCTION = NewCode("AUCTION") + OIN = NewCode("OIN") + ADEL = NewCode("ADEL") + KIMCHI = NewCode("KIMCHI") + CREAM = NewCode("CREAM") + DEGO = NewCode("DEGO") + SFG = NewCode("SFG") + CORE = NewCode("CORE") + ARNX = NewCode("ARNX") + ROSE = NewCode("ROSE") + COVER = NewCode("COVER") + BASE = NewCode("BASE") + HEGIC = NewCode("HEGIC") + DUSK = NewCode("DUSK") + UNFI = NewCode("UNFI") + GHST = NewCode("GHST") + ACH = NewCode("ACH") + FXS = NewCode("FXS") + BORING = NewCode("BORING") + LON = NewCode("LON") + POND = NewCode("POND") + DSD = NewCode("DSD") + SHARE = NewCode("SHARE") + ONC = NewCode("ONC") + ZKS = NewCode("ZKS") + RIF = NewCode("RIF") + PROPS = NewCode("PROPS") + LAYER = NewCode("LAYER") + QNT = NewCode("QNT") + YOP = NewCode("YOP") + BONDED = NewCode("BONDED") + ROOM = NewCode("ROOM") + UNISTAKE = NewCode("UNISTAKE") + FXF = NewCode("FXF") + TORN = NewCode("TORN") + UMB = NewCode("UMB") + JASMY = NewCode("JASMY") + BONDLY = NewCode("BONDLY") + BMI = NewCode("BMI") + RAY = NewCode("RAY") + POLIS = NewCode("POLIS") + WAG = NewCode("WAG") + CYS = NewCode("CYS") + SLRS = NewCode("SLRS") + LIKE = NewCode("LIKE") + PRT = NewCode("PRT") + SUNNY = NewCode("SUNNY") + MNGO = NewCode("MNGO") + STEP = NewCode("STEP") + FIDA = NewCode("FIDA") + PBR = NewCode("PBR") + HOPR = NewCode("HOPR") + PROM = NewCode("PROM") + TVK = NewCode("TVK") + A5T = NewCode("A5T") + CUDOS = NewCode("CUDOS") + COMBO = NewCode("COMBO") + DOWS = NewCode("DOWS") + KYL = NewCode("KYL") + EXRD = NewCode("EXRD") + ETHA = NewCode("ETHA") + ALN = NewCode("ALN") + HAPI = NewCode("HAPI") + BLANK = NewCode("BLANK") + ERN = NewCode("ERN") + KINE = NewCode("KINE") + FET = NewCode("FET") + ZEE = NewCode("ZEE") + POLC = NewCode("POLC") + XED = NewCode("XED") + ANC = NewCode("ANC") + DAFI = NewCode("DAFI") + TARA = NewCode("TARA") + PCNT = NewCode("PCNT") + DG = NewCode("DG") + SPI = NewCode("SPI") + BANK = NewCode("BANK") + UMX = NewCode("UMX") + TIDAL = NewCode("TIDAL") + LABS = NewCode("LABS") + OGN = NewCode("OGN") + BLES = NewCode("BLES") + OVR = NewCode("OVR") + HGET = NewCode("HGET") + NOIA = NewCode("NOIA") + COOK = NewCode("COOK") + FST = NewCode("FST") + AME = NewCode("AME") + STN = NewCode("STN") + SHOPX = NewCode("SHOPX") + SHFT = NewCode("SHFT") + RBC = NewCode("RBC") + VAI = NewCode("VAI") + FEI = NewCode("FEI") + XEND = NewCode("XEND") + SUKU = NewCode("SUKU") + LTO = NewCode("LTO") + TOTM = NewCode("TOTM") + RAZE = NewCode("RAZE") + DUCK2 = NewCode("DUCK2") + CEL = NewCode("CEL") + DDIM = NewCode("DDIM") + TLM = NewCode("TLM") + DDOS = NewCode("DDOS") + GS = NewCode("GS") + RAGE = NewCode("RAGE") + AKITA = NewCode("AKITA") + FORTH = NewCode("FORTH") + CARDS = NewCode("CARDS") + HORD = NewCode("HORD") + WBTC = NewCode("WBTC") + ARES = NewCode("ARES") + SUSD = NewCode("SUSD") + TCP = NewCode("TCP") + BLACK = NewCode("BLACK") + EZ = NewCode("EZ") + VSO = NewCode("VSO") + XAVA = NewCode("XAVA") + PNG = NewCode("PNG") + LOCG = NewCode("LOCG") + WSIENNA = NewCode("WSIENNA") + STBU = NewCode("STBU") + DFND = NewCode("DFND") + GDT = NewCode("GDT") + PRARE = NewCode("PRARE") + GYEN = NewCode("GYEN") + METIS = NewCode("METIS") + BZZ = NewCode("BZZ") + TENSET = NewCode("10SET") + STRING = NewCode("STRING") + PDEX = NewCode("PDEX") + FEAR = NewCode("FEAR") + ELON = NewCode("ELON") + NOA = NewCode("NOA") + NAOS = NewCode("NAOS") + GITCOIN = NewCode("GITCOIN") + XCAD = NewCode("XCAD") + LSS = NewCode("LSS") + CVX = NewCode("CVX") + PHTR = NewCode("PHTR") + APN = NewCode("APN") + DFYN = NewCode("DFYN") + LIME = NewCode("LIME") + FORM = NewCode("FORM") + KEX = NewCode("KEX") + DLTA = NewCode("DLTA") + DPR = NewCode("DPR") + CQT = NewCode("CQT") + OLY = NewCode("OLY") + FUSE = NewCode("FUSE") + SRK = NewCode("SRK") + BURP = NewCode("BURP") + CART = NewCode("CART") + C98 = NewCode("C98") + DNXC = NewCode("DNXC") + DERC = NewCode("DERC") + PLA = NewCode("PLA") + EFI = NewCode("EFI") + HMT = NewCode("HMT") + SKT = NewCode("SKT") + SPHRI = NewCode("SPHRI") + BIT = NewCode("BIT") + RARE = NewCode("RARE") + ZLW = NewCode("ZLW") + SKYRIM = NewCode("SKYRIM") + OCT = NewCode("OCT") + ATA = NewCode("ATA") + PUSH = NewCode("PUSH") + REVO = NewCode("REVO") + VENT = NewCode("VENT") + LDO = NewCode("LDO") + GEL = NewCode("GEL") + CTRC = NewCode("CTRC") + ITGR = NewCode("ITGR") + HOTCROSS = NewCode("HOTCROSS") + OPUL = NewCode("OPUL") + POLI = NewCode("POLI") + TAUR = NewCode("TAUR") + EQX = NewCode("EQX") + RBN = NewCode("RBN") + PHM = NewCode("PHM") + FLOKI = NewCode("FLOKI") + CIRUS = NewCode("CIRUS") + DYDX = NewCode("DYDX") + RGT = NewCode("RGT") + AGLD = NewCode("AGLD") + DOGNFT = NewCode("DOGNFT") + SOV = NewCode("SOV") + URUS = NewCode("URUS") + CFG = NewCode("CFG") + TBTC = NewCode("TBTC") + NFTX = NewCode("NFTX") + ORAI = NewCode("ORAI") + LIT = NewCode("LIT") + POOLZ = NewCode("POOLZ") + DODO = NewCode("DODO") + IPAD = NewCode("IPAD") + OPIUM = NewCode("OPIUM") + REEF = NewCode("REEF") + MAPS = NewCode("MAPS") + ZCN = NewCode("ZCN") + BAO = NewCode("BAO") + DIS = NewCode("DIS") + PBTC35A = NewCode("PBTC35A") + NORD = NewCode("NORD") + FLOW = NewCode("FLOW") + FIN = NewCode("FIN") + INJ = NewCode("INJ") + KP3R = NewCode("KP3R") + HYVE = NewCode("HYVE") + RAMP = NewCode("RAMP") + RARI = NewCode("RARI") + MPH = NewCode("MPH") + CVP = NewCode("CVP") + VALUE = NewCode("VALUE") + YFII = NewCode("YFII") + TROY = NewCode("TROY") + SPA = NewCode("SPA") + FOR = NewCode("FOR") + DIA = NewCode("DIA") + TRB = NewCode("TRB") + PEARL = NewCode("PEARL") + NFT = NewCode("NFT") + SLM = NewCode("SLM") + TAI = NewCode("TAI") + JFI = NewCode("JFI") + DKA = NewCode("DKA") + DOS = NewCode("DOS") + LBK = NewCode("LBK") + ASD = NewCode("ASD") + SWOP = NewCode("SWOP") + WEST = NewCode("WEST") + HYDRA = NewCode("HYDRA") + OLT = NewCode("OLT") + LAT = NewCode("LAT") + STC = NewCode("STC") + HNT = NewCode("HNT") + AKT = NewCode("AKT") + BTC3L = NewCode("BTC3L") + COTI3L = NewCode("COTI3L") + XCH3L = NewCode("XCH3L") + IOST3L = NewCode("IOST3L") + BZZ3L = NewCode("BZZ3L") + TRIBE3L = NewCode("TRIBE3L") + RAY3L = NewCode("RAY3L") + AR3L = NewCode("AR3L") + ONE3L = NewCode("ONE3L") + HBAR3L = NewCode("HBAR3L") + CSPR3L = NewCode("CSPR3L") + SXP3L = NewCode("SXP3L") + XEC3L = NewCode("XEC3L") + LIT3L = NewCode("LIT3L") + MINA3L = NewCode("MINA3L") + GALA3L = NewCode("GALA3L") + FTT3L = NewCode("FTT3L") + C983L = NewCode("C983L") + DYDX3L = NewCode("DYDX3L") + MTL3L = NewCode("MTL3L") + FTM3L = NewCode("FTM3L") + SAND3L = NewCode("SAND3L") + LUNA3L = NewCode("LUNA3L") + ALPHA3L = NewCode("ALPHA3L") + RUNE3L = NewCode("RUNE3L") + ICP3L = NewCode("ICP3L") + SHIB3L = NewCode("SHIB3L") + ACH3L = NewCode("ACH3L") + ALICE3L = NewCode("ALICE3L") + AXS3L = NewCode("AXS3L") + MATIC3L = NewCode("MATIC3L") + BTC5L = NewCode("BTC5L") + BCH5L = NewCode("BCH5L") + DOT5L = NewCode("DOT5L") + XRP5L = NewCode("XRP5L") + BSV5L = NewCode("BSV5L") + LTC5L = NewCode("LTC5L") + EOS5L = NewCode("EOS5L") + ETH5L = NewCode("ETH5L") + LINK3L = NewCode("LINK3L") + KAVA3L = NewCode("KAVA3L") + EGLD3L = NewCode("EGLD3L") + CHZ3L = NewCode("CHZ3L") + MKR3L = NewCode("MKR3L") + LRC3L = NewCode("LRC3L") + BAL3L = NewCode("BAL3L") + JST3L = NewCode("JST3L") + SERO3L = NewCode("SERO3L") + VET3L = NewCode("VET3L") + THETA3L = NewCode("THETA3L") + ZIL3L = NewCode("ZIL3L") + GRIN3L = NewCode("GRIN3L") + BEAM3L = NewCode("BEAM3L") + SOL3L = NewCode("SOL3L") + SKL3L = NewCode("SKL3L") + ONEINCH3L = NewCode("1INCH3L") + LON3L = NewCode("LON3L") + DOGE3L = NewCode("DOGE3L") + GRT3L = NewCode("GRT3L") + BNB3L = NewCode("BNB3L") + TRX3L = NewCode("TRX3L") + ATOM3L = NewCode("ATOM3L") + AVAX3L = NewCode("AVAX3L") + NEAR3L = NewCode("NEAR3L") + ROSE3L = NewCode("ROSE3L") + ZEN3L = NewCode("ZEN3L") + QTUM3L = NewCode("QTUM3L") + XLM3L = NewCode("XLM3L") + XRP3L = NewCode("XRP3L") + CFX3L = NewCode("CFX3L") + OMG3L = NewCode("OMG3L") + ALGO3L = NewCode("ALGO3L") + WAVES3L = NewCode("WAVES3L") + NEO3L = NewCode("NEO3L") + ONT3L = NewCode("ONT3L") + ETC3L = NewCode("ETC3L") + CVC3L = NewCode("CVC3L") + SNX3L = NewCode("SNX3L") + ADA3L = NewCode("ADA3L") + DASH3L = NewCode("DASH3L") + AAVE3L = NewCode("AAVE3L") + SRM3L = NewCode("SRM3L") + KSM3L = NewCode("KSM3L") + BTM3L = NewCode("BTM3L") + ZEC3L = NewCode("ZEC3L") + XMR3L = NewCode("XMR3L") + AMPL3L = NewCode("AMPL3L") + CRV3L = NewCode("CRV3L") + COMP3L = NewCode("COMP3L") + YFII3L = NewCode("YFII3L") + YFI3L = NewCode("YFI3L") + HT3L = NewCode("HT3L") + OKB3L = NewCode("OKB3L") + UNI3L = NewCode("UNI3L") + DOT3L = NewCode("DOT3L") + FIL3L = NewCode("FIL3L") + SUSHI3L = NewCode("SUSHI3L") + ETH3L = NewCode("ETH3L") + EOS3L = NewCode("EOS3L") + BSV3L = NewCode("BSV3L") + BCH3L = NewCode("BCH3L") + LTC3L = NewCode("LTC3L") + XTZ3L = NewCode("XTZ3L") + RVN = NewCode("RVN") + AR = NewCode("AR") + SNK = NewCode("SNK") + NSDX = NewCode("NSDX") + HIVE = NewCode("HIVE") + BCHA = NewCode("BCHA") + FLUX = NewCode("FLUX") + NAX = NewCode("NAX") + NBOT = NewCode("NBOT") + BEAM = NewCode("BEAM") + MINA = NewCode("MINA") + ABBC = NewCode("ABBC") + FIC = NewCode("FIC") + STOX = NewCode("STOX") + VIDYX = NewCode("VIDYX") + CNNS = NewCode("CNNS") + BTCBEAR = NewCode("BTCBEAR") + ETHBULL = NewCode("ETHBULL") + EOSBEAR = NewCode("EOSBEAR") + XRPBULL = NewCode("XRPBULL") + WGRT = NewCode("WGRT") + RUNE = NewCode("RUNE") + CBK = NewCode("CBK") + OPA = NewCode("OPA") + KABY = NewCode("KABY") + BP = NewCode("BP") + SFUND = NewCode("SFUND") + ASTRO = NewCode("ASTRO") + ARV = NewCode("ARV") + ROSN = NewCode("ROSN") + CPHR = NewCode("CPHR") + KWS = NewCode("KWS") + CTT = NewCode("CTT") + BEEFI = NewCode("BEEFI") + BLIN = NewCode("BLIN") + XPNET = NewCode("XPNET") + BABY = NewCode("BABY") + OPS = NewCode("OPS") + RACA = NewCode("RACA") + HOD = NewCode("HOD") + OLYMPUS = NewCode("OLYMPUS") + BMON = NewCode("BMON") + PVU = NewCode("PVU") + FAN = NewCode("FAN") + SKILL = NewCode("SKILL") + SPS = NewCode("SPS") + HERO = NewCode("HERO") + FEVR = NewCode("FEVR") + WEX = NewCode("WEX") + KALM = NewCode("KALM") + KPAD = NewCode("KPAD") + BABYDOGE = NewCode("BABYDOGE") + PIG = NewCode("PIG") + FINE = NewCode("FINE") + BSCS = NewCode("BSCS") + SAFEMARS = NewCode("SAFEMARS") + PSG = NewCode("PSG") + PET = NewCode("PET") + ALPACA = NewCode("ALPACA") + BRY = NewCode("BRY") + TOOLS = NewCode("TOOLS") + JULD = NewCode("JULD") + FRA = NewCode("FRA") + TWT = NewCode("TWT") + WIN = NewCode("WIN") + MTV = NewCode("MTV") + HPB = NewCode("HPB") + EGLD = NewCode("EGLD") + CSPR = NewCode("CSPR") + FIS = NewCode("FIS") + MDX = NewCode("MDX") + WAR = NewCode("WAR") + XNFT = NewCode("XNFT") + BXH = NewCode("BXH") + BAGS = NewCode("BAGS") + ALEPH = NewCode("ALEPH") + KEEP = NewCode("KEEP") + NXM = NewCode("NXM") + ONEINCH = NewCode("ONEINCH") + SKL = NewCode("SKL") + BOND = NewCode("BOND") + ALCX = NewCode("ALCX") + API3 = NewCode("API3") + DDX = NewCode("DDX") + FTM = NewCode("FTM") + CTX = NewCode("CTX") + ILV = NewCode("ILV") + MC02 = NewCode("MC02") + SLP = NewCode("SLP") + WTON = NewCode("WTON") + EFIL = NewCode("EFIL") + MTX = NewCode("MTX") + YGG = NewCode("YGG") + QCASH = NewCode("QCASH") + TV = NewCode("TV") + BCW = NewCode("BCW") + ENTC = NewCode("ENTC") + XWCC = NewCode("XWCC") + BRC = NewCode("BRC") + GRIN = NewCode("GRIN") + B91 = NewCode("B91") + YTNB = NewCode("YTNB") + NWT = NewCode("NWT") + BAR = NewCode("BAR") + ACC = NewCode("ACC") + HX = NewCode("HX") + LVN = NewCode("LVN") + TSR = NewCode("TSR") + FN = NewCode("FN") + HNS = NewCode("HNS") + KPG = NewCode("KPG") + LTG = NewCode("LTG") + UFO = NewCode("UFO") + GUCS = NewCode("GUCS") + VBT = NewCode("VBT") + DSF = NewCode("DSF") + GST = NewCode("GST") + DAWN = NewCode("DAWN") + UFC = NewCode("UFC") + EP = NewCode("EP") + ULU = NewCode("ULU") + DMD = NewCode("DMD") + NBS = NewCode("NBS") + BGPT = NewCode("BGPT") + DIP = NewCode("DIP") + QFIL = NewCode("QFIL") + RTF = NewCode("RTF") + M = NewCode("M") + FOMP = NewCode("FOMP") + BDM = NewCode("BDM") + DORA = NewCode("DORA") + UZ = NewCode("UZ") + BKH = NewCode("BKH") + CRU = NewCode("CRU") + IDV = NewCode("IDV") + NEAR = NewCode("NEAR") + DFL = NewCode("DFL") + BED = NewCode("BED") + SDOG = NewCode("SDOG") + CFX = NewCode("CFX") + CATE = NewCode("CATE") + ONETHOUSANDHOKK = NewCode("1000HOKK") + ONETHOUSANDKISHU = NewCode("1000KISHU") + XFLR = NewCode("XFLR") + ICP = NewCode("ICP") + BNA = NewCode("BNA") + DOM = NewCode("DOM") + POLS = NewCode("POLS") + O3 = NewCode("O3") + CLV = NewCode("CLV") + FARM = NewCode("FARM") + ORN = NewCode("ORN") + QUICK = NewCode("QUICK") + TRU = NewCode("TRU") + SANA = NewCode("SANA") + TRIBE = NewCode("TRIBE") + CELO = NewCode("CELO") + SDN = NewCode("SDN") + WNCG = NewCode("WNCG") + AMC = NewCode("AMC") + OOE = NewCode("OOE") + XYO = NewCode("XYO") + GALA = NewCode("GALA") + ZKN = NewCode("ZKN") + XCH = NewCode("XCH") + AC = NewCode("AC") + ABTC = NewCode("ABTC") + AFC = NewCode("AFC") + AGE = NewCode("AGE") + AIN = NewCode("AIN") + ALI = NewCode("ALI") + ALIX = NewCode("ALIX") + ANJ = NewCode("ANJ") + ANRX = NewCode("ANRX") + ANY = NewCode("ANY") + AOS = NewCode("AOS") + AQUAGOAT = NewCode("AQUAGOAT") + ARTCN = NewCode("ARTCN") + ARTE = NewCode("ARTE") + AT = NewCode("AT") + ATC = NewCode("ATC") + ATLAS = NewCode("ATLAS") + ATP = NewCode("ATP") + ATPNAS = NewCode("ATPNAS") + AURY = NewCode("AURY") + AUSD = NewCode("AUSD") + AUTO = NewCode("AUTO") + AVAX = NewCode("AVAX") + AVF = NewCode("AVF") + AWR = NewCode("AWR") + B20 = NewCode("B20") + BADGER = NewCode("BADGER") + BAFE = NewCode("BAFE") + BANANA = NewCode("BANANA") + BAS = NewCode("BAS") + BASEL = NewCode("BASEL") + BASID = NewCode("BASID") + BBC = NewCode("BBC") + BBCNP = NewCode("BBCNP") + BCK = NewCode("BCK") + BDP = NewCode("BDP") + BELT = NewCode("BELT") + SLIM = NewCode("SLIM") + SPN = NewCode("SPN") + VUSD = NewCode("VUSD") + POLYBUNNY = NewCode("POLYBUNNY") + STARL = NewCode("STARL") + KISC = NewCode("KISC") + MASS = NewCode("MASS") + MOYU = NewCode("MOYU") + PLUG = NewCode("PLUG") + SFC = NewCode("SFC") + TEP = NewCode("TEP") + GOFX = NewCode("GOFX") + KAINET = NewCode("KAINET") + BXA = NewCode("BXA") + SLOT = NewCode("SLOT") + EXVA = NewCode("EXVA") + MW = NewCode("MW") + BOO = NewCode("BOO") + BZKY = NewCode("BZKY") + NFTART = NewCode("NFTART") + QRDO = NewCode("QRDO") + SHILL = NewCode("SHILL") + SIT = NewCode("SIT") + USF = NewCode("USF") + EBSO = NewCode("EBSO") + GUSDT = NewCode("GUSDT") + BTRST = NewCode("BTRST") + DBX = NewCode("DBX") + MARSINU = NewCode("MARSINU") + GEMG = NewCode("GEMG") + HYPE = NewCode("HYPE") + ELCASH = NewCode("ELCASH") + FEG = NewCode("FEG") + MTC = NewCode("MTC") + NCT = NewCode("NCT") + PSYDUCK = NewCode("PSYDUCK") + SEAL = NewCode("SEAL") + DOGEKONGZILLA = NewCode("DOGEKONGZILLA") + DOUGH = NewCode("DOUGH") + SURFMOON = NewCode("SURFMOON") + BUIDL = NewCode("BUIDL") + DOGGY = NewCode("DOGGY") + VNX = NewCode("VNX") + BSB = NewCode("BSB") + GOF = NewCode("GOF") + GM = NewCode("GM") + TEN = NewCode("TEN") + CRT = NewCode("CRT") + FIL12 = NewCode("FIL12") + WAXE = NewCode("WAXE") + VEGA = NewCode("VEGA") + LSP = NewCode("LSP") + TOWER = NewCode("TOWER") + GL = NewCode("GL") + IBNB = NewCode("IBNB") + WDS = NewCode("WDS") + YYE = NewCode("YYE") + GHC = NewCode("GHC") + LBKL = NewCode("LBKL") + NASADOGE = NewCode("NASADOGE") + TKX = NewCode("TKX") + XWC2 = NewCode("XWC2") + CNEX = NewCode("CNEX") + DOKI = NewCode("DOKI") + MX = NewCode("MX") + UCA = NewCode("UCA") + NKGEN = NewCode("NKGEN") + STPL = NewCode("STPL") + CYE = NewCode("CYE") + KBC = NewCode("KBC") + X = NewCode("X") + GINU = NewCode("GINU") + MEDA = NewCode("MEDA") + TREES = NewCode("TREES") + UZUMAKI = NewCode("UZUMAKI") + CHLT = NewCode("CHLT") + DBZ = NewCode("DBZ") + IMX = NewCode("IMX") + LIEN = NewCode("LIEN") + ULTRA = NewCode("ULTRA") + BTSC = NewCode("BTSC") + EAI = NewCode("EAI") + CORGI = NewCode("CORGI") + MINISHIBA = NewCode("MINISHIBA") + BLOC = NewCode("BLOC") + PEPPA = NewCode("PEPPA") + FOUR = NewCode("FOUR") + PERP = NewCode("PERP") + THG = NewCode("THG") + COLLIE = NewCode("COLLIE") + FO = NewCode("FO") + HER = NewCode("HER") + DNS = NewCode("DNS") + ELS = NewCode("ELS") + MINISAITAMA = NewCode("MINISAITAMA") + PCH = NewCode("PCH") + SBREE = NewCode("SBREE") + BPRIVA = NewCode("BPRIVA") + DLX = NewCode("DLX") + NAFT = NewCode("NAFT") + SHIBLITE = NewCode("SHIBLITE") + BHD = NewCode("BHD") + THN = NewCode("THN") // nolint:misspell // false positive + DOGEDASH = NewCode("DOGEDASH") + FARA = NewCode("FARA") + FIL120 = NewCode("FIL120") + RABBIT = NewCode("RABBIT") + ZOON = NewCode("ZOON") + BONFIRE = NewCode("BONFIRE") + CHAIN = NewCode("CHAIN") + GGC = NewCode("GGC") + IOG = NewCode("IOG") + MEME = NewCode("MEME") + PINU = NewCode("PINU") + CCASH = NewCode("CCASH") + GART = NewCode("GART") + VALK = NewCode("VALK") + LM = NewCode("LM") + MINIDOGE = NewCode("MINIDOGE") + RAZOR = NewCode("RAZOR") + KILL = NewCode("KILL") + MASK = NewCode("MASK") + BUMN = NewCode("BUMN") + KLAYG = NewCode("KLAYG") + MICROSHIB = NewCode("MICROSHIB") + IDHUB = NewCode("IDHUB") + JT = NewCode("JT") + NTX = NewCode("NTX") + SAMO = NewCode("SAMO") + SANSHU = NewCode("SANSHU") + TASTE = NewCode("TASTE") + CXC = NewCode("CXC") + FLDT = NewCode("FLDT") + SAITO = NewCode("SAITO") + UIP = NewCode("UIP") + HTDF = NewCode("HTDF") + MOONRISE = NewCode("MOONRISE") + HOKK = NewCode("HOKK") + LT = NewCode("LT") + MINI = NewCode("MINI") + MOK = NewCode("MOK") + BLUESPARROW = NewCode("BLUESPARROW") + FTS = NewCode("FTS") + PN = NewCode("PN") + KDC = NewCode("KDC") + SAFEMOON = NewCode("SAFEMOON") + SON = NewCode("SON") + ZINU = NewCode("ZINU") + FIL72 = NewCode("FIL72") + PETS = NewCode("PETS") + POK = NewCode("POK") + RBASE = NewCode("RBASE") + TOKAU = NewCode("TOKAU") + UBEX = NewCode("UBEX") + VENA = NewCode("VENA") + FC = NewCode("FC") + OEX = NewCode("OEX") + DOGEBACK = NewCode("DOGEBACK") + IOEX = NewCode("IOEX") + MOVR = NewCode("MOVR") + PTT = NewCode("PTT") + ZOOT = NewCode("ZOOT") + CATGIRL = NewCode("CATGIRL") + CHOPPER = NewCode("CHOPPER") + EDEN = NewCode("EDEN") + GEP = NewCode("GEP") + LVI = NewCode("LVI") + PEG = NewCode("PEG") + SUTER = NewCode("SUTER") + CHECK = NewCode("CHECK") + DRO = NewCode("DRO") + FBC = NewCode("FBC") + KABOSU = NewCode("KABOSU") + CPX = NewCode("CPX") + ZAK = NewCode("ZAK") + ETERNAL = NewCode("ETERNAL") + MILKTEA = NewCode("MILKTEA") + KEANU = NewCode("KEANU") + NSFW = NewCode("NSFW") + XOM = NewCode("XOM") + EMPIRE = NewCode("EMPIRE") + FNK = NewCode("FNK") + SMRAT = NewCode("SMRAT") + TAPE = NewCode("TAPE") + IIC = NewCode("IIC") + IMI = NewCode("IMI") + KIWI = NewCode("KIWI") + POLO = NewCode("POLO") + BRIGHT = NewCode("BRIGHT") + HIKO = NewCode("HIKO") + HELIOS = NewCode("HELIOS") + KINGSHIB = NewCode("KINGSHIB") + DOGE2 = NewCode("DOGE2") + EFK = NewCode("EFK") + LMCSWAP = NewCode("LMCSWAP") + PMON = NewCode("PMON") + POODL = NewCode("POODL") + SSN = NewCode("SSN") + BIN = NewCode("BIN") + LFIL = NewCode("LFIL") + BFDT = NewCode("BFDT") + USDN = NewCode("USDN") + GDOGE = NewCode("GDOGE") + HUSD = NewCode("HUSD") + FOIN = NewCode("FOIN") + LARIX = NewCode("LARIX") + MARSRISE = NewCode("MARSRISE") + PUT = NewCode("PUT") + ZIQ = NewCode("ZIQ") + CCAR = NewCode("CCAR") + CZ = NewCode("CZ") + PLUGCN = NewCode("PLUGCN") + X2P = NewCode("X2P") + MOONX = NewCode("MOONX") + TUDA = NewCode("TUDA") + ZOE = NewCode("ZOE") + FCF = NewCode("FCF") + GHD = NewCode("GHD") + KALA = NewCode("KALA") + ULTI = NewCode("ULTI") + GRAMS = NewCode("GRAMS") + ODA = NewCode("ODA") + PHV = NewCode("PHV") + SAL = NewCode("SAL") + TKY = NewCode("TKY") + XWG = NewCode("XWG") + CCTC = NewCode("CCTC") + MKCY = NewCode("MKCY") + LFIL36 = NewCode("LFIL36") + PKMON = NewCode("PKMON") + RCKT = NewCode("RCKT") + VCC = NewCode("VCC") + CUMSTAR = NewCode("CUMSTAR") + JNTR = NewCode("JNTR") + JIND = NewCode("JIND") + SAITAMA = NewCode("SAITAMA") + ELT = NewCode("ELT") + FLOKIN = NewCode("FLOKIN") + NEX = NewCode("NEX") + TENA = NewCode("TENA") + CAP = NewCode("CAP") + LUFFY = NewCode("LUFFY") + ET = NewCode("ET") + DBNK = NewCode("DBNK") + SDT = NewCode("SDT") + NWC = NewCode("NWC") + PAMP = NewCode("PAMP") + XVIX = NewCode("XVIX") + BLADE = NewCode("BLADE") + GETH = NewCode("GETH") + HIGH = NewCode("HIGH") + PLF = NewCode("PLF") + DSG = NewCode("DSG") + GN = NewCode("GN") + TTT = NewCode("TTT") + HMR = NewCode("HMR") + SMD = NewCode("SMD") + WEYU = NewCode("WEYU") + BIKI = NewCode("BIKI") + VIKINGS = NewCode("VIKINGS") + BUGG = NewCode("BUGG") + LUNAPAD = NewCode("LUNAPAD") + EAURIC = NewCode("EAURIC") + HJW = NewCode("HJW") + LUC = NewCode("LUC") + BV = NewCode("BV") + COGE = NewCode("COGE") + DILI = NewCode("DILI") + XHDX = NewCode("XHDX") + XP = NewCode("XP") + XSTAR = NewCode("XSTAR") + FKX = NewCode("FKX") + RPL = NewCode("RPL") + JUS = NewCode("JUS") + KISHIMOTO = NewCode("KISHIMOTO") + NEST = NewCode("NEST") + SMBSWAP = NewCode("SMBSWAP") + WOLVERINU = NewCode("WOLVERINU") + GNBT = NewCode("GNBT") + HDS = NewCode("HDS") + QNUT = NewCode("QNUT") + ENS = NewCode("ENS") + FOG = NewCode("FOG") + NBTC = NewCode("NBTC") + CHS = NewCode("CHS") + GMT = NewCode("GMT") + ORCA = NewCode("ORCA") + SERO = NewCode("SERO") + BGLD = NewCode("BGLD") + CDB = NewCode("CDB") + SLA = NewCode("SLA") + UT = NewCode("UT") + POLYDOGE = NewCode("POLYDOGE") + SFP = NewCode("SFP") + HKUN = NewCode("HKUN") + WHALE = NewCode("WHALE") + CCXX = NewCode("CCXX") + DOR = NewCode("DOR") + OATH = NewCode("OATH") + GKI = NewCode("GKI") + PANDA = NewCode("PANDA") + OVO = NewCode("OVO") + CELT = NewCode("CELT") + OSST = NewCode("OSST") + OMNIS = NewCode("OMNIS") + TONE = NewCode("TONE") + MERI = NewCode("MERI") + MTA = NewCode("MTA") + MBF = NewCode("MBF") + Y1D1S = NewCode("Y1D1S") + GMCOIN = NewCode("GMCOIN") + KISHU = NewCode("KISHU") + OPX = NewCode("OPX") + PCE = NewCode("PCE") + SFIL = NewCode("SFIL") + BID = NewCode("BID") + BKS = NewCode("BKS") + PIZA = NewCode("PIZA") + POSI = NewCode("POSI") + WSG = NewCode("WSG") + K21 = NewCode("K21") + PAI = NewCode("PAI") + HEX = NewCode("HEX") + YFFII = NewCode("YFFII") + IMC = NewCode("IMC") + ONES = NewCode("ONES") + CRB = NewCode("CRB") + DBA = NewCode("DBA") + SEAD = NewCode("SEAD") + SYN = NewCode("SYN") + TAC = NewCode("TAC") + DAX = NewCode("DAX") + LFIL12 = NewCode("LFIL12") + LFW = NewCode("LFW") + TYB = NewCode("TYB") + FCL = NewCode("FCL") + GERA = NewCode("GERA") + LID = NewCode("LID") + TT = NewCode("TT") + WELL = NewCode("WELL") + GALT = NewCode("GALT") + GMC = NewCode("GMC") + BPX = NewCode("BPX") + DOE = NewCode("DOE") + REVV = NewCode("REVV") + VTT = NewCode("VTT") + MAI = NewCode("MAI") + PDF = NewCode("PDF") + SEER = NewCode("SEER") + GFI = NewCode("GFI") + GODS = NewCode("GODS") + FISH = NewCode("FISH") + MIST = NewCode("MIST") + SEOS = NewCode("SEOS") + AWF = NewCode("AWF") + DADDYDOGE = NewCode("DADDYDOGE") + MNSTRS = NewCode("MNSTRS") + TREE = NewCode("TREE") + BNX = NewCode("BNX") + DESIRE = NewCode("DESIRE") + FIC24 = NewCode("FIC24") + RYOSHI = NewCode("RYOSHI") + TABOO = NewCode("TABOO") + CMCX = NewCode("CMCX") + CRE = NewCode("CRE") + FIL6 = NewCode("FIL6") + HTMOON = NewCode("HTMOON") + PORNROCKET = NewCode("PORNROCKET") + QUID = NewCode("QUID") + SAIT = NewCode("SAIT") + TGC = NewCode("TGC") + CVA = NewCode("CVA") + EMAX = NewCode("EMAX") + XDOGE = NewCode("XDOGE") + TUBE2 = NewCode("TUBE2") + TZKI = NewCode("TZKI") + YOOSHI = NewCode("YOOSHI") + GLEEC = NewCode("GLEEC") + PNT = NewCode("PNT") + UMI = NewCode("UMI") + DALI = NewCode("DALI") + DUKE = NewCode("DUKE") + MLTPX = NewCode("MLTPX") + CHE = NewCode("CHE") + KING = NewCode("KING") + MEWTWO = NewCode("MEWTWO") + SEED = NewCode("SEED") + DEKU = NewCode("DEKU") + FSHIB = NewCode("FSHIB") + MFLOKIADA = NewCode("MFLOKIADA") + MNI = NewCode("MNI") + NBL = NewCode("NBL") + POVE = NewCode("POVE") + SMTY = NewCode("SMTY") + CPH = NewCode("CPH") + FLM = NewCode("FLM") + GAT = NewCode("GAT") + MONONOKEINU = NewCode("MONONOKEINU") + SBR = NewCode("SBR") + BMARS = NewCode("BMARS") + GOMI = NewCode("GOMI") + ONOT = NewCode("ONOT") // nolint:misspell // false positive + GOKU = NewCode("GOKU") + MINTYS = NewCode("MINTYS") + PONYO = NewCode("PONYO") + WZC = NewCode("WZC") + ELAMA = NewCode("ELAMA") + NAMI = NewCode("NAMI") + SLINK = NewCode("SLINK") + SQUID = NewCode("SQUID") + DOGEZILLA = NewCode("DOGEZILLA") + INSUR = NewCode("INSUR") + IDA = NewCode("IDA") + MDX1 = NewCode("MDX1") + TRR = NewCode("TRR") + DXN = NewCode("DXN") + FCH = NewCode("FCH") + KAWA = NewCode("KAWA") + MCB = NewCode("MCB") + NABOX = NewCode("NABOX") + WANA = NewCode("WANA") + DOGECOLA = NewCode("DOGECOLA") + ELONGATE = NewCode("ELONGATE") + TNS = NewCode("TNS") + LEAD = NewCode("LEAD") + SYBC = NewCode("SYBC") + WINRY = NewCode("WINRY") + DAWGS = NewCode("DAWGS") + SMOON = NewCode("SMOON") + FIL36 = NewCode("FIL36") + KDS = NewCode("KDS") + SHR = NewCode("SHR") + BTY = NewCode("BTY") + FODL = NewCode("FODL") + XIASI = NewCode("XIASI") + RVST = NewCode("RVST") + VO = NewCode("VO") + GDR = NewCode("GDR") + RELCOIN = NewCode("RELCOIN") + CISLA = NewCode("CISLA") + ECOP = NewCode("ECOP") + AXSOLD = NewCode("AXSOLD") + BETA = NewCode("BETA") + BLINK = NewCode("BLINK") + PORTO = NewCode("PORTO") + SPARTAOLD = NewCode("SPARTAOLD") + WNXM = NewCode("WNXM") + ASR = NewCode("ASR") + COVEROLD = NewCode("COVEROLD") + VRAB = NewCode("VRAB") + NSBT = NewCode("NSBT") + AGIX = NewCode("AGIX") + BOLT = NewCode("BOLT") + BIDR = NewCode("BIDR") + VAB = NewCode("VAB") + EOSBULL = NewCode("EOSBULL") + FIO = NewCode("FIO") + IDEX = NewCode("IDEX") + PROS = NewCode("PROS") + VITE = NewCode("VITE") + WSOL = NewCode("WSOL") + FIRO = NewCode("FIRO") + MTLX = NewCode("MTLX") + SLPOLD = NewCode("SLPOLD") + WING = NewCode("WING") + SPARTA = NewCode("SPARTA") + USDS = NewCode("USDS") + BNC = NewCode("BNC") + BEAR = NewCode("BEAR") + OG = NewCode("OG") + TKO = NewCode("TKO") + UFT = NewCode("UFT") + SNMOLD = NewCode("SNMOLD") + WRX = NewCode("WRX") + BKRW = NewCode("BKRW") + BNBBULL = NewCode("BNBBULL") + PERLOLD = NewCode("PERLOLD") + BOBA = NewCode("BOBA") + COCOS = NewCode("COCOS") + NVT = NewCode("NVT") + TBCC = NewCode("TBCC") + BTCST = NewCode("BTCST") + DEXE = NewCode("DEXE") + HARD = NewCode("HARD") + DREPOLD = NewCode("DREPOLD") + UND = NewCode("UND") + XDATA = NewCode("XDATA") + KEYFI = NewCode("KEYFI") + MA = NewCode("MA") + QI = NewCode("QI") + ACA = NewCode("ACA") + DF = NewCode("DF") + KNCL = NewCode("KNCL") + BVND = NewCode("BVND") + PERL = NewCode("PERL") + WETH = NewCode("WETH") + BETH = NewCode("BETH") + OM = NewCode("OM") + OMOLD = NewCode("OMOLD") + PHB = NewCode("PHB") + ASTR = NewCode("ASTR") + HNST = NewCode("HNST") + JEX = NewCode("JEX") + ZCX = NewCode("ZCX") + DAR = NewCode("DAR") + MDXT = NewCode("MDXT") + RENBTC = NewCode("RENBTC") + SSV = NewCode("SSV") + XRPBEAR = NewCode("XRPBEAR") + AVA = NewCode("AVA") + SGT = NewCode("SGT") + VGX = NewCode("VGX") + EASY = NewCode("EASY") + IRIS = NewCode("IRIS") + VRT = NewCode("VRT") + WBNB = NewCode("WBNB") + DON = NewCode("DON") + JUV = NewCode("JUV") + PHA = NewCode("PHA") + QISWAP = NewCode("QISWAP") + SUNOLD = NewCode("SUNOLD") + ETHBEAR = NewCode("ETHBEAR") + FRONT = NewCode("FRONT") + LAZIO = NewCode("LAZIO") + BCHSV = NewCode("BCHSV") + EPS = NewCode("EPS") + ETHBNT = NewCode("ETHBNT") + HBAR = NewCode("HBAR") + ACM = NewCode("ACM") + CBM = NewCode("CBM") + DREP = NewCode("DREP") + ERD = NewCode("ERD") + STMX = NewCode("STMX") + ANTOLD = NewCode("ANTOLD") + BULL = NewCode("BULL") + BNBBEAR = NewCode("BNBBEAR") + CITY = NewCode("CITY") + AKRO = NewCode("AKRO") + ENTRP = NewCode("ENTRP") + REPV1 = NewCode("REPV1") + VIDT = NewCode("VIDT") + BGBP = NewCode("BGBP") + LOOMOLD = NewCode("LOOMOLD") + MBOX = NewCode("MBOX") + ADXOLD = NewCode("ADXOLD") + IDRT = NewCode("IDRT") + PHBV1 = NewCode("PHBV1") + FRAX = NewCode("FRAX") + LUSD = NewCode("LUSD") + OUSD = NewCode("OUSD") + USDX = NewCode("USDX") + EURS = NewCode("EURS") + CUSD = NewCode("CUSD") + MUSD = NewCode("MUSD") + USDK = NewCode("USDK") + EOSDT = NewCode("EOSDT") + DGX = NewCode("DGX") + XCHF = NewCode("XCHF") + XAUR = NewCode("XAUR") + USNBT = NewCode("USNBT") + ITL = NewCode("ITL") + MIM = NewCode("MIM") + ALUSD = NewCode("ALUSD") + BRCP = NewCode("BRCP") + USDs = NewCode("USDs") + MTR = NewCode("MTR") + CEUR = NewCode("CEUR") + ONEGOLD = NewCode("1GOLD") + COFFIN = NewCode("COFFIN") + MDO = NewCode("MDO") + DPT = NewCode("DPT") + XIDR = NewCode("XIDR") + PAR = NewCode("PAR") + XUSD = NewCode("XUSD") + USDB = NewCode("USDB") + USDQ = NewCode("USDQ") + BITUSD = NewCode("BITUSD") + BITGOLD = NewCode("BITGOLD") + BITEUR = NewCode("BITEUR") + HGT = NewCode("HGT") + CONST = NewCode("CONST") + XEUR = NewCode("XEUR") + EBASE = NewCode("EBASE") + USDL = NewCode("USDL") + UETH = NewCode("UETH") + USDEX = NewCode("USDEX") + USDFL = NewCode("USDFL") + FLUSD = NewCode("FLUSD") + DUSD = NewCode("DUSD") + + stables = Currencies{ + USDT, + USDC, + BUSD, + UST, + DAI, + TUSD, + USDP, + USDN, + FEI, + TRIBE, + RSR, + FRAX, + LUSD, + HUSD, + OUSD, + XSGD, + GUSD, + USDX, + EURS, + CUSD, + SUSD, + QC, + VAI, + SBD, + DGD, + MUSD, + RSV, + USDK, + IDRT, + BITCNY, + EOSDT, + DGX, + XCHF, + XAUR, + USDS, + USNBT, + ITL, + MIM, + USDP, + EURT, + ALUSD, + BRCP, + TRYB, + USDs, + MTR, + CEUR, + ONEGOLD, + COFFIN, + MDO, + DPT, + MDS, + XIDR, + PAR, + XUSD, + USDB, + USDQ, + KBC, + ZUSD, + BITUSD, + BITGOLD, + BITEUR, + HGT, + CONST, + XEUR, + BGBP, + EBASE, + BKRW, + USDL, + UETH, + BVND, + USDEX, + USDFL, + FLUSD, + DUSD, + } ) diff --git a/currency/coinmarketcap/coinmarketcap.go b/currency/coinmarketcap/coinmarketcap.go index 2189823f..69340a7e 100644 --- a/currency/coinmarketcap/coinmarketcap.go +++ b/currency/coinmarketcap/coinmarketcap.go @@ -19,6 +19,16 @@ import ( "github.com/thrasher-corp/gocryptotrader/log" ) +// NewFromSettings returns a new coin market cap instance with supplied settings +func NewFromSettings(cfg Settings) (*Coinmarketcap, error) { + c := &Coinmarketcap{} + c.SetDefaults() + if err := c.Setup(cfg); err != nil { + return nil, err + } + return c, nil +} + // SetDefaults sets default values for the exchange func (c *Coinmarketcap) SetDefaults() { c.Name = "CoinMarketCap" @@ -41,7 +51,7 @@ func (c *Coinmarketcap) Setup(conf Settings) error { c.Enabled = true c.Verbose = conf.Verbose - c.APIkey = conf.APIkey + c.APIkey = conf.APIKey return c.SetAccountPlan(conf.AccountPlan) } @@ -714,7 +724,7 @@ func (c *Coinmarketcap) SetAccountPlan(s string) error { case "enterprise": c.Plan = Enterprise default: - log.Warnf(log.Global, "account plan %s not found, defaulting to basic", s) + log.Warnf(log.Currency, "account plan %s not found, defaulting to basic", s) c.Plan = Basic } return nil diff --git a/currency/coinmarketcap/coinmarketcap_test.go b/currency/coinmarketcap/coinmarketcap_test.go index 93d54c5a..9779c968 100644 --- a/currency/coinmarketcap/coinmarketcap_test.go +++ b/currency/coinmarketcap/coinmarketcap_test.go @@ -36,7 +36,7 @@ func TestSetup(t *testing.T) { c.SetDefaults() cfg := Settings{} - cfg.APIkey = apikey + cfg.APIKey = apikey cfg.AccountPlan = apiAccountPlanLevel cfg.Enabled = true cfg.AccountPlan = "basic" diff --git a/currency/coinmarketcap/coinmarketcap_types.go b/currency/coinmarketcap/coinmarketcap_types.go index aa80033d..333444b7 100644 --- a/currency/coinmarketcap/coinmarketcap_types.go +++ b/currency/coinmarketcap/coinmarketcap_types.go @@ -70,7 +70,7 @@ type Settings struct { Name string `json:"name"` Enabled bool `json:"enabled"` Verbose bool `json:"verbose"` - APIkey string `json:"apiKey"` + APIKey string `json:"apiKey"` AccountPlan string `json:"accountPlan"` } diff --git a/currency/conversion.go b/currency/conversion.go index edeec928..a0361e9a 100644 --- a/currency/conversion.go +++ b/currency/conversion.go @@ -19,9 +19,6 @@ type ConversionRates struct { func (c *ConversionRates) HasData() bool { c.mtx.Lock() defer c.mtx.Unlock() - if c.m == nil { - return false - } return len(c.m) != 0 } @@ -105,7 +102,7 @@ func (c *ConversionRates) Update(m map[string]float64) error { log.Debugln(log.Global, "Conversion rates are being updated.") } - solidvalues := make(map[Code]map[Code]float64) + solidvalues := make(map[*Item]map[*Item]float64) var list []Code // Verification list, cross check all currencies coming in @@ -113,26 +110,26 @@ func (c *ConversionRates) Update(m map[string]float64) error { for key, val := range m { code1 := storage.ValidateFiatCode(key[:3]) - if mainBaseCurrency == (Code{}) { + if mainBaseCurrency.Equal(EMPTYCODE) { mainBaseCurrency = code1 } code2 := storage.ValidateFiatCode(key[3:]) - if code1 == code2 { // Get rid of same conversions + if code1.Equal(code2) { // Get rid of same conversions continue } var codeOneFound, codeTwoFound bool // Check and add to our funky list for i := range list { - if list[i] == code1 { + if list[i].Equal(code1) { codeOneFound = true if codeTwoFound { break } } - if list[i] == code2 { + if list[i].Equal(code2) { codeTwoFound = true if codeOneFound { break @@ -148,39 +145,39 @@ func (c *ConversionRates) Update(m map[string]float64) error { list = append(list, code2) } - if solidvalues[code1] == nil { - solidvalues[code1] = make(map[Code]float64) + if solidvalues[code1.Item] == nil { + solidvalues[code1.Item] = make(map[*Item]float64) } - solidvalues[code1][code2] = val + solidvalues[code1.Item][code2.Item] = val // Input inverse values 1/val to swap from -> to and vice versa - if solidvalues[code2] == nil { - solidvalues[code2] = make(map[Code]float64) + if solidvalues[code2.Item] == nil { + solidvalues[code2.Item] = make(map[*Item]float64) } - solidvalues[code2][code1] = 1 / val + solidvalues[code2.Item][code1.Item] = 1 / val } for _, base := range list { for _, term := range list { - if base == term { + if base.Equal(term) { continue } - _, ok := solidvalues[base][term] + _, ok := solidvalues[base.Item][term.Item] if !ok { var crossRate float64 // Check inversion to speed things up - v, ok := solidvalues[term][base] + v, ok := solidvalues[term.Item][base.Item] if !ok { - v1, ok := solidvalues[mainBaseCurrency][base] + v1, ok := solidvalues[mainBaseCurrency.Item][base.Item] if !ok { return fmt.Errorf("value not found base %s term %s", mainBaseCurrency, base) } - v2, ok := solidvalues[mainBaseCurrency][term] + v2, ok := solidvalues[mainBaseCurrency.Item][term.Item] if !ok { return fmt.Errorf("value not found base %s term %s", mainBaseCurrency, @@ -197,7 +194,7 @@ func (c *ConversionRates) Update(m map[string]float64) error { term, crossRate) } - solidvalues[base][term] = crossRate + solidvalues[base.Item][term.Item] = crossRate } } } @@ -209,14 +206,14 @@ func (c *ConversionRates) Update(m map[string]float64) error { c.m = make(map[*Item]map[*Item]*float64) } - if c.m[key.Item] == nil { - c.m[key.Item] = make(map[*Item]*float64) + if c.m[key] == nil { + c.m[key] = make(map[*Item]*float64) } - p := c.m[key.Item][key2.Item] + p := c.m[key][key2] if p == nil { newPalsAndFriends := val2 - c.m[key.Item][key2.Item] = &newPalsAndFriends + c.m[key][key2] = &newPalsAndFriends } else { *p = val2 } @@ -281,7 +278,7 @@ func (c Conversion) IsInvalid() bool { // IsFiat checks to see if the from and to currency is a fiat e.g. EURUSD func (c Conversion) IsFiat() bool { - return storage.IsFiatCurrency(c.From) && storage.IsFiatCurrency(c.To) + return c.From.IsFiatCurrency() && c.To.IsFiatCurrency() } // String returns the stringed fields diff --git a/currency/currencies.go b/currency/currencies.go index caa6cb97..013b7223 100644 --- a/currency/currencies.go +++ b/currency/currencies.go @@ -7,7 +7,7 @@ import ( // NewCurrenciesFromStringArray returns a Currencies object from strings func NewCurrenciesFromStringArray(currencies []string) Currencies { - var list Currencies + list := make(Currencies, 0, len(currencies)) for i := range currencies { if currencies[i] == "" { continue @@ -22,17 +22,17 @@ type Currencies []Code // Strings returns an array of currency strings func (c Currencies) Strings() []string { - var list []string + list := make([]string, len(c)) for i := range c { - list = append(list, c[i].String()) + list[i] = c[i].String() } return list } // Contains checks to see if a currency code is contained in the currency list -func (c Currencies) Contains(cc Code) bool { +func (c Currencies) Contains(check Code) bool { for i := range c { - if c[i].Item == cc.Item { + if c[i].Equal(check) { return true } } @@ -52,10 +52,10 @@ func (c *Currencies) UnmarshalJSON(d []byte) error { return err } - var allTheCurrencies Currencies curr := strings.Split(configCurrencies, ",") + allTheCurrencies := make(Currencies, len(curr)) for i := range curr { - allTheCurrencies = append(allTheCurrencies, NewCode(curr[i])) + allTheCurrencies[i] = NewCode(curr[i]) } *c = allTheCurrencies @@ -76,7 +76,7 @@ func (c Currencies) Match(other Currencies) bool { match: for x := range c { for y := range other { - if c[x] == other[y] { + if c[x].Equal(other[y]) { continue match } } diff --git a/currency/currency.go b/currency/currency.go index 24b59786..4e3ff95f 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -1,5 +1,13 @@ package currency +import ( + "errors" + "fmt" + "strings" +) + +var errEmptyPairString = errors.New("empty pair string") + // GetDefaultExchangeRates returns the currency exchange rates based off the // default fiat values func GetDefaultExchangeRates() (Conversions, error) { @@ -56,11 +64,16 @@ func UpdateCurrencies(c Currencies, isCryptocurrency bool) { storage.UpdateEnabledFiatCurrencies(c) } -// ConvertCurrency converts an amount from one currency to another -func ConvertCurrency(amount float64, from, to Code) (float64, error) { +// ConvertFiat converts an fiat amount from one currency to another +func ConvertFiat(amount float64, from, to Code) (float64, error) { return storage.ConvertCurrency(amount, from, to) } +// GetForeignExchangeRate returns the foreign exchange rate for a fiat pair. +func GetForeignExchangeRate(quotation Pair) (float64, error) { + return storage.ConvertCurrency(1, quotation.Base, quotation.Quote) +} + // SeedForeignExchangeData seeds FX data with the currencies supplied func SeedForeignExchangeData(c Currencies) error { return storage.SeedForeignExchangeRatesByCurrencies(c) @@ -72,7 +85,7 @@ func GetTotalMarketCryptocurrencies() ([]Code, error) { } // RunStorageUpdater runs a new foreign exchange updater instance -func RunStorageUpdater(o BotOverrides, m *MainConfiguration, filepath string) error { +func RunStorageUpdater(o BotOverrides, m *Config, filepath string) error { return storage.RunUpdater(o, m, filepath) } @@ -88,43 +101,47 @@ func CopyPairFormat(p Pair, pairs []Pair, exact bool) Pair { if p.Equal(pairs[x]) { return pairs[x] } + continue } if p.EqualIncludeReciprocal(pairs[x]) { return pairs[x] } } - return Pair{} + return EMPTYPAIR } // FormatPairs formats a string array to a list of currency pairs with the // supplied currency pair format func FormatPairs(pairs []string, delimiter, index string) (Pairs, error) { - var result Pairs + var result = make(Pairs, len(pairs)) for x := range pairs { if pairs[x] == "" { - continue + return nil, fmt.Errorf("%w in slice %v", errEmptyPairString, pairs) } - var p Pair var err error - if delimiter != "" { - p, err = NewPairDelimiter(pairs[x], delimiter) - if err != nil { - return nil, err - } - } else { - if index != "" { - p, err = NewPairFromIndex(pairs[x], index) - if err != nil { - return Pairs{}, err - } - } else { - p, err = NewPairFromStrings(pairs[x][0:3], pairs[x][3:]) - if err != nil { - return Pairs{}, err - } - } + switch { + case delimiter != "": + result[x], err = NewPairDelimiter(pairs[x], delimiter) + case index != "": + result[x], err = NewPairFromIndex(pairs[x], index) + default: + result[x], err = NewPairFromStrings(pairs[x][:3], pairs[x][3:]) + } + if err != nil { + return nil, err } - result = append(result, p) } return result, nil } + +// IsEnabled returns if the individual foreign exchange config setting is +// enabled +func (settings AllFXSettings) IsEnabled(name string) bool { + for x := range settings { + if !strings.EqualFold(settings[x].Name, name) { + continue + } + return settings[x].Enabled + } + return false +} diff --git a/currency/currency_test.go b/currency/currency_test.go index b726c862..b524fc4b 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -1,6 +1,7 @@ package currency import ( + "errors" "testing" ) @@ -43,14 +44,14 @@ func TestUpdateBaseCurrency(t *testing.T) { t.Error("UpdateBaseCurrency() error cannot be nil") } - if GetBaseCurrency() != AUD { + if !GetBaseCurrency().Equal(AUD) { t.Errorf("GetBaseCurrency() expected %s but received %s", AUD, GetBaseCurrency()) } } func TestGetDefaultBaseCurrency(t *testing.T) { - if GetDefaultBaseCurrency() != USD { + if !GetDefaultBaseCurrency().Equal(USD) { t.Errorf("GetDefaultBaseCurrency() expected %s but received %s", USD, GetDefaultBaseCurrency()) } @@ -88,13 +89,28 @@ func TestUpdateCurrencies(t *testing.T) { } } -func TestConvertCurrency(t *testing.T) { - _, err := ConvertCurrency(100, AUD, USD) +func TestConvertFiat(t *testing.T) { + _, err := ConvertFiat(0, LTC, USD) + if !errors.Is(err, errInvalidAmount) { + t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidAmount) + } + + _, err = ConvertFiat(100, LTC, USD) + if !errors.Is(err, errNotFiatCurrency) { + t.Fatalf("received: '%v' but expected: '%v'", err, errNotFiatCurrency) + } + + _, err = ConvertFiat(100, USD, LTC) + if !errors.Is(err, errNotFiatCurrency) { + t.Fatalf("received: '%v' but expected: '%v'", err, errNotFiatCurrency) + } + + _, err = ConvertFiat(100, AUD, USD) if err != nil { t.Fatal(err) } - r, err := ConvertCurrency(100, AUD, AUD) + r, err := ConvertFiat(100, AUD, AUD) if err != nil { t.Fatal(err) } @@ -104,23 +120,64 @@ func TestConvertCurrency(t *testing.T) { 100.00, r) } - _, err = ConvertCurrency(100, USD, AUD) + _, err = ConvertFiat(100, USD, AUD) if err != nil { t.Fatal(err) } - _, err = ConvertCurrency(100, CNY, AUD) + _, err = ConvertFiat(100, CNY, AUD) if err != nil { t.Fatal(err) } - - _, err = ConvertCurrency(100, LTC, USD) - if err == nil { - t.Fatal("Expected err on non-existent currency") - } - - _, err = ConvertCurrency(100, USD, LTC) - if err == nil { - t.Fatal("Expected err on non-existent currency") - } +} + +func TestGetForeignExchangeRate(t *testing.T) { + _, err := GetForeignExchangeRate(NewPair(EMPTYCODE, EMPTYCODE)) + if !errors.Is(err, errNotFiatCurrency) { + t.Fatalf("received: '%v' but expected: '%v'", err, errNotFiatCurrency) + } + + _, err = GetForeignExchangeRate(NewPair(USD, EMPTYCODE)) + if !errors.Is(err, errNotFiatCurrency) { + t.Fatalf("received: '%v' but expected: '%v'", err, errNotFiatCurrency) + } + + one, err := GetForeignExchangeRate(NewPair(USD, USD)) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if one != 1 { + t.Fatal("unexpected value") + } + + rate, err := GetForeignExchangeRate(NewPair(AUD, USD)) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if rate <= 0 { + t.Fatal("unexpected value") + } +} + +func TestAllFXSettingsIsEnabled(t *testing.T) { + var settings AllFXSettings + if received := settings.IsEnabled("wow"); received { + t.Fatalf("received: '%v' but expected: '%v'", received, false) + } + + settings = []FXSettings{ + { + Name: "wOow", + }, + { + Name: "amICool?", + Enabled: true, + }, + } + + if received := settings.IsEnabled("AMICOOL?"); !received { + t.Fatalf("received: '%v' but expected: '%v'", received, true) + } } diff --git a/currency/currency_types.go b/currency/currency_types.go index e3494d5c..ced6f5bb 100644 --- a/currency/currency_types.go +++ b/currency/currency_types.go @@ -6,26 +6,35 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency/coinmarketcap" ) -// MainConfiguration is the main configuration from the config.json file -type MainConfiguration struct { - ForexProviders []FXSettings - CryptocurrencyProvider coinmarketcap.Settings - Cryptocurrencies Currencies - CurrencyPairFormat interface{} - FiatDisplayCurrency Code - CurrencyDelay time.Duration - FxRateDelay time.Duration +// Config holds all the information needed for currency related manipulation +type Config struct { + ForexProviders AllFXSettings `json:"forexProviders"` + CryptocurrencyProvider Provider `json:"cryptocurrencyProvider"` + CurrencyPairFormat *PairFormat `json:"currencyPairFormat"` + FiatDisplayCurrency Code `json:"fiatDisplayCurrency"` + CurrencyFileUpdateDuration time.Duration `json:"currencyFileUpdateDuration"` + ForeignExchangeUpdateDuration time.Duration `json:"foreignExchangeUpdateDuration"` +} + +// Provider defines coinmarketcap tools +type Provider struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + APIKey string `json:"apiKey"` + AccountPlan string `json:"accountPlan"` } // BotOverrides defines a bot overriding factor for quick running currency // subsystems type BotOverrides struct { - Coinmarketcap bool - FxCurrencyConverter bool - FxCurrencyLayer bool - FxFixer bool - FxOpenExchangeRates bool - FxExchangeRateHost bool + Coinmarketcap bool + CurrencyConverter bool + CurrencyLayer bool + ExchangeRates bool + Fixer bool + OpenExchangeRates bool + ExchangeRateHost bool } // CoinmarketcapSettings refers to settings @@ -40,26 +49,29 @@ type SystemsSettings struct { Openexchangerates FXSettings } +// AllFXSettings defines all the foreign exchange settings +type AllFXSettings []FXSettings + // FXSettings defines foreign exchange requester settings type FXSettings struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - RESTPollingDelay time.Duration `json:"restPollingDelay"` - APIKey string `json:"apiKey"` - APIKeyLvl int `json:"apiKeyLvl"` - PrimaryProvider bool `json:"primaryProvider"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + APIKey string `json:"apiKey"` + APIKeyLvl int `json:"apiKeyLvl"` + PrimaryProvider bool `json:"primaryProvider"` } // File defines a full currency file generated by the currency storage // analysis system type File struct { LastMainUpdate interface{} `json:"lastMainUpdate"` - Cryptocurrency []Item `json:"cryptocurrencies"` - FiatCurrency []Item `json:"fiatCurrencies"` - UnsetCurrency []Item `json:"unsetCurrencies"` - Contracts []Item `json:"contracts"` - Token []Item `json:"tokens"` + Cryptocurrency []*Item `json:"cryptocurrencies"` + FiatCurrency []*Item `json:"fiatCurrencies"` + UnsetCurrency []*Item `json:"unsetCurrencies"` + Contracts []*Item `json:"contracts"` + Token []*Item `json:"tokens"` + Stable []*Item `json:"stableCurrencies"` } // Const here are packaged defined delimiters diff --git a/currency/forexprovider/README.md b/currency/forexprovider/README.md index 930bff13..25118167 100644 --- a/currency/forexprovider/README.md +++ b/currency/forexprovider/README.md @@ -22,6 +22,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader + Currency Converter API support + Currency Layer support ++ Exchange Rates support + Fixer.io support + Open Exchange Rates support + ExchangeRate.host support diff --git a/currency/forexprovider/base/base.go b/currency/forexprovider/base/base.go index 82ac314a..935a76bc 100644 --- a/currency/forexprovider/base/base.go +++ b/currency/forexprovider/base/base.go @@ -29,11 +29,10 @@ const DefaultTimeOut = time.Second * 15 // Settings enforces standard variables across the provider packages type Settings struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Verbose bool `json:"verbose"` - RESTPollingDelay time.Duration `json:"restPollingDelay"` - APIKey string `json:"apiKey"` - APIKeyLvl int `json:"apiKeyLvl"` - PrimaryProvider bool `json:"primaryProvider"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + Verbose bool `json:"verbose"` + APIKey string `json:"apiKey"` + APIKeyLvl int `json:"apiKeyLvl"` + PrimaryProvider bool `json:"primaryProvider"` } diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go index f5340cb2..89f442de 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go @@ -21,7 +21,6 @@ func (c *CurrencyConverter) Setup(config base.Settings) error { c.APIKey = config.APIKey c.APIKeyLvl = config.APIKeyLvl c.Enabled = config.Enabled - c.RESTPollingDelay = config.RESTPollingDelay c.Verbose = config.Verbose c.PrimaryProvider = config.PrimaryProvider c.Requester = request.New(c.Name, diff --git a/currency/forexprovider/currencylayer/currencylayer.go b/currency/forexprovider/currencylayer/currencylayer.go index f9e127bb..7827fabe 100644 --- a/currency/forexprovider/currencylayer/currencylayer.go +++ b/currency/forexprovider/currencylayer/currencylayer.go @@ -40,7 +40,6 @@ func (c *CurrencyLayer) Setup(config base.Settings) error { c.APIKey = config.APIKey c.APIKeyLvl = config.APIKeyLvl c.Enabled = config.Enabled - c.RESTPollingDelay = config.RESTPollingDelay c.Verbose = config.Verbose c.PrimaryProvider = config.PrimaryProvider // Rate limit is based off a monthly counter - Open limit used. diff --git a/currency/forexprovider/currencylayer/currencylayer_types.go b/currency/forexprovider/currencylayer/currencylayer_types.go index 16753123..43848c7b 100644 --- a/currency/forexprovider/currencylayer/currencylayer_types.go +++ b/currency/forexprovider/currencylayer/currencylayer_types.go @@ -20,9 +20,6 @@ const ( APIEndpointConversion = "convert" APIEndpointTimeframe = "timeframe" APIEndpointChange = "change" - - authRate = 0 - unAuthRate = 0 ) // CurrencyLayer is a foreign exchange rate provider at diff --git a/currency/forexprovider/exchangerate.host/exchangerate.go b/currency/forexprovider/exchangerate.host/exchangerate.go index cc2314de..b0d89780 100644 --- a/currency/forexprovider/exchangerate.host/exchangerate.go +++ b/currency/forexprovider/exchangerate.host/exchangerate.go @@ -31,7 +31,6 @@ var ( 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, diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index 1d7f2d53..edcbc33e 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -25,7 +25,6 @@ func (e *ExchangeRates) 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.APIKey = config.APIKey diff --git a/currency/forexprovider/fixer.io/fixer.go b/currency/forexprovider/fixer.io/fixer.go index b4e22941..8c1568a8 100644 --- a/currency/forexprovider/fixer.io/fixer.go +++ b/currency/forexprovider/fixer.io/fixer.go @@ -34,7 +34,6 @@ func (f *Fixer) Setup(config base.Settings) error { f.APIKeyLvl = config.APIKeyLvl f.Enabled = config.Enabled f.Name = config.Name - f.RESTPollingDelay = config.RESTPollingDelay f.Verbose = config.Verbose f.PrimaryProvider = config.PrimaryProvider f.Requester = request.New(f.Name, @@ -220,10 +219,13 @@ func (f *Fixer) GetFluctuationData(startDate, endDate, baseCurrency string, symb // SendOpenHTTPRequest sends a typical get request func (f *Fixer) SendOpenHTTPRequest(endpoint string, v url.Values, result interface{}) error { - var path string + if v == nil { + v = url.Values{} + } v.Set("access_key", f.APIKey) var auth bool + var path string if f.APIKeyLvl == fixerAPIFree { path = fixerAPI + endpoint + "?" + v.Encode() } else { diff --git a/currency/forexprovider/fixer.io/fixer_test.go b/currency/forexprovider/fixer.io/fixer_test.go index ecfc366a..49a5ae4e 100644 --- a/currency/forexprovider/fixer.io/fixer_test.go +++ b/currency/forexprovider/fixer.io/fixer_test.go @@ -9,11 +9,6 @@ import ( // Please set API key and apikey subscription level for correct due diligence // testing - NOTE please be aware tests will diminish your monthly API calls -const ( - apikey = "" - apiKeyLvl = 3 -) - var f Fixer var isSetup bool diff --git a/currency/forexprovider/forexprovider.go b/currency/forexprovider/forexprovider.go index 6643d3ba..393a01fa 100644 --- a/currency/forexprovider/forexprovider.go +++ b/currency/forexprovider/forexprovider.go @@ -4,6 +4,7 @@ package forexprovider import ( "errors" + "fmt" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" currencyconverter "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/currencyconverterapi" @@ -14,6 +15,11 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/openexchangerates" ) +var ( + errUnhandledForeignExchangeProvider = errors.New("unhandled foreign exchange provider") + errNoPrimaryForexProviderEnabled = errors.New("no primary forex provider enabled") +) + // ForexProviders is a foreign exchange handler type type ForexProviders struct { base.FXHandler @@ -83,69 +89,37 @@ func (f *ForexProviders) SetProvider(b base.IFXProvider) error { // StartFXService starts the forex provider service and returns a pointer to it func StartFXService(fxProviders []base.Settings) (*ForexProviders, error) { handler := new(ForexProviders) - for i := range fxProviders { - switch { - case fxProviders[i].Name == "CurrencyConverter" && fxProviders[i].Enabled: - provider := new(currencyconverter.CurrencyConverter) - err := provider.Setup(fxProviders[i]) - if err != nil { - return nil, err - } - - err = handler.SetProvider(provider) - if err != nil { - return nil, err - } - case fxProviders[i].Name == "CurrencyLayer" && fxProviders[i].Enabled: - provider := new(currencylayer.CurrencyLayer) - err := provider.Setup(fxProviders[i]) - if err != nil { - return nil, err - } - - err = handler.SetProvider(provider) - if err != nil { - return nil, err - } - case fxProviders[i].Name == "ExchangeRates" && fxProviders[i].Enabled: - provider := new(exchangerates.ExchangeRates) - err := provider.Setup(fxProviders[i]) - if err != nil { - return nil, err - } - - err = handler.SetProvider(provider) - if err != nil { - return nil, err - } - case fxProviders[i].Name == "Fixer" && fxProviders[i].Enabled: - provider := new(fixer.Fixer) - err := provider.Setup(fxProviders[i]) - if err != nil { - return nil, err - } - - err = handler.SetProvider(provider) - if err != nil { - return nil, err - } - case fxProviders[i].Name == "OpenExchangeRates" && fxProviders[i].Enabled: - provider := new(openexchangerates.OXR) - err := provider.Setup(fxProviders[i]) - if err != nil { - return nil, err - } - - err = handler.SetProvider(provider) - if err != nil { - return nil, err - } + var provider base.IFXProvider + switch fxProviders[i].Name { + case "CurrencyConverter": + provider = new(currencyconverter.CurrencyConverter) + case "CurrencyLayer": + provider = new(currencylayer.CurrencyLayer) + case "ExchangeRates": + provider = new(exchangerates.ExchangeRates) + case "Fixer": + provider = new(fixer.Fixer) + case "OpenExchangeRates": + provider = new(openexchangerates.OXR) + case "ExchangeRateHost": + provider = new(exchangeratehost.ExchangeRateHost) + default: + return nil, fmt.Errorf("%s %w", fxProviders[i].Name, + errUnhandledForeignExchangeProvider) + } + err := provider.Setup(fxProviders[i]) + if err != nil { + return nil, err + } + err = handler.SetProvider(provider) + if err != nil { + return nil, err } } if handler.Primary.Provider == nil { - return nil, errors.New("no primary forex provider enabled") + return nil, errNoPrimaryForexProviderEnabled } return handler, nil diff --git a/currency/forexprovider/openexchangerates/openexchangerates.go b/currency/forexprovider/openexchangerates/openexchangerates.go index 8132cf31..5e64202a 100644 --- a/currency/forexprovider/openexchangerates/openexchangerates.go +++ b/currency/forexprovider/openexchangerates/openexchangerates.go @@ -35,7 +35,6 @@ func (o *OXR) Setup(config base.Settings) error { o.APIKeyLvl = config.APIKeyLvl o.Enabled = config.Enabled o.Name = config.Name - o.RESTPollingDelay = config.RESTPollingDelay o.Verbose = config.Verbose o.PrimaryProvider = config.PrimaryProvider o.Requester = request.New(o.Name, diff --git a/currency/manager.go b/currency/manager.go index 92534789..246aaea0 100644 --- a/currency/manager.go +++ b/currency/manager.go @@ -16,6 +16,12 @@ var ( ErrPairAlreadyEnabled = errors.New("pair already enabled") // ErrPairNotFound is returned when a currency pair is not found ErrPairNotFound = errors.New("pair not found") + // errAssetNotEnabled defines an error for the pairs management system + // that declares the asset is not enabled. + errAssetNotEnabled = errors.New("asset not enabled") + // ErrAssetIsNil is an error when the asset has not been populated by the + // configuration + ErrAssetIsNil = errors.New("asset is nil") ) // GetAssetTypes returns a list of stored asset types @@ -168,11 +174,11 @@ func (p *PairsManager) IsAssetEnabled(a asset.Item) error { } if c.AssetEnabled == nil { - return errors.New("cannot ascertain if asset is enabled, variable is nil") + return fmt.Errorf("%s %w", a, ErrAssetIsNil) } if !*c.AssetEnabled { - return fmt.Errorf("asset %s not enabled", a) + return fmt.Errorf("%s %w", a, errAssetNotEnabled) } return nil } diff --git a/currency/manager_test.go b/currency/manager_test.go index 00694d85..0ef80352 100644 --- a/currency/manager_test.go +++ b/currency/manager_test.go @@ -250,13 +250,13 @@ func TestDisablePair(t *testing.T) { // Test asset type which doesn't exist initTest(t) - if err := p.DisablePair(asset.Futures, Pair{}); err == nil { + if err := p.DisablePair(asset.Futures, EMPTYPAIR); err == nil { t.Error("unexpected result") } // Test asset type which has an empty pair store p.Pairs[asset.Spot] = nil - if err := p.DisablePair(asset.Spot, Pair{}); err == nil { + if err := p.DisablePair(asset.Spot, EMPTYPAIR); err == nil { t.Error("unexpected result") } @@ -281,13 +281,13 @@ func TestEnablePair(t *testing.T) { // Test asset type which doesn't exist initTest(t) - if err := p.EnablePair(asset.Futures, Pair{}); err == nil { + if err := p.EnablePair(asset.Futures, EMPTYPAIR); err == nil { t.Error("unexpected result") } // Test asset type which has an empty pair store p.Pairs[asset.Spot] = nil - if err := p.EnablePair(asset.Spot, Pair{}); err == nil { + if err := p.EnablePair(asset.Spot, EMPTYPAIR); err == nil { t.Error("unexpected result") } diff --git a/currency/pair.go b/currency/pair.go index cd800ade..13be4a0c 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -1,20 +1,23 @@ package currency import ( + "errors" "fmt" "strings" ) +var errCannotCreatePair = errors.New("cannot create currency pair") + // NewPairDelimiter splits the desired currency string at delimeter, the returns // a Pair struct func NewPairDelimiter(currencyPair, delimiter string) (Pair, error) { if !strings.Contains(currencyPair, delimiter) { - return Pair{}, + return EMPTYPAIR, fmt.Errorf("delimiter: [%s] not found in currencypair string", delimiter) } result := strings.Split(currencyPair, delimiter) if len(result) < 2 { - return Pair{}, + return EMPTYPAIR, fmt.Errorf("supplied pair: [%s] cannot be split with %s", currencyPair, delimiter) @@ -32,13 +35,13 @@ func NewPairDelimiter(currencyPair, delimiter string) (Pair, error) { // NewPairFromStrings returns a CurrencyPair without a delimiter func NewPairFromStrings(base, quote string) (Pair, error) { if strings.Contains(base, " ") { - return Pair{}, + return EMPTYPAIR, fmt.Errorf("cannot create pair, invalid base currency string [%s]", base) } if strings.Contains(quote, " ") { - return Pair{}, + return EMPTYPAIR, fmt.Errorf("cannot create pair, invalid quote currency string [%s]", quote) } @@ -68,7 +71,7 @@ func NewPairWithDelimiter(base, quote, delimiter string) Pair { func NewPairFromIndex(currencyPair, index string) (Pair, error) { i := strings.Index(currencyPair, index) if i == -1 { - return Pair{}, + return EMPTYPAIR, fmt.Errorf("index %s not found in currency pair string", index) } if i == 0 { @@ -87,8 +90,9 @@ func NewPairFromString(currencyPair string) (Pair, error) { } } if len(currencyPair) < 3 { - return Pair{}, - fmt.Errorf("cannot create pair from %s string", + return EMPTYPAIR, + fmt.Errorf("%w from %s string too short to be a current pair", + errCannotCreatePair, currencyPair) } return NewPairFromStrings(currencyPair[0:3], currencyPair[3:]) @@ -100,8 +104,7 @@ func NewPairFromString(currencyPair string) (Pair, error) { // apply the same format func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFormat) (Pair, error) { for x := range pairs { - fPair := pairFmt.Format(pairs[x]) - if strings.EqualFold(fPair, currencyPair) { + if strings.EqualFold(pairFmt.Format(pairs[x]), currencyPair) { return pairs[x], nil } } @@ -132,5 +135,5 @@ func MatchPairsWithNoDelimiter(currencyPair string, pairs Pairs, pairFmt PairFor } } } - return Pair{}, fmt.Errorf("currency %v not found in supplied pairs", currencyPair) + return EMPTYPAIR, fmt.Errorf("currency %v not found in supplied pairs", currencyPair) } diff --git a/currency/pair_methods.go b/currency/pair_methods.go index f377d07c..41460597 100644 --- a/currency/pair_methods.go +++ b/currency/pair_methods.go @@ -2,7 +2,6 @@ package currency import ( "encoding/json" - "strings" ) // String returns a currency pair string @@ -12,20 +11,16 @@ func (p Pair) String() string { // Lower converts the pair object to lowercase func (p Pair) Lower() Pair { - return Pair{ - Delimiter: p.Delimiter, - Base: p.Base.Lower(), - Quote: p.Quote.Lower(), - } + p.Base = p.Base.Lower() + p.Quote = p.Quote.Lower() + return p } // Upper converts the pair object to uppercase func (p Pair) Upper() Pair { - return Pair{ - Delimiter: p.Delimiter, - Base: p.Base.Upper(), - Quote: p.Quote.Upper(), - } + p.Base = p.Base.Upper() + p.Quote = p.Quote.Upper() + return p } // UnmarshalJSON comforms type to the umarshaler interface @@ -41,9 +36,7 @@ func (p *Pair) UnmarshalJSON(d []byte) error { return err } - p.Base = newPair.Base - p.Quote = newPair.Quote - p.Delimiter = newPair.Delimiter + *p = newPair return nil } @@ -55,49 +48,56 @@ func (p Pair) MarshalJSON() ([]byte, error) { // Format changes the currency based on user preferences overriding the default // String() display func (p Pair) Format(delimiter string, uppercase bool) Pair { - newP := Pair{Base: p.Base, Quote: p.Quote, Delimiter: delimiter} + p.Delimiter = delimiter if uppercase { - return newP.Upper() + return p.Upper() } - return newP.Lower() + return p.Lower() } // Equal compares two currency pairs and returns whether or not they are equal func (p Pair) Equal(cPair Pair) bool { - return strings.EqualFold(p.Base.String(), cPair.Base.String()) && - strings.EqualFold(p.Quote.String(), cPair.Quote.String()) + return p.Base.Equal(cPair.Base) && p.Quote.Equal(cPair.Quote) } // EqualIncludeReciprocal compares two currency pairs and returns whether or not // they are the same including reciprocal currencies. func (p Pair) EqualIncludeReciprocal(cPair Pair) bool { - if (p.Base.Item == cPair.Base.Item && p.Quote.Item == cPair.Quote.Item) || - (p.Base.Item == cPair.Quote.Item && p.Quote.Item == cPair.Base.Item) { - return true - } - return false + return (p.Base.Equal(cPair.Base) && p.Quote.Equal(cPair.Quote)) || + (p.Base.Equal(cPair.Quote) && p.Quote.Equal(cPair.Base)) } // IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC func (p Pair) IsCryptoPair() bool { - return storage.IsCryptocurrency(p.Base) && - storage.IsCryptocurrency(p.Quote) + return p.Base.IsCryptocurrency() && p.Quote.IsCryptocurrency() } // IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD func (p Pair) IsCryptoFiatPair() bool { - return (storage.IsCryptocurrency(p.Base) && storage.IsFiatCurrency(p.Quote)) || - (storage.IsFiatCurrency(p.Base) && storage.IsCryptocurrency(p.Quote)) + return (p.Base.IsCryptocurrency() && p.Quote.IsFiatCurrency()) || + (p.Base.IsFiatCurrency() && p.Quote.IsCryptocurrency()) } // IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD func (p Pair) IsFiatPair() bool { - return storage.IsFiatCurrency(p.Base) && storage.IsFiatCurrency(p.Quote) + return p.Base.IsFiatCurrency() && p.Quote.IsFiatCurrency() +} + +// IsCryptoStablePair checks to see if the pair is a crypto stable pair e.g. +// LTC-USDT +func (p Pair) IsCryptoStablePair() bool { + return (p.Base.IsCryptocurrency() && p.Quote.IsStableCurrency()) || + (p.Base.IsStableCurrency() && p.Quote.IsCryptocurrency()) +} + +// IsStablePair checks to see if the pair is a stable pair e.g. USDT-DAI +func (p Pair) IsStablePair() bool { + return p.Base.IsStableCurrency() && p.Quote.IsStableCurrency() } // IsInvalid checks invalid pair if base and quote are the same func (p Pair) IsInvalid() bool { - return p.Base.Item == p.Quote.Item + return p.Base.Equal(p.Quote) } // Swap turns the currency pair into its reciprocal @@ -111,7 +111,23 @@ func (p Pair) IsEmpty() bool { return p.Base.IsEmpty() && p.Quote.IsEmpty() } -// ContainsCurrency checks to see if a pair contains a specific currency -func (p Pair) ContainsCurrency(c Code) bool { - return p.Base.Item == c.Item || p.Quote.Item == c.Item +// Contains checks to see if a pair contains a specific currency +func (p Pair) Contains(c Code) bool { + return p.Base.Equal(c) || p.Quote.Equal(c) +} + +// Len derives full length for match exclusion. +func (p Pair) Len() int { + return len(p.Base.String()) + len(p.Quote.String()) +} + +// Other returns the other currency from pair, if not matched returns empty code. +func (p Pair) Other(c Code) (Code, error) { + if p.Base.Equal(c) { + return p.Quote, nil + } + if p.Quote.Equal(c) { + return p.Base, nil + } + return EMPTYCODE, ErrCurrencyCodeEmpty } diff --git a/currency/pair_test.go b/currency/pair_test.go index 09d66c47..771cda7f 100644 --- a/currency/pair_test.go +++ b/currency/pair_test.go @@ -2,6 +2,7 @@ package currency import ( "encoding/json" + "errors" "testing" ) @@ -120,6 +121,34 @@ func TestIsFiatPair(t *testing.T) { } } +func TestIsCryptoStablePair(t *testing.T) { + if !NewPair(BTC, USDT).IsCryptoStablePair() { + t.Error("TestIsCryptoStablePair. Expected true result") + } + + if !NewPair(DAI, USDT).IsCryptoStablePair() { + t.Error("TestIsCryptoStablePair. Expected true result") + } + + if NewPair(AUD, USDT).IsCryptoStablePair() { + t.Error("TestIsCryptoStablePair. Expected false result") + } +} + +func TestIsStablePair(t *testing.T) { + if !NewPair(USDT, DAI).IsStablePair() { + t.Error("TestIsStablePair. Expected true result") + } + + if NewPair(USDT, AUD).IsStablePair() { + t.Error("TestIsStablePair. Expected false result") + } + + if NewPair(USDT, LTC).IsStablePair() { + t.Error("TestIsStablePair. Expected false result") + } +} + func TestString(t *testing.T) { t.Parallel() pair := NewPair(BTC, USD) @@ -132,7 +161,7 @@ func TestString(t *testing.T) { func TestFirstCurrency(t *testing.T) { t.Parallel() pair := NewPair(BTC, USD) - if actual, expected := pair.Base, BTC; actual != expected { + if actual, expected := pair.Base, BTC; !actual.Equal(expected) { t.Errorf( "GetFirstCurrency(): %s was not equal to expected value: %s", actual, expected, @@ -143,7 +172,7 @@ func TestFirstCurrency(t *testing.T) { func TestSecondCurrency(t *testing.T) { t.Parallel() pair := NewPair(BTC, USD) - if actual, expected := pair.Quote, USD; actual != expected { + if actual, expected := pair.Quote, USD; !actual.Equal(expected) { t.Errorf( "GetSecondCurrency(): %s was not equal to expected value: %s", actual, expected, @@ -513,26 +542,22 @@ func TestNewPairFromFormattedPairs(t *testing.T) { func TestContainsCurrency(t *testing.T) { p := NewPair(BTC, USD) - if !p.ContainsCurrency(BTC) { - t.Error("TestContainsCurrency: Expected currency was not found") + if !p.Contains(BTC) { + t.Error("TestContains: Expected currency was not found") } - if p.ContainsCurrency(ETH) { - t.Error("TestContainsCurrency: Non-existent currency was found") + if p.Contains(ETH) { + t.Error("TestContains: Non-existent currency was found") } } func TestFormatPairs(t *testing.T) { - newP, err := FormatPairs([]string{""}, "-", "") - if err != nil { - t.Error("FormatPairs() error", err) + _, err := FormatPairs([]string{""}, "-", "") + if !errors.Is(err, errEmptyPairString) { + t.Fatalf("received: '%v' but expected: '%v'", err, errEmptyPairString) } - if len(newP) > 0 { - t.Error("TestFormatPairs: Empty string returned a valid pair") - } - - newP, err = FormatPairs([]string{defaultPairWDelimiter}, "-", "") + newP, err := FormatPairs([]string{defaultPairWDelimiter}, "-", "") if err != nil { t.Error("FormatPairs() error", err) } @@ -599,8 +624,8 @@ func TestFindPairDifferences(t *testing.T) { } emptyPairsList, err := NewPairsFromStrings([]string{""}) - if err != nil { - t.Fatal(err) + if !errors.Is(err, errCannotCreatePair) { + t.Fatalf("received: '%v' but expected: '%v'", err, errCannotCreatePair) } // Test that we don't allow empty strings for new pairs @@ -778,7 +803,7 @@ func TestPairFormat_Format(t *testing.T) { { name: "empty", fields: fields{}, - arg: Pair{}, + arg: EMPTYPAIR, want: "", }, { @@ -820,3 +845,23 @@ func TestPairFormat_Format(t *testing.T) { }) } } + +func TestOther(t *testing.T) { + received, err := NewPair(DAI, XRP).Other(DAI) + if err != nil { + t.Fatal(err) + } + if !received.Equal(XRP) { + t.Fatal("unexpected value") + } + received, err = NewPair(DAI, XRP).Other(XRP) + if err != nil { + t.Fatal(err) + } + if !received.Equal(DAI) { + t.Fatal("unexpected value") + } + if _, err := NewPair(DAI, XRP).Other(BTC); !errors.Is(err, ErrCurrencyCodeEmpty) { + t.Fatal("unexpected value") + } +} diff --git a/currency/pairs.go b/currency/pairs.go index e7ab204c..f415dbdb 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -2,36 +2,48 @@ package currency import ( "encoding/json" + "errors" + "fmt" "math/rand" "strings" "github.com/thrasher-corp/gocryptotrader/log" ) +var ( + errSymbolEmpty = errors.New("symbol is empty") + errPairsEmpty = errors.New("pairs are empty") + errNoDelimiter = errors.New("no delimiter was supplied") +) + // NewPairsFromStrings takes in currency pair strings and returns a currency // pair list func NewPairsFromStrings(pairs []string) (Pairs, error) { - var newPairs Pairs + allThePairs := make(Pairs, len(pairs)) + var err error for i := range pairs { - if pairs[i] == "" { - continue - } - - newPair, err := NewPairFromString(pairs[i]) + allThePairs[i], err = NewPairFromString(pairs[i]) if err != nil { return nil, err } - - newPairs = append(newPairs, newPair) } - return newPairs, nil + return allThePairs, nil +} + +// NewPairsFromString takes in a delimiter string and returns a Pairs +// type +func NewPairsFromString(pairs, delimiter string) (Pairs, error) { + if delimiter == "" { + return nil, errNoDelimiter + } + return NewPairsFromStrings(strings.Split(pairs, delimiter)) } // Strings returns a slice of strings referring to each currency pair func (p Pairs) Strings() []string { - var list []string + list := make([]string, len(p)) for i := range p { - list = append(list, p[i].String()) + list[i] = p[i].String() } return list } @@ -43,28 +55,22 @@ func (p Pairs) Join() string { // Format formats the pair list to the exchange format configuration func (p Pairs) Format(delimiter, index string, uppercase bool) Pairs { - var pairs Pairs - for i := range p { - var formattedPair = Pair{ - Delimiter: delimiter, - Base: p[i].Base, - Quote: p[i].Quote, - } + pairs := make(Pairs, 0, len(p)) + var err error + for _, format := range p { if index != "" { - newP, err := NewPairFromIndex(p[i].String(), index) + format, err = NewPairFromIndex(format.String(), index) if err != nil { log.Errorf(log.Global, "failed to create NewPairFromIndex. Err: %s\n", err) continue } - formattedPair.Base = newP.Base - formattedPair.Quote = newP.Quote } - + format.Delimiter = delimiter if uppercase { - pairs = append(pairs, formattedPair.Upper()) + pairs = append(pairs, format.Upper()) } else { - pairs = append(pairs, formattedPair.Lower()) + pairs = append(pairs, format.Lower()) } } return pairs @@ -83,18 +89,8 @@ func (p *Pairs) UnmarshalJSON(d []byte) error { return nil } - var allThePairs Pairs - oldPairs := strings.Split(pairs, ",") - for i := range oldPairs { - pair, err := NewPairFromString(oldPairs[i]) - if err != nil { - return err - } - allThePairs = append(allThePairs, pair) - } - - *p = allThePairs - return nil + *p, err = NewPairsFromString(pairs, ",") + return err } // MarshalJSON conforms type to the marshaler interface @@ -102,13 +98,22 @@ func (p Pairs) MarshalJSON() ([]byte, error) { return json.Marshal(p.Join()) } -// Upper returns an upper formatted pair list +// Upper updates the original pairs and returns the pairs for convenience if +// needed. func (p Pairs) Upper() Pairs { - var upper Pairs for i := range p { - upper = append(upper, p[i].Upper()) + p[i] = p[i].Upper() } - return upper + return p +} + +// Lower updates the original pairs and returns the pairs for convenience if +// needed. +func (p Pairs) Lower() Pairs { + for i := range p { + p[i] = p[i].Lower() + } + return p } // Contains checks to see if a specified pair exists inside a currency pair @@ -131,9 +136,22 @@ func (p Pairs) Contains(check Pair, exact bool) bool { // RemovePairsByFilter checks to see if a pair contains a specific currency // and removes it from the list of pairs func (p Pairs) RemovePairsByFilter(filter Code) Pairs { - var pairs Pairs + pairs := make(Pairs, 0, len(p)) for i := range p { - if p[i].ContainsCurrency(filter) { + if p[i].Contains(filter) { + continue + } + pairs = append(pairs, p[i]) + } + return pairs +} + +// GetPairsByFilter returns all pairs that have at least one match base or quote +// to the filter code. +func (p Pairs) GetPairsByFilter(filter Code) Pairs { + pairs := make(Pairs, 0, len(p)) + for i := range p { + if !p[i].Contains(filter) { continue } pairs = append(pairs, p[i]) @@ -143,7 +161,7 @@ func (p Pairs) RemovePairsByFilter(filter Code) Pairs { // Remove removes the specified pair from the list of pairs if it exists func (p Pairs) Remove(pair Pair) Pairs { - var pairs Pairs + pairs := make(Pairs, 0, len(p)) for x := range p { if p[x].Equal(pair) { continue @@ -162,6 +180,18 @@ func (p Pairs) Add(pair Pair) Pairs { return p } +// GetMatch returns either the pair that is equal including the reciprocal for +// when currencies are constructed from different exchange pairs e.g. Exchange +// one USDT-DAI to exchange two DAI-USDT enabled/available pairs. +func (p Pairs) GetMatch(pair Pair) (Pair, error) { + for x := range p { + if p[x].EqualIncludeReciprocal(pair) { + return p[x], nil + } + } + return EMPTYPAIR, ErrPairNotFound +} + // FindDifferences returns pairs which are new or have been removed func (p Pairs) FindDifferences(pairs Pairs) (newPairs, removedPairs Pairs) { for x := range pairs { @@ -188,5 +218,116 @@ func (p Pairs) GetRandomPair() Pair { if pairsLen := len(p); pairsLen != 0 { return p[rand.Intn(pairsLen)] // nolint:gosec // basic number generation required, no need for crypo/rand } - return Pair{} + return EMPTYPAIR +} + +// DeriveFrom matches symbol string to the available pairs list when no +// delimiter is supplied. +func (p Pairs) DeriveFrom(symbol string) (Pair, error) { + if len(p) == 0 { + return EMPTYPAIR, errPairsEmpty + } + if symbol == "" { + return EMPTYPAIR, errSymbolEmpty + } + symbol = strings.ToLower(symbol) +pairs: + for x := range p { + if p[x].Len() != len(symbol) { + continue + } + base := p[x].Base.Lower().String() + baseLength := len(base) + for y := 0; y < baseLength; y++ { + if base[y] != symbol[y] { + continue pairs + } + } + quote := p[x].Quote.Lower().String() + for y := 0; y < len(quote); y++ { + if quote[y] != symbol[baseLength+y] { + continue pairs + } + } + return p[x], nil + } + return EMPTYPAIR, fmt.Errorf("%w for symbol string %s", ErrPairNotFound, symbol) +} + +// GetCrypto returns all the cryptos contained in the list. +func (p Pairs) GetCrypto() Currencies { + m := make(map[*Item]bool) + for x := range p { + if p[x].Base.IsCryptocurrency() { + m[p[x].Base.Item] = p[x].Base.UpperCase + } + if p[x].Quote.IsCryptocurrency() { + m[p[x].Quote.Item] = p[x].Quote.UpperCase + } + } + return currencyConstructor(m) +} + +// GetFiat returns all the cryptos contained in the list. +func (p Pairs) GetFiat() Currencies { + m := make(map[*Item]bool) + for x := range p { + if p[x].Base.IsFiatCurrency() { + m[p[x].Base.Item] = p[x].Base.UpperCase + } + if p[x].Quote.IsFiatCurrency() { + m[p[x].Quote.Item] = p[x].Quote.UpperCase + } + } + return currencyConstructor(m) +} + +// GetCurrencies returns the full currency code list contained derived from the +// pairs list. +func (p Pairs) GetCurrencies() Currencies { + m := make(map[*Item]bool) + for x := range p { + m[p[x].Base.Item] = p[x].Base.UpperCase + m[p[x].Quote.Item] = p[x].Quote.UpperCase + } + return currencyConstructor(m) +} + +// GetStables returns the stable currency code list derived from the pairs list. +func (p Pairs) GetStables() Currencies { + m := make(map[*Item]bool) + for x := range p { + if p[x].Base.IsStableCurrency() { + m[p[x].Base.Item] = p[x].Base.UpperCase + } + if p[x].Quote.IsStableCurrency() { + m[p[x].Quote.Item] = p[x].Quote.UpperCase + } + } + return currencyConstructor(m) +} + +// currencyConstructor takes in an item map and returns the currencies with +// the same formatting. +func currencyConstructor(m map[*Item]bool) Currencies { + var cryptos = make([]Code, len(m)) + var target int + for code, upper := range m { + cryptos[target].Item = code + cryptos[target].UpperCase = upper + target++ + } + return cryptos +} + +// GetStablesMatch returns all stable pairs matched with code +func (p Pairs) GetStablesMatch(code Code) Pairs { + stablePairs := make([]Pair, 0, len(p)) + for x := range p { + if p[x].Base.IsStableCurrency() && p[x].Quote.Equal(code) || + p[x].Quote.IsStableCurrency() && p[x].Base.Equal(code) { + stablePairs = append(stablePairs, p[x]) + } + } + return stablePairs } diff --git a/currency/pairs_test.go b/currency/pairs_test.go index 87429817..aedd8721 100644 --- a/currency/pairs_test.go +++ b/currency/pairs_test.go @@ -2,6 +2,7 @@ package currency import ( "encoding/json" + "errors" "testing" ) @@ -10,9 +11,18 @@ func TestPairsUpper(t *testing.T) { if err != nil { t.Fatal(err) } - expected := "BTC_USD,BTC_AUD,BTC_LTC" + if expected := "BTC_USD,BTC_AUD,BTC_LTC"; pairs.Upper().Join() != expected { + t.Errorf("Pairs Join() error expected %s but received %s", + expected, pairs.Join()) + } +} - if pairs.Upper().Join() != expected { +func TestPairsLower(t *testing.T) { + pairs, err := NewPairsFromStrings([]string{"BTC_USD", "BTC_AUD", "BTC_LTC"}) + if err != nil { + t.Fatal(err) + } + if expected := "btc_usd,btc_aud,btc_ltc"; pairs.Lower().Join() != expected { t.Errorf("Pairs Join() error expected %s but received %s", expected, pairs.Join()) } @@ -33,6 +43,33 @@ func TestPairsString(t *testing.T) { } } +func TestPairsFromString(t *testing.T) { + if _, err := NewPairsFromString("", ""); !errors.Is(err, errNoDelimiter) { + t.Fatalf("received: '%v' but expected: '%v'", err, errNoDelimiter) + } + + if _, err := NewPairsFromString("", ","); !errors.Is(err, errCannotCreatePair) { + t.Fatalf("received: '%v' but expected: '%v'", err, errCannotCreatePair) + } + + pairs, err := NewPairsFromString("ALGO-AUD,BAT-AUD,BCH-AUD,BSV-AUD,BTC-AUD,COMP-AUD,ENJ-AUD,ETC-AUD,ETH-AUD,ETH-BTC,GNT-AUD,LINK-AUD,LTC-AUD,LTC-BTC,MCAU-AUD,OMG-AUD,POWR-AUD,UNI-AUD,USDT-AUD,XLM-AUD,XRP-AUD,XRP-BTC", ",") + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + expected := []string{"ALGO-AUD", "BAT-AUD", "BCH-AUD", "BSV-AUD", "BTC-AUD", + "COMP-AUD", "ENJ-AUD", "ETC-AUD", "ETH-AUD", "ETH-BTC", "GNT-AUD", + "LINK-AUD", "LTC-AUD", "LTC-BTC", "MCAU-AUD", "OMG-AUD", "POWR-AUD", + "UNI-AUD", "USDT-AUD", "XLM-AUD", "XRP-AUD", "XRP-BTC"} + + returned := pairs.Strings() + for x := range returned { + if returned[x] != expected[x] { + t.Fatalf("received: '%v' but expected: '%v'", returned[x], expected[x]) + } + } +} + func TestPairsJoin(t *testing.T) { pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) if err != nil { @@ -157,6 +194,20 @@ func TestRemovePairsByFilter(t *testing.T) { } } +func TestGetPairsByFilter(t *testing.T) { + var pairs = Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(LTC, USDT), + } + + filtered := pairs.GetPairsByFilter(LTC) + if !filtered.Contains(NewPair(LTC, USDT), true) && + !filtered.Contains(NewPair(LTC, USD), true) { + t.Error("TestRemovePairsByFilter unexpected result") + } +} + func TestRemove(t *testing.T) { var pairs = Pairs{ NewPair(BTC, USD), @@ -215,3 +266,242 @@ func TestContains(t *testing.T) { t.Errorf("TestContains: Non-existent pair was found") } } + +func TestDeriveFrom(t *testing.T) { + t.Parallel() + _, err := Pairs{}.DeriveFrom("") + if !errors.Is(err, errPairsEmpty) { + t.Fatalf("received: '%v' but expected: '%v'", err, errPairsEmpty) + } + var testCases = Pairs{ + NewPair(BTC, USDT), + NewPair(USDC, USDT), + NewPair(USDC, USD), + NewPair(BTC, LTC), + NewPair(LTC, SAFEMARS), + } + + _, err = testCases.DeriveFrom("") + if !errors.Is(err, errSymbolEmpty) { + t.Fatalf("received: '%v' but expected: '%v'", err, errSymbolEmpty) + } + + _, err = testCases.DeriveFrom("btcUSD") + if !errors.Is(err, ErrPairNotFound) { + t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairNotFound) + } + + got, err := testCases.DeriveFrom("USDCUSD") + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if got.Upper().String() != "USDCUSD" { + t.Fatalf("received: '%v' but expected: '%v'", got.Upper().String(), "USDCUSD") + } +} + +func TestGetCrypto(t *testing.T) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + } + contains(t, []Code{BTC, LTC, USDT}, pairs.GetCrypto()) +} + +func TestGetFiat(t *testing.T) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + } + contains(t, []Code{USD, NZD}, pairs.GetFiat()) +} + +func TestGetCurrencies(t *testing.T) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + } + contains(t, []Code{BTC, USD, LTC, NZD, USDT}, pairs.GetCurrencies()) +} + +func TestGetStables(t *testing.T) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + NewPair(DAI, USDT), + NewPair(LTC, USDC), + NewPair(USDP, USDT), + } + contains(t, []Code{USDT, USDP, USDC, DAI}, pairs.GetStables()) +} + +func contains(t *testing.T, c1, c2 []Code) { + t.Helper() +codes: + for x := range c1 { + for y := range c2 { + if c1[x].Equal(c2[y]) { + continue codes + } + } + t.Fatalf("cannot find currency %s in returned currency list %v", c1[x], c2) + } +} + +// Current: 6176922 260.0 ns/op 48 B/op 1 allocs/op +// Prior: 2575473 474.2 ns/op 112 B/op 3 allocs/op +func BenchmarkGetCrypto(b *testing.B) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + } + + for x := 0; x < b.N; x++ { + _ = pairs.GetCrypto() + } +} + +func TestGetMatch(t *testing.T) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + } + + _, err := pairs.GetMatch(NewPair(BTC, WABI)) + if !errors.Is(err, ErrPairNotFound) { + t.Fatalf("received: '%v' but expected '%v'", err, ErrPairNotFound) + } + + expected := NewPair(BTC, USD) + match, err := pairs.GetMatch(expected) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected '%v'", err, nil) + } + + if !match.Equal(expected) { + t.Fatalf("received: '%v' but expected '%v'", match, expected) + } + + match, err = pairs.GetMatch(NewPair(USD, BTC)) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected '%v'", err, nil) + } + if !match.Equal(expected) { + t.Fatalf("received: '%v' but expected '%v'", match, expected) + } +} + +func TestGetStablesMatch(t *testing.T) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + NewPair(LTC, DAI), + NewPair(USDT, XRP), + NewPair(DAI, XRP), + } + + stablePairs := pairs.GetStablesMatch(BTC) + if len(stablePairs) != 0 { + t.Fatal("unexpected value") + } + + stablePairs = pairs.GetStablesMatch(USD) + if len(stablePairs) != 0 { + t.Fatal("unexpected value") + } + + stablePairs = pairs.GetStablesMatch(LTC) + if len(stablePairs) != 2 { + t.Fatal("unexpected value") + } + + if !stablePairs[0].Equal(NewPair(LTC, USDT)) { + t.Fatal("unexpected value") + } + + if !stablePairs[1].Equal(NewPair(LTC, DAI)) { + t.Fatal("unexpected value") + } + + stablePairs = pairs.GetStablesMatch(XRP) + if len(stablePairs) != 2 { + t.Fatal("unexpected value") + } + + if !stablePairs[0].Equal(NewPair(USDT, XRP)) { + t.Fatal("unexpected value") + } + + if !stablePairs[1].Equal(NewPair(DAI, XRP)) { + t.Fatal("unexpected value") + } +} + +// Current: 5594431 217.4 ns/op 168 B/op 8 allocs/op +// Prev: 3490366 373.4 ns/op 296 B/op 11 allocs/op +func BenchmarkPairsString(b *testing.B) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + NewPair(LTC, DAI), + NewPair(USDT, XRP), + NewPair(DAI, XRP), + } + + for x := 0; x < b.N; x++ { + _ = pairs.Strings() + } +} + +// Current: 6691011 184.6 ns/op 352 B/op 1 allocs/op +// Prev: 3746151 317.1 ns/op 720 B/op 4 allocs/op +func BenchmarkPairsFormat(b *testing.B) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + NewPair(LTC, DAI), + NewPair(USDT, XRP), + NewPair(DAI, XRP), + } + + for x := 0; x < b.N; x++ { + _ = pairs.Format("/", "", false) + } +} + +// current: 13075897 100.4 ns/op 352 B/op 1 allocs/o +// prev: 8188616 148.0 ns/op 336 B/op 3 allocs/op +func BenchmarkRemovePairsByFilter(b *testing.B) { + pairs := Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + NewPair(LTC, DAI), + NewPair(USDT, XRP), + NewPair(DAI, XRP), + } + + for x := 0; x < b.N; x++ { + _ = pairs.RemovePairsByFilter(USD) + } +} diff --git a/currency/storage.go b/currency/storage.go index 8f688497..13ee017e 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -15,10 +15,38 @@ import ( "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 @@ -31,14 +59,19 @@ func (s *Storage) SetDefaults() { fiatCurrencies = append(fiatCurrencies, Code{Item: item, UpperCase: true}) } - err := s.SetDefaultFiatCurrencies(fiatCurrencies...) + err := s.SetDefaultFiatCurrencies(fiatCurrencies) if err != nil { - log.Errorf(log.Global, "Currency Storage: Setting default fiat currencies error: %s", err) + log.Errorf(log.Currency, "Currency Storage: Setting default fiat currencies error: %s", err) } - err = s.SetDefaultCryptocurrencies(BTC, LTC, ETH, DOGE, DASH, XRP, XMR, USDT, UST) + err = s.SetStableCoins(stables) if err != nil { - log.Errorf(log.Global, "Currency Storage: Setting default cryptocurrencies error: %s", err) + 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() @@ -48,142 +81,139 @@ func (s *Storage) SetDefaults() { // 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 *MainConfiguration, filePath string) error { - s.mtx.Lock() - s.shutdown = make(chan struct{}) - - if !settings.Cryptocurrencies.HasData() { - s.mtx.Unlock() - return errors.New("currency storage error, no cryptocurrencies loaded") - } - s.cryptocurrencies = settings.Cryptocurrencies - +func (s *Storage) RunUpdater(overrides BotOverrides, settings *Config, filePath string) error { if settings.FiatDisplayCurrency.IsEmpty() { - s.mtx.Unlock() - return errors.New("currency storage error, no fiat display currency set in config") + return errFiatDisplayCurrencyUnset } - s.baseCurrency = settings.FiatDisplayCurrency - log.Debugf(log.Global, - "Fiat display currency: %s.\n", - s.baseCurrency) - if settings.CryptocurrencyProvider.Enabled { - log.Debugln(log.Global, - "Setting up currency analysis system with Coinmarketcap...") - c := &coinmarketcap.Coinmarketcap{} - c.SetDefaults() - err := c.Setup(coinmarketcap.Settings{ - Name: settings.CryptocurrencyProvider.Name, - Enabled: settings.CryptocurrencyProvider.Enabled, - AccountPlan: settings.CryptocurrencyProvider.AccountPlan, - APIkey: settings.CryptocurrencyProvider.APIkey, - Verbose: settings.CryptocurrencyProvider.Verbose, - }) - if err != nil { - log.Errorf(log.Global, - "Unable to setup CoinMarketCap analysis. Error: %s", err) - settings.CryptocurrencyProvider.Enabled = false - } else { - s.currencyAnalysis = c - } + if !settings.FiatDisplayCurrency.IsFiatCurrency() { + return fmt.Errorf("%s: %w", settings.FiatDisplayCurrency, ErrFiatDisplayCurrencyIsNotFiat) } if filePath == "" { - s.mtx.Unlock() - return errors.New("currency package runUpdater error filepath not set") + 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 - if settings.CurrencyDelay.Nanoseconds() == 0 { - s.currencyFileUpdateDelay = DefaultCurrencyFileDelay - } else { - s.currencyFileUpdateDelay = settings.CurrencyDelay - } - - if settings.FxRateDelay.Nanoseconds() == 0 { - s.foreignExchangeUpdateDelay = DefaultForeignExchangeDelay - } else { - s.foreignExchangeUpdateDelay = settings.FxRateDelay + 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 { - switch settings.ForexProviders[i].Name { - case "CurrencyConverter": - if overrides.FxCurrencyConverter || - settings.ForexProviders[i].Enabled { - settings.ForexProviders[i].Enabled = true - fxSettings = append(fxSettings, - base.Settings(settings.ForexProviders[i])) + 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 } - case "CurrencyLayer": - if overrides.FxCurrencyLayer || settings.ForexProviders[i].Enabled { - settings.ForexProviders[i].Enabled = true - fxSettings = append(fxSettings, - base.Settings(settings.ForexProviders[i])) + 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) } + } - case "Fixer": - if overrides.FxFixer || settings.ForexProviders[i].Enabled { - settings.ForexProviders[i].Enabled = true - fxSettings = append(fxSettings, - base.Settings(settings.ForexProviders[i])) + 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])) + } - case "OpenExchangeRates": - if overrides.FxOpenExchangeRates || - settings.ForexProviders[i].Enabled { - settings.ForexProviders[i].Enabled = true - fxSettings = append(fxSettings, - base.Settings(settings.ForexProviders[i])) - } - - case "ExchangeRates": - // TODO ADD OVERRIDE - if settings.ForexProviders[i].Enabled { - settings.ForexProviders[i].Enabled = true - 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])) + 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 { - var err error - s.fiatExchangeMarkets, err = forexprovider.StartFXService(fxSettings) - if err != nil { - s.mtx.Unlock() - return err - } - - log.Debugf(log.Global, - "Primary foreign exchange conversion provider %s enabled\n", - s.fiatExchangeMarkets.Primary.Provider.GetName()) - - for i := range s.fiatExchangeMarkets.Support { - log.Debugf(log.Global, - "Support forex conversion provider %s enabled\n", - s.fiatExchangeMarkets.Support[i].Provider.GetName()) - } - - // Mutex present in this go routine to lock down retrieving rate data - // until this system initially updates - go s.ForeignExchangeUpdater() - } else { - log.Warnln(log.Global, - "No foreign exchange providers enabled in config.json") + 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 } @@ -196,7 +226,7 @@ func (s *Storage) SetupConversionRates() { // SetDefaultFiatCurrencies assigns the default fiat currency list and adds it // to the running list -func (s *Storage) SetDefaultFiatCurrencies(c ...Code) error { +func (s *Storage) SetDefaultFiatCurrencies(c Currencies) error { for i := range c { err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Fiat) if err != nil { @@ -208,9 +238,22 @@ func (s *Storage) SetDefaultFiatCurrencies(c ...Code) error { 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 ...Code) error { +func (s *Storage) SetDefaultCryptocurrencies(c Currencies) error { for i := range c { err := s.currencyCodes.UpdateCurrency("", c[i].String(), @@ -240,20 +283,17 @@ func (s *Storage) SetupForexProviders(setting ...base.Settings) error { // ForeignExchangeUpdater is a routine that seeds foreign exchange rate and keeps // updated as fast as possible func (s *Storage) ForeignExchangeUpdater() { - log.Debugln(log.Global, - "Foreign exchange updater started, seeding FX rate list..") - - s.wg.Add(1) 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.Global, err) + log.Errorln(log.Currency, err) } err = s.SeedForeignExchangeRates() if err != nil { - log.Errorln(log.Global, err) + log.Errorln(log.Currency, err) } // Unlock main rate retrieval mutex so all routines waiting can get access @@ -270,20 +310,18 @@ func (s *Storage) ForeignExchangeUpdater() { select { case <-s.shutdown: return - case <-SeedForeignExchangeTick.C: go func() { err := s.SeedForeignExchangeRates() if err != nil { - log.Errorln(log.Global, err) + log.Errorln(log.Currency, err) } }() - case <-SeedCurrencyAnalysisTick.C: go func() { err := s.SeedCurrencyAnalysisData() if err != nil { - log.Errorln(log.Global, err) + log.Errorln(log.Currency, err) } }() } @@ -324,7 +362,7 @@ func (s *Storage) SeedCurrencyAnalysisData() error { // loads it into memory func (s *Storage) FetchCurrencyAnalysisData() error { if s.currencyAnalysis == nil { - log.Warnln(log.Global, + 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") } @@ -354,48 +392,55 @@ func (s *Storage) WriteCurrencyDataToFile(path string, mainUpdate bool) error { 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 { - contract := f.Contracts[i] - contract.Role = Contract - err := s.currencyCodes.LoadItem(&contract) + err := s.checkFileCurrencyData(f.Contracts[i], Contract) if err != nil { return err } } for i := range f.Cryptocurrency { - crypto := f.Cryptocurrency[i] - crypto.Role = Cryptocurrency - err := s.currencyCodes.LoadItem(&crypto) + err := s.checkFileCurrencyData(f.Cryptocurrency[i], Cryptocurrency) if err != nil { return err } } for i := range f.Token { - token := f.Token[i] - token.Role = Token - err := s.currencyCodes.LoadItem(&token) + err := s.checkFileCurrencyData(f.Token[i], Token) if err != nil { return err } } for i := range f.FiatCurrency { - fiat := f.FiatCurrency[i] - fiat.Role = Fiat - err := s.currencyCodes.LoadItem(&fiat) + err := s.checkFileCurrencyData(f.FiatCurrency[i], Fiat) if err != nil { return err } } for i := range f.UnsetCurrency { - unset := f.UnsetCurrency[i] - unset.Role = Unset - err := s.currencyCodes.LoadItem(&unset) + 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 } @@ -526,21 +571,6 @@ func (s *Storage) updateExchangeRates(m map[string]float64) error { return s.fxRates.Update(m) } -// SetupCryptoProvider sets congiguration parameters and starts a new instance -// of the currency analyser -func (s *Storage) SetupCryptoProvider(settings coinmarketcap.Settings) error { - if settings.APIkey == "" || - settings.APIkey == "key" || - settings.AccountPlan == "" || - settings.AccountPlan == "accountPlan" { - return errors.New("currencyprovider error api key or plan not set in config.json") - } - - s.currencyAnalysis = new(coinmarketcap.Coinmarketcap) - s.currencyAnalysis.SetDefaults() - return s.currencyAnalysis.Setup(settings) -} - // GetTotalMarketCryptocurrencies returns the total seeded market // cryptocurrencies func (s *Storage) GetTotalMarketCryptocurrencies() (Currencies, error) { @@ -553,8 +583,8 @@ func (s *Storage) GetTotalMarketCryptocurrencies() (Currencies, error) { // 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].Match(c) || - s.defaultFiatCurrencies[i].Match(GetTranslation(c)) { + if s.defaultFiatCurrencies[i].Equal(c) || + s.defaultFiatCurrencies[i].Equal(GetTranslation(c)) { return true } } @@ -565,83 +595,30 @@ func (s *Storage) IsDefaultCurrency(c Code) bool { // cryptocurrency func (s *Storage) IsDefaultCryptocurrency(c Code) bool { for i := range s.defaultCryptoCurrencies { - if s.defaultCryptoCurrencies[i].Match(c) || - s.defaultCryptoCurrencies[i].Match(GetTranslation(c)) { + if s.defaultCryptoCurrencies[i].Equal(c) || + s.defaultCryptoCurrencies[i].Equal(GetTranslation(c)) { return true } } return false } -// IsFiatCurrency returns if a currency is part of the enabled fiat currency -// list -func (s *Storage) IsFiatCurrency(c Code) bool { - if c.Item.Role != Unset { - return c.Item.Role == Fiat - } - - if c == USDT { - return false - } - - for i := range s.fiatCurrencies { - if s.fiatCurrencies[i].Match(c) || - s.fiatCurrencies[i].Match(GetTranslation(c)) { - return true - } - } - - return false -} - -// IsCryptocurrency returns if a cryptocurrency is part of the enabled -// cryptocurrency list -func (s *Storage) IsCryptocurrency(c Code) bool { - if c.Item.Role != Unset { - return c.Item.Role == Cryptocurrency - } - - if c == USD { - return false - } - - for i := range s.cryptocurrencies { - if s.cryptocurrencies[i].Match(c) || - s.cryptocurrencies[i].Match(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) + 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.RegisterFiat(newCode) + c := s.currencyCodes.Register(newCode, Fiat) if !s.fiatCurrencies.Contains(c) { s.fiatCurrencies = append(s.fiatCurrencies, c) } return c } -// ValidateCryptoCode validates a cryptocurrency string and returns a currency -// code -// TODO: Update and add in RegisterCrypto member func -func (s *Storage) ValidateCryptoCode(newCode string) Code { - c := s.currencyCodes.Register(newCode) - if !s.cryptocurrencies.Contains(c) { - s.cryptocurrencies = append(s.cryptocurrencies, c) - } - return c -} - // UpdateBaseCurrency changes base currency func (s *Storage) UpdateBaseCurrency(c Code) error { if c.IsFiatCurrency() { @@ -705,9 +682,22 @@ func (s *Storage) UpdateEnabledFiatCurrencies(c Currencies) { // 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 { @@ -762,7 +752,11 @@ func (s *Storage) IsVerbose() bool { 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) } diff --git a/currency/storage_test.go b/currency/storage_test.go index 699f3d7d..093fd447 100644 --- a/currency/storage_test.go +++ b/currency/storage_test.go @@ -1,28 +1,249 @@ package currency -import "testing" +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/thrasher-corp/gocryptotrader/database/testhelpers" +) + +func TestMain(m *testing.M) { + var err error + testhelpers.TempDir, err = ioutil.TempDir("", "gct-temp") + if err != nil { + fmt.Printf("failed to create temp file: %v", err) + os.Exit(1) + } + + t := m.Run() + + err = os.RemoveAll(testhelpers.TempDir) + if err != nil { + fmt.Printf("Failed to remove temp db file: %v", err) + } + + os.Exit(t) +} func TestRunUpdater(t *testing.T) { var newStorage Storage - emptyMainConfig := MainConfiguration{} + emptyMainConfig := Config{} err := newStorage.RunUpdater(BotOverrides{}, &emptyMainConfig, "") if err == nil { t.Fatal("storage RunUpdater() error cannot be nil") } - mainConfig := MainConfiguration{ - Cryptocurrencies: NewCurrenciesFromStringArray([]string{"BTC"}), - FiatDisplayCurrency: USD, - } - + mainConfig := Config{} err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "") - if err == nil { - t.Fatal("storage RunUpdater() error cannot be nil") + if !errors.Is(err, errFiatDisplayCurrencyUnset) { + t.Fatalf("received: '%v' but expected: '%v'", err, errFiatDisplayCurrencyUnset) } - err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "/bla") + mainConfig.FiatDisplayCurrency = BTC + err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "") + if !errors.Is(err, ErrFiatDisplayCurrencyIsNotFiat) { + t.Fatalf("received: '%v' but expected: '%v'", err, ErrFiatDisplayCurrencyIsNotFiat) + } + + mainConfig.FiatDisplayCurrency = AUD + err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "") + if !errors.Is(err, errNoFilePathSet) { + t.Fatalf("received: '%v' but expected: '%v'", err, errNoFilePathSet) + } + + tempDir := testhelpers.TempDir + + err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, tempDir) + if !errors.Is(err, errInvalidCurrencyFileUpdateDuration) { + t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidCurrencyFileUpdateDuration) + } + + mainConfig.CurrencyFileUpdateDuration = DefaultCurrencyFileDelay + err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, tempDir) + if !errors.Is(err, errInvalidForeignExchangeUpdateDuration) { + t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidForeignExchangeUpdateDuration) + } + + mainConfig.ForeignExchangeUpdateDuration = DefaultForeignExchangeDelay + err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, tempDir) + if !errors.Is(err, errNoForeignExchangeProvidersEnabled) { + t.Fatalf("received: '%v' but expected: '%v'", err, errNoForeignExchangeProvidersEnabled) + } + + settings := FXSettings{ + Name: "Fixer", + Enabled: true, + APIKey: "wo", + } + + mainConfig.ForexProviders = AllFXSettings{settings} + err = newStorage.RunUpdater(BotOverrides{Fixer: true}, &mainConfig, tempDir) + if errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, "an error") + } + + err = newStorage.Shutdown() if err != nil { - t.Fatal("storage RunUpdater() error", err) + t.Fatal(err) + } + + settings.Name = "CurrencyConverter" + mainConfig.ForexProviders = AllFXSettings{settings} + err = newStorage.RunUpdater(BotOverrides{CurrencyConverter: true}, &mainConfig, tempDir) + if errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, "an error") + } + + err = newStorage.Shutdown() + if err != nil { + t.Fatal(err) + } + + settings.Name = "CurrencyLayer" + mainConfig.ForexProviders = AllFXSettings{settings} + err = newStorage.RunUpdater(BotOverrides{CurrencyLayer: true}, &mainConfig, tempDir) + if errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, "an error") + } + + err = newStorage.Shutdown() + if err != nil { + t.Fatal(err) + } + + settings.Name = "OpenExchangeRates" + mainConfig.ForexProviders = AllFXSettings{settings} + err = newStorage.RunUpdater(BotOverrides{OpenExchangeRates: true}, &mainConfig, tempDir) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + err = newStorage.Shutdown() + if err != nil { + t.Fatal(err) + } + + settings.Name = "ExchangeRates" + mainConfig.ForexProviders = AllFXSettings{settings} + err = newStorage.RunUpdater(BotOverrides{ExchangeRates: true}, &mainConfig, tempDir) + if errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, "an error") + } + + err = newStorage.Shutdown() + if err != nil { + t.Fatal(err) + } + + settings.Name = "ExchangeRateHost" + mainConfig.ForexProviders = AllFXSettings{settings} + err = newStorage.RunUpdater(BotOverrides{ExchangeRateHost: true}, &mainConfig, tempDir) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + err = newStorage.Shutdown() + if err != nil { + t.Fatal(err) + } + + // old config where two providers enabled + oldDefault := FXSettings{ + Name: "ExchangeRates", + Enabled: true, + APIKey: "", // old default provider which did not need api keys. + PrimaryProvider: true, + } + other := FXSettings{ + Name: "OpenExchangeRates", + Enabled: true, + APIKey: "enabled-keys", // Has keys enabled and will fall over to primary + } + defaultProvider := FXSettings{ + // From config this will be included but not necessarily enabled. + Name: "ExchangeRateHost", + Enabled: false, + } + + mainConfig.ForexProviders = AllFXSettings{oldDefault, other, defaultProvider} + err = newStorage.RunUpdater(BotOverrides{ExchangeRates: true, OpenExchangeRates: true}, &mainConfig, tempDir) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if mainConfig.ForexProviders[0].Enabled { + t.Fatal("old default provider should not be enabled due to unset keys") + } + + if mainConfig.ForexProviders[0].PrimaryProvider { + t.Fatal("old default provider should not be a primary provider anymore") + } + + if !mainConfig.ForexProviders[1].Enabled { + t.Fatal("open exchange rates provider with keys set should be enabled") + } + + if !mainConfig.ForexProviders[1].PrimaryProvider { + t.Fatal("open exchange rates provider with keys set should be set as primary provider") + } + + if mainConfig.ForexProviders[2].Enabled { + t.Fatal("actual default provider should not be enabled") + } + + if mainConfig.ForexProviders[2].PrimaryProvider { + t.Fatal("actual default provider should not be designated as primary provider") + } + + err = newStorage.Shutdown() + if err != nil { + t.Fatal(err) + } + + // old config where two providers enabled + oldDefault = FXSettings{ + Name: "ExchangeRates", + Enabled: true, + APIKey: "", // old default provider which did not need api keys. + PrimaryProvider: true, + } + other = FXSettings{ + Name: "OpenExchangeRates", + Enabled: true, + APIKey: "", // Has no keys enabled will fall over to new default provider. + } + + mainConfig.ForexProviders = AllFXSettings{oldDefault, other, defaultProvider} + err = newStorage.RunUpdater(BotOverrides{ExchangeRates: true, OpenExchangeRates: true}, &mainConfig, tempDir) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + if mainConfig.ForexProviders[0].Enabled { + t.Fatal("old default provider should not be enabled due to unset keys") + } + + if mainConfig.ForexProviders[0].PrimaryProvider { + t.Fatal("old default provider should not be a primary provider anymore") + } + + if mainConfig.ForexProviders[1].Enabled { + t.Fatal("open exchange rates provider with keys unset should not be enabled") + } + + if mainConfig.ForexProviders[1].PrimaryProvider { + t.Fatal("open exchange rates provider with keys unset should not be set as primary provider") + } + + if !mainConfig.ForexProviders[2].Enabled { + t.Fatal("actual default provider should not be disabled") + } + + if !mainConfig.ForexProviders[2].PrimaryProvider { + t.Fatal("actual default provider should be designated as primary provider") } } diff --git a/currency/storage_types.go b/currency/storage_types.go index 7398ac11..3891031d 100644 --- a/currency/storage_types.go +++ b/currency/storage_types.go @@ -8,29 +8,19 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency/forexprovider" ) -// CurrencyFileUpdateDelay defines the rate at which the currency.json file is -// updated -const ( - DefaultCurrencyFileDelay = 168 * time.Hour - DefaultForeignExchangeDelay = 1 * time.Minute - DefaultStorageFile = "currency.json" -) - -// storage is an overarching type that keeps track of and updates currency, -// currency exchange rates and pairs -var storage Storage - // Storage contains the loaded storage currencies supported on available crypto // or fiat marketplaces // NOTE: All internal currencies are upper case type Storage struct { - // FiatCurrencies defines the running fiat currencies in the currency + // fiatCurrencies defines the running fiat currencies in the currency // storage fiatCurrencies Currencies - // Cryptocurrencies defines the running cryptocurrencies in the currency + // cryptocurrencies defines the running cryptocurrencies in the currency // storage cryptocurrencies Currencies - // CurrencyCodes is a full basket of currencies either crypto, fiat, ico or + // stableCurrencies defines the running stable currencies + stableCurrencies Currencies + // currencyCodes is a full basket of currencies either crypto, fiat, ico or // contract being tracked by the currency storage system currencyCodes BaseCodes // Main converting currency diff --git a/currency/symbol_test.go b/currency/symbol_test.go index fb6d0e67..a18a7c8f 100644 --- a/currency/symbol_test.go +++ b/currency/symbol_test.go @@ -12,7 +12,7 @@ func TestGetSymbolByCurrencyName(t *testing.T) { t.Errorf("TestGetSymbolByCurrencyName differing values") } - _, err = GetSymbolByCurrencyName(Code{}) + _, err = GetSymbolByCurrencyName(EMPTYCODE) if err == nil { t.Errorf("TestGetSymbolByCurrencyNam returned nil on non-existent currency") } diff --git a/currency/translation.go b/currency/translation.go index 5b9075b0..7948ef08 100644 --- a/currency/translation.go +++ b/currency/translation.go @@ -3,20 +3,20 @@ package currency // GetTranslation returns similar strings for a particular currency if not found // returns the code back func GetTranslation(currency Code) Code { - val, ok := translations[currency] + val, ok := translations[currency.Item] if !ok { return currency } return val } -var translations = map[Code]Code{ - BTC: XBT, - ETH: XETH, - DOGE: XDG, - USD: USDT, - XBT: BTC, - XETH: ETH, - XDG: DOGE, - USDT: USD, +var translations = map[*Item]Code{ + BTC.Item: XBT, + ETH.Item: XETH, + DOGE.Item: XDG, + USD.Item: USDT, + XBT.Item: BTC, + XETH.Item: ETH, + XDG.Item: DOGE, + USDT.Item: USD, } diff --git a/currency/translation_test.go b/currency/translation_test.go index 95415b38..d1254bfd 100644 --- a/currency/translation_test.go +++ b/currency/translation_test.go @@ -1,18 +1,20 @@ package currency -import "testing" +import ( + "testing" +) func TestGetTranslation(t *testing.T) { currencyPair := NewPair(BTC, USD) expected := XBT actual := GetTranslation(currencyPair.Base) - if expected != actual { + if !expected.Equal(actual) { t.Error("GetTranslation: translation result was different to expected result") } currencyPair.Base = NEO actual = GetTranslation(currencyPair.Base) - if actual != currencyPair.Base { + if !actual.Equal(currencyPair.Base) { t.Error("GetTranslation: no error on non translatable currency") } @@ -20,7 +22,15 @@ func TestGetTranslation(t *testing.T) { currencyPair.Base = XBT actual = GetTranslation(currencyPair.Base) - if expected != actual { + if !expected.Equal(actual) { t.Error("GetTranslation: translation result was different to expected result") } + + // This test accentuates the issue of comparing code types as this will + // not match for lower and upper differences and a key (*Item) needs to be + // used. + // Code{Item: 0xc000094140, Upper: true} != Code{Item: 0xc000094140, Upper: false} + if actual = GetTranslation(NewCode("btc")); !XBT.Equal(actual) { + t.Errorf("received: '%v', but expected: '%v'", actual, XBT) + } } diff --git a/engine/currency_state_manager_test.go b/engine/currency_state_manager_test.go index 65dda094..c0e24099 100644 --- a/engine/currency_state_manager_test.go +++ b/engine/currency_state_manager_test.go @@ -240,7 +240,7 @@ func TestGetAllRPC(t *testing.T) { func TestCanWithdrawRPC(t *testing.T) { t.Parallel() - _, err := (*CurrencyStateManager)(nil).CanWithdrawRPC("", currency.Code{}, "") + _, err := (*CurrencyStateManager)(nil).CanWithdrawRPC("", currency.EMPTYCODE, "") if !errors.Is(err, ErrSubSystemNotStarted) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted) } @@ -248,7 +248,7 @@ func TestCanWithdrawRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true}, - }).CanWithdrawRPC("", currency.Code{}, "") + }).CanWithdrawRPC("", currency.EMPTYCODE, "") if !errors.Is(err, errManager) { t.Fatalf("received: '%v' but expected: '%v'", err, errManager) } @@ -256,7 +256,7 @@ func TestCanWithdrawRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true}, - }).CanWithdrawRPC("", currency.Code{}, "") + }).CanWithdrawRPC("", currency.EMPTYCODE, "") if !errors.Is(err, errExchange) { t.Fatalf("received: '%v' but expected: '%v'", err, errExchange) } @@ -264,7 +264,7 @@ func TestCanWithdrawRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{}, - }).CanWithdrawRPC("", currency.Code{}, "") + }).CanWithdrawRPC("", currency.EMPTYCODE, "") if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) } @@ -272,7 +272,7 @@ func TestCanWithdrawRPC(t *testing.T) { func TestCanDepositRPC(t *testing.T) { t.Parallel() - _, err := (*CurrencyStateManager)(nil).CanDepositRPC("", currency.Code{}, "") + _, err := (*CurrencyStateManager)(nil).CanDepositRPC("", currency.EMPTYCODE, "") if !errors.Is(err, ErrSubSystemNotStarted) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted) } @@ -280,7 +280,7 @@ func TestCanDepositRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true}, - }).CanDepositRPC("", currency.Code{}, "") + }).CanDepositRPC("", currency.EMPTYCODE, "") if !errors.Is(err, errManager) { t.Fatalf("received: '%v' but expected: '%v'", err, errManager) } @@ -288,7 +288,7 @@ func TestCanDepositRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true}, - }).CanDepositRPC("", currency.Code{}, "") + }).CanDepositRPC("", currency.EMPTYCODE, "") if !errors.Is(err, errExchange) { t.Fatalf("received: '%v' but expected: '%v'", err, errExchange) } @@ -296,7 +296,7 @@ func TestCanDepositRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{}, - }).CanDepositRPC("", currency.Code{}, "") + }).CanDepositRPC("", currency.EMPTYCODE, "") if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) } @@ -304,7 +304,7 @@ func TestCanDepositRPC(t *testing.T) { func TestCanTradeRPC(t *testing.T) { t.Parallel() - _, err := (*CurrencyStateManager)(nil).CanTradeRPC("", currency.Code{}, "") + _, err := (*CurrencyStateManager)(nil).CanTradeRPC("", currency.EMPTYCODE, "") if !errors.Is(err, ErrSubSystemNotStarted) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted) } @@ -312,7 +312,7 @@ func TestCanTradeRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true}, - }).CanTradeRPC("", currency.Code{}, "") + }).CanTradeRPC("", currency.EMPTYCODE, "") if !errors.Is(err, errManager) { t.Fatalf("received: '%v' but expected: '%v'", err, errManager) } @@ -320,7 +320,7 @@ func TestCanTradeRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true}, - }).CanTradeRPC("", currency.Code{}, "") + }).CanTradeRPC("", currency.EMPTYCODE, "") if !errors.Is(err, errExchange) { t.Fatalf("received: '%v' but expected: '%v'", err, errExchange) } @@ -328,7 +328,7 @@ func TestCanTradeRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{}, - }).CanTradeRPC("", currency.Code{}, "") + }).CanTradeRPC("", currency.EMPTYCODE, "") if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) } @@ -336,7 +336,7 @@ func TestCanTradeRPC(t *testing.T) { func TestCanTradePairRPC(t *testing.T) { t.Parallel() - _, err := (*CurrencyStateManager)(nil).CanTradePairRPC("", currency.Pair{}, "") + _, err := (*CurrencyStateManager)(nil).CanTradePairRPC("", currency.EMPTYPAIR, "") if !errors.Is(err, ErrSubSystemNotStarted) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrSubSystemNotStarted) } @@ -344,7 +344,7 @@ func TestCanTradePairRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{ErrorMeOne: true}, - }).CanTradePairRPC("", currency.Pair{}, "") + }).CanTradePairRPC("", currency.EMPTYPAIR, "") if !errors.Is(err, errManager) { t.Fatalf("received: '%v' but expected: '%v'", err, errManager) } @@ -352,7 +352,7 @@ func TestCanTradePairRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{ErrorMeTwo: true}, - }).CanTradePairRPC("", currency.Pair{}, "") + }).CanTradePairRPC("", currency.EMPTYPAIR, "") if !errors.Is(err, errExchange) { t.Fatalf("received: '%v' but expected: '%v'", err, errExchange) } @@ -360,7 +360,7 @@ func TestCanTradePairRPC(t *testing.T) { _, err = (&CurrencyStateManager{ started: 1, iExchangeManager: &fakeExchangeManagerino{}, - }).CanTradePairRPC("", currency.Pair{}, "") + }).CanTradePairRPC("", currency.EMPTYPAIR, "") if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) } diff --git a/engine/engine.go b/engine/engine.go index 9a341274..f857c361 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -15,7 +15,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/currency/coinmarketcap" "github.com/thrasher-corp/gocryptotrader/dispatch" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -146,40 +145,44 @@ func loadConfigWithSettings(settings *Settings, flagSet map[string]bool) (*confi return conf, conf.CheckConfig() } +// FlagSet defines set flags from command line args for comparison methods +type FlagSet map[string]bool + +// WithBool checks the supplied flag. If set it will overide the config boolean +// value as a command line takes precedence. If not set will fall back to config +// options. +func (f FlagSet) WithBool(key string, flagValue *bool, configValue bool) { + isSet := f[key] + *flagValue = !isSet && configValue || isSet && *flagValue +} + // validateSettings validates and sets all bot settings -func validateSettings(b *Engine, s *Settings, flagSet map[string]bool) { +func validateSettings(b *Engine, s *Settings, flagSet FlagSet) { b.Settings = *s - b.Settings.EnableDataHistoryManager = (flagSet["datahistorymanager"] && b.Settings.EnableDatabaseManager) || b.Config.DataHistoryManager.Enabled + flagSet.WithBool("coinmarketcap", &b.Settings.EnableCoinmarketcapAnalysis, b.Config.Currency.CryptocurrencyProvider.Enabled) - b.Settings.EnableCurrencyStateManager = (flagSet["currencystatemanager"] && - b.Settings.EnableCurrencyStateManager) || - b.Config.CurrencyStateManager.Enabled != nil && - *b.Config.CurrencyStateManager.Enabled + flagSet.WithBool("currencyconverter", &b.Settings.EnableCurrencyConverter, b.Config.Currency.ForexProviders.IsEnabled("currencyconverter")) - b.Settings.EnableGCTScriptManager = b.Settings.EnableGCTScriptManager && - (flagSet["gctscriptmanager"] || b.Config.GCTScript.Enabled) + flagSet.WithBool("currencylayer", &b.Settings.EnableCurrencyLayer, b.Config.Currency.ForexProviders.IsEnabled("currencylayer")) + flagSet.WithBool("exchangerates", &b.Settings.EnableExchangeRates, b.Config.Currency.ForexProviders.IsEnabled("exchangerates")) + flagSet.WithBool("fixer", &b.Settings.EnableFixer, b.Config.Currency.ForexProviders.IsEnabled("fixer")) + flagSet.WithBool("openexchangerates", &b.Settings.EnableOpenExchangeRates, b.Config.Currency.ForexProviders.IsEnabled("openexchangerates")) + flagSet.WithBool("exchangeratehost", &b.Settings.EnableExchangeRateHost, b.Config.Currency.ForexProviders.IsEnabled("exchangeratehost")) + + flagSet.WithBool("datahistorymanager", &b.Settings.EnableDataHistoryManager, b.Config.DataHistoryManager.Enabled) + flagSet.WithBool("currencystatemanager", &b.Settings.EnableCurrencyStateManager, b.Config.CurrencyStateManager.Enabled != nil && *b.Config.CurrencyStateManager.Enabled) + flagSet.WithBool("gctscriptmanager", &b.Settings.EnableGCTScriptManager, b.Config.GCTScript.Enabled) if b.Settings.EnablePortfolioManager && b.Settings.PortfolioManagerDelay <= 0 { b.Settings.PortfolioManagerDelay = PortfolioSleepDelay } - if !flagSet["grpc"] { - b.Settings.EnableGRPC = b.Config.RemoteControl.GRPC.Enabled - } - - if !flagSet["grpcproxy"] { - b.Settings.EnableGRPCProxy = b.Config.RemoteControl.GRPC.GRPCProxyEnabled - } - - if !flagSet["websocketrpc"] { - b.Settings.EnableWebsocketRPC = b.Config.RemoteControl.WebsocketRPC.Enabled - } - - if !flagSet["deprecatedrpc"] { - b.Settings.EnableDeprecatedRPC = b.Config.RemoteControl.DeprecatedRPC.Enabled - } + flagSet.WithBool("grpc", &b.Settings.EnableGRPC, b.Config.RemoteControl.GRPC.Enabled) + flagSet.WithBool("grpcproxy", &b.Settings.EnableGRPCProxy, b.Config.RemoteControl.GRPC.GRPCProxyEnabled) + flagSet.WithBool("websocketrpc", &b.Settings.EnableWebsocketRPC, b.Config.RemoteControl.WebsocketRPC.Enabled) + flagSet.WithBool("deprecatedrpc", &b.Settings.EnableDeprecatedRPC, b.Config.RemoteControl.DeprecatedRPC.Enabled) if flagSet["maxvirtualmachines"] { maxMachines := uint8(b.Settings.MaxVirtualMachines) @@ -250,7 +253,7 @@ func PrintSettings(s *Settings) { gctlog.Debugf(gctlog.Global, "\t Enable dry run mode: %v", s.EnableDryRun) gctlog.Debugf(gctlog.Global, "\t Enable all exchanges: %v", s.EnableAllExchanges) gctlog.Debugf(gctlog.Global, "\t Enable all pairs: %v", s.EnableAllPairs) - gctlog.Debugf(gctlog.Global, "\t Enable coinmarketcap analysis: %v", s.EnableCoinmarketcapAnalysis) + gctlog.Debugf(gctlog.Global, "\t Enable CoinMarketCap analysis: %v", s.EnableCoinmarketcapAnalysis) gctlog.Debugf(gctlog.Global, "\t Enable portfolio manager: %v", s.EnablePortfolioManager) gctlog.Debugf(gctlog.Global, "\t Enable data history manager: %v", s.EnableDataHistoryManager) gctlog.Debugf(gctlog.Global, "\t Enable currency state manager: %v", s.EnableCurrencyStateManager) @@ -273,16 +276,17 @@ func PrintSettings(s *Settings) { gctlog.Debugf(gctlog.Global, "\t Dispatch package jobs limit: %d", s.DispatchJobsLimit) gctlog.Debugf(gctlog.Global, "- EXCHANGE SYNCER SETTINGS:\n") gctlog.Debugf(gctlog.Global, "\t Exchange sync continuously: %v\n", s.SyncContinuously) - gctlog.Debugf(gctlog.Global, "\t Exchange sync workers: %v\n", s.SyncWorkers) + gctlog.Debugf(gctlog.Global, "\t Exchange sync workers count: %v\n", s.SyncWorkersCount) gctlog.Debugf(gctlog.Global, "\t Enable ticker syncing: %v\n", s.EnableTickerSyncing) gctlog.Debugf(gctlog.Global, "\t Enable orderbook syncing: %v\n", s.EnableOrderbookSyncing) gctlog.Debugf(gctlog.Global, "\t Enable trade syncing: %v\n", s.EnableTradeSyncing) gctlog.Debugf(gctlog.Global, "\t Exchange REST sync timeout: %v\n", s.SyncTimeoutREST) gctlog.Debugf(gctlog.Global, "\t Exchange Websocket sync timeout: %v\n", s.SyncTimeoutWebsocket) gctlog.Debugf(gctlog.Global, "- FOREX SETTINGS:") - gctlog.Debugf(gctlog.Global, "\t Enable currency converter: %v", s.EnableCurrencyConverter) - 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 Currency Converter: %v", s.EnableCurrencyConverter) + gctlog.Debugf(gctlog.Global, "\t Enable Currency Layer: %v", s.EnableCurrencyLayer) + gctlog.Debugf(gctlog.Global, "\t Enable ExchangeRatesAPI.io: %v", s.EnableExchangeRates) + 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:") @@ -407,32 +411,20 @@ func (bot *Engine) Start() error { } } } - if bot.Settings.EnableCoinmarketcapAnalysis || - bot.Settings.EnableCurrencyConverter || - bot.Settings.EnableCurrencyLayer || - bot.Settings.EnableFixer || - 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(), - CryptocurrencyProvider: coinmarketcap.Settings(bot.Config.Currency.CryptocurrencyProvider), - Cryptocurrencies: bot.Config.Currency.Cryptocurrencies, - FiatDisplayCurrency: bot.Config.Currency.FiatDisplayCurrency, - CurrencyDelay: bot.Config.Currency.CurrencyFileUpdateDuration, - FxRateDelay: bot.Config.Currency.ForeignExchangeUpdateDuration, - }, - bot.Settings.DataDir) - if err != nil { - gctlog.Errorf(gctlog.Global, "ExchangeSettings updater system failed to start %s", err) - } + + err = currency.RunStorageUpdater(currency.BotOverrides{ + Coinmarketcap: bot.Settings.EnableCoinmarketcapAnalysis, + CurrencyConverter: bot.Settings.EnableCurrencyConverter, + CurrencyLayer: bot.Settings.EnableCurrencyLayer, + ExchangeRates: bot.Settings.EnableExchangeRates, + Fixer: bot.Settings.EnableFixer, + OpenExchangeRates: bot.Settings.EnableOpenExchangeRates, + ExchangeRateHost: bot.Settings.EnableExchangeRateHost, + }, + &bot.Config.Currency, + bot.Settings.DataDir) + if err != nil { + gctlog.Errorf(gctlog.Global, "ExchangeSettings updater system failed to start %s", err) } if bot.Settings.EnableGRPC { @@ -524,15 +516,17 @@ func (bot *Engine) Start() error { } if bot.Settings.EnableExchangeSyncManager { - exchangeSyncCfg := &Config{ - SyncTicker: bot.Settings.EnableTickerSyncing, - SyncOrderbook: bot.Settings.EnableOrderbookSyncing, - SyncTrades: bot.Settings.EnableTradeSyncing, - SyncContinuously: bot.Settings.SyncContinuously, - NumWorkers: bot.Settings.SyncWorkers, - Verbose: bot.Settings.Verbose, - SyncTimeoutREST: bot.Settings.SyncTimeoutREST, - SyncTimeoutWebsocket: bot.Settings.SyncTimeoutWebsocket, + exchangeSyncCfg := &SyncManagerConfig{ + SynchronizeTicker: bot.Settings.EnableTickerSyncing, + SynchronizeOrderbook: bot.Settings.EnableOrderbookSyncing, + SynchronizeTrades: bot.Settings.EnableTradeSyncing, + SynchronizeContinuously: bot.Settings.SyncContinuously, + TimeoutREST: bot.Settings.SyncTimeoutREST, + TimeoutWebsocket: bot.Settings.SyncTimeoutWebsocket, + NumWorkers: bot.Settings.SyncWorkersCount, + Verbose: bot.Settings.Verbose, + FiatDisplayCurrency: bot.Config.Currency.FiatDisplayCurrency, + PairFormatDisplay: bot.Config.Currency.CurrencyPairFormat, } bot.currencyPairSyncer, err = setupSyncManager( @@ -692,15 +686,8 @@ func (bot *Engine) Stop() { } } - if bot.Settings.EnableCoinmarketcapAnalysis || - bot.Settings.EnableCurrencyConverter || - bot.Settings.EnableCurrencyLayer || - bot.Settings.EnableFixer || - bot.Settings.EnableOpenExchangeRates || - bot.Settings.EnableExchangeRateHost { - if err := currency.ShutdownStorageUpdater(); err != nil { - gctlog.Errorf(gctlog.Global, "ExchangeSettings storage system. Error: %v", err) - } + if err := currency.ShutdownStorageUpdater(); err != nil { + gctlog.Errorf(gctlog.Global, "ExchangeSettings storage system. Error: %v", err) } if !bot.Settings.EnableDryRun { diff --git a/engine/engine_test.go b/engine/engine_test.go index 0f0333f7..1e4f0cf9 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -93,7 +93,7 @@ func TestStartStopDoesNotCausePanic(t *testing.T) { DataDir: tempDir, }, nil) if err != nil { - t.Error(err) + t.Fatal(err) } botOne.Settings.EnableGRPCProxy = false for i := range botOne.Config.Exchanges { @@ -281,3 +281,41 @@ func TestDryRunParamInteraction(t *testing.T) { t.Error("dryrun should be true and verbose should be true") } } + +func TestFlagSetWith(t *testing.T) { + var isRunning bool + flags := make(FlagSet) + // Flag not set default to config + flags.WithBool("NOT SET", &isRunning, true) + if !isRunning { + t.Fatalf("received: '%v' but expected: '%v'", isRunning, true) + } + flags.WithBool("NOT SET", &isRunning, false) + if isRunning { + t.Fatalf("received: '%v' but expected: '%v'", isRunning, false) + } + + flags["IS SET"] = true + isRunning = true + // Flag set true which will overide config + flags.WithBool("IS SET", &isRunning, true) + if !isRunning { + t.Fatalf("received: '%v' but expected: '%v'", isRunning, true) + } + flags.WithBool("IS SET", &isRunning, false) + if !isRunning { + t.Fatalf("received: '%v' but expected: '%v'", isRunning, true) + } + + flags["IS SET"] = true + isRunning = false + // Flag set false which will overide config + flags.WithBool("IS SET", &isRunning, true) + if isRunning { + t.Fatalf("received: '%v' but expected: '%v'", isRunning, false) + } + flags.WithBool("IS SET", &isRunning, false) + if isRunning { + t.Fatalf("received: '%v' but expected: '%v'", isRunning, false) + } +} diff --git a/engine/engine_types.go b/engine/engine_types.go index f3bc77b6..3ecd6a86 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -44,7 +44,7 @@ type Settings struct { EnableTickerSyncing bool EnableOrderbookSyncing bool EnableTradeSyncing bool - SyncWorkers int + SyncWorkersCount int SyncContinuously bool SyncTimeoutREST time.Duration SyncTimeoutWebsocket time.Duration @@ -52,6 +52,7 @@ type Settings struct { // Forex settings EnableCurrencyConverter bool EnableCurrencyLayer bool + EnableExchangeRates bool EnableFixer bool EnableOpenExchangeRates bool EnableExchangeRateHost bool diff --git a/engine/helpers.go b/engine/helpers.go index 84f0c7a7..9aea4f1f 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -184,15 +184,16 @@ func (bot *Engine) SetSubsystem(subSystemName string, enable bool) error { case SyncManagerName: if enable { if bot.currencyPairSyncer == nil { - exchangeSyncCfg := &Config{ - SyncTicker: bot.Settings.EnableTickerSyncing, - SyncOrderbook: bot.Settings.EnableOrderbookSyncing, - SyncTrades: bot.Settings.EnableTradeSyncing, - SyncContinuously: bot.Settings.SyncContinuously, - NumWorkers: bot.Settings.SyncWorkers, - Verbose: bot.Settings.Verbose, - SyncTimeoutREST: bot.Settings.SyncTimeoutREST, - SyncTimeoutWebsocket: bot.Settings.SyncTimeoutWebsocket, + exchangeSyncCfg := &SyncManagerConfig{ + SynchronizeTicker: bot.Settings.EnableTickerSyncing, + SynchronizeOrderbook: bot.Settings.EnableOrderbookSyncing, + SynchronizeTrades: bot.Settings.EnableTradeSyncing, + SynchronizeContinuously: bot.Settings.SyncContinuously, + TimeoutREST: bot.Settings.SyncTimeoutREST, + TimeoutWebsocket: bot.Settings.SyncTimeoutWebsocket, + NumWorkers: bot.Settings.SyncWorkersCount, + FiatDisplayCurrency: bot.Config.Currency.FiatDisplayCurrency, + Verbose: bot.Settings.Verbose, } bot.currencyPairSyncer, err = setupSyncManager( exchangeSyncCfg, @@ -376,9 +377,9 @@ func (bot *Engine) GetSpecificAvailablePairs(enabledExchangesOnly, fiatPairs, in for x := range supportedPairs { if fiatPairs { if supportedPairs[x].IsCryptoFiatPair() && - !supportedPairs[x].ContainsCurrency(currency.USDT) || + !supportedPairs[x].Contains(currency.USDT) || (includeUSDT && - supportedPairs[x].ContainsCurrency(currency.USDT) && + supportedPairs[x].Contains(currency.USDT) && supportedPairs[x].IsCryptoPair()) { if pairList.Contains(supportedPairs[x], false) { continue @@ -428,18 +429,12 @@ func (bot *Engine) MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnl continue } - result, ok := currencyExchange[exchName] - if !ok { - var pairs []currency.Pair - pairs = append(pairs, p[x]) - currencyExchange[exchName] = pairs - } else { - if result.Contains(p[x], false) { - continue - } - result = append(result, p[x]) - currencyExchange[exchName] = result + result := currencyExchange[exchName] + if result.Contains(p[x], false) { + continue } + result = append(result, p[x]) + currencyExchange[exchName] = result } } return currencyExchange @@ -468,18 +463,14 @@ func (bot *Engine) GetExchangeNamesByCurrency(p currency.Pair, enabled bool, ass func GetRelatableCryptocurrencies(p currency.Pair) currency.Pairs { var pairs currency.Pairs cryptocurrencies := currency.GetCryptocurrencies() - for x := range cryptocurrencies { newPair := currency.NewPair(p.Base, cryptocurrencies[x]) if newPair.IsInvalid() { continue } - - if newPair.Base.Upper() == p.Base.Upper() && - newPair.Quote.Upper() == p.Quote.Upper() { + if newPair.Equal(p) { continue } - if pairs.Contains(newPair, false) { continue } @@ -496,12 +487,11 @@ func GetRelatableFiatCurrencies(p currency.Pair) currency.Pairs { for x := range fiatCurrencies { newPair := currency.NewPair(p.Base, fiatCurrencies[x]) - if newPair.Base.Upper() == newPair.Quote.Upper() { + if newPair.Base.Equal(newPair.Quote) { continue } - if newPair.Base.Upper() == p.Base.Upper() && - newPair.Quote.Upper() == p.Quote.Upper() { + if newPair.Equal(p) { continue } @@ -532,17 +522,17 @@ func GetRelatableCurrencies(p currency.Pair, incOrig, incUSDT bool) currency.Pai } first := currency.GetTranslation(p.Base) - if first != p.Base { + if !first.Equal(p.Base) { addPair(currency.NewPair(first, p.Quote)) second := currency.GetTranslation(p.Quote) - if second != p.Quote { + if !second.Equal(p.Quote) { addPair(currency.NewPair(first, second)) } } second := currency.GetTranslation(p.Quote) - if second != p.Quote { + if !second.Equal(p.Quote) { addPair(currency.NewPair(p.Base, second)) } } @@ -641,30 +631,17 @@ func (bot *Engine) GetCryptocurrenciesByExchange(exchangeName string, enabledExc } var err error - var pairs []currency.Pair + var pairs currency.Pairs if enabledPairs { pairs, err = bot.Config.GetEnabledPairs(exchangeName, assetType) - if err != nil { - return nil, err - } } else { pairs, err = bot.Config.GetAvailablePairs(exchangeName, assetType) - if err != nil { - return nil, err - } } - - for y := range pairs { - if pairs[y].Base.IsCryptocurrency() && - !common.StringDataCompareInsensitive(cryptocurrencies, pairs[y].Base.String()) { - cryptocurrencies = append(cryptocurrencies, pairs[y].Base.String()) - } - - if pairs[y].Quote.IsCryptocurrency() && - !common.StringDataCompareInsensitive(cryptocurrencies, pairs[y].Quote.String()) { - cryptocurrencies = append(cryptocurrencies, pairs[y].Quote.String()) - } + if err != nil { + return nil, err } + cryptocurrencies = pairs.GetCrypto().Strings() + break } return cryptocurrencies, nil } @@ -681,7 +658,7 @@ func (bot *Engine) GetCryptocurrencyDepositAddressesByExchange(exchName string) result := bot.GetAllExchangeCryptocurrencyDepositAddresses() r, ok := result[exchName] if !ok { - return nil, ErrExchangeNotFound + return nil, fmt.Errorf("%s %w", exchName, ErrExchangeNotFound) } return r, nil } @@ -689,7 +666,9 @@ func (bot *Engine) GetCryptocurrencyDepositAddressesByExchange(exchName string) // GetExchangeCryptocurrencyDepositAddress returns the cryptocurrency deposit address for a particular // exchange func (bot *Engine) GetExchangeCryptocurrencyDepositAddress(ctx context.Context, exchName, accountID, chain string, item currency.Code, bypassCache bool) (*deposit.Address, error) { - if bot.DepositAddressManager != nil && bot.DepositAddressManager.IsSynced() && !bypassCache { + if bot.DepositAddressManager != nil && + bot.DepositAddressManager.IsSynced() && + !bypassCache { resp, err := bot.DepositAddressManager.GetDepositAddressByExchangeAndCurrency(exchName, chain, item) return &resp, err } diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 1421ae2c..e394f488 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -99,7 +99,7 @@ func CreateTestBot(t *testing.T) *Engine { func TestGetSubsystemsStatus(t *testing.T) { m := (&Engine{}).GetSubsystemsStatus() if len(m) != 15 { - t.Fatalf("subsystem count is wrong expecting: %d but received: %d", 14, len(m)) + t.Fatalf("subsystem count is wrong expecting: %d but received: %d", 15, len(m)) } } @@ -1029,10 +1029,10 @@ func (f fakeDepositExchange) GetAvailableTransferChains(_ context.Context, c cur if f.ThrowTransferChainError { return nil, errors.New("unable to get available transfer chains") } - if c.Match(currency.XRP) { + if c.Equal(currency.XRP) { return nil, nil } - if c.Match(currency.USDT) { + if c.Equal(currency.USDT) { return []string{"sol", "btc", "usdt"}, nil } return []string{"BITCOIN"}, nil diff --git a/engine/order_manager.go b/engine/order_manager.go index 9a5c60be..479231d7 100644 --- a/engine/order_manager.go +++ b/engine/order_manager.go @@ -653,8 +653,9 @@ func (m *OrderManager) processOrders() { upsertResponse, err := m.UpsertOrder(&result[z]) if err != nil { log.Error(log.OrderMgr, err) + } else { + requiresProcessing[upsertResponse.OrderDetails.InternalOrderID] = false } - requiresProcessing[upsertResponse.OrderDetails.InternalOrderID] = false } if !exchanges[i].GetBase().GetSupportedFeatures().RESTCapabilities.GetOrder { continue diff --git a/engine/order_manager_test.go b/engine/order_manager_test.go index 95bed763..cf769416 100644 --- a/engine/order_manager_test.go +++ b/engine/order_manager_test.go @@ -519,14 +519,14 @@ func TestCancelOrder(t *testing.T) { func TestGetOrderInfo(t *testing.T) { m := OrdersSetup(t) - _, err := m.GetOrderInfo(context.Background(), "", "", currency.Pair{}, "") + _, err := m.GetOrderInfo(context.Background(), "", "", currency.EMPTYPAIR, "") if err == nil { t.Error("Expected error due to empty order") } var result order.Detail result, err = m.GetOrderInfo(context.Background(), - testExchange, "1337", currency.Pair{}, "") + testExchange, "1337", currency.EMPTYPAIR, "") if err != nil { t.Error(err) } @@ -535,7 +535,7 @@ func TestGetOrderInfo(t *testing.T) { } result, err = m.GetOrderInfo(context.Background(), - testExchange, "1337", currency.Pair{}, "") + testExchange, "1337", currency.EMPTYPAIR, "") if err != nil { t.Error(err) } diff --git a/engine/portfolio_manager.go b/engine/portfolio_manager.go index 2e100d0d..1f139749 100644 --- a/engine/portfolio_manager.go +++ b/engine/portfolio_manager.go @@ -164,7 +164,7 @@ func (m *portfolioManager) seedExchangeAccountInfo(accounts []account.Holdings) for z := range accounts[x].Accounts[y].Currencies { var update bool for i := range currencies { - if accounts[x].Accounts[y].Currencies[z].CurrencyName == currencies[i].CurrencyName { + if accounts[x].Accounts[y].Currencies[z].CurrencyName.Equal(currencies[i].CurrencyName) { currencies[i].Hold += accounts[x].Accounts[y].Currencies[z].Hold currencies[i].TotalValue += accounts[x].Accounts[y].Currencies[z].TotalValue update = true diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 810785cc..6135b3a1 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -559,7 +559,7 @@ func (s *RPCServer) GetAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfo return nil, err } - err = checkParams(r.Exchange, exch, assetType, currency.Pair{}) + err = checkParams(r.Exchange, exch, assetType, currency.EMPTYPAIR) if err != nil { return nil, err } @@ -584,7 +584,7 @@ func (s *RPCServer) UpdateAccountInfo(ctx context.Context, r *gctrpc.GetAccountI return nil, err } - err = checkParams(r.Exchange, exch, assetType, currency.Pair{}) + err = checkParams(r.Exchange, exch, assetType, currency.EMPTYPAIR) if err != nil { return nil, err } @@ -627,7 +627,7 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream return err } - err = checkParams(r.Exchange, exch, assetType, currency.Pair{}) + err = checkParams(r.Exchange, exch, assetType, currency.EMPTYPAIR) if err != nil { return err } @@ -832,7 +832,7 @@ func (s *RPCServer) GetForexProviders(_ context.Context, _ *gctrpc.GetForexProvi Name: providers[x].Name, Enabled: providers[x].Enabled, Verbose: providers[x].Verbose, - RestPollingDelay: providers[x].RESTPollingDelay.String(), + RestPollingDelay: s.Config.Currency.ForeignExchangeUpdateDuration.String(), ApiKey: providers[x].APIKey, ApiKeyLevel: int64(providers[x].APIKeyLvl), PrimaryProvider: providers[x].PrimaryProvider, @@ -1954,7 +1954,7 @@ func (s *RPCServer) SetExchangePair(_ context.Context, r *gctrpc.SetExchangePair return nil, err } - err = checkParams(r.Exchange, exch, a, currency.Pair{}) + err = checkParams(r.Exchange, exch, a, currency.EMPTYPAIR) if err != nil { return nil, err } diff --git a/engine/sync_manager.go b/engine/sync_manager.go index 047acd25..8f8b7c5b 100644 --- a/engine/sync_manager.go +++ b/engine/sync_manager.go @@ -42,8 +42,12 @@ var ( ) // setupSyncManager starts a new CurrencyPairSyncer -func setupSyncManager(c *Config, exchangeManager iExchangeManager, remoteConfig *config.RemoteControlConfig, websocketRoutineManagerEnabled bool) (*syncManager, error) { - if !c.SyncOrderbook && !c.SyncTicker && !c.SyncTrades { +func setupSyncManager(c *SyncManagerConfig, exchangeManager iExchangeManager, remoteConfig *config.RemoteControlConfig, websocketRoutineManagerEnabled bool) (*syncManager, error) { + if c == nil { + return nil, fmt.Errorf("%T %w", c, common.ErrNilPointer) + } + + if !c.SynchronizeOrderbook && !c.SynchronizeTicker && !c.SynchronizeTrades { return nil, errNoSyncItemsEnabled } if exchangeManager == nil { @@ -57,12 +61,24 @@ func setupSyncManager(c *Config, exchangeManager iExchangeManager, remoteConfig c.NumWorkers = DefaultSyncerWorkers } - if c.SyncTimeoutREST <= time.Duration(0) { - c.SyncTimeoutREST = DefaultSyncerTimeoutREST + if c.TimeoutREST <= time.Duration(0) { + c.TimeoutREST = DefaultSyncerTimeoutREST } - if c.SyncTimeoutWebsocket <= time.Duration(0) { - c.SyncTimeoutWebsocket = DefaultSyncerTimeoutWebsocket + if c.TimeoutWebsocket <= time.Duration(0) { + c.TimeoutWebsocket = DefaultSyncerTimeoutWebsocket + } + + if c.FiatDisplayCurrency.IsEmpty() { + return nil, fmt.Errorf("FiatDisplayCurrency %w", currency.ErrCurrencyCodeEmpty) + } + + if !c.FiatDisplayCurrency.IsFiatCurrency() { + return nil, fmt.Errorf("%s %w", c.FiatDisplayCurrency, currency.ErrFiatDisplayCurrencyIsNotFiat) + } + + if c.PairFormatDisplay == nil { + return nil, fmt.Errorf("%T %w", c.PairFormatDisplay, common.ErrNilPointer) } s := &syncManager{ @@ -70,27 +86,26 @@ func setupSyncManager(c *Config, exchangeManager iExchangeManager, remoteConfig remoteConfig: remoteConfig, exchangeManager: exchangeManager, websocketRoutineManagerEnabled: websocketRoutineManagerEnabled, + fiatDisplayCurrency: c.FiatDisplayCurrency, + delimiter: c.PairFormatDisplay.Delimiter, + uppercase: c.PairFormatDisplay.Uppercase, + tickerBatchLastRequested: make(map[string]time.Time), } - s.tickerBatchLastRequested = make(map[string]time.Time) - log.Debugf(log.SyncMgr, "Exchange currency pair syncer config: continuous: %v ticker: %v"+ " orderbook: %v trades: %v workers: %v verbose: %v timeout REST: %v"+ " timeout Websocket: %v", - s.config.SyncContinuously, s.config.SyncTicker, s.config.SyncOrderbook, - s.config.SyncTrades, s.config.NumWorkers, s.config.Verbose, s.config.SyncTimeoutREST, - s.config.SyncTimeoutWebsocket) + s.config.SynchronizeContinuously, s.config.SynchronizeTicker, s.config.SynchronizeOrderbook, + s.config.SynchronizeTrades, s.config.NumWorkers, s.config.Verbose, s.config.TimeoutREST, + s.config.TimeoutWebsocket) s.inService.Add(1) return s, nil } // IsRunning safely checks whether the subsystem is running func (m *syncManager) IsRunning() bool { - if m == nil { - return false - } - return atomic.LoadInt32(&m.started) == 1 + return m != nil && atomic.LoadInt32(&m.started) == 1 } // Start runs the subsystem @@ -169,13 +184,13 @@ func (m *syncManager) Start() error { IsUsingREST: usingREST || !wsAssetSupported, IsUsingWebsocket: usingWebsocket && wsAssetSupported, } - if m.config.SyncTicker { + if m.config.SynchronizeTicker { c.Ticker = sBase } - if m.config.SyncOrderbook { + if m.config.SynchronizeOrderbook { c.Orderbook = sBase } - if m.config.SyncTrades { + if m.config.SynchronizeTrades { c.Trade = sBase } @@ -199,7 +214,7 @@ func (m *syncManager) Start() error { log.Debugf(log.SyncMgr, "Exchange CurrencyPairSyncer initial sync took %v [%v sync items].", completedTime.Sub(m.initSyncStartTime), createdCounter) - if !m.config.SyncContinuously { + if !m.config.SynchronizeContinuously { log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer stopping.") err := m.Stop() if err != nil { @@ -210,7 +225,7 @@ func (m *syncManager) Start() error { } }() - if atomic.LoadInt32(&m.initSyncCompleted) == 1 && !m.config.SyncContinuously { + if atomic.LoadInt32(&m.initSyncCompleted) == 1 && !m.config.SynchronizeContinuously { return nil } @@ -267,7 +282,7 @@ func (m *syncManager) add(c *currencyPairSyncAgent) { m.mux.Lock() defer m.mux.Unlock() - if m.config.SyncTicker { + if m.config.SynchronizeTicker { if m.config.Verbose { log.Debugf(log.SyncMgr, "%s: Added ticker sync item %v: using websocket: %v using REST: %v", @@ -280,7 +295,7 @@ func (m *syncManager) add(c *currencyPairSyncAgent) { } } - if m.config.SyncOrderbook { + if m.config.SynchronizeOrderbook { if m.config.Verbose { log.Debugf(log.SyncMgr, "%s: Added orderbook sync item %v: using websocket: %v using REST: %v", @@ -293,7 +308,7 @@ func (m *syncManager) add(c *currencyPairSyncAgent) { } } - if m.config.SyncTrades { + if m.config.SynchronizeTrades { if m.config.Verbose { log.Debugf(log.SyncMgr, "%s: Added trade sync item %v: using websocket: %v using REST: %v", @@ -367,15 +382,15 @@ func (m *syncManager) Update(exchangeName string, p currency.Pair, a asset.Item, switch syncType { case SyncItemOrderbook: - if !m.config.SyncOrderbook { + if !m.config.SynchronizeOrderbook { return nil } case SyncItemTicker: - if !m.config.SyncTicker { + if !m.config.SynchronizeTicker { return nil } case SyncItemTrade: - if !m.config.SyncTrades { + if !m.config.SynchronizeTrades { return nil } default: @@ -517,15 +532,15 @@ func (m *syncManager) worker() { IsUsingWebsocket: usingWebsocket && wsAssetSupported, } - if m.config.SyncTicker { + if m.config.SynchronizeTicker { c.Ticker = sBase } - if m.config.SyncOrderbook { + if m.config.SynchronizeOrderbook { c.Orderbook = sBase } - if m.config.SyncTrades { + if m.config.SynchronizeTrades { c.Trade = sBase } @@ -542,13 +557,13 @@ func (m *syncManager) worker() { switchedToRest = false } - if m.config.SyncOrderbook { + if m.config.SynchronizeOrderbook { if !m.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemOrderbook) { if c.Orderbook.LastUpdated.IsZero() || - (time.Since(c.Orderbook.LastUpdated) > m.config.SyncTimeoutREST && c.Orderbook.IsUsingREST) || - (time.Since(c.Orderbook.LastUpdated) > m.config.SyncTimeoutWebsocket && c.Orderbook.IsUsingWebsocket) { + (time.Since(c.Orderbook.LastUpdated) > m.config.TimeoutREST && c.Orderbook.IsUsingREST) || + (time.Since(c.Orderbook.LastUpdated) > m.config.TimeoutWebsocket && c.Orderbook.IsUsingWebsocket) { if c.Orderbook.IsUsingWebsocket { - if time.Since(c.Created) < m.config.SyncTimeoutWebsocket { + if time.Since(c.Created) < m.config.TimeoutWebsocket { continue } if supportsREST { @@ -560,7 +575,7 @@ func (m *syncManager) worker() { c.Exchange, m.FormatCurrency(c.Pair).String(), strings.ToUpper(c.AssetType.String()), - m.config.SyncTimeoutWebsocket, + m.config.TimeoutWebsocket, ) switchedToRest = true m.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, false) @@ -586,13 +601,13 @@ func (m *syncManager) worker() { } } - if m.config.SyncTicker { + if m.config.SynchronizeTicker { if !m.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemTicker) { if c.Ticker.LastUpdated.IsZero() || - (time.Since(c.Ticker.LastUpdated) > m.config.SyncTimeoutREST && c.Ticker.IsUsingREST) || - (time.Since(c.Ticker.LastUpdated) > m.config.SyncTimeoutWebsocket && c.Ticker.IsUsingWebsocket) { + (time.Since(c.Ticker.LastUpdated) > m.config.TimeoutREST && c.Ticker.IsUsingREST) || + (time.Since(c.Ticker.LastUpdated) > m.config.TimeoutWebsocket && c.Ticker.IsUsingWebsocket) { if c.Ticker.IsUsingWebsocket { - if time.Since(c.Created) < m.config.SyncTimeoutWebsocket { + if time.Since(c.Created) < m.config.TimeoutWebsocket { continue } @@ -605,7 +620,7 @@ func (m *syncManager) worker() { c.Exchange, m.FormatCurrency(enabledPairs[i]).String(), strings.ToUpper(c.AssetType.String()), - m.config.SyncTimeoutWebsocket, + m.config.TimeoutWebsocket, ) switchedToRest = true m.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTicker, false) @@ -625,7 +640,7 @@ func (m *syncManager) worker() { } m.mux.Unlock() - if batchLastDone.IsZero() || time.Since(batchLastDone) > m.config.SyncTimeoutREST { + if batchLastDone.IsZero() || time.Since(batchLastDone) > m.config.TimeoutREST { m.mux.Lock() if m.config.Verbose { log.Debugf(log.SyncMgr, "Initialising %s REST ticker batching", exchangeName) @@ -666,9 +681,9 @@ func (m *syncManager) worker() { } } - if m.config.SyncTrades { + if m.config.SynchronizeTrades { if !m.isProcessing(exchangeName, c.Pair, c.AssetType, SyncItemTrade) { - if c.Trade.LastUpdated.IsZero() || time.Since(c.Trade.LastUpdated) > m.config.SyncTimeoutREST { + if c.Trade.LastUpdated.IsZero() || time.Since(c.Trade.LastUpdated) > m.config.TimeoutREST { m.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemTrade, true) err := m.Update(c.Exchange, c.Pair, c.AssetType, SyncItemTrade, nil) if err != nil { @@ -693,12 +708,14 @@ func printCurrencyFormat(price float64, displayCurrency currency.Code) string { return fmt.Sprintf("%s%.8f", displaySymbol, price) } -func printConvertCurrencyFormat(origCurrency currency.Code, origPrice float64, displayCurrency currency.Code) string { - conv, err := currency.ConvertCurrency(origPrice, - origCurrency, - displayCurrency) - if err != nil { - log.Errorf(log.SyncMgr, "Failed to convert currency: %s", err) +func printConvertCurrencyFormat(origPrice float64, origCurrency, displayCurrency currency.Code) string { + var conv float64 + if origPrice > 0 { + var err error + conv, err = currency.ConvertFiat(origPrice, origCurrency, displayCurrency) + if err != nil { + log.Errorf(log.SyncMgr, "Failed to convert currency: %s", err) + } } displaySymbol, err := currency.GetSymbolByCurrencyName(displayCurrency) @@ -745,7 +762,7 @@ func (m *syncManager) PrintTickerSummary(result *ticker.Price, protocol string, _ = stats.Add(result.ExchangeName, result.Pair, result.AssetType, result.Last, result.Volume) if result.Pair.Quote.IsFiatCurrency() && - result.Pair.Quote != m.fiatDisplayCurrency && + !result.Pair.Quote.Equal(m.fiatDisplayCurrency) && !m.fiatDisplayCurrency.IsEmpty() { origCurrency := result.Pair.Quote.Upper() log.Infof(log.Ticker, "%s %s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", @@ -753,15 +770,15 @@ func (m *syncManager) PrintTickerSummary(result *ticker.Price, protocol string, protocol, m.FormatCurrency(result.Pair), strings.ToUpper(result.AssetType.String()), - printConvertCurrencyFormat(origCurrency, result.Last, m.fiatDisplayCurrency), - printConvertCurrencyFormat(origCurrency, result.Ask, m.fiatDisplayCurrency), - printConvertCurrencyFormat(origCurrency, result.Bid, m.fiatDisplayCurrency), - printConvertCurrencyFormat(origCurrency, result.High, m.fiatDisplayCurrency), - printConvertCurrencyFormat(origCurrency, result.Low, m.fiatDisplayCurrency), + printConvertCurrencyFormat(result.Last, origCurrency, m.fiatDisplayCurrency), + printConvertCurrencyFormat(result.Ask, origCurrency, m.fiatDisplayCurrency), + printConvertCurrencyFormat(result.Bid, origCurrency, m.fiatDisplayCurrency), + printConvertCurrencyFormat(result.High, origCurrency, m.fiatDisplayCurrency), + printConvertCurrencyFormat(result.Low, origCurrency, m.fiatDisplayCurrency), result.Volume) } else { if result.Pair.Quote.IsFiatCurrency() && - result.Pair.Quote == m.fiatDisplayCurrency && + result.Pair.Quote.Equal(m.fiatDisplayCurrency) && !m.fiatDisplayCurrency.IsEmpty() { log.Infof(log.Ticker, "%s %s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", result.ExchangeName, @@ -838,11 +855,15 @@ func (m *syncManager) PrintOrderbookSummary(result *orderbook.Base, protocol str var bidValueResult, askValueResult string switch { - case result.Pair.Quote.IsFiatCurrency() && result.Pair.Quote != m.fiatDisplayCurrency && !m.fiatDisplayCurrency.IsEmpty(): + case result.Pair.Quote.IsFiatCurrency() && !result.Pair.Quote.Equal(m.fiatDisplayCurrency) && !m.fiatDisplayCurrency.IsEmpty(): origCurrency := result.Pair.Quote.Upper() - bidValueResult = printConvertCurrencyFormat(origCurrency, bidsValue, m.fiatDisplayCurrency) - askValueResult = printConvertCurrencyFormat(origCurrency, asksValue, m.fiatDisplayCurrency) - case result.Pair.Quote.IsFiatCurrency() && result.Pair.Quote == m.fiatDisplayCurrency && !m.fiatDisplayCurrency.IsEmpty(): + if bidsValue > 0 { + bidValueResult = printConvertCurrencyFormat(bidsValue, origCurrency, m.fiatDisplayCurrency) + } + if asksValue > 0 { + askValueResult = printConvertCurrencyFormat(asksValue, origCurrency, m.fiatDisplayCurrency) + } + case result.Pair.Quote.IsFiatCurrency() && result.Pair.Quote.Equal(m.fiatDisplayCurrency) && !m.fiatDisplayCurrency.IsEmpty(): bidValueResult = printCurrencyFormat(bidsValue, m.fiatDisplayCurrency) askValueResult = printCurrencyFormat(asksValue, m.fiatDisplayCurrency) default: diff --git a/engine/sync_manager_test.go b/engine/sync_manager_test.go index ac963591..fe8a9a90 100644 --- a/engine/sync_manager_test.go +++ b/engine/sync_manager_test.go @@ -14,22 +14,42 @@ import ( func TestSetupSyncManager(t *testing.T) { t.Parallel() - _, err := setupSyncManager(&Config{}, nil, nil, false) + _, err := setupSyncManager(nil, nil, nil, false) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("error '%v', expected '%v'", err, common.ErrNilPointer) + } + + _, err = setupSyncManager(&SyncManagerConfig{}, nil, nil, false) if !errors.Is(err, errNoSyncItemsEnabled) { t.Errorf("error '%v', expected '%v'", err, errNoSyncItemsEnabled) } - _, err = setupSyncManager(&Config{SyncTrades: true}, nil, nil, false) + _, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true}, nil, nil, false) if !errors.Is(err, errNilExchangeManager) { t.Errorf("error '%v', expected '%v'", err, errNilExchangeManager) } - _, err = setupSyncManager(&Config{SyncTrades: true}, &ExchangeManager{}, nil, false) + _, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true}, &ExchangeManager{}, nil, false) if !errors.Is(err, errNilConfig) { t.Errorf("error '%v', expected '%v'", err, errNilConfig) } - m, err := setupSyncManager(&Config{SyncTrades: true}, &ExchangeManager{}, &config.RemoteControlConfig{}, true) + _, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true}, &ExchangeManager{}, &config.RemoteControlConfig{}, true) + if !errors.Is(err, currency.ErrCurrencyCodeEmpty) { + t.Errorf("error '%v', expected '%v'", err, currency.ErrCurrencyCodeEmpty) + } + + _, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, FiatDisplayCurrency: currency.BTC}, &ExchangeManager{}, &config.RemoteControlConfig{}, true) + if !errors.Is(err, currency.ErrFiatDisplayCurrencyIsNotFiat) { + t.Errorf("error '%v', expected '%v'", err, currency.ErrFiatDisplayCurrencyIsNotFiat) + } + + _, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, FiatDisplayCurrency: currency.USD}, &ExchangeManager{}, &config.RemoteControlConfig{}, true) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("error '%v', expected '%v'", err, common.ErrNilPointer) + } + + m, err := setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, &ExchangeManager{}, &config.RemoteControlConfig{}, true) if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } @@ -40,7 +60,7 @@ func TestSetupSyncManager(t *testing.T) { func TestSyncManagerStart(t *testing.T) { t.Parallel() - m, err := setupSyncManager(&Config{SyncTrades: true}, &ExchangeManager{}, &config.RemoteControlConfig{}, true) + m, err := setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, &ExchangeManager{}, &config.RemoteControlConfig{}, true) if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } @@ -52,7 +72,7 @@ func TestSyncManagerStart(t *testing.T) { exch.SetDefaults() em.Add(exch) m.exchangeManager = em - m.config.SyncContinuously = true + m.config.SynchronizeContinuously = true err = m.Start() if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) @@ -85,7 +105,7 @@ func TestSyncManagerStop(t *testing.T) { } exch.SetDefaults() em.Add(exch) - m, err = setupSyncManager(&Config{SyncTrades: true, SyncContinuously: true}, em, &config.RemoteControlConfig{}, false) + m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, em, &config.RemoteControlConfig{}, false) if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } @@ -115,7 +135,7 @@ func TestPrintCurrencyFormat(t *testing.T) { func TestPrintConvertCurrencyFormat(t *testing.T) { t.Parallel() - c := printConvertCurrencyFormat(currency.BTC, 1337, currency.USD) + c := printConvertCurrencyFormat(1337, currency.BTC, currency.USD) if c == "" { t.Error("expected formatted currency") } @@ -133,7 +153,7 @@ func TestPrintTickerSummary(t *testing.T) { } exch.SetDefaults() em.Add(exch) - m, err = setupSyncManager(&Config{SyncTrades: true, SyncContinuously: true}, em, &config.RemoteControlConfig{}, false) + m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, em, &config.RemoteControlConfig{}, false) if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } @@ -172,7 +192,7 @@ func TestPrintOrderbookSummary(t *testing.T) { } exch.SetDefaults() em.Add(exch) - m, err = setupSyncManager(&Config{SyncTrades: true, SyncContinuously: true}, em, &config.RemoteControlConfig{}, false) + m, err = setupSyncManager(&SyncManagerConfig{SynchronizeTrades: true, SynchronizeContinuously: true, FiatDisplayCurrency: currency.USD, PairFormatDisplay: ¤cy.PairFormat{}}, em, &config.RemoteControlConfig{}, false) if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } diff --git a/engine/sync_manager_types.go b/engine/sync_manager_types.go index 854eac89..e0656e04 100644 --- a/engine/sync_manager_types.go +++ b/engine/sync_manager_types.go @@ -30,16 +30,18 @@ type currencyPairSyncAgent struct { Trade syncBase } -// Config stores the currency pair config -type Config struct { - SyncTicker bool - SyncOrderbook bool - SyncTrades bool - SyncContinuously bool - SyncTimeoutREST time.Duration - SyncTimeoutWebsocket time.Duration - NumWorkers int - Verbose bool +// SyncManagerConfig stores the currency pair synchronization manager config +type SyncManagerConfig struct { + SynchronizeTicker bool + SynchronizeOrderbook bool + SynchronizeTrades bool + SynchronizeContinuously bool + TimeoutREST time.Duration + TimeoutWebsocket time.Duration + NumWorkers int + FiatDisplayCurrency currency.Code + PairFormatDisplay *currency.PairFormat + Verbose bool } // syncManager stores the exchange currency pair syncer object @@ -60,6 +62,6 @@ type syncManager struct { tickerBatchLastRequested map[string]time.Time remoteConfig *config.RemoteControlConfig - config Config + config SyncManagerConfig exchangeManager iExchangeManager } diff --git a/engine/websocketroutine_manager.go b/engine/websocketroutine_manager.go index 3e3ffc9b..358a70da 100644 --- a/engine/websocketroutine_manager.go +++ b/engine/websocketroutine_manager.go @@ -5,7 +5,6 @@ import ( "sync/atomic" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/fill" @@ -18,7 +17,7 @@ import ( ) // setupWebsocketRoutineManager creates a new websocket routine manager -func setupWebsocketRoutineManager(exchangeManager iExchangeManager, orderManager iOrderManager, syncer iCurrencyPairSyncer, cfg *config.CurrencyConfig, verbose bool) (*websocketRoutineManager, error) { +func setupWebsocketRoutineManager(exchangeManager iExchangeManager, orderManager iOrderManager, syncer iCurrencyPairSyncer, cfg *currency.Config, verbose bool) (*websocketRoutineManager, error) { if exchangeManager == nil { return nil, errNilExchangeManager } diff --git a/engine/websocketroutine_manager_test.go b/engine/websocketroutine_manager_test.go index 0ca86bbc..34cf56a9 100644 --- a/engine/websocketroutine_manager_test.go +++ b/engine/websocketroutine_manager_test.go @@ -5,7 +5,6 @@ import ( "sync" "testing" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -34,12 +33,12 @@ func TestWebsocketRoutineManagerSetup(t *testing.T) { t.Errorf("error '%v', expected '%v'", err, errNilCurrencyConfig) } - _, err = setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, &config.CurrencyConfig{}, true) + _, err = setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{}, true) if !errors.Is(err, errNilCurrencyPairFormat) { t.Errorf("error '%v', expected '%v'", err, errNilCurrencyPairFormat) } - m, err := setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, &config.CurrencyConfig{}, false) + m, err := setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{}, false) if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } @@ -54,7 +53,7 @@ func TestWebsocketRoutineManagerStart(t *testing.T) { if !errors.Is(err, ErrNilSubsystem) { t.Errorf("error '%v', expected '%v'", err, ErrNilSubsystem) } - cfg := &config.CurrencyConfig{CurrencyPairFormat: &config.CurrencyPairFormatConfig{ + cfg := ¤cy.Config{CurrencyPairFormat: ¤cy.PairFormat{ Uppercase: false, Delimiter: "-", }} @@ -78,7 +77,7 @@ func TestWebsocketRoutineManagerIsRunning(t *testing.T) { t.Error("expected false") } - m, err := setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, &config.CurrencyConfig{}, false) + m, err := setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{}, false) if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } @@ -102,7 +101,7 @@ func TestWebsocketRoutineManagerStop(t *testing.T) { t.Errorf("error '%v', expected '%v'", err, ErrNilSubsystem) } - m, err = setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, &config.CurrencyConfig{}, false) + m, err = setupWebsocketRoutineManager(SetupExchangeManager(), &OrderManager{}, &syncManager{}, ¤cy.Config{}, false) if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } @@ -140,7 +139,7 @@ func TestWebsocketRoutineManagerHandleData(t *testing.T) { if !errors.Is(err, nil) { t.Errorf("error '%v', expected '%v'", err, nil) } - cfg := &config.CurrencyConfig{CurrencyPairFormat: &config.CurrencyPairFormatConfig{ + cfg := ¤cy.Config{CurrencyPairFormat: ¤cy.PairFormat{ Uppercase: false, Delimiter: "-", }} diff --git a/engine/websocketroutine_manager_types.go b/engine/websocketroutine_manager_types.go index 33acb92a..c1fc7b58 100644 --- a/engine/websocketroutine_manager_types.go +++ b/engine/websocketroutine_manager_types.go @@ -4,7 +4,7 @@ import ( "errors" "sync" - "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/currency" ) // websocketRoutineManager is used to process websocket updates from a unified location @@ -14,7 +14,7 @@ type websocketRoutineManager struct { exchangeManager iExchangeManager orderManager iOrderManager syncer iCurrencyPairSyncer - currencyConfig *config.CurrencyConfig + currencyConfig *currency.Config shutdown chan struct{} wg sync.WaitGroup } diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index a1fd153b..ec3afcd0 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -230,7 +230,7 @@ func TestUGetMarkPrice(t *testing.T) { if err != nil { t.Error(err) } - _, err = b.UGetMarkPrice(context.Background(), currency.Pair{}) + _, err = b.UGetMarkPrice(context.Background(), currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -254,7 +254,7 @@ func TestU24HTickerPriceChangeStats(t *testing.T) { if err != nil { t.Error(err) } - _, err = b.U24HTickerPriceChangeStats(context.Background(), currency.Pair{}) + _, err = b.U24HTickerPriceChangeStats(context.Background(), currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -266,7 +266,7 @@ func TestUSymbolPriceTicker(t *testing.T) { if err != nil { t.Error(err) } - _, err = b.USymbolPriceTicker(context.Background(), currency.Pair{}) + _, err = b.USymbolPriceTicker(context.Background(), currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -278,7 +278,7 @@ func TestUSymbolOrderbookTicker(t *testing.T) { if err != nil { t.Error(err) } - _, err = b.USymbolOrderbookTicker(context.Background(), currency.Pair{}) + _, err = b.USymbolOrderbookTicker(context.Background(), currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -370,7 +370,7 @@ func TestUCompositeIndexInfo(t *testing.T) { if err != nil { t.Error(err) } - _, err = b.UCompositeIndexInfo(context.Background(), currency.Pair{}) + _, err = b.UCompositeIndexInfo(context.Background(), currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -489,7 +489,7 @@ func TestUAllAccountOrders(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("skipping test: api keys not set") } - _, err := b.UAllAccountOrders(context.Background(), currency.Pair{}, 0, 0, time.Time{}, time.Time{}) + _, err := b.UAllAccountOrders(context.Background(), currency.EMPTYPAIR, 0, 0, time.Time{}, time.Time{}) if err != nil { t.Error(err) } @@ -592,7 +592,7 @@ func TestUAccountIncomeHistory(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("skipping test: api keys not set") } - _, err := b.UAccountIncomeHistory(context.Background(), currency.Pair{}, "", 5, time.Now().Add(-time.Hour*48), time.Now()) + _, err := b.UAccountIncomeHistory(context.Background(), currency.EMPTYPAIR, "", 5, time.Now().Add(-time.Hour*48), time.Now()) if err != nil { t.Error(err) } @@ -764,7 +764,7 @@ func TestGetFuturesSwapTickerChangeStats(t *testing.T) { if err != nil { t.Error(err) } - _, err = b.GetFuturesSwapTickerChangeStats(context.Background(), currency.Pair{}, "") + _, err = b.GetFuturesSwapTickerChangeStats(context.Background(), currency.EMPTYPAIR, "") if err != nil { t.Error(err) } @@ -810,7 +810,7 @@ func TestGetFuturesSymbolPriceTicker(t *testing.T) { func TestGetFuturesOrderbookTicker(t *testing.T) { t.Parallel() - _, err := b.GetFuturesOrderbookTicker(context.Background(), currency.Pair{}, "") + _, err := b.GetFuturesOrderbookTicker(context.Background(), currency.EMPTYPAIR, "") if err != nil { t.Error(err) } @@ -822,7 +822,7 @@ func TestGetFuturesOrderbookTicker(t *testing.T) { func TestGetFuturesLiquidationOrders(t *testing.T) { t.Parallel() - _, err := b.GetFuturesLiquidationOrders(context.Background(), currency.Pair{}, "", 0, time.Time{}, time.Time{}) + _, err := b.GetFuturesLiquidationOrders(context.Background(), currency.EMPTYPAIR, "", 0, time.Time{}, time.Time{}) if err != nil { t.Error(err) } @@ -1124,7 +1124,7 @@ func TestFuturesIncomeHistory(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("skipping test: api keys not set") } - _, err := b.FuturesIncomeHistory(context.Background(), currency.Pair{}, "TRANSFER", time.Time{}, time.Time{}, 5) + _, err := b.FuturesIncomeHistory(context.Background(), currency.EMPTYPAIR, "TRANSFER", time.Time{}, time.Time{}, 5) if err != nil { t.Error(err) } @@ -1135,7 +1135,7 @@ func TestFuturesForceOrders(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("skipping test: api keys not set") } - _, err := b.FuturesForceOrders(context.Background(), currency.Pair{}, "ADL", time.Time{}, time.Time{}) + _, err := b.FuturesForceOrders(context.Background(), currency.EMPTYPAIR, "ADL", time.Time{}, time.Time{}) if err != nil { t.Error(err) } @@ -1161,7 +1161,7 @@ func TestFuturesPositionsADLEstimate(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("skipping test: api keys not set") } - _, err := b.FuturesPositionsADLEstimate(context.Background(), currency.Pair{}) + _, err := b.FuturesPositionsADLEstimate(context.Background(), currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -1341,7 +1341,7 @@ func TestOpenOrders(t *testing.T) { if !areTestAPIKeysSet() { t.Skip() } - _, err := b.OpenOrders(context.Background(), currency.Pair{}) + _, err := b.OpenOrders(context.Background(), currency.EMPTYPAIR) if err != nil { t.Error(err) } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index e4f089d9..3452153a 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -489,7 +489,7 @@ func (b *Binance) UpdateTickers(ctx context.Context, a asset.Item) error { } } case asset.USDTMarginedFutures: - tick, err := b.U24HTickerPriceChangeStats(ctx, currency.Pair{}) + tick, err := b.U24HTickerPriceChangeStats(ctx, currency.EMPTYPAIR) if err != nil { return err } @@ -516,7 +516,7 @@ func (b *Binance) UpdateTickers(ctx context.Context, a asset.Item) error { } } case asset.CoinMarginedFutures: - tick, err := b.GetFuturesSwapTickerChangeStats(ctx, currency.Pair{}, "") + tick, err := b.GetFuturesSwapTickerChangeStats(ctx, currency.EMPTYPAIR, "") if err != nil { return err } @@ -1323,7 +1323,7 @@ func (b *Binance) GetActiveOrders(ctx context.Context, req *order.GetOrdersReque } if len(req.Pairs) == 0 || len(req.Pairs) >= 40 { // sending an empty currency pair retrieves data for all currencies - req.Pairs = append(req.Pairs, currency.Pair{}) + req.Pairs = append(req.Pairs, currency.EMPTYPAIR) } var orders []order.Detail for i := range req.Pairs { @@ -1832,7 +1832,7 @@ func (b *Binance) GetAvailableTransferChains(ctx context.Context, cryptocurrency func (b *Binance) FormatExchangeCurrency(p currency.Pair, a asset.Item) (currency.Pair, error) { pairFmt, err := b.GetPairFormat(a, true) if err != nil { - return currency.Pair{}, err + return currency.EMPTYPAIR, err } if a == asset.USDTMarginedFutures { return b.formatUSDTMarginedFuturesPair(p, pairFmt), nil diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 2ba2bff5..302d0ed8 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -1148,7 +1148,8 @@ func TestUpdateTicker(t *testing.T) { } func TestUpdateTickers(t *testing.T) { - err := b.UpdateTickers(context.Background(), asset.Spot) + t.Parallel() + err := b.UpdateTickers(context.Background(), asset.PerpetualContract) if err != nil { t.Fatal(err) } diff --git a/exchanges/currencystate/currency_state_test.go b/exchanges/currencystate/currency_state_test.go index de008c53..b8a2ef80 100644 --- a/exchanges/currencystate/currency_state_test.go +++ b/exchanges/currencystate/currency_state_test.go @@ -43,12 +43,12 @@ func TestGetSnapshot(t *testing.T) { func TestCanTradePair(t *testing.T) { t.Parallel() - err := (*States)(nil).CanTradePair(currency.Pair{}, "") + err := (*States)(nil).CanTradePair(currency.EMPTYPAIR, "") if !errors.Is(err, errNilStates) { t.Fatalf("received: %v, but expected: %v", err, errNilStates) } - err = (&States{}).CanTradePair(currency.Pair{}, "") + err = (&States{}).CanTradePair(currency.EMPTYPAIR, "") if !errors.Is(err, errEmptyCurrency) { t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency) } @@ -115,11 +115,11 @@ func TestCanTradePair(t *testing.T) { func TestStatesCanTrade(t *testing.T) { t.Parallel() - err := (*States)(nil).CanTrade(currency.Code{}, "") + err := (*States)(nil).CanTrade(currency.EMPTYCODE, "") if !errors.Is(err, errNilStates) { t.Fatalf("received: %v, but expected: %v", err, errNilStates) } - err = (&States{}).CanTrade(currency.Code{}, "") + err = (&States{}).CanTrade(currency.EMPTYCODE, "") if !errors.Is(err, errEmptyCurrency) { t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency) } @@ -127,11 +127,11 @@ func TestStatesCanTrade(t *testing.T) { func TestStatesCanWithdraw(t *testing.T) { t.Parallel() - err := (*States)(nil).CanWithdraw(currency.Code{}, "") + err := (*States)(nil).CanWithdraw(currency.EMPTYCODE, "") if !errors.Is(err, errNilStates) { t.Fatalf("received: %v, but expected: %v", err, errNilStates) } - err = (&States{}).CanWithdraw(currency.Code{}, "") + err = (&States{}).CanWithdraw(currency.EMPTYCODE, "") if !errors.Is(err, errEmptyCurrency) { t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency) } @@ -161,11 +161,11 @@ func TestStatesCanWithdraw(t *testing.T) { func TestStatesCanDeposit(t *testing.T) { t.Parallel() - err := (*States)(nil).CanDeposit(currency.Code{}, "") + err := (*States)(nil).CanDeposit(currency.EMPTYCODE, "") if !errors.Is(err, errNilStates) { t.Fatalf("received: %v, but expected: %v", err, errNilStates) } - err = (&States{}).CanDeposit(currency.Code{}, "") + err = (&States{}).CanDeposit(currency.EMPTYCODE, "") if !errors.Is(err, errEmptyCurrency) { t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency) } @@ -246,12 +246,12 @@ func TestStatesUpdateAll(t *testing.T) { func TestStatesUpdate(t *testing.T) { t.Parallel() - err := (*States)(nil).Update(currency.Code{}, "", Options{}) + err := (*States)(nil).Update(currency.EMPTYCODE, "", Options{}) if !errors.Is(err, errNilStates) { t.Fatalf("received: %v, but expected: %v", err, errNilStates) } - err = (&States{}).Update(currency.Code{}, "", Options{}) + err = (&States{}).Update(currency.EMPTYCODE, "", Options{}) if !errors.Is(err, errEmptyCurrency) { t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency) } @@ -273,12 +273,12 @@ func TestStatesUpdate(t *testing.T) { func TestStatesGet(t *testing.T) { t.Parallel() - _, err := (*States)(nil).Get(currency.Code{}, "") + _, err := (*States)(nil).Get(currency.EMPTYCODE, "") if !errors.Is(err, errNilStates) { t.Fatalf("received: %v, but expected: %v", err, errNilStates) } - _, err = (&States{}).Get(currency.Code{}, "") + _, err = (&States{}).Get(currency.EMPTYCODE, "") if !errors.Is(err, errEmptyCurrency) { t.Fatalf("received: %v, but expected: %v", err, errEmptyCurrency) } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index c2bae861..013385c0 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -535,7 +535,7 @@ func (b *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType asset.I func (b *Base) FormatExchangeCurrency(p currency.Pair, assetType asset.Item) (currency.Pair, error) { pairFmt, err := b.GetPairFormat(assetType, true) if err != nil { - return currency.Pair{}, err + return currency.EMPTYPAIR, err } return p.Format(pairFmt.Delimiter, pairFmt.Uppercase), nil } diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 80ffd6fc..f6e914ec 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -694,7 +694,7 @@ func TestLoadConfigPairs(t *testing.T) { } p = pairs[2].Format(pFmt.Delimiter, pFmt.Uppercase).String() if p != "xrp/usd" { - t.Error("incorrect value, expected xrp/usd") + t.Error("incorrect value, expected xrp/usd", p) } avail, err = b.GetAvailablePairs(asset.Spot) @@ -708,7 +708,7 @@ func TestLoadConfigPairs(t *testing.T) { } p = format.String() if p != "xrp~usd" { - t.Error("incorrect value, expected xrp~usd") + t.Error("incorrect value, expected xrp~usd", p) } ps, err := b.Config.CurrencyPairs.Get(asset.Spot) if err != nil { @@ -1609,7 +1609,7 @@ func TestUpdatePairs(t *testing.T) { t.Fatal(err) } pairs := currency.Pairs{ - currency.Pair{}, + currency.EMPTYPAIR, p, } err = UAC.UpdatePairs(pairs, asset.Spot, true, true) diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 774974e2..705c8f02 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -506,7 +506,7 @@ func TestGetOrderInfo(t *testing.T) { } _, err := g.GetOrderInfo(context.Background(), - "917591554", currency.Pair{}, asset.Spot) + "917591554", currency.EMPTYPAIR, asset.Spot) if err != nil { if err.Error() != "no order found with id 917591554" && err.Error() != "failed to get open orders" { t.Fatalf("GetOrderInfo() returned an error skipping test: %v", err) diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index b852edca..5a36b1c9 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -101,7 +101,7 @@ func TestStart(t *testing.T) { func TestGetCurrenciesIncludingChains(t *testing.T) { t.Parallel() - r, err := h.GetCurrenciesIncludingChains(context.Background(), currency.Code{}) + r, err := h.GetCurrenciesIncludingChains(context.Background(), currency.EMPTYCODE) if err != nil { t.Error(err) } @@ -119,7 +119,7 @@ func TestGetCurrenciesIncludingChains(t *testing.T) { func TestFGetContractInfo(t *testing.T) { t.Parallel() - _, err := h.FGetContractInfo(context.Background(), "", "", currency.Pair{}) + _, err := h.FGetContractInfo(context.Background(), "", "", currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -136,7 +136,7 @@ func TestFIndexPriceInfo(t *testing.T) { func TestFContractPriceLimitations(t *testing.T) { t.Parallel() _, err := h.FContractPriceLimitations(context.Background(), - "BTC", "next_quarter", currency.Pair{}) + "BTC", "next_quarter", currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -145,7 +145,7 @@ func TestFContractPriceLimitations(t *testing.T) { func TestFContractOpenInterest(t *testing.T) { t.Parallel() _, err := h.FContractOpenInterest(context.Background(), - "BTC", "next_quarter", currency.Pair{}) + "BTC", "next_quarter", currency.EMPTYPAIR) if err != nil { t.Error(err) } @@ -289,7 +289,7 @@ func TestFGetAccountInfo(t *testing.T) { t.Skip("skipping test: api keys not set") } t.Parallel() - _, err := h.FGetAccountInfo(context.Background(), currency.Code{}) + _, err := h.FGetAccountInfo(context.Background(), currency.EMPTYCODE) if err != nil { t.Error(err) } @@ -300,7 +300,7 @@ func TestFGetPositionsInfo(t *testing.T) { t.Skip("skipping test: api keys not set") } t.Parallel() - _, err := h.FGetPositionsInfo(context.Background(), currency.Code{}) + _, err := h.FGetPositionsInfo(context.Background(), currency.EMPTYCODE) if err != nil { t.Error(err) } @@ -311,7 +311,7 @@ func TestFGetAllSubAccountAssets(t *testing.T) { t.Skip("skipping test: api keys not set") } t.Parallel() - _, err := h.FGetAllSubAccountAssets(context.Background(), currency.Code{}) + _, err := h.FGetAllSubAccountAssets(context.Background(), currency.EMPTYCODE) if err != nil { t.Error(err) } @@ -368,7 +368,7 @@ func TestFContractTradingFee(t *testing.T) { t.Skip("skipping test: api keys not set") } t.Parallel() - _, err := h.FContractTradingFee(context.Background(), currency.Code{}) + _, err := h.FContractTradingFee(context.Background(), currency.EMPTYCODE) if err != nil { t.Error(err) } @@ -379,7 +379,7 @@ func TestFGetTransferLimits(t *testing.T) { t.Skip("skipping test: api keys not set") } t.Parallel() - _, err := h.FGetTransferLimits(context.Background(), currency.Code{}) + _, err := h.FGetTransferLimits(context.Background(), currency.EMPTYCODE) if err != nil { t.Error(err) } @@ -390,7 +390,7 @@ func TestFGetPositionLimits(t *testing.T) { t.Skip("skipping test: api keys not set") } t.Parallel() - _, err := h.FGetPositionLimits(context.Background(), currency.Code{}) + _, err := h.FGetPositionLimits(context.Background(), currency.EMPTYCODE) if err != nil { t.Error(err) } @@ -460,7 +460,7 @@ func TestFOrder(t *testing.T) { t.Error(err) } _, err = h.FOrder(context.Background(), - currency.Pair{}, cp.Base.Upper().String(), + currency.EMPTYPAIR, cp.Base.Upper().String(), "quarter", "123", "BUY", "open", "limit", 1, 1, 1) if err != nil { t.Error(err) @@ -542,7 +542,7 @@ func TestFFlashCloseOrder(t *testing.T) { } t.Parallel() _, err := h.FFlashCloseOrder(context.Background(), - currency.Pair{}, "BTC", "quarter", "BUY", "lightning", "", 1) + currency.EMPTYPAIR, "BTC", "quarter", "BUY", "lightning", "", 1) if err != nil { t.Error(err) } @@ -600,7 +600,7 @@ func TestFGetOrderHistory(t *testing.T) { t.Error(err) } _, err = h.FGetOrderHistory(context.Background(), - currency.Pair{}, cp.Base.Upper().String(), + currency.EMPTYPAIR, cp.Base.Upper().String(), "all", "all", "limit", []order.Status{}, 5, 0, 0) @@ -615,7 +615,7 @@ func TestFTradeHistory(t *testing.T) { } t.Parallel() _, err := h.FTradeHistory(context.Background(), - currency.Pair{}, "BTC", "all", 10, 0, 0) + currency.EMPTYPAIR, "BTC", "all", 10, 0, 0) if err != nil { t.Error(err) } @@ -627,7 +627,7 @@ func TestFPlaceTriggerOrder(t *testing.T) { } t.Parallel() _, err := h.FPlaceTriggerOrder(context.Background(), - currency.Pair{}, "EOS", "quarter", "greaterOrEqual", + currency.EMPTYPAIR, "EOS", "quarter", "greaterOrEqual", "limit", "buy", "close", 1.1, 1.05, 5, 2) if err != nil { t.Error(err) @@ -651,7 +651,7 @@ func TestFCancelAllTriggerOrders(t *testing.T) { } t.Parallel() _, err := h.FCancelAllTriggerOrders(context.Background(), - currency.Pair{}, "BTC", "this_week") + currency.EMPTYPAIR, "BTC", "this_week") if err != nil { t.Error(err) } @@ -663,7 +663,7 @@ func TestFQueryTriggerOpenOrders(t *testing.T) { } t.Parallel() _, err := h.FQueryTriggerOpenOrders(context.Background(), - currency.Pair{}, "BTC", 0, 0) + currency.EMPTYPAIR, "BTC", 0, 0) if err != nil { t.Error(err) } @@ -675,7 +675,7 @@ func TestFQueryTriggerOrderHistory(t *testing.T) { } t.Parallel() _, err := h.FQueryTriggerOrderHistory(context.Background(), - currency.Pair{}, "EOS", "all", "all", 10, 0, 0) + currency.EMPTYPAIR, "EOS", "all", "all", 10, 0, 0) if err != nil { t.Error(err) } @@ -1571,7 +1571,7 @@ func TestGetSwapTriggerOrderHistory(t *testing.T) { func TestGetSwapMarkets(t *testing.T) { t.Parallel() - _, err := h.GetSwapMarkets(context.Background(), currency.Pair{}) + _, err := h.GetSwapMarkets(context.Background(), currency.EMPTYPAIR) if err != nil { t.Error(err) } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 7cf52173..ea457912 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -360,7 +360,7 @@ func (h *HUOBI) FetchTradablePairs(ctx context.Context, a asset.Item) ([]string, } case asset.CoinMarginedFutures: - symbols, err := h.GetSwapMarkets(ctx, currency.Pair{}) + symbols, err := h.GetSwapMarkets(ctx, currency.EMPTYPAIR) if err != nil { return nil, err } @@ -375,7 +375,7 @@ func (h *HUOBI) FetchTradablePairs(ctx context.Context, a asset.Item) ([]string, } } case asset.Futures: - symbols, err := h.FGetContractInfo(ctx, "", "", currency.Pair{}) + symbols, err := h.FGetContractInfo(ctx, "", "", currency.EMPTYPAIR) if err != nil { return nil, err } @@ -707,7 +707,7 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac case asset.CoinMarginedFutures: // fetch swap account info - acctInfo, err := h.GetSwapAccountInfo(ctx, currency.Pair{}) + acctInfo, err := h.GetSwapAccountInfo(ctx, currency.EMPTYPAIR) if err != nil { return info, err } @@ -727,14 +727,14 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac }) // fetch subaccounts data - subAccsData, err := h.GetSwapAllSubAccAssets(ctx, currency.Pair{}) + subAccsData, err := h.GetSwapAllSubAccAssets(ctx, currency.EMPTYPAIR) if err != nil { return info, err } var currencyDetails []account.Balance for x := range subAccsData.Data { a, err := h.SwapSingleSubAccAssets(ctx, - currency.Pair{}, + currency.EMPTYPAIR, subAccsData.Data[x].SubUID) if err != nil { return info, err @@ -750,7 +750,7 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac acc.Currencies = currencyDetails case asset.Futures: // fetch main account data - mainAcctData, err := h.FGetAccountInfo(ctx, currency.Code{}) + mainAcctData, err := h.FGetAccountInfo(ctx, currency.EMPTYCODE) if err != nil { return info, err } @@ -770,7 +770,7 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac }) // fetch subaccounts data - subAccsData, err := h.FGetAllSubAccountAssets(ctx, currency.Code{}) + subAccsData, err := h.FGetAllSubAccountAssets(ctx, currency.EMPTYCODE) if err != nil { return info, err } diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index b5cbe68a..dccdc104 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -163,7 +163,7 @@ func TestWrapperGetOrderInfo(t *testing.T) { t.Skip("skipping test: api keys not set") } _, err := k.GetOrderInfo(context.Background(), - "123", currency.Pair{}, asset.Futures) + "123", currency.EMPTYPAIR, asset.Futures) if err != nil { t.Error(err) } @@ -882,7 +882,7 @@ func TestGetOrderInfo(t *testing.T) { } _, err := k.GetOrderInfo(context.Background(), - "OZPTPJ-HVYHF-EDIGXS", currency.Pair{}, asset.Spot) + "OZPTPJ-HVYHF-EDIGXS", currency.EMPTYPAIR, asset.Spot) if !areTestAPIKeysSet() && err == nil { t.Error("Expecting error") } diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 4df70be1..7cf5225c 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -381,7 +381,7 @@ func TestGetOrderInfo(t *testing.T) { t.Skip("API keys required but not set, skipping test") } _, err := l.GetOrderInfo(context.Background(), - "9ead39f5-701a-400b-b635-d7349eb0f6b", currency.Pair{}, asset.Spot) + "9ead39f5-701a-400b-b635-d7349eb0f6b", currency.EMPTYPAIR, asset.Spot) if err != nil { t.Error(err) } diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index 92d6c5eb..0951aab9 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -847,7 +847,7 @@ func (o *OKGroup) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channels[y], - Currency: currency.NewPair(newP.Base, currency.Code{}), + Currency: currency.NewPair(newP.Base, currency.EMPTYCODE), Asset: asset.Futures, }) futuresAccountCodes = append(futuresAccountCodes, newP.Base) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 76e587eb..b7ef4dfe 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -374,7 +374,7 @@ func TestFilterOrdersByCurrencies(t *testing.T) { if len(orders) != 1 { t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) } - currencies = append(currencies, currency.Pair{}) + currencies = append(currencies, currency.EMPTYPAIR) FilterOrdersByCurrencies(&orders, currencies) if len(orders) != 1 { t.Errorf("Orders failed to be filtered. Expected %v, received %v", 1, len(orders)) @@ -718,7 +718,7 @@ func TestUpdateOrderFromModify(t *testing.T) { AssetType: "", Date: time.Time{}, LastUpdated: time.Time{}, - Pair: currency.Pair{}, + Pair: currency.EMPTYPAIR, Trades: nil, } updated := time.Now() @@ -910,7 +910,7 @@ func TestUpdateOrderFromDetail(t *testing.T) { AssetType: "", Date: time.Time{}, LastUpdated: time.Time{}, - Pair: currency.Pair{}, + Pair: currency.EMPTYPAIR, Trades: nil, } updated := time.Now() diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index c3519379..c10d3bbc 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -112,7 +112,9 @@ func (d *Detail) UpdateOrderFromDetail(m *Detail) { d.PostOnly = m.PostOnly updated = true } - if !m.Pair.IsEmpty() && m.Pair != d.Pair { + if !m.Pair.IsEmpty() && !m.Pair.Equal(d.Pair) { + // TODO: Add a check to see if the original pair is empty as well, but + // error if it is changing from BTC-USD -> LTC-USD. d.Pair = m.Pair updated = true } @@ -274,7 +276,9 @@ func (d *Detail) UpdateOrderFromModify(m *Modify) { d.PostOnly = m.PostOnly updated = true } - if !m.Pair.IsEmpty() && m.Pair != d.Pair { + if !m.Pair.IsEmpty() && !m.Pair.Equal(d.Pair) { + // TODO: Add a check to see if the original pair is empty as well, but + // error if it is changing from BTC-USD -> LTC-USD. d.Pair = m.Pair updated = true } diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 2387f659..46b4a0a9 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -303,7 +303,7 @@ func TestDeployDepth(t *testing.T) { if !errors.Is(err, errExchangeNameUnset) { t.Fatalf("expecting %s error but received %v", errExchangeNameUnset, err) } - _, err = DeployDepth("test", currency.Pair{}, asset.Spot) + _, err = DeployDepth("test", currency.EMPTYPAIR, asset.Spot) if !errors.Is(err, errPairNotSet) { t.Fatalf("expecting %s error but received %v", errPairNotSet, err) } @@ -370,7 +370,7 @@ func TestProcessOrderbook(t *testing.T) { } // test for empty pair - base.Pair = currency.Pair{} + base.Pair = currency.EMPTYPAIR err = base.Process() if err == nil { t.Error("empty pair should throw an err") diff --git a/exchanges/poloniex/currency_details.go b/exchanges/poloniex/currency_details.go index 42aedeba..71e7b50f 100644 --- a/exchanges/poloniex/currency_details.go +++ b/exchanges/poloniex/currency_details.go @@ -101,7 +101,7 @@ func (w *CurrencyDetails) GetPair(id float64) (currency.Pair, error) { w.m.RLock() defer w.m.RUnlock() if w.pairs == nil { - return currency.Pair{}, errPairMapIsNil + return currency.EMPTYPAIR, errPairMapIsNil } p, ok := w.pairs[id] @@ -124,13 +124,13 @@ func (w *CurrencyDetails) GetCode(id float64) (currency.Code, error) { w.m.RLock() defer w.m.RUnlock() if w.codes == nil { - return currency.Code{}, errCodeMapIsNil + return currency.EMPTYCODE, errCodeMapIsNil } c, ok := w.codes[id] if ok { return c.Currency, nil } - return currency.Code{}, errIDNotFoundInCodeMap + return currency.EMPTYCODE, errIDNotFoundInCodeMap } // GetWithdrawalTXFee returns withdrawal transaction fee for the currency diff --git a/exchanges/poloniex/currency_details_test.go b/exchanges/poloniex/currency_details_test.go index d5c68584..79989e7c 100644 --- a/exchanges/poloniex/currency_details_test.go +++ b/exchanges/poloniex/currency_details_test.go @@ -35,32 +35,32 @@ func TestWsCurrencyMap(t *testing.T) { t.Fatalf("expected: %v but received: %v", errCodeMapIsNil, err) } - _, err = m.GetWithdrawalTXFee(currency.Code{}) + _, err = m.GetWithdrawalTXFee(currency.EMPTYCODE) if !errors.Is(err, errCodeMapIsNil) { t.Fatalf("expected: %v but received: %v", errCodeMapIsNil, err) } - _, err = m.GetDepositAddress(currency.Code{}) + _, err = m.GetDepositAddress(currency.EMPTYCODE) if !errors.Is(err, errCodeMapIsNil) { t.Fatalf("expected: %v but received: %v", errCodeMapIsNil, err) } - _, err = m.IsWithdrawAndDepositsEnabled(currency.Code{}) + _, err = m.IsWithdrawAndDepositsEnabled(currency.EMPTYCODE) if !errors.Is(err, errCodeMapIsNil) { t.Fatalf("expected: %v but received: %v", errCodeMapIsNil, err) } - _, err = m.IsTradingEnabledForCurrency(currency.Code{}) + _, err = m.IsTradingEnabledForCurrency(currency.EMPTYCODE) if !errors.Is(err, errCodeMapIsNil) { t.Fatalf("expected: %v but received: %v", errCodeMapIsNil, err) } - _, err = m.IsTradingEnabledForPair(currency.Pair{}) + _, err = m.IsTradingEnabledForPair(currency.EMPTYPAIR) if !errors.Is(err, errCodeMapIsNil) { t.Fatalf("expected: %v but received: %v", errCodeMapIsNil, err) } - _, err = m.IsPostOnlyForPair(currency.Pair{}) + _, err = m.IsPostOnlyForPair(currency.EMPTYPAIR) if !errors.Is(err, errCodeMapIsNil) { t.Fatalf("expected: %v but received: %v", errCodeMapIsNil, err) } @@ -178,32 +178,32 @@ func TestWsCurrencyMap(t *testing.T) { t.Fatal("unexpected results") } - _, err = m.GetWithdrawalTXFee(currency.Code{}) + _, err = m.GetWithdrawalTXFee(currency.EMPTYCODE) if !errors.Is(err, errCurrencyNotFoundInMap) { t.Fatalf("expected: %v but received: %v", errCurrencyNotFoundInMap, err) } - _, err = m.GetDepositAddress(currency.Code{}) + _, err = m.GetDepositAddress(currency.EMPTYCODE) if !errors.Is(err, errCurrencyNotFoundInMap) { t.Fatalf("expected: %v but received: %v", errCurrencyNotFoundInMap, err) } - _, err = m.IsWithdrawAndDepositsEnabled(currency.Code{}) + _, err = m.IsWithdrawAndDepositsEnabled(currency.EMPTYCODE) if !errors.Is(err, errCurrencyNotFoundInMap) { t.Fatalf("expected: %v but received: %v", errCurrencyNotFoundInMap, err) } - _, err = m.IsTradingEnabledForCurrency(currency.Code{}) + _, err = m.IsTradingEnabledForCurrency(currency.EMPTYCODE) if !errors.Is(err, errCurrencyNotFoundInMap) { t.Fatalf("expected: %v but received: %v", errCurrencyNotFoundInMap, err) } - _, err = m.IsTradingEnabledForPair(currency.Pair{}) + _, err = m.IsTradingEnabledForPair(currency.EMPTYPAIR) if !errors.Is(err, errCurrencyNotFoundInMap) { t.Fatalf("expected: %v but received: %v", errCurrencyNotFoundInMap, err) } - _, err = m.IsPostOnlyForPair(currency.Pair{}) + _, err = m.IsPostOnlyForPair(currency.EMPTYPAIR) if !errors.Is(err, errCurrencyNotFoundInMap) { t.Fatalf("expected: %v but received: %v", errCurrencyNotFoundInMap, err) } diff --git a/exchanges/stats/stats.go b/exchanges/stats/stats.go index 85b145dc..9668a481 100644 --- a/exchanges/stats/stats.go +++ b/exchanges/stats/stats.go @@ -61,7 +61,7 @@ func Add(exchange string, p currency.Pair, a asset.Item, price, volume float64) return errors.New("cannot add or update, invalid params") } - if p.Base == currency.XBT { + if p.Base.Equal(currency.XBT) { newPair, err := currency.NewPairFromStrings(currency.BTC.String(), p.Quote.String()) if err != nil { @@ -70,7 +70,7 @@ func Add(exchange string, p currency.Pair, a asset.Item, price, volume float64) Append(exchange, newPair, a, price, volume) } - if p.Quote == currency.USDT { + if p.Quote.Equal(currency.USDT) { newPair, err := currency.NewPairFromStrings(p.Base.String(), currency.USD.String()) if err != nil { return err diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 311abca1..e5627806 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -29,7 +29,7 @@ func TestMain(m *testing.M) { var cpyMux *dispatch.Mux func TestSubscribeTicker(t *testing.T) { - _, err := SubscribeTicker("", currency.Pair{}, asset.Item("")) + _, err := SubscribeTicker("", currency.EMPTYPAIR, asset.Item("")) if err == nil { t.Error("error cannot be nil") } diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 0f7d3787..6a0e51de 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -125,7 +125,7 @@ func TestGetOpenOrders(t *testing.T) { func TestGetOrderInfo(t *testing.T) { t.Parallel() _, err := y.GetOrderInfo(context.Background(), - "6196974", currency.Pair{}, asset.Spot) + "6196974", currency.EMPTYPAIR, asset.Spot) if err == nil { t.Error("GetOrderInfo() Expected error") } diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index 8d322585..c691e068 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -971,15 +971,15 @@ func Test_FormatExchangeKlineInterval(t *testing.T) { } func TestValidateCandlesRequest(t *testing.T) { - _, err := z.validateCandlesRequest(currency.Pair{}, "", time.Time{}, time.Time{}, kline.Interval(-1)) + _, err := z.validateCandlesRequest(currency.EMPTYPAIR, "", time.Time{}, time.Time{}, kline.Interval(-1)) if !errors.Is(err, common.ErrDateUnset) { t.Error(err) } - _, err = z.validateCandlesRequest(currency.Pair{}, "", time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC), time.Time{}, kline.Interval(-1)) + _, err = z.validateCandlesRequest(currency.EMPTYPAIR, "", time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC), time.Time{}, kline.Interval(-1)) if !errors.Is(err, common.ErrDateUnset) { t.Error(err) } - _, err = z.validateCandlesRequest(currency.Pair{}, asset.Spot, time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC), time.Date(2020, 1, 1, 1, 1, 1, 3, time.UTC), kline.OneHour) + _, err = z.validateCandlesRequest(currency.EMPTYPAIR, asset.Spot, time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC), time.Date(2020, 1, 1, 1, 1, 1, 3, time.UTC), kline.OneHour) if err != nil && err.Error() != "pair not enabled" { t.Error(err) } diff --git a/gctscript/wrappers/gct/exchange/exchange_test.go b/gctscript/wrappers/gct/exchange/exchange_test.go index 876603ff..ebd19913 100644 --- a/gctscript/wrappers/gct/exchange/exchange_test.go +++ b/gctscript/wrappers/gct/exchange/exchange_test.go @@ -140,7 +140,7 @@ func TestExchange_QueryOrder(t *testing.T) { } t.Parallel() _, err := exchangeTest.QueryOrder(context.Background(), - exchName, orderID, currency.Pair{}, assetType) + exchName, orderID, currency.EMPTYPAIR, assetType) if err != nil { t.Fatal(err) } diff --git a/gctscript/wrappers/validator/validator_test.go b/gctscript/wrappers/validator/validator_test.go index d198d142..76a28eec 100644 --- a/gctscript/wrappers/validator/validator_test.go +++ b/gctscript/wrappers/validator/validator_test.go @@ -99,7 +99,7 @@ func TestWrapper_CancelOrder(t *testing.T) { } _, err = testWrapper.CancelOrder(context.Background(), - exchName, orderID, currency.Pair{}, assetType) + exchName, orderID, currency.EMPTYPAIR, assetType) if err != nil { t.Error(err) } @@ -163,13 +163,13 @@ func TestWrapper_QueryOrder(t *testing.T) { t.Parallel() _, err := testWrapper.QueryOrder(context.Background(), - exchName, orderID, currency.Pair{}, assetType) + exchName, orderID, currency.EMPTYPAIR, assetType) if err != nil { t.Fatal(err) } _, err = testWrapper.QueryOrder(context.Background(), - exchError.String(), "", currency.Pair{}, assetType) + exchError.String(), "", currency.EMPTYPAIR, assetType) if err == nil { t.Fatal("expected QueryOrder to return error on invalid name") } diff --git a/log/logger_setup.go b/log/logger_setup.go index 7f815b7f..47553ef6 100644 --- a/log/logger_setup.go +++ b/log/logger_setup.go @@ -181,4 +181,5 @@ func init() { OrderBook = registerNewSubLogger("ORDERBOOK") Trade = registerNewSubLogger("TRADE") Fill = registerNewSubLogger("FILL") + Currency = registerNewSubLogger("CURRENCY") } diff --git a/log/sublogger_types.go b/log/sublogger_types.go index 76a36d32..885fdea9 100644 --- a/log/sublogger_types.go +++ b/log/sublogger_types.go @@ -35,6 +35,7 @@ var ( OrderBook *SubLogger Trade *SubLogger Fill *SubLogger + Currency *SubLogger ) // SubLogger defines a sub logger can be used externally for packages wanted to diff --git a/main.go b/main.go index 9d3c9eb3..402c2a42 100644 --- a/main.go +++ b/main.go @@ -63,7 +63,7 @@ func main() { flag.BoolVar(&settings.EnableTickerSyncing, "tickersync", true, "enables ticker syncing for all enabled exchanges") flag.BoolVar(&settings.EnableOrderbookSyncing, "orderbooksync", true, "enables orderbook syncing for all enabled exchanges") flag.BoolVar(&settings.EnableTradeSyncing, "tradesync", false, "enables trade syncing for all enabled exchanges") - flag.IntVar(&settings.SyncWorkers, "syncworkers", engine.DefaultSyncerWorkers, "the amount of workers (goroutines) to use for syncing exchange data") + flag.IntVar(&settings.SyncWorkersCount, "syncworkers", engine.DefaultSyncerWorkers, "the amount of workers (goroutines) to use for syncing exchange data") flag.BoolVar(&settings.SyncContinuously, "synccontinuously", true, "whether to sync exchange data continuously (ticker, orderbook and trade history info") flag.DurationVar(&settings.SyncTimeoutREST, "synctimeoutrest", engine.DefaultSyncerTimeoutREST, "the amount of time before the syncer will switch from rest protocol to the streaming protocol (e.g. from REST to websocket)") @@ -73,6 +73,7 @@ func main() { // Forex provider settings flag.BoolVar(&settings.EnableCurrencyConverter, "currencyconverter", false, "overrides config and sets up foreign exchange Currency Converter") flag.BoolVar(&settings.EnableCurrencyLayer, "currencylayer", false, "overrides config and sets up foreign exchange Currency Layer") + flag.BoolVar(&settings.EnableExchangeRates, "exchangerates", false, "overrides config and sets up foreign exchange exchangeratesapi.io") 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") diff --git a/portfolio/banking/banking.go b/portfolio/banking/banking.go index 1b517129..daa556fb 100644 --- a/portfolio/banking/banking.go +++ b/portfolio/banking/banking.go @@ -102,7 +102,7 @@ func (b *Account) ValidateForWithdrawal(exchange string, cur currency.Code) (err err = append(err, ErrCurrencyNotSupportedByAccount) } - if cur.Upper() == currency.AUD { + if cur.Equal(currency.AUD) { if b.BSBNumber == "" { err = append(err, ErrBSBRequiredForAUD) } diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index a5b0bd55..f6b0c05e 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -113,7 +113,7 @@ func (b *Base) GetAddressBalance(address, description string, coinType currency. for x := range b.Addresses { if b.Addresses[x].Address == address && b.Addresses[x].Description == description && - b.Addresses[x].CoinType == coinType { + b.Addresses[x].CoinType.Equal(coinType) { return b.Addresses[x].Balance, true } } @@ -145,7 +145,7 @@ func (b *Base) AddressExists(address string) bool { // associated with the portfolio base func (b *Base) ExchangeAddressExists(exchangeName string, coinType currency.Code) bool { for x := range b.Addresses { - if b.Addresses[x].Address == exchangeName && b.Addresses[x].CoinType == coinType { + if b.Addresses[x].Address == exchangeName && b.Addresses[x].CoinType.Equal(coinType) { return true } } @@ -176,7 +176,7 @@ func (b *Base) UpdateAddressBalance(address string, amount float64) { // RemoveExchangeAddress removes an exchange address from the portfolio. func (b *Base) RemoveExchangeAddress(exchangeName string, coinType currency.Code) { for x := range b.Addresses { - if b.Addresses[x].Address == exchangeName && b.Addresses[x].CoinType == coinType { + if b.Addresses[x].Address == exchangeName && b.Addresses[x].CoinType.Equal(coinType) { b.Addresses = append(b.Addresses[:x], b.Addresses[x+1:]...) return } @@ -187,7 +187,7 @@ func (b *Base) RemoveExchangeAddress(exchangeName string, coinType currency.Code // against correct exchangeName and coinType. func (b *Base) UpdateExchangeAddressBalance(exchangeName string, coinType currency.Code, balance float64) { for x := range b.Addresses { - if b.Addresses[x].Address == exchangeName && b.Addresses[x].CoinType == coinType { + if b.Addresses[x].Address == exchangeName && b.Addresses[x].CoinType.Equal(coinType) { b.Addresses[x].Balance = balance } } @@ -237,7 +237,7 @@ func (b *Base) RemoveAddress(address, description string, coinType currency.Code for x := range b.Addresses { if b.Addresses[x].Address == address && - b.Addresses[x].CoinType == coinType && + b.Addresses[x].CoinType.Equal(coinType) && b.Addresses[x].Description == description { b.Addresses = append(b.Addresses[:x], b.Addresses[x+1:]...) return nil diff --git a/portfolio/portfolio_test.go b/portfolio/portfolio_test.go index 1974c2da..2a6d7cd0 100644 --- a/portfolio/portfolio_test.go +++ b/portfolio/portfolio_test.go @@ -174,7 +174,7 @@ func TestUpdateAddressBalance(t *testing.T) { newBase.UpdateAddressBalance("someaddress", 0.03) value := newBase.GetPortfolioSummary() - if value.Totals[0].Coin != currency.LTC && + if !value.Totals[0].Coin.Equal(currency.LTC) && value.Totals[0].Balance != 0.03 { t.Error("UpdateUpdateAddressBalance error") } @@ -245,7 +245,7 @@ func TestUpdateExchangeAddressBalance(t *testing.T) { b.UpdateExchangeAddressBalance("someaddress", currency.LTC, 0.04) value := b.GetPortfolioSummary() - if value.Totals[0].Coin != currency.LTC && value.Totals[0].Balance != 0.04 { + if !value.Totals[0].Coin.Equal(currency.LTC) && value.Totals[0].Balance != 0.04 { t.Error("incorrect portfolio balance") } } @@ -487,18 +487,18 @@ func TestGetPortfolioSummary(t *testing.T) { getTotalsVal := func(c currency.Code) Coin { for x := range value.Totals { - if value.Totals[x].Coin == c { + if value.Totals[x].Coin.Equal(c) { return value.Totals[x] } } return Coin{} } - if getTotalsVal(currency.LTC).Coin != currency.LTC { + if !getTotalsVal(currency.LTC).Coin.Equal(currency.LTC) { t.Error("mismatched currency") } - if getTotalsVal(currency.ETH).Coin == currency.LTC { + if getTotalsVal(currency.ETH).Coin.Equal(currency.LTC) { t.Error("mismatched currency") } diff --git a/portfolio/withdraw/validate.go b/portfolio/withdraw/validate.go index 99f62463..297cfe8b 100644 --- a/portfolio/withdraw/validate.go +++ b/portfolio/withdraw/validate.go @@ -23,18 +23,18 @@ func (r *Request) Validate(opt ...validate.Checker) (err error) { allErrors = append(allErrors, ErrStrAmountMustBeGreaterThanZero) } - if (r.Currency == currency.Code{}) { + if r.Currency.Equal(currency.EMPTYCODE) { allErrors = append(allErrors, ErrStrNoCurrencySet) } switch r.Type { case Fiat: - if (r.Currency != currency.Code{}) && !r.Currency.IsFiatCurrency() { + if !r.Currency.Equal(currency.EMPTYCODE) && !r.Currency.IsFiatCurrency() { allErrors = append(allErrors, ErrStrCurrencyNotFiat) } allErrors = append(allErrors, r.validateFiat()...) case Crypto: - if (r.Currency != currency.Code{}) && !r.Currency.IsCryptocurrency() { + if !r.Currency.Equal(currency.EMPTYCODE) && !r.Currency.IsCryptocurrency() { allErrors = append(allErrors, ErrStrCurrencyNotCrypto) } allErrors = append(allErrors, r.validateCrypto()...) diff --git a/portfolio/withdraw/validate_test.go b/portfolio/withdraw/validate_test.go index 0984a10c..7fee4a62 100644 --- a/portfolio/withdraw/validate_test.go +++ b/portfolio/withdraw/validate_test.go @@ -92,17 +92,6 @@ var ( Type: Crypto, } - invalidCryptoNonWhiteListedAddressRequest = &Request{ - Exchange: "Binance", - Crypto: CryptoRequest{ - Address: testBTCAddress, - }, - Currency: currency.BTC, - Description: "Test Withdrawal", - Amount: 0.1, - Type: Crypto, - } - invalidType = &Request{ Exchange: "test", Type: Unknown,