mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
currency: boost retrieval speed when calling currency.NewCode() (#1014)
* currency: optimization pass * currency: reimplement and fix tests and run benchmark comparison * Update currency/code_test.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * glorious: nits Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
164
currency/code.go
164
currency/code.go
@@ -27,6 +27,8 @@ var (
|
||||
errRoleUnset = errors.New("role unset")
|
||||
)
|
||||
|
||||
// String implements the stringer interface and returns a string representation
|
||||
// of the underlying role.
|
||||
func (r Role) String() string {
|
||||
switch r {
|
||||
case Fiat:
|
||||
@@ -89,22 +91,24 @@ func (b *BaseCodes) GetFullCurrencyData() (File, error) {
|
||||
var file File
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
switch b.Items[i].Role {
|
||||
case Unset:
|
||||
file.UnsetCurrency = append(file.UnsetCurrency, b.Items[i])
|
||||
case Fiat:
|
||||
file.FiatCurrency = append(file.FiatCurrency, b.Items[i])
|
||||
case Cryptocurrency:
|
||||
file.Cryptocurrency = append(file.Cryptocurrency, b.Items[i])
|
||||
case Token:
|
||||
file.Token = append(file.Token, b.Items[i])
|
||||
case Contract:
|
||||
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")
|
||||
for _, stored := range b.Items {
|
||||
for x := range stored {
|
||||
switch stored[x].Role {
|
||||
case Unset:
|
||||
file.UnsetCurrency = append(file.UnsetCurrency, stored[x])
|
||||
case Fiat:
|
||||
file.FiatCurrency = append(file.FiatCurrency, stored[x])
|
||||
case Cryptocurrency:
|
||||
file.Cryptocurrency = append(file.Cryptocurrency, stored[x])
|
||||
case Token:
|
||||
file.Token = append(file.Token, stored[x])
|
||||
case Contract:
|
||||
file.Contracts = append(file.Contracts, stored[x])
|
||||
case Stable:
|
||||
file.Stable = append(file.Stable, stored[x])
|
||||
default:
|
||||
return file, errors.New("role undefined")
|
||||
}
|
||||
}
|
||||
}
|
||||
file.LastMainUpdate = b.LastMainUpdate.Unix()
|
||||
@@ -116,47 +120,60 @@ func (b *BaseCodes) GetFullCurrencyData() (File, error) {
|
||||
func (b *BaseCodes) GetCurrencies() Currencies {
|
||||
b.mtx.Lock()
|
||||
currencies := make(Currencies, len(b.Items))
|
||||
for i := range b.Items {
|
||||
currencies[i] = Code{Item: b.Items[i]}
|
||||
var target int
|
||||
for _, stored := range b.Items {
|
||||
if len(stored) == 0 {
|
||||
continue
|
||||
}
|
||||
currencies[target] = Code{Item: stored[0]}
|
||||
target++
|
||||
}
|
||||
b.mtx.Unlock()
|
||||
return 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("cannot update currency %w for %s", errRoleUnset, symbol)
|
||||
func (b *BaseCodes) UpdateCurrency(update *Item) error {
|
||||
if update == nil {
|
||||
return errItemIsNil
|
||||
}
|
||||
|
||||
if update.Symbol == "" {
|
||||
return errSymbolEmpty
|
||||
}
|
||||
|
||||
if update.Role == Unset {
|
||||
return fmt.Errorf("cannot update currency %w for %s", errRoleUnset, update.Symbol)
|
||||
}
|
||||
|
||||
update.Symbol = strings.ToUpper(update.Symbol)
|
||||
update.Lower = strings.ToLower(update.Symbol)
|
||||
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
if b.Items[i].Symbol != symbol || (b.Items[i].Role != Unset && b.Items[i].Role != r) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fullName != "" {
|
||||
b.Items[i].FullName = fullName
|
||||
stored, ok := b.Items[update.Symbol]
|
||||
if ok {
|
||||
for x := range stored {
|
||||
if stored[x].Role != Unset && stored[x].Role != update.Role {
|
||||
continue
|
||||
}
|
||||
|
||||
stored[x].Role = update.Role // NOTE: Update role is checked above.
|
||||
|
||||
if update.FullName != "" {
|
||||
stored[x].FullName = update.FullName
|
||||
}
|
||||
if update.AssocChain != "" {
|
||||
stored[x].AssocChain = update.AssocChain
|
||||
}
|
||||
if update.ID != 0 {
|
||||
stored[x].ID = update.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
b.Items = append(b.Items, &Item{
|
||||
Symbol: symbol,
|
||||
FullName: fullName,
|
||||
Role: r,
|
||||
AssocChain: blockchain,
|
||||
ID: id,
|
||||
})
|
||||
b.Items[update.Symbol] = append(b.Items[update.Symbol], update)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -181,26 +198,30 @@ func (b *BaseCodes) Register(c string, newRole Role) Code {
|
||||
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
if b.Items[i].Symbol != c {
|
||||
continue
|
||||
}
|
||||
|
||||
if newRole != Unset {
|
||||
if b.Items[i].Role == Unset {
|
||||
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: format}
|
||||
if b.Items == nil {
|
||||
b.Items = make(map[string][]*Item)
|
||||
}
|
||||
|
||||
stored, ok := b.Items[c]
|
||||
if ok {
|
||||
for x := range stored {
|
||||
if newRole != Unset {
|
||||
if stored[x].Role != Unset && stored[x].Role != newRole {
|
||||
// This will duplicate item with same name but different
|
||||
// role if not matched in stored list.
|
||||
// TODO: This will need a specific update to NewCode() or
|
||||
// new function to find the exact name and role.
|
||||
continue
|
||||
}
|
||||
stored[x].Role = newRole
|
||||
}
|
||||
return Code{Item: stored[x], UpperCase: format}
|
||||
}
|
||||
}
|
||||
|
||||
newItem := &Item{Symbol: c, Lower: strings.ToLower(c), Role: newRole}
|
||||
b.Items = append(b.Items, newItem)
|
||||
b.Items[c] = append(b.Items[c], newItem)
|
||||
return Code{Item: newItem, UpperCase: format}
|
||||
}
|
||||
|
||||
@@ -219,14 +240,21 @@ func (b *BaseCodes) LoadItem(item *Item) error {
|
||||
|
||||
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) {
|
||||
continue
|
||||
|
||||
stored, ok := b.Items[item.Symbol]
|
||||
if ok {
|
||||
for x := range stored {
|
||||
if stored[x].Role == item.Role {
|
||||
return nil
|
||||
}
|
||||
|
||||
if stored[x].Role == Unset && item.Role != Unset {
|
||||
stored[x].Role = item.Role
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
b.Items = append(b.Items, item)
|
||||
b.Items[item.Symbol] = append(b.Items[item.Symbol], item)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -152,12 +152,16 @@ 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 {
|
||||
stored, ok := b.Items[c.Item.Symbol]
|
||||
if !ok {
|
||||
t.Fatal("code pointer not found")
|
||||
}
|
||||
for x := range stored {
|
||||
if stored[x] != c.Item {
|
||||
continue
|
||||
}
|
||||
if b.Items[x].Role != r {
|
||||
t.Fatal("unexpected role")
|
||||
if stored[x].Role != r {
|
||||
t.Fatalf("unexpected role received: %v but expected: %v", stored[x].Role, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -231,29 +235,42 @@ func TestBaseCode(t *testing.T) {
|
||||
}
|
||||
|
||||
main.Register("XBTUSD", Unset)
|
||||
|
||||
err := main.UpdateCurrency("Bitcoin Perpetual",
|
||||
"XBTUSD",
|
||||
"",
|
||||
0,
|
||||
Contract)
|
||||
err := main.UpdateCurrency(&Item{
|
||||
FullName: "Bitcoin Perpetual",
|
||||
Symbol: "XBTUSD",
|
||||
Role: Contract,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
main.Register("BTC", Unset)
|
||||
err = main.UpdateCurrency("Bitcoin", "BTC", "", 1337, Unset)
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "Bitcoin",
|
||||
Symbol: "BTC",
|
||||
ID: 1337,
|
||||
})
|
||||
if !errors.Is(err, errRoleUnset) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errRoleUnset)
|
||||
}
|
||||
|
||||
err = main.UpdateCurrency("Bitcoin", "BTC", "", 1337, Cryptocurrency)
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "Bitcoin",
|
||||
Symbol: "BTC",
|
||||
ID: 1337,
|
||||
Role: Cryptocurrency,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aud := main.Register("AUD", Unset)
|
||||
err = main.UpdateCurrency("Unreal Dollar", "AUD", "", 1111, Fiat)
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "Unreal Dollar",
|
||||
Symbol: "AUD",
|
||||
ID: 1111,
|
||||
Role: Fiat,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -262,13 +279,23 @@ func TestBaseCode(t *testing.T) {
|
||||
t.Error("Expected fullname to update for AUD")
|
||||
}
|
||||
|
||||
err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat)
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "Australian Dollar",
|
||||
Symbol: "AUD",
|
||||
ID: 1336,
|
||||
Role: Fiat,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aud.Item.Role = Unset
|
||||
err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat)
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "Australian Dollar",
|
||||
Symbol: "AUD",
|
||||
ID: 1336,
|
||||
Role: Fiat,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -277,7 +304,13 @@ func TestBaseCode(t *testing.T) {
|
||||
}
|
||||
|
||||
main.Register("PPT", Unset)
|
||||
err = main.UpdateCurrency("Populous", "PPT", "ETH", 1335, Token)
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "Populous",
|
||||
Symbol: "PPT",
|
||||
AssocChain: "ETH",
|
||||
ID: 1335,
|
||||
Role: Token,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -377,35 +410,48 @@ func TestBaseCode(t *testing.T) {
|
||||
t.Error("Expected 'Role undefined'")
|
||||
}
|
||||
|
||||
main.Items[0].FullName = "Hello"
|
||||
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Fiat)
|
||||
main.Items["CATS"][0].FullName = "Hello"
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "MEWOW",
|
||||
Symbol: "CATS",
|
||||
ID: 1338,
|
||||
Role: Fiat,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if main.Items[0].FullName != "MEWOW" {
|
||||
if main.Items["CATS"][0].FullName != "MEWOW" {
|
||||
t.Error("Fullname not updated")
|
||||
}
|
||||
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Fiat)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = main.UpdateCurrency("WOWCATS", "CATS", "", 3, Fiat)
|
||||
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "WOWCATS",
|
||||
Symbol: "CATS",
|
||||
ID: 3,
|
||||
Role: Fiat,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Creates a new item under a different currency role
|
||||
if main.Items[0].ID != 3 {
|
||||
if main.Items["CATS"][0].ID != 3 {
|
||||
t.Error("ID not updated")
|
||||
}
|
||||
|
||||
main.Items[0].Role = Unset
|
||||
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency)
|
||||
main.Items["CATS"][0].Role = Unset
|
||||
|
||||
err = main.UpdateCurrency(&Item{
|
||||
FullName: "MEWOW",
|
||||
Symbol: "CATS",
|
||||
ID: 1338,
|
||||
Role: Cryptocurrency,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if main.Items[0].ID != 1338 {
|
||||
if main.Items["CATS"][0].ID != 1338 {
|
||||
t.Error("ID not updated")
|
||||
}
|
||||
}
|
||||
@@ -507,29 +553,22 @@ func TestCodeMarshalJSON(t *testing.T) {
|
||||
|
||||
func TestIsFiatCurrency(t *testing.T) {
|
||||
if EMPTYCODE.IsFiatCurrency() {
|
||||
t.Errorf("TestIsFiatCurrency cannot match currency, %s.",
|
||||
EMPTYCODE)
|
||||
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", EMPTYCODE)
|
||||
}
|
||||
if !USD.IsFiatCurrency() {
|
||||
t.Errorf(
|
||||
"TestIsFiatCurrency cannot match currency, %s.", USD)
|
||||
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", USD)
|
||||
}
|
||||
if !CNY.IsFiatCurrency() {
|
||||
t.Errorf(
|
||||
"TestIsFiatCurrency cannot match currency, %s.", CNY)
|
||||
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", CNY)
|
||||
}
|
||||
if LINO.IsFiatCurrency() {
|
||||
t.Errorf(
|
||||
"TestIsFiatCurrency cannot match currency, %s.", LINO,
|
||||
)
|
||||
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", LINO)
|
||||
}
|
||||
if USDT.IsFiatCurrency() {
|
||||
t.Errorf(
|
||||
"TestIsFiatCurrency cannot match currency, %s.", USD)
|
||||
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", USDT)
|
||||
}
|
||||
if DAI.IsFiatCurrency() {
|
||||
t.Errorf(
|
||||
"TestIsFiatCurrency cannot match currency, %s.", USD)
|
||||
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", DAI)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,3 +634,13 @@ func TestItemString(t *testing.T) {
|
||||
&newItem)
|
||||
}
|
||||
}
|
||||
|
||||
// 28848025 40.84 ns/op 8 B/op 1 allocs/op // Current
|
||||
//
|
||||
// 546290 2192 ns/op 8 B/op 1 allocs/op // Previous
|
||||
func BenchmarkNewCode(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for x := 0; x < b.N; x++ {
|
||||
_ = NewCode("someCode")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type Role uint8
|
||||
|
||||
// BaseCodes defines a basket of bare currency codes
|
||||
type BaseCodes struct {
|
||||
Items []*Item
|
||||
Items map[string][]*Item
|
||||
LastMainUpdate time.Time
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
@@ -228,7 +228,14 @@ func (s *Storage) SetupConversionRates() {
|
||||
// to the running list
|
||||
func (s *Storage) SetDefaultFiatCurrencies(c Currencies) error {
|
||||
for i := range c {
|
||||
err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Fiat)
|
||||
err := s.currencyCodes.UpdateCurrency(&Item{
|
||||
ID: c[i].Item.ID,
|
||||
FullName: c[i].Item.FullName,
|
||||
Symbol: c[i].Item.Symbol,
|
||||
Lower: c[i].Item.Lower,
|
||||
Role: Fiat,
|
||||
AssocChain: c[i].Item.AssocChain,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -242,7 +249,14 @@ func (s *Storage) SetDefaultFiatCurrencies(c Currencies) error {
|
||||
// list
|
||||
func (s *Storage) SetStableCoins(c Currencies) error {
|
||||
for i := range c {
|
||||
err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Stable)
|
||||
err := s.currencyCodes.UpdateCurrency(&Item{
|
||||
ID: c[i].Item.ID,
|
||||
FullName: c[i].Item.FullName,
|
||||
Symbol: c[i].Item.Symbol,
|
||||
Lower: c[i].Item.Lower,
|
||||
Role: Stable,
|
||||
AssocChain: c[i].Item.AssocChain,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -255,11 +269,14 @@ func (s *Storage) SetStableCoins(c Currencies) error {
|
||||
// it to the running list
|
||||
func (s *Storage) SetDefaultCryptocurrencies(c Currencies) error {
|
||||
for i := range c {
|
||||
err := s.currencyCodes.UpdateCurrency("",
|
||||
c[i].String(),
|
||||
"",
|
||||
0,
|
||||
Cryptocurrency)
|
||||
err := s.currencyCodes.UpdateCurrency(&Item{
|
||||
ID: c[i].Item.ID,
|
||||
FullName: c[i].Item.FullName,
|
||||
Symbol: c[i].Item.Symbol,
|
||||
Lower: c[i].Item.Lower,
|
||||
Role: Cryptocurrency,
|
||||
AssocChain: c[i].Item.AssocChain,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -464,33 +481,29 @@ func (s *Storage) LoadFileCurrencyData(f *File) error {
|
||||
|
||||
// UpdateCurrencies updates currency role and information using coin market cap
|
||||
func (s *Storage) UpdateCurrencies() error {
|
||||
m, err := s.currencyAnalysis.GetCryptocurrencyIDMap()
|
||||
currencyUpdates, err := s.currencyAnalysis.GetCryptocurrencyIDMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for x := range m {
|
||||
if m[x].IsActive != 1 {
|
||||
for x := range currencyUpdates {
|
||||
if currencyUpdates[x].IsActive != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if m[x].Platform.Symbol != "" {
|
||||
err = s.currencyCodes.UpdateCurrency(m[x].Name,
|
||||
m[x].Symbol,
|
||||
m[x].Platform.Symbol,
|
||||
m[x].ID,
|
||||
Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
update := &Item{
|
||||
FullName: currencyUpdates[x].Name,
|
||||
Symbol: currencyUpdates[x].Symbol,
|
||||
AssocChain: currencyUpdates[x].Platform.Symbol,
|
||||
ID: currencyUpdates[x].ID,
|
||||
Role: Cryptocurrency,
|
||||
}
|
||||
|
||||
err = s.currencyCodes.UpdateCurrency(m[x].Name,
|
||||
m[x].Symbol,
|
||||
"",
|
||||
m[x].ID,
|
||||
Cryptocurrency)
|
||||
if currencyUpdates[x].Platform.Symbol != "" {
|
||||
update.Role = Token
|
||||
}
|
||||
|
||||
err = s.currencyCodes.UpdateCurrency(update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user