mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-03 15:10:49 +00:00
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:
@@ -1,6 +1,7 @@
|
||||
package kucoin
|
||||
|
||||
import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
@@ -330,14 +331,14 @@ type FuturesFundingHistory struct {
|
||||
|
||||
// FuturesAccount holds futures account detail information
|
||||
type FuturesAccount struct {
|
||||
AccountEquity float64 `json:"accountEquity"` // marginBalance + Unrealised PNL
|
||||
UnrealisedPNL float64 `json:"unrealisedPNL"` // unrealised profit and loss
|
||||
MarginBalance float64 `json:"marginBalance"` // positionMargin + orderMargin + frozenFunds + availableBalance - unrealisedPNL
|
||||
PositionMargin float64 `json:"positionMargin"`
|
||||
OrderMargin float64 `json:"orderMargin"`
|
||||
FrozenFunds float64 `json:"frozenFunds"` // frozen funds for withdrawal and out-transfer
|
||||
AvailableBalance float64 `json:"availableBalance"`
|
||||
Currency string `json:"currency"`
|
||||
AccountEquity float64 `json:"accountEquity"` // marginBalance + Unrealised PNL
|
||||
UnrealisedPNL float64 `json:"unrealisedPNL"` // unrealised profit and loss
|
||||
MarginBalance float64 `json:"marginBalance"` // positionMargin + orderMargin + frozenFunds + availableBalance - unrealisedPNL
|
||||
PositionMargin float64 `json:"positionMargin"`
|
||||
OrderMargin float64 `json:"orderMargin"`
|
||||
FrozenFunds float64 `json:"frozenFunds"` // frozen funds for withdrawal and out-transfer
|
||||
AvailableBalance float64 `json:"availableBalance"`
|
||||
Currency currency.Code `json:"currency"`
|
||||
}
|
||||
|
||||
// FuturesTransactionHistory represents a transaction history
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package kucoin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -2338,11 +2339,24 @@ func TestGetAuthenticatedServersInstances(t *testing.T) {
|
||||
|
||||
func TestPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
ku := testInstance(t)
|
||||
ku.SetCredentials("mock", "test", "test", "", "", "")
|
||||
ku.API.AuthenticatedSupport = true
|
||||
ku.API.AuthenticatedWebsocketSupport = true
|
||||
testexch.FixtureToDataHandler(t, "testdata/wsHandleData.json", ku.wsHandleData)
|
||||
|
||||
e := testInstance(t) //nolint:govet // Intentional shadow
|
||||
e.SetCredentials("mock", "test", "test", "", "", "")
|
||||
e.API.AuthenticatedSupport = true
|
||||
e.API.AuthenticatedWebsocketSupport = true
|
||||
|
||||
fErrs := testexch.FixtureToDataHandlerWithErrors(t, "testdata/wsHandleData.json", func(ctx context.Context, r []byte) error {
|
||||
if bytes.Contains(r, []byte("FANGLE-ACCOUNTS")) {
|
||||
hold := e.Accounts
|
||||
e.Accounts = nil
|
||||
defer func() { e.Accounts = hold }()
|
||||
}
|
||||
return e.wsHandleData(ctx, r)
|
||||
})
|
||||
close(e.Websocket.DataHandler)
|
||||
assert.Len(t, e.Websocket.DataHandler, 29, "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")
|
||||
}
|
||||
|
||||
func TestGenerateSubscriptions(t *testing.T) {
|
||||
@@ -2954,12 +2968,12 @@ func getFirstTradablePairOfAssets(ctx context.Context) {
|
||||
futuresTradablePair.Delimiter = ""
|
||||
}
|
||||
|
||||
func TestUpdateAccountInfo(t *testing.T) {
|
||||
func TestUpdateAccountBalances(t *testing.T) {
|
||||
t.Parallel()
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
|
||||
assetTypes := e.GetAssetTypes(true)
|
||||
for _, assetType := range assetTypes {
|
||||
result, err := e.UpdateAccountInfo(t.Context(), assetType)
|
||||
result, err := e.UpdateAccountBalances(t.Context(), assetType)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
@@ -763,12 +763,12 @@ type StopOrder struct {
|
||||
|
||||
// AccountInfo represents account information
|
||||
type AccountInfo struct {
|
||||
ID string `json:"id"`
|
||||
Currency string `json:"currency"`
|
||||
AccountType string `json:"type"` // Account type:,main、trade、trade_hf、margin
|
||||
Balance types.Number `json:"balance"`
|
||||
Available types.Number `json:"available"`
|
||||
Holds types.Number `json:"holds"`
|
||||
ID string `json:"id"`
|
||||
Currency currency.Code `json:"currency"`
|
||||
AccountType string `json:"type"` // Account type: main, trade, trade_hf, margin
|
||||
Balance types.Number `json:"balance"`
|
||||
Available types.Number `json:"available"`
|
||||
Holds types.Number `json:"holds"`
|
||||
}
|
||||
|
||||
// CrossMarginAccountDetail represents a cross-margin account details
|
||||
@@ -1410,14 +1410,14 @@ type WsTradeOrder struct {
|
||||
|
||||
// WsAccountBalance represents a Account Balance push data
|
||||
type WsAccountBalance struct {
|
||||
Total float64 `json:"total,string"`
|
||||
Available float64 `json:"available,string"`
|
||||
AvailableChange float64 `json:"availableChange,string"`
|
||||
Currency string `json:"currency"`
|
||||
Hold float64 `json:"hold,string"`
|
||||
HoldChange float64 `json:"holdChange,string"`
|
||||
RelationEvent string `json:"relationEvent"`
|
||||
RelationEventID string `json:"relationEventId"`
|
||||
Total float64 `json:"total,string"`
|
||||
Available float64 `json:"available,string"`
|
||||
AvailableChange float64 `json:"availableChange,string"`
|
||||
Currency currency.Code `json:"currency"`
|
||||
Hold float64 `json:"hold,string"`
|
||||
HoldChange float64 `json:"holdChange,string"`
|
||||
RelationEvent string `json:"relationEvent"`
|
||||
RelationEventID string `json:"relationEventId"`
|
||||
RelationContext struct {
|
||||
Symbol string `json:"symbol"`
|
||||
TradeID string `json:"tradeId"`
|
||||
@@ -1630,10 +1630,10 @@ type WsFuturesOrderMarginEvent struct {
|
||||
|
||||
// WsFuturesAvailableBalance represents an available balance push data for futures account
|
||||
type WsFuturesAvailableBalance struct {
|
||||
AvailableBalance float64 `json:"availableBalance"`
|
||||
HoldBalance float64 `json:"holdBalance"`
|
||||
Currency string `json:"currency"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
AvailableBalance float64 `json:"availableBalance"`
|
||||
HoldBalance float64 `json:"holdBalance"`
|
||||
Currency currency.Code `json:"currency"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// WsFuturesWithdrawalAmountAndTransferOutAmountEvent represents Withdrawal Amount & Transfer-Out Amount Event push data
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"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"
|
||||
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/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
@@ -359,24 +359,18 @@ func (e *Exchange) processFuturesAccountBalanceEvent(ctx context.Context, respDa
|
||||
if err := json.Unmarshal(respData, &resp); err != nil {
|
||||
return err
|
||||
}
|
||||
creds, err := e.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
subAccts := accounts.SubAccounts{accounts.NewSubAccount(asset.Futures, "")}
|
||||
subAccts[0].Balances.Set(resp.Currency, accounts.Balance{
|
||||
Total: resp.AvailableBalance + resp.HoldBalance,
|
||||
Hold: resp.HoldBalance,
|
||||
Free: resp.AvailableBalance,
|
||||
UpdatedAt: resp.Timestamp.Time(),
|
||||
})
|
||||
if err := e.Accounts.Save(ctx, subAccts, false); err != nil {
|
||||
return err
|
||||
}
|
||||
changes := []account.Change{
|
||||
{
|
||||
AssetType: asset.Futures,
|
||||
Balance: &account.Balance{
|
||||
Currency: currency.NewCode(resp.Currency),
|
||||
Total: resp.AvailableBalance + resp.HoldBalance,
|
||||
Hold: resp.HoldBalance,
|
||||
Free: resp.AvailableBalance,
|
||||
UpdatedAt: resp.Timestamp.Time(),
|
||||
},
|
||||
},
|
||||
}
|
||||
e.Websocket.DataHandler <- changes
|
||||
return account.ProcessChange(e.Name, changes, creds)
|
||||
e.Websocket.DataHandler <- subAccts
|
||||
return nil
|
||||
}
|
||||
|
||||
// processFuturesStopOrderLifecycleEvent processes futures stop orders lifecycle events.
|
||||
@@ -684,29 +678,22 @@ func (e *Exchange) processMarginLendingTradeOrderEvent(respData []byte) error {
|
||||
|
||||
// processAccountBalanceChange processes an account balance change
|
||||
func (e *Exchange) processAccountBalanceChange(ctx context.Context, respData []byte) error {
|
||||
response := WsAccountBalance{}
|
||||
err := json.Unmarshal(respData, &response)
|
||||
if err != nil {
|
||||
resp := WsAccountBalance{}
|
||||
if err := json.Unmarshal(respData, &resp); err != nil {
|
||||
return err
|
||||
}
|
||||
creds, err := e.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
subAccts := accounts.SubAccounts{accounts.NewSubAccount(asset.Futures, "")}
|
||||
subAccts[0].Balances.Set(resp.Currency, accounts.Balance{
|
||||
Total: resp.Total,
|
||||
Hold: resp.Hold,
|
||||
Free: resp.Available,
|
||||
UpdatedAt: resp.Time.Time(),
|
||||
})
|
||||
if err := e.Accounts.Save(ctx, subAccts, false); err != nil {
|
||||
return err
|
||||
}
|
||||
changes := []account.Change{
|
||||
{
|
||||
AssetType: asset.Futures,
|
||||
Balance: &account.Balance{
|
||||
Currency: currency.NewCode(response.Currency),
|
||||
Total: response.Total,
|
||||
Hold: response.Hold,
|
||||
Free: response.Available,
|
||||
UpdatedAt: response.Time.Time(),
|
||||
},
|
||||
},
|
||||
}
|
||||
e.Websocket.DataHandler <- changes
|
||||
return account.ProcessChange(e.Name, changes, creds)
|
||||
e.Websocket.DataHandler <- subAccts
|
||||
return nil
|
||||
}
|
||||
|
||||
// processOrderChangeEvent processes order update events.
|
||||
|
||||
@@ -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/collateral"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
|
||||
@@ -406,60 +406,46 @@ func (e *Exchange) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset
|
||||
return orderbook.Get(e.Name, p, a)
|
||||
}
|
||||
|
||||
// UpdateAccountInfo retrieves balances for all enabled currencies
|
||||
func (e *Exchange) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
|
||||
holding := account.Holdings{Exchange: e.Name}
|
||||
err := e.CurrencyPairs.IsAssetEnabled(assetType)
|
||||
if err != nil {
|
||||
return holding, fmt.Errorf("%w %v", asset.ErrNotSupported, assetType)
|
||||
// UpdateAccountBalances retrieves currency balances
|
||||
func (e *Exchange) UpdateAccountBalances(ctx context.Context, assetType asset.Item) (accounts.SubAccounts, error) {
|
||||
if err := e.CurrencyPairs.IsAssetEnabled(assetType); err != nil {
|
||||
return nil, fmt.Errorf("%w: %q", asset.ErrNotSupported, assetType)
|
||||
}
|
||||
subAccts := accounts.SubAccounts{accounts.NewSubAccount(assetType, "")}
|
||||
switch assetType {
|
||||
case asset.Futures:
|
||||
balances := make([]account.Balance, 2)
|
||||
for i, settlement := range []string{"XBT", "USDT"} {
|
||||
accountH, err := e.GetFuturesAccountOverview(ctx, settlement)
|
||||
for _, settlement := range []string{"XBT", "USDT"} {
|
||||
resp, err := e.GetFuturesAccountOverview(ctx, settlement)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
|
||||
balances[i] = account.Balance{
|
||||
Currency: currency.NewCode(accountH.Currency),
|
||||
Total: accountH.AvailableBalance + accountH.FrozenFunds,
|
||||
Hold: accountH.FrozenFunds,
|
||||
Free: accountH.AvailableBalance,
|
||||
return nil, err
|
||||
}
|
||||
subAccts[0].Balances.Set(resp.Currency, accounts.Balance{
|
||||
Total: resp.AvailableBalance + resp.FrozenFunds,
|
||||
Hold: resp.FrozenFunds,
|
||||
Free: resp.AvailableBalance,
|
||||
})
|
||||
}
|
||||
holding.Accounts = append(holding.Accounts, account.SubAccount{
|
||||
AssetType: assetType,
|
||||
Currencies: balances,
|
||||
})
|
||||
case asset.Spot, asset.Margin:
|
||||
accountH, err := e.GetAllAccounts(ctx, currency.EMPTYCODE, "")
|
||||
resp, err := e.GetAllAccounts(ctx, currency.EMPTYCODE, "")
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
return nil, err
|
||||
}
|
||||
for x := range accountH {
|
||||
if accountH[x].AccountType == "margin" && assetType == asset.Spot {
|
||||
for i := range resp {
|
||||
if resp[i].AccountType == "margin" && assetType == asset.Spot {
|
||||
continue
|
||||
} else if accountH[x].AccountType == "trade" && assetType == asset.Margin {
|
||||
} else if resp[i].AccountType == "trade" && assetType == asset.Margin {
|
||||
continue
|
||||
}
|
||||
holding.Accounts = append(holding.Accounts, account.SubAccount{
|
||||
AssetType: assetType,
|
||||
Currencies: []account.Balance{
|
||||
{
|
||||
Currency: currency.NewCode(accountH[x].Currency),
|
||||
Total: accountH[x].Balance.Float64(),
|
||||
Hold: accountH[x].Holds.Float64(),
|
||||
Free: accountH[x].Available.Float64(),
|
||||
},
|
||||
},
|
||||
subAccts[0].Balances.Set(resp[i].Currency, accounts.Balance{
|
||||
Total: resp[i].Balance.Float64(),
|
||||
Hold: resp[i].Holds.Float64(),
|
||||
Free: resp[i].Available.Float64(),
|
||||
})
|
||||
}
|
||||
default:
|
||||
return holding, fmt.Errorf("%w %v", asset.ErrNotSupported, assetType)
|
||||
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, assetType)
|
||||
}
|
||||
return holding, nil
|
||||
return subAccts, e.Accounts.Save(ctx, subAccts, true)
|
||||
}
|
||||
|
||||
// GetAccountFundingHistory returns funding history, deposits and
|
||||
@@ -1717,7 +1703,7 @@ func (e *Exchange) ValidateCredentials(ctx context.Context, assetType asset.Item
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = e.UpdateAccountInfo(ctx, assetType)
|
||||
_, err = e.UpdateAccountBalances(ctx, assetType)
|
||||
return e.CheckTransientError(err)
|
||||
}
|
||||
|
||||
@@ -1860,10 +1846,9 @@ func (e *Exchange) GetAvailableTransferChains(ctx context.Context, cryptocurrenc
|
||||
return chains, nil
|
||||
}
|
||||
|
||||
// ValidateAPICredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
// ValidateAPICredentials validates current credentials used for wrapper functionality
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
1
exchanges/kucoin/testdata/wsHandleData.json
vendored
1
exchanges/kucoin/testdata/wsHandleData.json
vendored
@@ -37,3 +37,4 @@
|
||||
{"topic":"/contract/instrument:ETHUSDCM","subject":"funding.rate","data":{"granularity":60000,"fundingRate":-0.002966,"timestamp":1551770400000}}
|
||||
{"topic":"/contract/instrument:ETHUSDCM","subject":"mark.index.price","data":{"granularity":1000,"indexPrice":4000.23,"markPrice":4010.52,"timestamp":1551770400000}}
|
||||
{"type":"message","topic":"/market/level2:BTC-USDT","subject":"trade.l2update","data":{"changes":{"asks":[["18906","0.00331","14103845"],["18907.3","0.58751503","14103844"]],"bids":[["18891.9","0.15688","14103847"]]},"sequenceEnd":14103847,"sequenceStart":14103844,"symbol":"BTC-USDT","time":1663747970273}}
|
||||
{"userId":"xbc453tg732eba53a88ggyt8c","topic":"/contractAccount/wallet","subject":"availableBalance.change","data":{"availableBalance":5923,"holdBalance":2312,"currency":"FANGLE-ACCOUNTS","timestamp":1553842862614}}
|
||||
|
||||
Reference in New Issue
Block a user