mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
exchanges/account: add UpdatedAt field to Balance and ProtectedBalance (#1827)
* add updatedAt for account balance Signed-off-by: Ye Sijun <junnplus@gmail.com> * add nil pointer test for load Signed-off-by: Ye Sijun <junnplus@gmail.com> * account: set default updatedAt to balance for Update Signed-off-by: Ye Sijun <junnplus@gmail.com> * engine: add UpdatedAt for account info Signed-off-by: Ye Sijun <junnplus@gmail.com> * account: force updatedAt for load balance Signed-off-by: Ye Sijun <junnplus@gmail.com> * minor change for test Signed-off-by: Ye Sijun <junnplus@gmail.com> --------- Signed-off-by: Ye Sijun <junnplus@gmail.com>
This commit is contained in:
@@ -31,6 +31,9 @@ var (
|
||||
errBalanceIsNil = errors.New("balance is nil")
|
||||
errNoCredentialBalances = errors.New("no balances associated with credentials")
|
||||
errCredentialsAreNil = errors.New("credentials are nil")
|
||||
errOutOfSequence = errors.New("out of sequence")
|
||||
errUpdatedAtIsZero = errors.New("updatedAt may not be zero")
|
||||
errLoadingBalance = errors.New("error loading balance")
|
||||
)
|
||||
|
||||
// CollectBalances converts a map of sub-account balances into a slice
|
||||
@@ -103,7 +106,7 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
|
||||
return Holdings{}, fmt.Errorf("%s %s %s %w %w", exch, creds, assetType, errNoCredentialBalances, ErrExchangeHoldingsNotFound)
|
||||
}
|
||||
|
||||
var currencyBalances = make([]Balance, 0, len(subAccountHoldings))
|
||||
currencyBalances := make([]Balance, 0, len(subAccountHoldings))
|
||||
cpy := *creds
|
||||
for mapKey, assetHoldings := range subAccountHoldings {
|
||||
if mapKey.Asset != assetType {
|
||||
@@ -117,6 +120,7 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
|
||||
Free: assetHoldings.free,
|
||||
AvailableWithoutBorrow: assetHoldings.availableWithoutBorrow,
|
||||
Borrowed: assetHoldings.borrowed,
|
||||
UpdatedAt: assetHoldings.updatedAt,
|
||||
})
|
||||
assetHoldings.m.Unlock()
|
||||
if cpy.SubAccount == "" && mapKey.SubAccount != "" {
|
||||
@@ -239,6 +243,9 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error {
|
||||
}
|
||||
|
||||
for y := range incoming.Accounts[x].Currencies {
|
||||
if incoming.Accounts[x].Currencies[y].UpdatedAt.IsZero() {
|
||||
incoming.Accounts[x].Currencies[y].UpdatedAt = time.Now()
|
||||
}
|
||||
// Note: Sub accounts are case sensitive and an account "name" is
|
||||
// different to account "naMe".
|
||||
bal, ok := subAccounts[key.SubAccountCurrencyAsset{
|
||||
@@ -254,7 +261,14 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error {
|
||||
Asset: incoming.Accounts[x].AssetType,
|
||||
}] = bal
|
||||
}
|
||||
bal.load(incoming.Accounts[x].Currencies[y])
|
||||
if err := bal.load(&incoming.Accounts[x].Currencies[y]); err != nil {
|
||||
errs = common.AppendError(errs, fmt.Errorf("%w for account ID `%s` [%s %s]: %w",
|
||||
errLoadingBalance,
|
||||
incoming.Accounts[x].ID,
|
||||
incoming.Accounts[x].AssetType,
|
||||
incoming.Accounts[x].Currencies[y].Currency,
|
||||
err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,22 +282,34 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error {
|
||||
|
||||
// load checks to see if there is a change from incoming balance, if there is a
|
||||
// change it will change then alert external routines.
|
||||
func (b *ProtectedBalance) load(change Balance) {
|
||||
func (b *ProtectedBalance) load(change *Balance) error {
|
||||
if change == nil {
|
||||
return fmt.Errorf("%w for '%T'", common.ErrNilPointer, change)
|
||||
}
|
||||
if change.UpdatedAt.IsZero() {
|
||||
return errUpdatedAtIsZero
|
||||
}
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
if !b.updatedAt.IsZero() && !b.updatedAt.Before(change.UpdatedAt) {
|
||||
return errOutOfSequence
|
||||
}
|
||||
if b.total == change.Total &&
|
||||
b.hold == change.Hold &&
|
||||
b.free == change.Free &&
|
||||
b.availableWithoutBorrow == change.AvailableWithoutBorrow &&
|
||||
b.borrowed == change.Borrowed {
|
||||
return
|
||||
b.borrowed == change.Borrowed &&
|
||||
b.updatedAt.Equal(change.UpdatedAt) {
|
||||
return nil
|
||||
}
|
||||
b.total = change.Total
|
||||
b.hold = change.Hold
|
||||
b.free = change.Free
|
||||
b.availableWithoutBorrow = change.AvailableWithoutBorrow
|
||||
b.borrowed = change.Borrowed
|
||||
b.updatedAt = change.UpdatedAt
|
||||
b.notice.Alert()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for a change in amounts for an asset type. This will pause
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"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"
|
||||
@@ -85,7 +86,8 @@ func TestGetHoldings(t *testing.T) {
|
||||
Accounts: []SubAccount{
|
||||
{
|
||||
ID: "1337",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, happyCredentials)
|
||||
assert.ErrorIs(t, err, asset.ErrNotSupported)
|
||||
|
||||
@@ -106,7 +108,8 @@ func TestGetHoldings(t *testing.T) {
|
||||
Hold: 20,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, happyCredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -124,7 +127,8 @@ func TestGetHoldings(t *testing.T) {
|
||||
Hold: 20,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, happyCredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -292,28 +296,32 @@ func TestBalanceInternalWait(t *testing.T) {
|
||||
func TestBalanceInternalLoad(t *testing.T) {
|
||||
t.Parallel()
|
||||
bi := &ProtectedBalance{}
|
||||
bi.load(Balance{Total: 1, Hold: 2, Free: 3, AvailableWithoutBorrow: 4, Borrowed: 5})
|
||||
err := bi.load(nil)
|
||||
assert.ErrorIs(t, err, common.ErrNilPointer, "should error nil pointer correctly")
|
||||
|
||||
err = bi.load(&Balance{Total: 1, Hold: 2, Free: 3, AvailableWithoutBorrow: 4, Borrowed: 5})
|
||||
assert.ErrorIs(t, err, errUpdatedAtIsZero, "should error correctly when updatedAt is not set")
|
||||
|
||||
now := time.Now()
|
||||
err = bi.load(&Balance{UpdatedAt: now, Total: 1, Hold: 2, Free: 3, AvailableWithoutBorrow: 4, Borrowed: 5})
|
||||
require.NoError(t, err)
|
||||
|
||||
bi.m.Lock()
|
||||
if bi.total != 1 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
if bi.hold != 2 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
if bi.free != 3 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
if bi.availableWithoutBorrow != 4 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
if bi.borrowed != 5 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
assert.Equal(t, now, bi.updatedAt)
|
||||
assert.Equal(t, 1.0, bi.total)
|
||||
assert.Equal(t, 2.0, bi.hold)
|
||||
assert.Equal(t, 3.0, bi.free)
|
||||
assert.Equal(t, 4.0, bi.availableWithoutBorrow)
|
||||
assert.Equal(t, 5.0, bi.borrowed)
|
||||
bi.m.Unlock()
|
||||
|
||||
if bi.GetFree() != 3 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
assert.Equal(t, 3.0, bi.GetFree())
|
||||
|
||||
err = bi.load(&Balance{UpdatedAt: now, Total: 2, Hold: 3, Free: 4, AvailableWithoutBorrow: 5, Borrowed: 6})
|
||||
assert.ErrorIs(t, err, errOutOfSequence, "should error correctly with same UpdatedAt")
|
||||
|
||||
err = bi.load(&Balance{UpdatedAt: now.Add(time.Second), Total: 2, Hold: 3, Free: 4, AvailableWithoutBorrow: 5, Borrowed: 6})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetFree(t *testing.T) {
|
||||
@@ -408,11 +416,7 @@ func TestUpdate(t *testing.T) {
|
||||
t.Fatal("account should be loaded")
|
||||
}
|
||||
|
||||
if b.total != 100 {
|
||||
t.Errorf("expecting 100 but received %f", b.total)
|
||||
}
|
||||
|
||||
if b.hold != 20 {
|
||||
t.Errorf("expecting 20 but received %f", b.hold)
|
||||
}
|
||||
assert.Equal(t, 100.0, b.total)
|
||||
assert.Equal(t, 20.0, b.hold)
|
||||
assert.NotEmpty(t, b.updatedAt)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package account
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/key"
|
||||
@@ -59,6 +60,7 @@ type Balance struct {
|
||||
Free float64
|
||||
AvailableWithoutBorrow float64
|
||||
Borrowed float64
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// Change defines incoming balance change on currency holdings
|
||||
@@ -78,6 +80,7 @@ type ProtectedBalance struct {
|
||||
availableWithoutBorrow float64
|
||||
borrowed float64
|
||||
m sync.Mutex
|
||||
updatedAt time.Time
|
||||
|
||||
// notice alerts for when the balance changes for strategy inspection and
|
||||
// usage.
|
||||
|
||||
Reference in New Issue
Block a user