maps: expansion of Key concept (#1349)

* moves everything to use single map keys, also breaks

* full rollout

* tests

* fix a little bug

* minor test fixups

* Fix Key use

* rm 🔑 from 🔑 struct name
This commit is contained in:
Scott
2023-10-04 09:19:41 +10:00
committed by GitHub
parent 033a72b61a
commit 91d699be9d
47 changed files with 1478 additions and 1339 deletions

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -95,7 +96,6 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
return Holdings{}, fmt.Errorf("%s %s %w", exch, assetType, errExchangeHoldingsNotFound)
}
var accountsHoldings []SubAccount
subAccountHoldings, ok := accounts.SubAccounts[*creds]
if !ok {
return Holdings{}, fmt.Errorf("%s %s %s %w",
@@ -105,44 +105,39 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
errNoCredentialBalances)
}
for subAccount, assetHoldings := range subAccountHoldings {
for ai, currencyHoldings := range assetHoldings {
if ai != assetType {
continue
}
var currencyBalances = make([]Balance, len(currencyHoldings))
target := 0
for item, balance := range currencyHoldings {
balance.m.Lock()
currencyBalances[target] = Balance{
Currency: currency.Code{Item: item, UpperCase: true},
Total: balance.total,
Hold: balance.hold,
Free: balance.free,
AvailableWithoutBorrow: balance.availableWithoutBorrow,
Borrowed: balance.borrowed,
}
balance.m.Unlock()
target++
}
if len(currencyBalances) == 0 {
continue
}
cpy := *creds
if cpy.SubAccount == "" {
cpy.SubAccount = subAccount
}
accountsHoldings = append(accountsHoldings, SubAccount{
Credentials: Protected{creds: cpy},
ID: subAccount,
AssetType: ai,
Currencies: currencyBalances,
})
break
var currencyBalances = make([]Balance, 0, len(subAccountHoldings))
accountsHoldings := make([]SubAccount, 0, len(subAccountHoldings))
for mapKey, assetHoldings := range subAccountHoldings {
if mapKey.Asset != assetType {
continue
}
assetHoldings.m.Lock()
currencyBalances = append(currencyBalances, Balance{
Currency: currency.Code{Item: mapKey.Currency, UpperCase: true},
Total: assetHoldings.total,
Hold: assetHoldings.hold,
Free: assetHoldings.free,
AvailableWithoutBorrow: assetHoldings.availableWithoutBorrow,
Borrowed: assetHoldings.borrowed,
})
assetHoldings.m.Unlock()
if len(currencyBalances) == 0 {
continue
}
cpy := *creds
if cpy.SubAccount == "" {
cpy.SubAccount = mapKey.SubAccount
}
accountsHoldings = append(accountsHoldings, SubAccount{
Credentials: Protected{creds: cpy},
ID: mapKey.SubAccount,
AssetType: mapKey.Asset,
Currencies: currencyBalances,
})
break
}
if len(accountsHoldings) == 0 {
@@ -187,22 +182,14 @@ func GetBalance(exch, subAccount string, creds *Credentials, ai asset.Item, c cu
exch, creds, errNoCredentialBalances)
}
assetBalances, ok := subAccounts[subAccount]
if !ok {
return nil, fmt.Errorf("%s %s %w",
exch, subAccount, errNoExchangeSubAccountBalances)
}
currencyBalances, ok := assetBalances[ai]
if !ok {
return nil, fmt.Errorf("%s %s %s %w",
exch, subAccount, ai, errAssetHoldingsNotFound)
}
bal, ok := currencyBalances[c.Item]
bal, ok := subAccounts[key.SubAccountCurrencyAsset{
SubAccount: subAccount,
Currency: c.Item,
Asset: ai,
}]
if !ok {
return nil, fmt.Errorf("%s %s %s %s %w",
exch, subAccount, ai, c, errNoBalanceFound)
exch, subAccount, ai, c, errNoExchangeSubAccountBalances)
}
return bal, nil
}
@@ -232,7 +219,7 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error {
}
accounts = &Accounts{
ID: id,
SubAccounts: make(map[Credentials]map[string]map[asset.Item]map[*currency.Item]*ProtectedBalance),
SubAccounts: make(map[Credentials]map[key.SubAccountCurrencyAsset]*ProtectedBalance),
}
s.exchangeAccounts[exch] = accounts
}
@@ -257,34 +244,28 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error {
}
incoming.Accounts[x].Credentials.creds = cpy
var subAccounts map[string]map[asset.Item]map[*currency.Item]*ProtectedBalance
var subAccounts map[key.SubAccountCurrencyAsset]*ProtectedBalance
subAccounts, ok = accounts.SubAccounts[*creds]
if !ok {
subAccounts = make(map[string]map[asset.Item]map[*currency.Item]*ProtectedBalance)
subAccounts = make(map[key.SubAccountCurrencyAsset]*ProtectedBalance)
accounts.SubAccounts[*creds] = subAccounts
}
var accountAssets map[asset.Item]map[*currency.Item]*ProtectedBalance
accountAssets, ok = subAccounts[incoming.Accounts[x].ID]
if !ok {
accountAssets = make(map[asset.Item]map[*currency.Item]*ProtectedBalance)
for y := range incoming.Accounts[x].Currencies {
// Note: Sub accounts are case sensitive and an account "name" is
// different to account "naMe".
subAccounts[incoming.Accounts[x].ID] = accountAssets
}
var currencyBalances map[*currency.Item]*ProtectedBalance
currencyBalances, ok = accountAssets[incoming.Accounts[x].AssetType]
if !ok {
currencyBalances = make(map[*currency.Item]*ProtectedBalance)
accountAssets[incoming.Accounts[x].AssetType] = currencyBalances
}
for y := range incoming.Accounts[x].Currencies {
bal := currencyBalances[incoming.Accounts[x].Currencies[y].Currency.Item]
if bal == nil {
bal, ok := subAccounts[key.SubAccountCurrencyAsset{
SubAccount: incoming.Accounts[x].ID,
Currency: incoming.Accounts[x].Currencies[y].Currency.Item,
Asset: incoming.Accounts[x].AssetType,
}]
if !ok || bal == nil {
bal = &ProtectedBalance{}
currencyBalances[incoming.Accounts[x].Currencies[y].Currency.Item] = bal
subAccounts[key.SubAccountCurrencyAsset{
SubAccount: incoming.Accounts[x].ID,
Currency: incoming.Accounts[x].Currencies[y].Currency.Item,
Asset: incoming.Accounts[x].AssetType,
}] = bal
}
bal.load(incoming.Accounts[x].Currencies[y])
}

View File

@@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -292,13 +293,8 @@ func TestGetBalance(t *testing.T) {
}
_, err = GetBalance("bruh", "1337", happyCredentials, asset.Futures, currency.BTC)
if !errors.Is(err, errAssetHoldingsNotFound) {
t.Fatalf("received: '%v' but expected: '%v'", err, errAssetHoldingsNotFound)
}
_, err = GetBalance("bruh", "1337", happyCredentials, asset.Spot, currency.BTC)
if !errors.Is(err, errNoBalanceFound) {
t.Fatalf("received: '%v' but expected: '%v'", err, errNoBalanceFound)
if !errors.Is(err, errNoExchangeSubAccountBalances) {
t.Fatalf("received: '%v' but expected: '%v'", err, errNoExchangeSubAccountBalances)
}
err = Process(&Holdings{
@@ -473,7 +469,11 @@ func TestUpdate(t *testing.T) {
t.Fatal("account should be loaded")
}
b, ok := acc.SubAccounts[Credentials{Key: "AAAAA"}]["1337"][asset.Spot][currency.BTC.Item]
b, ok := acc.SubAccounts[Credentials{Key: "AAAAA"}][key.SubAccountCurrencyAsset{
SubAccount: "1337",
Currency: currency.BTC.Item,
Asset: asset.Spot,
}]
if !ok {
t.Fatal("account should be loaded")
}

View File

@@ -5,6 +5,7 @@ import (
"sync"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/alert"
@@ -32,7 +33,7 @@ type Accounts struct {
// TODO: Credential tracker to match to keys that are managed and return
// pointer.
// TODO: Have different cred struct for centralized verse DEFI exchanges.
SubAccounts map[Credentials]map[string]map[asset.Item]map[*currency.Item]*ProtectedBalance
SubAccounts map[Credentials]map[key.SubAccountCurrencyAsset]*ProtectedBalance
}
// Holdings is a generic type to hold each exchange's holdings for all enabled

View File

@@ -12,6 +12,8 @@ var (
ErrNotSupported = errors.New("unsupported asset type")
// ErrNotEnabled is an error for an asset not enabled
ErrNotEnabled = errors.New("asset type not enabled")
// ErrInvalidAsset is returned when the assist isn't valid
ErrInvalidAsset = errors.New("asset is invalid")
)
// Item stores the asset type

View File

@@ -619,7 +619,7 @@ func TestGetHistoricCandles(t *testing.T) {
if err != nil {
t.Fatal(err)
}
startTime := time.Now().AddDate(0, -2, 0)
startTime := time.Now().AddDate(0, -1, 0)
_, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, startTime, time.Now())
if err != nil {
t.Fatal(err)

View File

@@ -46,6 +46,9 @@ const (
)
var (
// ErrExchangeNameIsEmpty is returned when the exchange name is empty
ErrExchangeNameIsEmpty = errors.New("exchange name is empty")
errEndpointStringNotFound = errors.New("endpoint string not found")
errConfigPairFormatRequiresDelimiter = errors.New("config pair format requires delimiter")
errSymbolCannotBeMatched = errors.New("symbol cannot be matched")

View File

@@ -10,6 +10,7 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
@@ -20,7 +21,7 @@ import (
// to track futures orders
func SetupPositionController() PositionController {
return PositionController{
multiPositionTrackers: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker),
multiPositionTrackers: make(map[key.ExchangePairAsset]*MultiPositionTracker),
}
}
@@ -41,24 +42,14 @@ func (c *PositionController) TrackNewOrder(d *order.Detail) error {
}
c.m.Lock()
defer c.m.Unlock()
exchMap, ok := c.multiPositionTrackers[d.Exchange]
exchMap, ok := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: d.Exchange,
Base: d.Pair.Base.Item,
Quote: d.Pair.Quote.Item,
Asset: d.AssetType,
}]
if !ok {
exchMap = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[d.Exchange] = exchMap
}
itemMap, ok := exchMap[d.AssetType]
if !ok {
itemMap = make(map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
exchMap[d.AssetType] = itemMap
}
baseMap, ok := itemMap[d.Pair.Base.Item]
if !ok {
baseMap = make(map[*currency.Item]*MultiPositionTracker)
itemMap[d.Pair.Base.Item] = baseMap
}
quoteMap, ok := baseMap[d.Pair.Quote.Item]
if !ok {
quoteMap, err = SetupMultiPositionTracker(&MultiPositionTrackerSetup{
exchMap, err = SetupMultiPositionTracker(&MultiPositionTrackerSetup{
Exchange: d.Exchange,
Asset: d.AssetType,
Pair: d.Pair,
@@ -67,9 +58,14 @@ func (c *PositionController) TrackNewOrder(d *order.Detail) error {
if err != nil {
return err
}
baseMap[d.Pair.Quote.Item] = quoteMap
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: d.Exchange,
Base: d.Pair.Base.Item,
Quote: d.Pair.Quote.Item,
Asset: d.AssetType,
}] = exchMap
}
err = quoteMap.TrackNewOrder(d)
err = exchMap.TrackNewOrder(d)
if err != nil {
return err
}
@@ -91,7 +87,12 @@ func (c *PositionController) SetCollateralCurrency(exch string, item asset.Item,
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return fmt.Errorf("%w no open position for %v %v %v", ErrPositionNotFound, exch, item, pair)
}
@@ -119,7 +120,12 @@ func (c *PositionController) GetPositionsForExchange(exch string, item asset.Ite
}
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return nil, fmt.Errorf("%w no open position for %v %v %v", ErrPositionNotFound, exch, item, pair)
}
@@ -142,7 +148,12 @@ func (c *PositionController) TrackFundingDetails(d *fundingrate.Rates) error {
}
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[d.Exchange][d.Asset][d.Pair.Base.Item][d.Pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: d.Exchange,
Base: d.Pair.Base.Item,
Quote: d.Pair.Quote.Item,
Asset: d.Asset,
}]
if tracker == nil {
return fmt.Errorf("%w no open position for %v %v %v", ErrPositionNotFound, d.Exchange, d.Asset, d.Pair)
}
@@ -177,7 +188,12 @@ func (c *PositionController) GetOpenPosition(exch string, item asset.Item, pair
}
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return nil, fmt.Errorf("%w no open position for %v %v %v", ErrPositionNotFound, exch, item, pair)
}
@@ -200,19 +216,13 @@ func (c *PositionController) GetAllOpenPositions() ([]Position, error) {
c.m.Lock()
defer c.m.Unlock()
var openPositions []Position
for _, exchMap := range c.multiPositionTrackers {
for _, itemMap := range exchMap {
for _, baseMap := range itemMap {
for _, multiPositionTracker := range baseMap {
positions := multiPositionTracker.GetPositions()
for i := range positions {
if positions[i].Status.IsInactive() {
continue
}
openPositions = append(openPositions, positions[i])
}
}
for _, multiPositionTracker := range c.multiPositionTrackers {
positions := multiPositionTracker.GetPositions()
for i := range positions {
if positions[i].Status.IsInactive() {
continue
}
openPositions = append(openPositions, positions[i])
}
}
if len(openPositions) == 0 {
@@ -235,7 +245,12 @@ func (c *PositionController) UpdateOpenPositionUnrealisedPNL(exch string, item a
}
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return decimal.Zero, fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionNotFound)
}
@@ -327,7 +342,12 @@ func (c *PositionController) ClearPositionsForExchange(exch string, item asset.I
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionNotFound)
}
@@ -345,7 +365,12 @@ func (c *PositionController) ClearPositionsForExchange(exch string, item asset.I
if err != nil {
return err
}
c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item] = newMPT
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}] = newMPT
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
@@ -601,14 +602,23 @@ func TestGetPositionsForExchange(t *testing.T) {
if len(pos) != 0 {
t.Error("expected zero")
}
c.multiPositionTrackers = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange] = nil
c.multiPositionTrackers = make(map[key.ExchangePairAsset]*MultiPositionTracker)
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = nil
_, err = c.GetPositionsForExchange(testExchange, asset.Futures, p)
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
}
c.multiPositionTrackers[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures] = nil
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = nil
_, err = c.GetPositionsForExchange(testExchange, asset.Futures, p)
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
@@ -618,9 +628,12 @@ func TestGetPositionsForExchange(t *testing.T) {
t.Errorf("received '%v' expected '%v", err, ErrNotFuturesAsset)
}
c.multiPositionTrackers[testExchange][asset.Futures] = make(map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item] = make(map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = &MultiPositionTracker{
exchange: testExchange,
}
@@ -631,7 +644,12 @@ func TestGetPositionsForExchange(t *testing.T) {
if len(pos) != 0 {
t.Fatal("expected zero")
}
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = &MultiPositionTracker{
exchange: testExchange,
positions: []*PositionTracker{
{
@@ -669,29 +687,23 @@ func TestClearPositionsForExchange(t *testing.T) {
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
}
c.multiPositionTrackers = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange] = nil
err = c.ClearPositionsForExchange(testExchange, asset.Futures, p)
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
}
c.multiPositionTrackers[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures] = nil
c.multiPositionTrackers = make(map[key.ExchangePairAsset]*MultiPositionTracker)
err = c.ClearPositionsForExchange(testExchange, asset.Futures, p)
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
}
err = c.ClearPositionsForExchange(testExchange, asset.Spot, p)
if !errors.Is(err, ErrNotFuturesAsset) {
t.Errorf("received '%v' expected '%v", err, ErrNotFuturesAsset)
}
c.multiPositionTrackers[testExchange][asset.Futures] = make(map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item] = make(map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
exchange: testExchange,
}
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = &MultiPositionTracker{
exchange: testExchange,
underlying: currency.DOGE,
positions: []*PositionTracker{
@@ -704,7 +716,12 @@ func TestClearPositionsForExchange(t *testing.T) {
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v", err, nil)
}
if len(c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item].positions) != 0 {
if len(c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}].positions) != 0 {
t.Fatal("expected 0")
}
c = nil
@@ -940,46 +957,36 @@ func TestUpdateOpenPositionUnrealisedPNL(t *testing.T) {
func TestSetCollateralCurrency(t *testing.T) {
t.Parallel()
var expectedError = errExchangeNameEmpty
pc := SetupPositionController()
err := pc.SetCollateralCurrency("", asset.Spot, currency.EMPTYPAIR, currency.Code{})
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, errExchangeNameEmpty) {
t.Errorf("received '%v' expected '%v", err, errExchangeNameEmpty)
}
expectedError = ErrNotFuturesAsset
err = pc.SetCollateralCurrency("hi", asset.Spot, currency.EMPTYPAIR, currency.Code{})
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, ErrNotFuturesAsset) {
t.Errorf("received '%v' expected '%v", err, ErrNotFuturesAsset)
}
p := currency.NewPair(currency.BTC, currency.USDT)
pc.multiPositionTrackers = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
pc.multiPositionTrackers = make(map[key.ExchangePairAsset]*MultiPositionTracker)
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
expectedError = ErrPositionNotFound
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
}
pc.multiPositionTrackers["hi"] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, ErrPositionNotFound) {
t.Fatalf("received '%v' expected '%v", err, ErrPositionNotFound)
}
pc.multiPositionTrackers["hi"][asset.Futures] = make(map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, ErrPositionNotFound) {
t.Fatalf("received '%v' expected '%v", err, ErrPositionNotFound)
}
expectedError = ErrPositionNotFound
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item] = make(map[*currency.Item]*MultiPositionTracker)
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item] = nil
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
mapKey := key.ExchangePairAsset{
Exchange: "hi",
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
pc.multiPositionTrackers[mapKey] = &MultiPositionTracker{
exchange: "hi",
asset: asset.Futures,
pair: p,
@@ -1000,34 +1007,30 @@ func TestSetCollateralCurrency(t *testing.T) {
}
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
expectedError = nil
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v", err, nil)
}
if !pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].collateralCurrency.Equal(currency.DOGE) {
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].collateralCurrency, currency.DOGE)
if !pc.multiPositionTrackers[mapKey].collateralCurrency.Equal(currency.DOGE) {
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers[mapKey].collateralCurrency, currency.DOGE)
}
if !pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].positions[0].collateralCurrency.Equal(currency.DOGE) {
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].positions[0].collateralCurrency, currency.DOGE)
if !pc.multiPositionTrackers[mapKey].positions[0].collateralCurrency.Equal(currency.DOGE) {
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers[mapKey].positions[0].collateralCurrency, currency.DOGE)
}
var nilPC *PositionController
err = nilPC.SetCollateralCurrency("hi", asset.Spot, currency.EMPTYPAIR, currency.Code{})
expectedError = common.ErrNilPointer
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, common.ErrNilPointer) {
t.Errorf("received '%v' expected '%v", err, common.ErrNilPointer)
}
}
func TestMPTUpdateOpenPositionUnrealisedPNL(t *testing.T) {
t.Parallel()
var err, expectedError error
expectedError = nil
p := currency.NewPair(currency.BTC, currency.USDT)
pc := SetupPositionController()
err = pc.TrackNewOrder(&order.Detail{
err := pc.TrackNewOrder(&order.Detail{
Date: time.Now(),
Exchange: "hi",
Pair: p,
@@ -1037,30 +1040,35 @@ func TestMPTUpdateOpenPositionUnrealisedPNL(t *testing.T) {
Price: 1,
Amount: 1,
})
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v", err, nil)
}
result, err := pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
mapKey := key.ExchangePairAsset{
Exchange: "hi",
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}
result, err := pc.multiPositionTrackers[mapKey].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v", err, nil)
}
if result.Equal(decimal.NewFromInt(1337)) {
t.Error("")
}
expectedError = ErrPositionClosed
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].positions[0].status = order.Closed
_, err = pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
pc.multiPositionTrackers[mapKey].positions[0].status = order.Closed
_, err = pc.multiPositionTrackers[mapKey].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, ErrPositionClosed) {
t.Fatalf("received '%v' expected '%v", err, ErrPositionClosed)
}
expectedError = ErrPositionNotFound
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].positions = nil
_, err = pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
pc.multiPositionTrackers[mapKey].positions = nil
_, err = pc.multiPositionTrackers[mapKey].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, ErrPositionNotFound) {
t.Fatalf("received '%v' expected '%v", err, ErrPositionNotFound)
}
}
@@ -1305,8 +1313,16 @@ func TestPCTrackFundingDetails(t *testing.T) {
Payment: decimal.NewFromInt(1337),
},
}
pc.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item].orderPositions["lol"].openingDate = tn.Add(-time.Hour)
pc.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item].orderPositions["lol"].lastUpdated = tn
mapKey := key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}
pc.multiPositionTrackers[mapKey].orderPositions["lol"].openingDate = tn.Add(-time.Hour)
pc.multiPositionTrackers[mapKey].orderPositions["lol"].lastUpdated = tn
err = pc.TrackFundingDetails(rates)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v", err, nil)

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/collateral"
@@ -85,7 +86,7 @@ type TotalCollateralResponse struct {
// the position controller and its all tracked happily
type PositionController struct {
m sync.Mutex
multiPositionTrackers map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker
multiPositionTrackers map[key.ExchangePairAsset]*MultiPositionTracker
updated time.Time
}

View File

@@ -2009,7 +2009,7 @@ func TestGetQuoteAmountFromNominalSlippage(t *testing.T) {
t.Fatalf("%s received: '%v' but expected: '%v'", tt.Name, err, tt.ExpectedError)
}
if !quote.IsEqual(tt.ExpectedShift) {
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
t.Fatalf("%s quote received: \n'%+v' \nbut expected: \n'%+v'",
tt.Name, quote, tt.ExpectedShift)
}
})

