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

@@ -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
}