accounts: Move to instance methods, fix races and isolate tests (#1923)

* Bybit: Fix race in TestUpdateAccountInfo and  TestWSHandleData

* DriveBy rename TestWSHandleData
* This doesn't address running with -race=2+ due to the singleton

* Accounts: Add account.GetService()

* exchange: Assertify TestSetupDefaults

* Exchanges: Add account.Service override for testing

* Exchanges: Remove duplicate IsWebsocketEnabled test from TestSetupDefaults

* Dispatch: Replace nil checks with NilGuard

* Engine: Remove deprecated printAccountHoldingsChangeSummary

* Dispatcher: Add EnsureRunning method

* Accounts: Move singleton accounts service to exchange Accounts

* Move singleton accounts service to exchange Accounts

This maintains the concept of a global store, whilst allowing exchanges
to override it when needed, particularly for testing.

APIServer:

* Remove getAllActiveAccounts from apiserver

Deprecated apiserver only thing using this, so remove it instead of
updating it

* Update comment for UpdateAccountBalances everywhere

* Docs: Add punctuation to function comments

* Bybit: Coverage for wsProcessWalletPushData Save
This commit is contained in:
Gareth Kirwan
2025-10-28 09:52:45 +07:00
committed by GitHub
parent bda9bbec66
commit 73e200e4e7
140 changed files with 3515 additions and 4025 deletions

View File

@@ -18,17 +18,7 @@ import (
var mockTests = false
func TestMain(m *testing.M) {
e = new(Exchange)
if err := testexch.Setup(e); err != nil {
log.Fatalf("Bybit Setup error: %s", err)
}
if apiKey != "" && apiSecret != "" {
e.API.AuthenticatedSupport = true
e.API.AuthenticatedWebsocketSupport = true
e.SetCredentials(apiKey, apiSecret, "", "", "", "")
e.Websocket.SetCanUseAuthenticatedEndpoints(true)
}
e = testInstance()
if e.API.AuthenticatedSupport {
if _, err := e.FetchAccountType(context.Background()); err != nil {
@@ -41,6 +31,21 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func testInstance() *Bybit {
e := new(Exchange)
if err := testexch.Setup(e); err != nil {
log.Fatalf("Bybit Setup error: %s", err)
}
if apiKey != "" && apiSecret != "" {
e.API.AuthenticatedSupport = true
e.API.AuthenticatedWebsocketSupport = true
e.SetCredentials(apiKey, apiSecret, "", "", "", "")
e.Websocket.SetCanUseAuthenticatedEndpoints(true)
}
return e
}
func instantiateTradablePairs() {
handleError := func(msg string, err error) {
if err != nil {

View File

@@ -18,16 +18,7 @@ import (
var mockTests = true
func TestMain(m *testing.M) {
e = new(Exchange)
if err := testexch.Setup(e); err != nil {
log.Fatalf("Bybit Setup error: %s", err)
}
e.SetCredentials("mock", "tester", "", "", "", "") // Hack for UpdateAccountInfo test
if err := testexch.MockHTTPInstance(e); err != nil {
log.Fatalf("Bybit MockHTTPInstance error: %s", err)
}
e = testInstance()
if err := e.UpdateTradablePairs(context.Background()); err != nil {
log.Fatalf("Bybit unable to UpdateTradablePairs: %s", err)
@@ -57,3 +48,18 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func testInstance() *Exchange {
b := new(Exchange)
if err := testexch.Setup(b); err != nil {
log.Fatalf("Bybit Setup error: %s", err)
}
b.SetCredentials("mock", "tester", "", "", "", "") // Hack for UpdateAccountBalances test
if err := testexch.MockHTTPInstance(b); err != nil {
log.Fatalf("Bybit MockHTTPInstance error: %s", err)
}
return b
}

View File

@@ -19,10 +19,10 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"github.com/thrasher-corp/gocryptotrader/exchange/accounts"
"github.com/thrasher-corp/gocryptotrader/exchange/order/limits"
"github.com/thrasher-corp/gocryptotrader/exchange/websocket"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fill"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
@@ -2778,44 +2778,49 @@ func TestGetBrokerEarning(t *testing.T) {
}
}
func TestUpdateAccountInfo(t *testing.T) {
func TestUpdateAccountBalances(t *testing.T) {
t.Parallel()
if !mockTests {
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
}
r, err := e.UpdateAccountInfo(t.Context(), asset.Spot)
require.NoError(t, err, "UpdateAccountInfo must not error")
require.NotEmpty(t, r, "UpdateAccountInfo must return account info")
e := testInstance() //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
subAccts, err := e.UpdateAccountBalances(t.Context(), asset.Spot)
require.NoError(t, err, "UpdateAccountBalances must not error")
require.NotEmpty(t, subAccts, "UpdateAccountBalances must return account info")
if mockTests {
require.Len(t, r.Accounts, 1, "Accounts must have 1 item")
require.Len(t, r.Accounts[0].Currencies, 3, "Accounts currencies must have 3 currency items")
require.Len(t, subAccts, 1, "Accounts must have 1 item")
require.Len(t, subAccts[0].Balances, 3, "Accounts currencies must have 3 currency items")
for x := range r.Accounts[0].Currencies {
switch x {
case 0:
assert.Equal(t, currency.USDC, r.Accounts[0].Currencies[x].Currency, "Currency should be USDC")
assert.Equal(t, -30723.63021638, r.Accounts[0].Currencies[x].Total, "Total amount should be correct")
assert.Zero(t, r.Accounts[0].Currencies[x].Hold, "Hold amount should be zero")
assert.Equal(t, 30723.630216383711792744, r.Accounts[0].Currencies[x].Borrowed, "Borrowed amount should be correct")
assert.Zero(t, r.Accounts[0].Currencies[x].Free, "Free amount should be zero")
assert.Zero(t, r.Accounts[0].Currencies[x].AvailableWithoutBorrow, "AvailableWithoutBorrow amount should be zero")
case 1:
assert.Equal(t, currency.AVAX, r.Accounts[0].Currencies[x].Currency, "Currency should be AVAX")
assert.Equal(t, 2473.9, r.Accounts[0].Currencies[x].Total, "Total amount should be correct")
assert.Zero(t, r.Accounts[0].Currencies[x].Hold, "Hold amount should be zero")
assert.Zero(t, r.Accounts[0].Currencies[x].Borrowed, "Borrowed amount should be zero")
assert.Equal(t, 2473.9, r.Accounts[0].Currencies[x].Free, "Free amount should be correct")
assert.Equal(t, 1005.79191187, r.Accounts[0].Currencies[x].AvailableWithoutBorrow, "AvailableWithoutBorrow amount should be correct")
case 2:
assert.Equal(t, currency.USDT, r.Accounts[0].Currencies[x].Currency, "Currency should be USDT")
assert.Equal(t, 935.1415, r.Accounts[0].Currencies[x].Total, "Total amount should be correct")
assert.Zero(t, r.Accounts[0].Currencies[x].Borrowed, "Borrowed amount should be zero")
assert.Zero(t, r.Accounts[0].Currencies[x].Hold, "Hold amount should be zero")
assert.Equal(t, 935.1415, r.Accounts[0].Currencies[x].Free, "Free amount should be correct")
assert.Equal(t, 935.1415, r.Accounts[0].Currencies[x].AvailableWithoutBorrow, "AvailableWithoutBorrow amount should be correct")
}
for _, curr := range []currency.Code{currency.USDC, currency.AVAX, currency.USDT} {
t.Run(curr.String(), func(t *testing.T) {
t.Parallel()
require.Contains(t, subAccts[0].Balances, curr, "Balances must contain currency")
bal := subAccts[0].Balances[curr]
assert.Equal(t, curr, bal.Currency, "Balance Currency should be set")
switch curr {
case currency.USDC:
assert.Equal(t, -30723.63021638, bal.Total, "Total amount should be correct")
assert.Zero(t, bal.Hold, "Hold amount should be zero")
assert.Equal(t, 30723.630216383711792744, bal.Borrowed, "Borrowed amount should be correct")
assert.Zero(t, bal.Free, "Free amount should be zero")
assert.Zero(t, bal.AvailableWithoutBorrow, "AvailableWithoutBorrow amount should be zero")
case currency.AVAX:
assert.Equal(t, 2473.9, bal.Total, "Total amount should be correct")
assert.Zero(t, bal.Hold, "Hold amount should be zero")
assert.Zero(t, bal.Borrowed, "Borrowed amount should be zero")
assert.Equal(t, 2473.9, bal.Free, "Free amount should be correct")
assert.Equal(t, 1005.79191187, bal.AvailableWithoutBorrow, "AvailableWithoutBorrow amount should be correct")
case currency.USDT:
assert.Equal(t, 935.1415, bal.Total, "Total amount should be correct")
assert.Zero(t, bal.Borrowed, "Borrowed amount should be zero")
assert.Zero(t, bal.Hold, "Hold amount should be zero")
assert.Equal(t, 935.1415, bal.Free, "Free amount should be correct")
assert.Equal(t, 935.1415, bal.AvailableWithoutBorrow, "AvailableWithoutBorrow amount should be correct")
}
})
}
}
}
@@ -2980,9 +2985,11 @@ var pushDataMap = map[string]string{
"unhandled": `{"topic": "unhandled"}`,
}
func TestPushDataPublic(t *testing.T) {
func TestWSHandleData(t *testing.T) {
t.Parallel()
e := testInstance() //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
keys := slices.Collect(maps.Keys(pushDataMap))
slices.Sort(keys)
for x := range keys {
@@ -3009,14 +3016,21 @@ func TestWSHandleAuthenticatedData(t *testing.T) {
e.API.AuthenticatedSupport = true
e.API.AuthenticatedWebsocketSupport = true
e.SetCredentials("test", "test", "", "", "", "")
testexch.FixtureToDataHandler(t, "testdata/wsAuth.json", func(ctx context.Context, r []byte) error {
fErrs := testexch.FixtureToDataHandlerWithErrors(t, "testdata/wsAuth.json", func(ctx context.Context, r []byte) error {
if bytes.Contains(r, []byte("%s")) {
r = fmt.Appendf(nil, string(r), optionsTradablePair.String())
}
if bytes.Contains(r, []byte("FANGLE-ACCOUNTS")) {
hold := e.Accounts
e.Accounts = nil
defer func() { e.Accounts = hold }()
}
return e.wsHandleAuthenticatedData(ctx, &FixtureConnection{match: websocket.NewMatch()}, r)
})
close(e.Websocket.DataHandler)
require.Len(t, e.Websocket.DataHandler, 6, "Should see correct number of messages")
require.Len(t, fErrs, 1, "Must get exactly one error message")
assert.ErrorContains(t, fErrs[0].Err, "cannot save holdings: nil pointer: *accounts.Accounts")
i := 0
for data := range e.Websocket.DataHandler {
@@ -3081,63 +3095,39 @@ func TestWSHandleAuthenticatedData(t *testing.T) {
assert.Equal(t, 0.358635, v[0].Fee, "fee should be correct")
assert.Equal(t, time.UnixMilli(1672364262444), v[0].Date, "Created time should be correct")
assert.Equal(t, time.UnixMilli(1672364262457), v[0].LastUpdated, "Updated time should be correct")
case []account.Change:
require.Len(t, v, 6, "must see 6 items")
for i, change := range v {
assert.Empty(t, change.Account, "Account type should be empty")
assert.Equal(t, asset.Spot, change.AssetType, "Asset type should be Spot")
require.NotNil(t, change.Balance, "balance must not be nil")
switch i {
case 0:
assert.True(t, currency.USDC.Equal(change.Balance.Currency), "currency should match")
assert.Zero(t, change.Balance.AvailableWithoutBorrow, "AvailableWithoutBorrow should zero")
assert.Zero(t, change.Balance.Borrowed, "Borrowed should be 0")
assert.Equal(t, 201.34882644, change.Balance.Free, "Free should be correct")
assert.Zero(t, change.Balance.Hold, "Hold should be 0")
assert.Equal(t, 201.34882644, change.Balance.Total, "Total should be correct")
assert.Equal(t, time.UnixMilli(1672364262482), change.Balance.UpdatedAt, "Last updated should be correct")
case 1:
assert.True(t, currency.BTC.Equal(change.Balance.Currency), "currency should match")
assert.Equal(t, 0.06488393, change.Balance.Free, "Free should be correct")
assert.Zero(t, change.Balance.AvailableWithoutBorrow, "AvailableWithoutBorrow should zero")
assert.Zero(t, change.Balance.Borrowed, "Borrowed should be 0")
assert.Zero(t, change.Balance.Hold, "Hold should be 0")
assert.Equal(t, 0.06488393, change.Balance.Total, "Total should be correct")
assert.Equal(t, time.UnixMilli(1672364262482), change.Balance.UpdatedAt, "Last updated should be correct")
case 2:
assert.True(t, currency.ETH.Equal(change.Balance.Currency), "currency should match")
assert.Zero(t, change.Balance.Free, "Free should be 0")
assert.Zero(t, change.Balance.AvailableWithoutBorrow, "AvailableWithoutBorrow should zero")
assert.Zero(t, change.Balance.Borrowed, "Borrowed should be 0")
assert.Zero(t, change.Balance.Hold, "Hold should be 0")
assert.Zero(t, change.Balance.Total, "Total should be 0")
assert.Equal(t, time.UnixMilli(1672364262482), change.Balance.UpdatedAt, "Last updated should be correct")
case 3:
assert.True(t, currency.USDT.Equal(change.Balance.Currency), "currency should match")
assert.Equal(t, 11728.54414904, change.Balance.Free, "Free should be correct")
assert.Zero(t, change.Balance.AvailableWithoutBorrow, "AvailableWithoutBorrow should be 0")
assert.Zero(t, change.Balance.Borrowed, "Borrowed should be 0")
assert.Zero(t, change.Balance.Hold, "Hold should be 0")
assert.Equal(t, 11728.54414904, change.Balance.Total, "Total should be correct")
assert.Equal(t, time.UnixMilli(1672364262482), change.Balance.UpdatedAt, "Last updated should be correct")
case 4:
assert.True(t, currency.NewCode("EOS3L").Equal(change.Balance.Currency), "currency should match")
assert.Equal(t, 215.0570412, change.Balance.Free, "Free should be correct")
assert.Zero(t, change.Balance.AvailableWithoutBorrow, "AvailableWithoutBorrow should be 0")
assert.Zero(t, change.Balance.Borrowed, "Borrowed should be 0")
assert.Zero(t, change.Balance.Hold, "Hold should be 0")
assert.Equal(t, 215.0570412, change.Balance.Total, "Total should be correct")
assert.Equal(t, time.UnixMilli(1672364262482), change.Balance.UpdatedAt, "Last updated should be correct")
case 5:
assert.True(t, currency.BIT.Equal(change.Balance.Currency), "currency should match")
assert.Equal(t, 1.82, change.Balance.Free, "Free should be correct")
assert.Zero(t, change.Balance.AvailableWithoutBorrow, "AvailableWithoutBorrow should be 0")
assert.Zero(t, change.Balance.Borrowed, "Borrowed should be 0")
assert.Zero(t, change.Balance.Hold, "Hold should be 0")
assert.Equal(t, 1.82, change.Balance.Total, "Total should be correct")
assert.Equal(t, time.UnixMilli(1672364262482), change.Balance.UpdatedAt, "Last updated should be correct")
}
}
case accounts.SubAccounts:
require.Len(t, v, 1, "Must have correct number of SubAccounts")
assert.Equal(t, asset.Spot, v[0].AssetType, "Asset type should be correct")
exp := accounts.CurrencyBalances{}
exp.Set(currency.ETH, accounts.Balance{
UpdatedAt: time.UnixMilli(1672364262482),
})
exp.Set(currency.USDT, accounts.Balance{
UpdatedAt: time.UnixMilli(1672364262482),
Total: 11728.54414904,
Free: 11728.54414904,
})
exp.Set(currency.EOS3L, accounts.Balance{
UpdatedAt: time.UnixMilli(1672364262482),
Total: 215.0570412,
Free: 215.0570412,
})
exp.Set(currency.BIT, accounts.Balance{
UpdatedAt: time.UnixMilli(1672364262482),
Total: 1.82,
Free: 1.82,
})
exp.Set(currency.USDC, accounts.Balance{
UpdatedAt: time.UnixMilli(1672364262482),
Total: 201.34882644,
Free: 201.34882644,
})
exp.Set(currency.BTC, accounts.Balance{
UpdatedAt: time.UnixMilli(1672364262482),
Total: 0.06488393,
Free: 0.06488393,
})
assert.Equal(t, exp, v[0].Balances, "Balances should be correct")
case *GreeksResponse:
assert.Equal(t, "592324fa945a30-2603-49a5-b865-21668c29f2a6", v.ID, "ID should be correct")
assert.Equal(t, "greeks", v.Topic, "Topic should be correct")
@@ -3162,7 +3152,7 @@ func TestWSHandleAuthenticatedData(t *testing.T) {
assert.Equal(t, 0.3374, v[0].Price, "price should be correct")
assert.Equal(t, 25.0, v[0].Amount, "amount should be correct")
default:
t.Errorf("Unexpected data received: %v", v)
t.Errorf("Unexpected data received: %T %v", v, v)
}
}
}
@@ -3170,11 +3160,11 @@ func TestWSHandleAuthenticatedData(t *testing.T) {
func TestWsTicker(t *testing.T) {
t.Parallel()
e := new(Exchange) //nolint:govet // Intentional shadow
require.NoError(t, testexch.Setup(e), "Test instance Setup must not error")
assetRouting := []asset.Item{
asset.Spot, asset.Options, asset.USDTMarginedFutures, asset.USDTMarginedFutures,
asset.USDCMarginedFutures, asset.USDCMarginedFutures, asset.CoinMarginedFutures, asset.CoinMarginedFutures,
}
require.NoError(t, testexch.Setup(e), "Test instance Setup must not error")
testexch.FixtureToDataHandler(t, "testdata/wsTicker.json", func(_ context.Context, r []byte) error {
defer slices.Delete(assetRouting, 0, 1)
return e.wsHandleData(nil, assetRouting[0], r)
@@ -3730,7 +3720,7 @@ func TestWebsocketAuthenticatePrivateConnection(t *testing.T) {
e.API.AuthenticatedSupport = true
e.API.AuthenticatedWebsocketSupport = true
e.Websocket.SetCanUseAuthenticatedEndpoints(true)
ctx := account.DeployCredentialsToContext(t.Context(), &account.Credentials{Key: "dummy", Secret: "dummy"})
ctx := accounts.DeployCredentialsToContext(t.Context(), &accounts.Credentials{Key: "dummy", Secret: "dummy"})
err = e.WebsocketAuthenticatePrivateConnection(ctx, &FixtureConnection{})
require.NoError(t, err)
err = e.WebsocketAuthenticatePrivateConnection(ctx, &FixtureConnection{sendMessageReturnResponseOverride: []byte(`{"success":false,"ret_msg":"failed auth","conn_id":"5758770c-8152-4545-a84f-dae089e56499","req_id":"1","op":"subscribe"}`)})
@@ -3749,7 +3739,7 @@ func TestWebsocketAuthenticateTradeConnection(t *testing.T) {
e.API.AuthenticatedSupport = true
e.API.AuthenticatedWebsocketSupport = true
e.Websocket.SetCanUseAuthenticatedEndpoints(true)
ctx := account.DeployCredentialsToContext(t.Context(), &account.Credentials{Key: "dummy", Secret: "dummy"})
ctx := accounts.DeployCredentialsToContext(t.Context(), &accounts.Credentials{Key: "dummy", Secret: "dummy"})
err = e.WebsocketAuthenticateTradeConnection(ctx, &FixtureConnection{sendMessageReturnResponseOverride: []byte(`{"retCode":0,"retMsg":"OK","op":"auth","connId":"d2a641kgcg7ab33b7mdg-4x6a"}`)})
require.NoError(t, err)
err = e.WebsocketAuthenticateTradeConnection(ctx, &FixtureConnection{sendMessageReturnResponseOverride: []byte(`{"retCode":10004,"retMsg":"Invalid sign","op":"auth","connId":"d2a63t6p49kk82nefh90-4ye8"}`)})

View File

@@ -17,8 +17,8 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"github.com/thrasher-corp/gocryptotrader/exchange/accounts"
"github.com/thrasher-corp/gocryptotrader/exchange/websocket"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fill"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
@@ -315,26 +315,21 @@ func (e *Exchange) wsProcessWalletPushData(ctx context.Context, resp []byte) err
if err := json.Unmarshal(resp, &result); err != nil {
return err
}
creds, err := e.GetCredentials(ctx)
if err != nil {
return err
}
var changes []account.Change
subAccts := accounts.SubAccounts{accounts.NewSubAccount(asset.Spot, "")}
for x := range result.Data {
for y := range result.Data[x].Coin {
changes = append(changes, account.Change{
AssetType: asset.Spot,
Balance: &account.Balance{
Currency: result.Data[x].Coin[y].Coin,
Total: result.Data[x].Coin[y].WalletBalance.Float64(),
Free: result.Data[x].Coin[y].WalletBalance.Float64(),
UpdatedAt: result.CreationTime.Time(),
},
subAccts[0].Balances.Set(result.Data[x].Coin[y].Coin, accounts.Balance{
Total: result.Data[x].Coin[y].WalletBalance.Float64(),
Free: result.Data[x].Coin[y].WalletBalance.Float64(),
UpdatedAt: result.CreationTime.Time(),
})
}
}
e.Websocket.DataHandler <- changes
return account.ProcessChange(e.Name, changes, creds)
if err := e.Accounts.Save(ctx, subAccts, false); err != nil {
return err
}
e.Websocket.DataHandler <- subAccts
return nil
}
// wsProcessOrder the order stream to see changes to your orders in real-time.

View File

@@ -13,11 +13,11 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchange/accounts"
"github.com/thrasher-corp/gocryptotrader/exchange/order/limits"
"github.com/thrasher-corp/gocryptotrader/exchange/websocket"
"github.com/thrasher-corp/gocryptotrader/exchange/websocket/buffer"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
@@ -653,16 +653,13 @@ func (e *Exchange) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTy
return orderbook.Get(e.Name, p, assetType)
}
// UpdateAccountInfo retrieves balances for all enabled currencies
func (e *Exchange) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
var info account.Holdings
var acc account.SubAccount
var accountType string
info.Exchange = e.Name
// UpdateAccountBalances retrieves currency balances
func (e *Exchange) UpdateAccountBalances(ctx context.Context, assetType asset.Item) (accounts.SubAccounts, error) {
at, err := e.FetchAccountType(ctx)
if err != nil {
return info, err
return nil, err
}
var accountType string
switch assetType {
case asset.Spot, asset.Options, asset.USDCMarginedFutures, asset.USDTMarginedFutures:
switch at {
@@ -678,15 +675,15 @@ func (e *Exchange) UpdateAccountInfo(ctx context.Context, assetType asset.Item)
case asset.CoinMarginedFutures:
accountType = "CONTRACT"
default:
return info, fmt.Errorf("%s %w", assetType, asset.ErrNotSupported)
return nil, fmt.Errorf("%s %w", assetType, asset.ErrNotSupported)
}
balances, err := e.GetWalletBalance(ctx, accountType, "")
resp, err := e.GetWalletBalance(ctx, accountType, "")
if err != nil {
return info, err
return nil, err
}
currencyBalance := []account.Balance{}
for i := range balances.List {
for _, c := range balances.List[i].Coin {
subAccts := accounts.SubAccounts{accounts.NewSubAccount(assetType, "")}
for i := range resp.List {
for _, c := range resp.List[i].Coin {
// borrow amounts get truncated to 8 dec places when total and equity are calculated on the exchange
truncBorrow := c.BorrowAmount.Decimal().Truncate(8).InexactFloat64()
@@ -699,8 +696,7 @@ func (e *Exchange) UpdateAccountInfo(ctx context.Context, assetType asset.Item)
freeBalance = c.AvailableBalanceForSpot.Float64()
}
currencyBalance = append(currencyBalance, account.Balance{
Currency: c.Coin,
subAccts[0].Balances.Set(c.Coin, accounts.Balance{
Total: c.WalletBalance.Float64(),
Free: freeBalance,
Borrowed: c.BorrowAmount.Float64(),
@@ -709,18 +705,7 @@ func (e *Exchange) UpdateAccountInfo(ctx context.Context, assetType asset.Item)
})
}
}
acc.Currencies = currencyBalance
acc.AssetType = assetType
info.Accounts = append(info.Accounts, acc)
creds, err := e.GetCredentials(ctx)
if err != nil {
return account.Holdings{}, err
}
err = account.Process(&info, creds)
if err != nil {
return account.Holdings{}, err
}
return info, nil
return subAccts, e.Accounts.Save(ctx, subAccts, true)
}
// GetAccountFundingHistory returns funding history, deposits and
@@ -1474,7 +1459,7 @@ func (e *Exchange) getCategoryFromPair(pair currency.Pair) []asset.Item {
// ValidateAPICredentials validates current credentials used for wrapper
func (e *Exchange) ValidateAPICredentials(ctx context.Context, assetType asset.Item) error {
_, err := e.UpdateAccountInfo(ctx, assetType)
_, err := e.UpdateAccountBalances(ctx, assetType)
return e.CheckTransientError(err)
}

View File

@@ -3,4 +3,5 @@
{ "id": "5923242c464be9-25ca-483d-a743-c60101fc656f", "topic": "wallet", "creationTime": 1672364262482, "data": [ { "accountIMRate": "0.016", "accountMMRate": "0.003", "totalEquity": "12837.78330098", "totalWalletBalance": "12840.4045924", "totalMarginBalance": "12837.78330188", "totalAvailableBalance": "12632.05767702", "totalPerpUPL": "-2.62129051", "totalInitialMargin": "205.72562486", "totalMaintenanceMargin": "39.42876721", "coin": [ { "coin": "USDC", "equity": "200.62572554", "usdValue": "200.62572554", "walletBalance": "201.34882644", "availableToWithdraw": "0", "availableToBorrow": "1500000", "borrowAmount": "0", "accruedInterest": "0", "totalOrderIM": "0", "totalPositionIM": "202.99874213", "totalPositionMM": "39.14289747", "unrealisedPnl": "74.2768991", "cumRealisedPnl": "-209.1544627", "bonus": "0" }, { "coin": "BTC", "equity": "0.06488393", "usdValue": "1023.08402268", "walletBalance": "0.06488393", "availableToWithdraw": "0.06488393", "availableToBorrow": "2.5", "borrowAmount": "0", "accruedInterest": "0", "totalOrderIM": "0", "totalPositionIM": "0", "totalPositionMM": "0", "unrealisedPnl": "0", "cumRealisedPnl": "0", "bonus": "0" }, { "coin": "ETH", "equity": "0", "usdValue": "0", "walletBalance": "0", "availableToWithdraw": "0", "availableToBorrow": "26", "borrowAmount": "0", "accruedInterest": "0", "totalOrderIM": "0", "totalPositionIM": "0", "totalPositionMM": "0", "unrealisedPnl": "0", "cumRealisedPnl": "0", "bonus": "0" }, { "coin": "USDT", "equity": "11726.64664904", "usdValue": "11613.58597018", "walletBalance": "11728.54414904", "availableToWithdraw": "11723.92075829", "availableToBorrow": "2500000", "borrowAmount": "0", "accruedInterest": "0", "totalOrderIM": "0", "totalPositionIM": "2.72589075", "totalPositionMM": "0.28576575", "unrealisedPnl": "-1.8975", "cumRealisedPnl": "0.64782276", "bonus": "0" }, { "coin": "EOS3L", "equity": "215.0570412", "usdValue": "0", "walletBalance": "215.0570412", "availableToWithdraw": "215.0570412", "availableToBorrow": "0", "borrowAmount": "0", "accruedInterest": "", "totalOrderIM": "0", "totalPositionIM": "0", "totalPositionMM": "0", "unrealisedPnl": "0", "cumRealisedPnl": "0", "bonus": "0" }, { "coin": "BIT", "equity": "1.82", "usdValue": "0.48758257", "walletBalance": "1.82", "availableToWithdraw": "1.82", "availableToBorrow": "0", "borrowAmount": "0", "accruedInterest": "", "totalOrderIM": "0", "totalPositionIM": "0", "totalPositionMM": "0", "unrealisedPnl": "0", "cumRealisedPnl": "0", "bonus": "0" } ], "accountType": "UNIFIED", "accountLTV": "0.017" } ] }
{ "id": "592324fa945a30-2603-49a5-b865-21668c29f2a6", "topic": "greeks", "creationTime": 1672364262482, "data": [ { "baseCoin": "ETH", "totalDelta": "0.06999986", "totalGamma": "-0.00000001", "totalVega": "-0.00000024", "totalTheta": "0.00001314" } ] }
{"id": "592324803b2785-26fa-4214-9963-bdd4727f07be", "topic": "execution", "creationTime": 1672364174455, "data": [ { "category": "linear", "symbol": "XRPUSDT", "execFee": "0.005061", "execId": "7e2ae69c-4edf-5800-a352-893d52b446aa", "execPrice": "0.3374", "execQty": "25", "execType": "Trade", "execValue": "8.435", "isMaker": false, "feeRate": "0.0006", "tradeIv": "", "markIv": "", "blockTradeId": "", "markPrice": "0.3391", "indexPrice": "", "underlyingPrice": "", "leavesQty": "0", "orderId": "f6e324ff-99c2-4e89-9739-3086e47f9381", "orderLinkId": "", "orderPrice": "0.3207", "orderQty":"25","orderType":"Market","stopOrderType":"UNKNOWN","side":"Sell","execTime":"1672364174443","isLeverage": "0","closedSize": "","seq":4688002127}]}
{ "id": "someID", "topic": "order", "creationTime": 1672364262474, "data": [{"category":"linear","symbol":"BTCUSDT","orderId":"c1956690-b731-4191-97c0-94b00422231b","orderLinkId":"","blockTradeId":"","side":"Sell","positionIdx":0,"orderStatus":"Filled","cancelType":"UNKNOWN","rejectReason":"EC_NoError","timeInForce":"IOC","isLeverage":"","price":"4.033","qty":"1.7","avgPrice":"4.24","leavesQty":"0","leavesValue":"0","cumExecQty":"1.7","cumExecValue":"7.2086","cumExecFee":"0.00288344","orderType":"Market","stopOrderType":"","orderIv":"","triggerPrice":"","takeProfit":"","stopLoss":"","triggerBy":"","tpTriggerBy":"","slTriggerBy":"","triggerDirection":0,"placeType":"","lastPriceOnCreated":"4.245","closeOnTrigger":false,"reduceOnly":false,"smpGroup":0,"smpType":"None","smpOrderId":"","slLimitPrice":"0","tpLimitPrice":"0","tpslMode":"UNKNOWN","createType":"CreateByUser","marketUnit":"","createdTime":"1733778525913","updatedTime":"1733778525917","feeCurrency":"","closedPnl":"0"}]}
{ "id": "someID", "topic": "order", "creationTime": 1672364262474, "data": [{"category":"linear","symbol":"BTCUSDT","orderId":"c1956690-b731-4191-97c0-94b00422231b","orderLinkId":"","blockTradeId":"","side":"Sell","positionIdx":0,"orderStatus":"Filled","cancelType":"UNKNOWN","rejectReason":"EC_NoError","timeInForce":"IOC","isLeverage":"","price":"4.033","qty":"1.7","avgPrice":"4.24","leavesQty":"0","leavesValue":"0","cumExecQty":"1.7","cumExecValue":"7.2086","cumExecFee":"0.00288344","orderType":"Market","stopOrderType":"","orderIv":"","triggerPrice":"","takeProfit":"","stopLoss":"","triggerBy":"","tpTriggerBy":"","slTriggerBy":"","triggerDirection":0,"placeType":"","lastPriceOnCreated":"4.245","closeOnTrigger":false,"reduceOnly":false,"smpGroup":0,"smpType":"None","smpOrderId":"","slLimitPrice":"0","tpLimitPrice":"0","tpslMode":"UNKNOWN","createType":"CreateByUser","marketUnit":"","createdTime":"1733778525913","updatedTime":"1733778525917","feeCurrency":"","closedPnl":"0"}]}
{ "id": "FANGLE-ACCOUNTS", "topic": "wallet", "creationTime": 1672364262483, "data": [ { "coin": [ { "coin": "BREAK", "walletBalance": "201.34882644"}]}]}