From 11da520dc809f5a05903711c37d0c07b8611268c Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 17 Feb 2022 16:24:57 +1100 Subject: [PATCH] Currency: Add additional functionality, refactor and improvements (#881) * currency: Add method to derive pair * currency: Add method to lower entire charset but used the slice copy and returned that. This will change the original, just gotta see if this is an issue, but the slice usually goes out of scope anyway. * currency/pairs: add filter method * currency: add function to derive select currencies from currency pairs * currency/engine: slight adjustments * currency: fix linter issue also shift burden of proof to caller instead of repair, more performant. * currency: more linter * pairs: optimize; reduce allocs/op and B/op * currency: Add in function 'NewPairsFromString' for testing purposes * currency: don't suppress error * currency: stop panic on empty currency code * currency: Add helper method to match currencies between exchanges * currency: fixed my bad spelling * currency: Implement stable coin checks, refactored base code methods, optimized upper and lower case strings for currency code/pairs * currency: add pairs method to derive stable coins from internal list. * Currency: Cleanup, fix tests. * engine/exchanges/currency: fix whoops * Currency: force govet no copy on Item datatype * Currency: fix naughty linter issues * exchange: revert change * currency/config: fix config upgrade mistake * currency: re-implement currency sub-systems * *RetrieveConfigCurrencyPairs removed *CheckCurrencyConfigValues to only provide warnings, add additional support when, disable when support is lost or not available and set default values. *Drop Cryptocurrencies from configuration as this is not needed. *Drop REST Poll delay field as this was unused. *Update default values for currencyFileUpdateDuration & foreignExchangeUpdateDuration. *Allow Role to be marshalled for file type. *Refactor RunUpdater to verify and check config values and set default running foreign exchange provider. * currency: cleanup * currency: change match -> equal for comparison which is more of a standard and little easier to find * currency: address nits * currency: fix whoops * currency: Add some more pairs methods * currency: linter issues * currency: RM unused field * currency: rm verbose * currency: fix word * currency: gocritic * currency: fix another whoopsie * example_config: default to show log system name * Currency: Force all support packages to use Equal method for comparison as there is a small comparison bug when checking upper and lower casing, this has a more of a pronounced impact between exchanges and client instances of currency generation * currency: fix log name * ordermanager: fix potential panic * currency: small optim. * engine: display correct bool and force shutdown * currency: add function and fix regression * Change ConvertCurrency -> ConvertFiat to be more precise * ADD GetForeignExchangeRate to get specific exchange rate for fiat pair * Fix currency display and formatting regression and tied in with config.Currency fields * engine: fix tests * currency: return the amount when no conversion needs to take place * currency: reduce method name * currency: Address nits glorious nits * currency: fix linter * currency: addr nits * currency: check underlying role in test * gct: change to EMPTYCODE and EMPTYPAIR across codebase * currency: fix nits * currency: this fixes test race but this issue has not been resolved. Please see: https://trello.com/c/54eizOIo/143-currency-package-upgrades * currency: Add temp dir for testing * Update engine/engine.go Co-authored-by: Adrian Gallagher * documentation: update and regen * currency: Address niterinos * currency: Add test case for config upgrade when falling over to exchange rate host as default from exchangeRates provider * currency: addr nits * currency: fix whoops Co-authored-by: Ryan O'Hara-Reid Co-authored-by: Adrian Gallagher --- README.md | 10 +- backtester/backtest/backtest.go | 8 +- backtester/eventhandlers/exchange/exchange.go | 2 +- .../eventhandlers/exchange/exchange_test.go | 6 +- .../eventhandlers/portfolio/portfolio_test.go | 4 +- .../statistics/fundingstatistics.go | 4 +- backtester/funding/funding.go | 22 +- backtester/funding/funding_test.go | 8 +- .../trackingcurrencies/trackingcurrencies.go | 6 +- .../trackingcurrencies_test.go | 16 +- cmd/documentation/currency_templates/fx.tmpl | 1 + .../root_templates/root_readme.tmpl | 2 +- .../exchange_wrapper_issues_test.go | 2 +- cmd/exchange_wrapper_issues/main.go | 4 +- cmd/portfolio/portfolio.go | 48 +- cmd/portfolio/portfolio_test.go | 7 - config/config.go | 259 +- config/config_test.go | 112 +- config/config_types.go | 32 +- config_example.json | 2 +- currency/code.go | 186 +- currency/code_test.go | 229 +- currency/code_types.go | 4652 +++++++++++------ currency/coinmarketcap/coinmarketcap.go | 14 +- currency/coinmarketcap/coinmarketcap_test.go | 2 +- currency/coinmarketcap/coinmarketcap_types.go | 2 +- currency/conversion.go | 47 +- currency/currencies.go | 16 +- currency/currency.go | 67 +- currency/currency_test.go | 91 +- currency/currency_types.go | 66 +- currency/forexprovider/README.md | 1 + currency/forexprovider/base/base.go | 13 +- .../currencyconverterapi.go | 1 - .../currencylayer/currencylayer.go | 1 - .../currencylayer/currencylayer_types.go | 3 - .../exchangerate.host/exchangerate.go | 1 - .../exchangeratesapi.io/exchangeratesapi.go | 1 - currency/forexprovider/fixer.io/fixer.go | 6 +- currency/forexprovider/fixer.io/fixer_test.go | 5 - currency/forexprovider/forexprovider.go | 90 +- .../openexchangerates/openexchangerates.go | 1 - currency/manager.go | 10 +- currency/manager_test.go | 8 +- currency/pair.go | 23 +- currency/pair_methods.go | 82 +- currency/pair_test.go | 79 +- currency/pairs.go | 229 +- currency/pairs_test.go | 294 +- currency/storage.go | 426 +- currency/storage_test.go | 243 +- currency/storage_types.go | 20 +- currency/symbol_test.go | 2 +- currency/translation.go | 20 +- currency/translation_test.go | 18 +- engine/currency_state_manager_test.go | 32 +- engine/engine.go | 133 +- engine/engine_test.go | 40 +- engine/engine_types.go | 3 +- engine/helpers.go | 85 +- engine/helpers_test.go | 6 +- engine/order_manager.go | 3 +- engine/order_manager_test.go | 6 +- engine/portfolio_manager.go | 2 +- engine/rpcserver.go | 10 +- engine/sync_manager.go | 139 +- engine/sync_manager_test.go | 40 +- engine/sync_manager_types.go | 24 +- engine/websocketroutine_manager.go | 3 +- engine/websocketroutine_manager_test.go | 13 +- engine/websocketroutine_manager_types.go | 4 +- exchanges/binance/binance_test.go | 28 +- exchanges/binance/binance_wrapper.go | 8 +- exchanges/bitmex/bitmex_test.go | 3 +- .../currencystate/currency_state_test.go | 24 +- exchanges/exchange.go | 2 +- exchanges/exchange_test.go | 6 +- exchanges/gateio/gateio_test.go | 2 +- exchanges/huobi/huobi_test.go | 38 +- exchanges/huobi/huobi_wrapper.go | 14 +- exchanges/kraken/kraken_test.go | 4 +- exchanges/lbank/lbank_test.go | 2 +- exchanges/okgroup/okgroup_websocket.go | 2 +- exchanges/order/order_test.go | 6 +- exchanges/order/orders.go | 8 +- exchanges/orderbook/orderbook_test.go | 4 +- exchanges/poloniex/currency_details.go | 6 +- exchanges/poloniex/currency_details_test.go | 24 +- exchanges/stats/stats.go | 4 +- exchanges/ticker/ticker_test.go | 2 +- exchanges/yobit/yobit_test.go | 2 +- exchanges/zb/zb_test.go | 6 +- .../wrappers/gct/exchange/exchange_test.go | 2 +- .../wrappers/validator/validator_test.go | 6 +- log/logger_setup.go | 1 + log/sublogger_types.go | 1 + main.go | 3 +- portfolio/banking/banking.go | 2 +- portfolio/portfolio.go | 10 +- portfolio/portfolio_test.go | 10 +- portfolio/withdraw/validate.go | 6 +- portfolio/withdraw/validate_test.go | 11 - 102 files changed, 5199 insertions(+), 3095 deletions(-) delete mode 100644 cmd/portfolio/portfolio_test.go 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,