mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-24 15:10:19 +00:00
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:
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user