View File

@@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -44,6 +45,12 @@ func SubscribeToExchangeOrderbooks(exchange string) (dispatch.Pipe, error) {
// Update stores orderbook data
func (s *Service) Update(b *Base) error {
name := strings.ToLower(b.Exchange)
mapKey := key.PairAsset{
Base: b.Pair.Base.Item,
Quote: b.Pair.Quote.Item,
Asset: b.Asset,
}
s.mu.Lock()
m1, ok := s.books[name]
if !ok {
@@ -53,29 +60,16 @@ func (s *Service) Update(b *Base) error {
return err
}
m1 = Exchange{
m: make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Depth),
m: make(map[key.PairAsset]*Depth),
ID: id,
}
s.books[name] = m1
}
m2, ok := m1.m[b.Asset]
if !ok {
m2 = make(map[*currency.Item]map[*currency.Item]*Depth)
m1.m[b.Asset] = m2
}
m3, ok := m2[b.Pair.Base.Item]
if !ok {
m3 = make(map[*currency.Item]*Depth)
m2[b.Pair.Base.Item] = m3
}
book, ok := m3[b.Pair.Quote.Item]
book, ok := m1.m[mapKey]
if !ok {
book = NewDepth(m1.ID)
book.AssignOptions(b)
m3[b.Pair.Quote.Item] = book
m1.m[mapKey] = book
}
err := book.LoadSnapshot(b.Bids, b.Asks, b.LastUpdateID, b.LastUpdated, true)
s.mu.Unlock()
@@ -97,6 +91,12 @@ func (s *Service) DeployDepth(exchange string, p currency.Pair, a asset.Item) (*
if !a.IsValid() {
return nil, errAssetTypeNotSet
}
mapKey := key.PairAsset{
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}
s.mu.Lock()
defer s.mu.Unlock()
m1, ok := s.books[strings.ToLower(exchange)]
@@ -106,28 +106,18 @@ func (s *Service) DeployDepth(exchange string, p currency.Pair, a asset.Item) (*
return nil, err
}
m1 = Exchange{
m: make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Depth),
m: make(map[key.PairAsset]*Depth),
ID: id,
}
s.books[strings.ToLower(exchange)] = m1
}
m2, ok := m1.m[a]
if !ok {
m2 = make(map[*currency.Item]map[*currency.Item]*Depth)
m1.m[a] = m2
}
m3, ok := m2[p.Base.Item]
if !ok {
m3 = make(map[*currency.Item]*Depth)
m2[p.Base.Item] = m3
}
book, ok := m3[p.Quote.Item]
book, ok := m1.m[mapKey]
if !ok {
book = NewDepth(m1.ID)
book.exchange = exchange
book.pair = p
book.asset = a
m3[p.Quote.Item] = book
m1.m[mapKey] = book
}
return book, nil
}
@@ -143,21 +133,11 @@ func (s *Service) GetDepth(exchange string, p currency.Pair, a asset.Item) (*Dep
errCannotFindOrderbook, exchange)
}
m2, ok := m1.m[a]
if !ok {
return nil, fmt.Errorf("%w associated with asset type %s",
errCannotFindOrderbook,
a)
}
m3, ok := m2[p.Base.Item]
if !ok {
return nil, fmt.Errorf("%w associated with base currency %s",
errCannotFindOrderbook,
p.Base)
}
book, ok := m3[p.Quote.Item]
book, ok := m1.m[key.PairAsset{
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("%w associated with base currency %s",
errCannotFindOrderbook,
@@ -175,6 +155,7 @@ func (s *Service) Retrieve(exchange string, p currency.Pair, a asset.Item) (*Bas
if !a.IsValid() {
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
s.mu.Lock()
defer s.mu.Unlock()
m1, ok := s.books[strings.ToLower(exchange)]
@@ -183,19 +164,11 @@ func (s *Service) Retrieve(exchange string, p currency.Pair, a asset.Item) (*Bas
errCannotFindOrderbook,
exchange)
}
m2, ok := m1.m[a]
if !ok {
return nil, fmt.Errorf("%w associated with asset type %s",
errCannotFindOrderbook,
a)
}
m3, ok := m2[p.Base.Item]
if !ok {
return nil, fmt.Errorf("%w associated with base currency %s",
errCannotFindOrderbook,
p.Base)
}
book, ok := m3[p.Quote.Item]
book, ok := m1.m[key.PairAsset{
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("%w associated with base currency %s",
errCannotFindOrderbook,

View File

@@ -6,6 +6,7 @@ import (
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -49,7 +50,7 @@ type Service struct {
// Exchange defines a holder for the exchange specific depth items with a
// specific ID associated with that exchange
type Exchange struct {
m map[asset.Item]map[*currency.Item]map[*currency.Item]*Depth
m map[key.PairAsset]*Depth
ID uuid.UUID
}

View File

@@ -6,6 +6,7 @@ import (
"sort"
"time"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -60,7 +61,7 @@ func (w *Orderbook) Setup(exchangeConfig *config.Exchange, c *Config, dataHandle
w.updateEntriesByID = c.UpdateEntriesByID
w.exchangeName = exchangeConfig.Name
w.dataHandler = dataHandler
w.ob = make(map[Key]*orderbookHolder)
w.ob = make(map[key.PairAsset]*orderbookHolder)
w.verbose = exchangeConfig.Verbose
// set default publish period if missing
@@ -93,7 +94,7 @@ func (w *Orderbook) Update(u *orderbook.Update) error {
}
w.mtx.Lock()
defer w.mtx.Unlock()
book, ok := w.ob[Key{Base: u.Pair.Base.Item, Quote: u.Pair.Quote.Item, Asset: u.Asset}]
book, ok := w.ob[key.PairAsset{Base: u.Pair.Base.Item, Quote: u.Pair.Quote.Item, Asset: u.Asset}]
if !ok {
return fmt.Errorf("%w for Exchange %s CurrencyPair: %s AssetType: %s",
errDepthNotFound,
@@ -311,7 +312,7 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error {
w.mtx.Lock()
defer w.mtx.Unlock()
holder, ok := w.ob[Key{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}]
holder, ok := w.ob[key.PairAsset{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}]
if !ok {
// Associate orderbook pointer with local exchange depth map
var depth *orderbook.Depth
@@ -327,7 +328,7 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error {
ticker = time.NewTicker(w.publishPeriod)
}
holder = &orderbookHolder{ob: depth, buffer: &buffer, ticker: ticker}
w.ob[Key{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}] = holder
w.ob[key.PairAsset{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}] = holder
}
holder.updateID = book.LastUpdateID
@@ -364,7 +365,7 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error {
func (w *Orderbook) GetOrderbook(p currency.Pair, a asset.Item) (*orderbook.Base, error) {
w.mtx.Lock()
defer w.mtx.Unlock()
book, ok := w.ob[Key{Base: p.Base.Item, Quote: p.Quote.Item, Asset: a}]
book, ok := w.ob[key.PairAsset{Base: p.Base.Item, Quote: p.Quote.Item, Asset: a}]
if !ok {
return nil, fmt.Errorf("%s %s %s %w", w.exchangeName, p, a, errDepthNotFound)
}
@@ -375,7 +376,7 @@ func (w *Orderbook) GetOrderbook(p currency.Pair, a asset.Item) (*orderbook.Base
// connection is lost and reconnected
func (w *Orderbook) FlushBuffer() {
w.mtx.Lock()
w.ob = make(map[Key]*orderbookHolder)
w.ob = make(map[key.PairAsset]*orderbookHolder)
w.mtx.Unlock()
}
@@ -383,7 +384,7 @@ func (w *Orderbook) FlushBuffer() {
func (w *Orderbook) FlushOrderbook(p currency.Pair, a asset.Item) error {
w.mtx.Lock()
defer w.mtx.Unlock()
book, ok := w.ob[Key{Base: p.Base.Item, Quote: p.Quote.Item, Asset: a}]
book, ok := w.ob[key.PairAsset{Base: p.Base.Item, Quote: p.Quote.Item, Asset: a}]
if !ok {
return fmt.Errorf("cannot flush orderbook %s %s %s %w",
w.exchangeName,

View File

@@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -44,7 +45,7 @@ func createSnapshot() (holder *Orderbook, asks, bids orderbook.Items, err error)
LastUpdated: time.Now(),
}
newBook := make(map[Key]*orderbookHolder)
newBook := make(map[key.PairAsset]*orderbookHolder)
ch := make(chan interface{})
go func(<-chan interface{}) { // reader
@@ -93,7 +94,7 @@ func BenchmarkUpdateBidsByPrice(b *testing.B) {
UpdateTime: time.Now(),
Asset: asset.Spot,
}
holder := ob.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
holder := ob.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
err = holder.updateByPrice(update)
if err != nil {
b.Fatal(err)
@@ -116,7 +117,7 @@ func BenchmarkUpdateAsksByPrice(b *testing.B) {
UpdateTime: time.Now(),
Asset: asset.Spot,
}
holder := ob.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
holder := ob.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
err = holder.updateByPrice(update)
if err != nil {
b.Fatal(err)
@@ -246,7 +247,7 @@ func TestUpdates(t *testing.T) {
t.Error(err)
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
err = book.updateByPrice(&orderbook.Update{
Bids: itemArray[5],
Asks: itemArray[5],
@@ -302,7 +303,7 @@ func TestHittingTheBuffer(t *testing.T) {
}
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
askLen, err := book.ob.GetAskLength()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -350,7 +351,7 @@ func TestInsertWithIDs(t *testing.T) {
}
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
askLen, err := book.ob.GetAskLength()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -393,7 +394,7 @@ func TestSortIDs(t *testing.T) {
t.Fatal(err)
}
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
askLen, err := book.ob.GetAskLength()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -438,7 +439,7 @@ func TestOutOfOrderIDs(t *testing.T) {
t.Fatal(err)
}
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
cpy, err := book.ob.Retrieve()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -570,7 +571,7 @@ func TestRunUpdateWithoutAnyUpdates(t *testing.T) {
func TestRunSnapshotWithNoData(t *testing.T) {
t.Parallel()
var obl Orderbook
obl.ob = make(map[Key]*orderbookHolder)
obl.ob = make(map[key.PairAsset]*orderbookHolder)
obl.dataHandler = make(chan interface{}, 1)
var snapShot1 orderbook.Base
snapShot1.Asset = asset.Spot
@@ -589,7 +590,7 @@ func TestLoadSnapshot(t *testing.T) {
t.Parallel()
var obl Orderbook
obl.dataHandler = make(chan interface{}, 100)
obl.ob = make(map[Key]*orderbookHolder)
obl.ob = make(map[key.PairAsset]*orderbookHolder)
var snapShot1 orderbook.Base
snapShot1.Exchange = "SnapshotWithOverride"
asks := []orderbook.Item{
@@ -615,11 +616,11 @@ func TestFlushBuffer(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if obl.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] == nil {
if obl.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] == nil {
t.Error("expected ob to have ask entries")
}
obl.FlushBuffer()
if obl.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] != nil {
if obl.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] != nil {
t.Error("expected ob be flushed")
}
}
@@ -629,7 +630,7 @@ func TestInsertingSnapShots(t *testing.T) {
t.Parallel()
var holder Orderbook
holder.dataHandler = make(chan interface{}, 100)
holder.ob = make(map[Key]*orderbookHolder)
holder.ob = make(map[key.PairAsset]*orderbookHolder)
var snapShot1 orderbook.Base
snapShot1.Exchange = "WSORDERBOOKTEST1"
asks := []orderbook.Item{
@@ -795,7 +796,7 @@ func TestGetOrderbook(t *testing.T) {
if err != nil {
t.Fatal(err)
}
bufferOb := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
bufferOb := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
b, err := bufferOb.ob.Retrieve()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -888,7 +889,7 @@ func TestEnsureMultipleUpdatesViaPrice(t *testing.T) {
}
asks := bidAskGenerator()
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
err = book.updateByPrice(&orderbook.Update{
Bids: asks,
Asks: asks,

View File

@@ -4,8 +4,7 @@ import (
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
)
@@ -30,7 +29,7 @@ type Config struct {
// Orderbook defines a local cache of orderbooks for amending, appending
// and deleting changes and updates the main store for a stream
type Orderbook struct {
ob map[Key]*orderbookHolder
ob map[key.PairAsset]*orderbookHolder
obBufferLimit int
bufferEnabled bool
sortBuffer bool
@@ -67,10 +66,3 @@ type orderbookHolder struct {
ticker *time.Ticker
updateID int64
}
// Key defines a unique orderbook key for a specific pair and asset
type Key struct {
Base *currency.Item
Quote *currency.Item
Asset asset.Item
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -22,7 +23,7 @@ var (
func init() {
service = new(Service)
service.Tickers = make(map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker)
service.Tickers = make(map[key.ExchangePairAsset]*Ticker)
service.Exchange = make(map[string]uuid.UUID)
service.mux = dispatch.GetNewMux(nil)
}
@@ -33,8 +34,12 @@ func SubscribeTicker(exchange string, p currency.Pair, a asset.Item) (dispatch.P
exchange = strings.ToLower(exchange)
service.mu.Lock()
defer service.mu.Unlock()
tick, ok := service.Tickers[exchange][p.Base.Item][p.Quote.Item][a]
tick, ok := service.Tickers[key.ExchangePairAsset{
Exchange: exchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return dispatch.Pipe{}, fmt.Errorf("ticker item not found for %s %s %s",
exchange,
@@ -72,30 +77,18 @@ func GetTicker(exchange string, p currency.Pair, a asset.Item) (*Price, error) {
exchange = strings.ToLower(exchange)
service.mu.Lock()
defer service.mu.Unlock()
m1, ok := service.Tickers[exchange]
tick, ok := service.Tickers[key.ExchangePairAsset{
Exchange: exchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("no tickers for %s exchange", exchange)
return nil, fmt.Errorf("no tickers associated with asset type %s %s %s",
exchange, p, a)
}
m2, ok := m1[p.Base.Item]
if !ok {
return nil, fmt.Errorf("no tickers associated with base currency %s",
p.Base)
}
m3, ok := m2[p.Quote.Item]
if !ok {
return nil, fmt.Errorf("no tickers associated with quote currency %s",
p.Quote)
}
t, ok := m3[a]
if !ok {
return nil, fmt.Errorf("no tickers associated with asset type %s",
a)
}
cpy := t.Price // Don't let external functions have access to underlying
cpy := tick.Price // Don't let external functions have access to underlying
return &cpy, nil
}
@@ -103,20 +96,10 @@ func GetTicker(exchange string, p currency.Pair, a asset.Item) (*Price, error) {
func FindLast(p currency.Pair, a asset.Item) (float64, error) {
service.mu.Lock()
defer service.mu.Unlock()
for _, m1 := range service.Tickers {
m2, ok := m1[p.Base.Item]
if !ok {
for mapKey, t := range service.Tickers {
if !mapKey.MatchesPairAsset(p, a) {
continue
}
m3, ok := m2[p.Quote.Item]
if !ok {
continue
}
t, ok := m3[a]
if !ok {
continue
}
if t.Last == 0 {
return 0, errInvalidTicker
}
@@ -178,27 +161,14 @@ func ProcessTicker(p *Price) error {
// update updates ticker price
func (s *Service) update(p *Price) error {
name := strings.ToLower(p.ExchangeName)
mapKey := key.ExchangePairAsset{
Exchange: name,
Base: p.Pair.Base.Item,
Quote: p.Pair.Quote.Item,
Asset: p.AssetType,
}
s.mu.Lock()
m1, ok := service.Tickers[name]
if !ok {
m1 = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker)
service.Tickers[name] = m1
}
m2, ok := m1[p.Pair.Base.Item]
if !ok {
m2 = make(map[*currency.Item]map[asset.Item]*Ticker)
m1[p.Pair.Base.Item] = m2
}
m3, ok := m2[p.Pair.Quote.Item]
if !ok {
m3 = make(map[asset.Item]*Ticker)
m2[p.Pair.Quote.Item] = m3
}
t, ok := m3[p.AssetType]
t, ok := service.Tickers[mapKey]
if !ok || t == nil {
newTicker := &Ticker{}
err := s.setItemID(newTicker, p, name)
@@ -206,7 +176,7 @@ func (s *Service) update(p *Price) error {
s.mu.Unlock()
return err
}
m3[p.AssetType] = newTicker
service.Tickers[mapKey] = newTicker
s.mu.Unlock()
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -25,7 +26,7 @@ var (
// Service holds ticker information for each individual exchange
type Service struct {
Tickers map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker
Tickers map[key.ExchangePairAsset]*Ticker
Exchange map[string]uuid.UUID
mux *dispatch.Mux
mu sync.Mutex