mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-18 07:26:50 +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:
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/dispatch"
|
||||
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/binance"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/binanceus"
|
||||
@@ -539,45 +538,6 @@ func GetRelatableCurrencies(p currency.Pair, incOrig, incUSDT bool) currency.Pai
|
||||
return pairs
|
||||
}
|
||||
|
||||
// GetCollatedExchangeAccountInfoByCoin collates individual exchange account
|
||||
// information and turns it into a map string of exchange.AccountCurrencyInfo
|
||||
func GetCollatedExchangeAccountInfoByCoin(accounts []account.Holdings) map[currency.Code]account.Balance {
|
||||
result := make(map[currency.Code]account.Balance)
|
||||
for x := range accounts {
|
||||
for y := range accounts[x].Accounts {
|
||||
for z := range accounts[x].Accounts[y].Currencies {
|
||||
currencyName := accounts[x].Accounts[y].Currencies[z].Currency
|
||||
total := accounts[x].Accounts[y].Currencies[z].Total
|
||||
onHold := accounts[x].Accounts[y].Currencies[z].Hold
|
||||
avail := accounts[x].Accounts[y].Currencies[z].AvailableWithoutBorrow
|
||||
free := accounts[x].Accounts[y].Currencies[z].Free
|
||||
borrowed := accounts[x].Accounts[y].Currencies[z].Borrowed
|
||||
|
||||
info, ok := result[currencyName]
|
||||
if !ok {
|
||||
accountInfo := account.Balance{
|
||||
Currency: currencyName,
|
||||
Total: total,
|
||||
Hold: onHold,
|
||||
Free: free,
|
||||
AvailableWithoutBorrow: avail,
|
||||
Borrowed: borrowed,
|
||||
}
|
||||
result[currencyName] = accountInfo
|
||||
} else {
|
||||
info.Hold += onHold
|
||||
info.Total += total
|
||||
info.Free += free
|
||||
info.AvailableWithoutBorrow += avail
|
||||
info.Borrowed += borrowed
|
||||
result[currencyName] = info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest
|
||||
// price for a given currency pair and asset type
|
||||
func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, a asset.Item) (string, error) {
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
"github.com/thrasher-corp/gocryptotrader/dispatch"
|
||||
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/protocol"
|
||||
@@ -707,67 +706,6 @@ func TestGetExchangeNamesByCurrency(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) {
|
||||
t.Parallel()
|
||||
CreateTestBot(t)
|
||||
|
||||
var exchangeInfo []account.Holdings
|
||||
|
||||
var bitfinexHoldings account.Holdings
|
||||
bitfinexHoldings.Exchange = "Bitfinex"
|
||||
bitfinexHoldings.Accounts = append(bitfinexHoldings.Accounts,
|
||||
account.SubAccount{
|
||||
Currencies: []account.Balance{
|
||||
{
|
||||
Currency: currency.BTC,
|
||||
Total: 100,
|
||||
Hold: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
exchangeInfo = append(exchangeInfo, bitfinexHoldings)
|
||||
|
||||
var bitstampHoldings account.Holdings
|
||||
bitstampHoldings.Exchange = testExchange
|
||||
bitstampHoldings.Accounts = append(bitstampHoldings.Accounts,
|
||||
account.SubAccount{
|
||||
Currencies: []account.Balance{
|
||||
{
|
||||
Currency: currency.LTC,
|
||||
Total: 100,
|
||||
Hold: 0,
|
||||
},
|
||||
{
|
||||
Currency: currency.BTC,
|
||||
Total: 100,
|
||||
Hold: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
exchangeInfo = append(exchangeInfo, bitstampHoldings)
|
||||
|
||||
result := GetCollatedExchangeAccountInfoByCoin(exchangeInfo)
|
||||
if len(result) == 0 {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
amount, ok := result[currency.BTC]
|
||||
if !ok {
|
||||
t.Fatal("Expected currency was not found in result map")
|
||||
}
|
||||
|
||||
if amount.Total != 200 {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
_, ok = result[currency.ETH]
|
||||
if ok {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) {
|
||||
t.Parallel()
|
||||
CreateTestBot(t)
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
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/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio"
|
||||
@@ -124,152 +124,97 @@ func (m *portfolioManager) processPortfolio() {
|
||||
}
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
exchanges, err := m.exchangeManager.GetExchanges()
|
||||
if err != nil {
|
||||
log.Errorf(log.PortfolioMgr, "Portfolio manager cannot get exchanges: %v", err)
|
||||
if err := m.updateExchangeBalances(); err != nil {
|
||||
log.Errorf(log.PortfolioMgr, "Portfolio updateExchangeBalances error: %v", err)
|
||||
}
|
||||
allExchangesHoldings := m.getExchangeAccountInfo(exchanges)
|
||||
m.seedExchangeAccountInfo(allExchangesHoldings)
|
||||
|
||||
data := m.base.GetPortfolioAddressesGroupedByCoin()
|
||||
for key, value := range data {
|
||||
if err := m.base.UpdatePortfolio(context.TODO(), value, key); err != nil {
|
||||
log.Errorf(log.PortfolioMgr, "Portfolio manager: UpdatePortfolio error: %s for currency %s\n", err, key)
|
||||
log.Errorf(log.PortfolioMgr, "Portfolio manager: UpdatePortfolio error: %s for currency %s", err, key)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio manager: Successfully updated address balance for %s address(es) %s\n", key, value)
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio manager: Successfully updated address balance for %s address(es) %s", key, value)
|
||||
}
|
||||
atomic.CompareAndSwapInt32(&m.processing, 1, 0)
|
||||
}
|
||||
|
||||
// seedExchangeAccountInfo seeds account info
|
||||
func (m *portfolioManager) seedExchangeAccountInfo(accounts []account.Holdings) {
|
||||
if len(accounts) == 0 {
|
||||
return
|
||||
// updateExchangeBalances calls UpdateAccountBalance on each exchange, and transfers the account balances into portfolio
|
||||
func (m *portfolioManager) updateExchangeBalances() error {
|
||||
if err := common.NilGuard(m); err != nil {
|
||||
return err
|
||||
}
|
||||
for x := range accounts {
|
||||
var currencies []account.Balance
|
||||
for y := range accounts[x].Accounts {
|
||||
next:
|
||||
for z := range accounts[x].Accounts[y].Currencies {
|
||||
for i := range currencies {
|
||||
if !accounts[x].Accounts[y].Currencies[z].Currency.Equal(currencies[i].Currency) {
|
||||
continue
|
||||
}
|
||||
currencies[i].Hold += accounts[x].Accounts[y].Currencies[z].Hold
|
||||
currencies[i].Total += accounts[x].Accounts[y].Currencies[z].Total
|
||||
currencies[i].AvailableWithoutBorrow += accounts[x].Accounts[y].Currencies[z].AvailableWithoutBorrow
|
||||
currencies[i].Free += accounts[x].Accounts[y].Currencies[z].Free
|
||||
currencies[i].Borrowed += accounts[x].Accounts[y].Currencies[z].Borrowed
|
||||
continue next
|
||||
}
|
||||
currencies = append(currencies, account.Balance{
|
||||
Currency: accounts[x].Accounts[y].Currencies[z].Currency,
|
||||
Total: accounts[x].Accounts[y].Currencies[z].Total,
|
||||
Hold: accounts[x].Accounts[y].Currencies[z].Hold,
|
||||
Free: accounts[x].Accounts[y].Currencies[z].Free,
|
||||
AvailableWithoutBorrow: accounts[x].Accounts[y].Currencies[z].AvailableWithoutBorrow,
|
||||
Borrowed: accounts[x].Accounts[y].Currencies[z].Borrowed,
|
||||
})
|
||||
exchanges, errs := m.exchangeManager.GetExchanges()
|
||||
if errs != nil {
|
||||
return fmt.Errorf("portfolio manager cannot get exchanges: %w", errs)
|
||||
}
|
||||
for _, e := range exchanges {
|
||||
if !e.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
if !e.IsRESTAuthenticationSupported() {
|
||||
if m.base.Verbose {
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio skipping %s due to disabled authenticated API support", e.GetName())
|
||||
}
|
||||
continue
|
||||
}
|
||||
assetTypes := asset.Items{asset.Spot}
|
||||
if e.HasAssetTypeAccountSegregation() {
|
||||
assetTypes = e.GetAssetTypes(true)
|
||||
}
|
||||
|
||||
for j := range currencies {
|
||||
if !m.base.ExchangeAddressCoinExists(accounts[x].Exchange, currencies[j].Currency) {
|
||||
if currencies[j].Total <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio: Adding new exchange address: %s, %s, %f, %s\n",
|
||||
accounts[x].Exchange,
|
||||
currencies[j].Currency,
|
||||
currencies[j].Total,
|
||||
portfolio.ExchangeAddress)
|
||||
|
||||
m.base.Addresses = append(m.base.Addresses, portfolio.Address{
|
||||
Address: accounts[x].Exchange,
|
||||
CoinType: currencies[j].Currency,
|
||||
Balance: currencies[j].Total,
|
||||
Description: portfolio.ExchangeAddress,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if currencies[j].Total <= 0 {
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio: Removing %s %s entry.\n",
|
||||
accounts[x].Exchange,
|
||||
currencies[j].Currency)
|
||||
m.base.RemoveExchangeAddress(accounts[x].Exchange, currencies[j].Currency)
|
||||
continue
|
||||
}
|
||||
|
||||
balance, ok := m.base.GetAddressBalance(accounts[x].Exchange,
|
||||
portfolio.ExchangeAddress,
|
||||
currencies[j].Currency)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if balance != currencies[j].Total {
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio: Updating %s %s entry with balance %f.\n",
|
||||
accounts[x].Exchange,
|
||||
currencies[j].Currency,
|
||||
currencies[j].Total)
|
||||
m.base.UpdateExchangeAddressBalance(accounts[x].Exchange,
|
||||
currencies[j].Currency,
|
||||
currencies[j].Total)
|
||||
for _, a := range assetTypes {
|
||||
if _, err := e.UpdateAccountBalances(context.TODO(), a); err != nil {
|
||||
errs = common.AppendError(errs, fmt.Errorf("error updating %s %s account balances: %w", e.GetName(), a, err))
|
||||
}
|
||||
}
|
||||
if err := m.updateExchangeAddressBalances(e); err != nil {
|
||||
errs = common.AppendError(errs, fmt.Errorf("error updating %s account balances: %w", e.GetName(), err))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// getExchangeAccountInfo returns all the current enabled exchanges
|
||||
func (m *portfolioManager) getExchangeAccountInfo(exchanges []exchange.IBotExchange) []account.Holdings {
|
||||
response := make([]account.Holdings, 0, len(exchanges))
|
||||
for x := range exchanges {
|
||||
if !exchanges[x].IsEnabled() {
|
||||
continue
|
||||
}
|
||||
if !exchanges[x].IsRESTAuthenticationSupported() {
|
||||
if m.base.Verbose {
|
||||
log.Debugf(log.PortfolioMgr,
|
||||
"skipping %s due to disabled authenticated API support.\n",
|
||||
exchanges[x].GetName())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
assetTypes := asset.Items{asset.Spot}
|
||||
if exchanges[x].HasAssetTypeAccountSegregation() {
|
||||
// Get enabled exchange asset types to sync account information.
|
||||
// TODO: Update with further api key asset segration e.g. Kraken has
|
||||
// individual keys associated with different asset types.
|
||||
assetTypes = exchanges[x].GetAssetTypes(true)
|
||||
}
|
||||
|
||||
exchangeHoldings := account.Holdings{
|
||||
Exchange: exchanges[x].GetName(),
|
||||
Accounts: make([]account.SubAccount, 0, len(assetTypes)),
|
||||
}
|
||||
for y := range assetTypes {
|
||||
// Update account info to process account updates in memory on
|
||||
// every fetch.
|
||||
accountHoldings, err := exchanges[x].UpdateAccountInfo(context.TODO(), assetTypes[y])
|
||||
if err != nil {
|
||||
log.Errorf(log.PortfolioMgr,
|
||||
"Error encountered retrieving exchange account info for %s. Error %s\n",
|
||||
exchanges[x].GetName(),
|
||||
err)
|
||||
// updateExchangeAddressBalances fetches and collates all account balances with their deposit addresses
|
||||
func (m *portfolioManager) updateExchangeAddressBalances(e exchange.IBotExchange) error {
|
||||
if err := common.NilGuard(m, e); err != nil {
|
||||
return err
|
||||
}
|
||||
currs, err := e.GetBase().Accounts.CurrencyBalances(nil, asset.All)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eName := e.GetName()
|
||||
for c, b := range currs {
|
||||
if !m.base.ExchangeAddressCoinExists(e.GetName(), c) {
|
||||
if b.Total <= 0 {
|
||||
continue
|
||||
}
|
||||
exchangeHoldings.Accounts = append(exchangeHoldings.Accounts, accountHoldings.Accounts...)
|
||||
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio: Adding new exchange address: %s, %s, %f, %s", eName, c, b.Total, portfolio.ExchangeAddress)
|
||||
|
||||
m.base.Addresses = append(m.base.Addresses, portfolio.Address{
|
||||
Address: eName,
|
||||
CoinType: c,
|
||||
Balance: b.Total,
|
||||
Description: portfolio.ExchangeAddress,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if len(exchangeHoldings.Accounts) > 0 {
|
||||
response = append(response, exchangeHoldings)
|
||||
|
||||
if b.Total <= 0 {
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio: Removing %s %s entry", eName, c)
|
||||
m.base.RemoveExchangeAddress(eName, c)
|
||||
continue
|
||||
}
|
||||
|
||||
if balance, ok := m.base.GetAddressBalance(eName, portfolio.ExchangeAddress, c); ok && balance != b.Total {
|
||||
log.Debugf(log.PortfolioMgr, "Portfolio: Updating %s %s entry with balance %f", eName, c, b.Total)
|
||||
m.base.UpdateExchangeAddressBalance(eName, c, b.Total)
|
||||
}
|
||||
}
|
||||
return response
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAddress adds a new portfolio address for the portfolio manager to track
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchange/accounts"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio"
|
||||
)
|
||||
|
||||
func TestSetupPortfolioManager(t *testing.T) {
|
||||
@@ -94,3 +101,98 @@ func TestProcessPortfolio(t *testing.T) {
|
||||
|
||||
m.processPortfolio()
|
||||
}
|
||||
|
||||
func TestUpdateExchangeBalances(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.ErrorContains(t, (*portfolioManager)(nil).updateExchangeBalances(), "nil pointer: *engine.portfolioManager")
|
||||
assert.ErrorIs(t, new(portfolioManager).updateExchangeBalances(), ErrNilSubsystem)
|
||||
|
||||
m, err := setupPortfolioManager(NewExchangeManager(), 0, &portfolio.Base{Verbose: true})
|
||||
require.NoError(t, err, "setupPortfolioManager must not error")
|
||||
assert.NoError(t, m.updateExchangeBalances(), "updateExchangeBalances should not error with an empty exchange list")
|
||||
|
||||
e := &mockExchange{err: errors.New("Mock UpdateBalanceError")}
|
||||
m.exchangeManager.exchanges = map[string]exchange.IBotExchange{"mock": e}
|
||||
assert.NoError(t, m.updateExchangeBalances(), "updateExchangeBalances should not error on disabled exchanges")
|
||||
|
||||
e.enabled = true
|
||||
assert.NoError(t, m.updateExchangeBalances(), "updateExchangeBalances should skip exchange without auth support")
|
||||
|
||||
e.authSupported = true
|
||||
assert.ErrorIs(t, m.updateExchangeBalances(), e.err, "error should contain the UpdateAccountBalances error message")
|
||||
}
|
||||
|
||||
func TestUpdateExchangeAddressBalances(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.ErrorContains(t, (*portfolioManager)(nil).updateExchangeAddressBalances(nil), "nil pointer: *engine.portfolioManager")
|
||||
assert.ErrorContains(t, new(portfolioManager).updateExchangeAddressBalances(nil), "nil pointer: <nil>")
|
||||
|
||||
e := &mockExchange{enabled: false, err: errors.New("Mock UpdateBalanceError")}
|
||||
m, err := setupPortfolioManager(NewExchangeManager(), 0, nil)
|
||||
require.NoError(t, err, "setupPortfolioManager must not error")
|
||||
assert.ErrorContains(t, m.updateExchangeAddressBalances(e), "nil pointer: *accounts.Accounts", "updateExchangeAddressBalances should propagate CurrencyBalances errors")
|
||||
|
||||
a := accounts.MustNewAccounts(e)
|
||||
e.accounts = a
|
||||
subAcct := accounts.NewSubAccount(asset.Spot, "")
|
||||
subAcct.Balances.Set(currency.BTC, accounts.Balance{Total: 1.5})
|
||||
subAcct.Balances.Set(currency.ETH, accounts.Balance{Total: 0})
|
||||
require.NoError(t, a.Save(t.Context(), accounts.SubAccounts{subAcct}, false), "accounts.Save must not error")
|
||||
require.NoError(t, m.updateExchangeAddressBalances(e))
|
||||
require.Len(t, m.base.Addresses, 1, "must have one address for the positive balance")
|
||||
assert.Equal(t, 1.5, m.base.Addresses[0].Balance, "balance should match on a new address")
|
||||
|
||||
subAcct.Balances.Set(currency.BTC, accounts.Balance{Total: 2})
|
||||
require.NoError(t, a.Save(t.Context(), accounts.SubAccounts{subAcct}, true), "accounts.Save must not error")
|
||||
require.NoError(t, m.updateExchangeAddressBalances(e))
|
||||
require.Len(t, m.base.Addresses, 1, "must have one address for the positive balance")
|
||||
assert.Equal(t, 2.0, m.base.Addresses[0].Balance, "balance should match after update existing address")
|
||||
|
||||
subAcct.Balances.Set(currency.BTC, accounts.Balance{Total: 0})
|
||||
require.NoError(t, a.Save(t.Context(), accounts.SubAccounts{subAcct}, true), "accounts.Save must not error")
|
||||
require.NoError(t, m.updateExchangeAddressBalances(e))
|
||||
assert.Empty(t, m.base.Addresses, "should have removed address with no balance")
|
||||
}
|
||||
|
||||
// mockExchange is a minimal mock for testing
|
||||
type mockExchange struct {
|
||||
exchange.IBotExchange
|
||||
enabled bool
|
||||
authSupported bool
|
||||
err error
|
||||
accounts *accounts.Accounts
|
||||
}
|
||||
|
||||
func (m *mockExchange) GetName() string {
|
||||
return "mocky"
|
||||
}
|
||||
|
||||
func (m *mockExchange) IsEnabled() bool {
|
||||
return m.enabled
|
||||
}
|
||||
|
||||
func (m *mockExchange) IsRESTAuthenticationSupported() bool {
|
||||
return m.authSupported
|
||||
}
|
||||
|
||||
func (m *mockExchange) HasAssetTypeAccountSegregation() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *mockExchange) GetAssetTypes(bool) asset.Items {
|
||||
return asset.Items{asset.Spot, asset.Futures}
|
||||
}
|
||||
|
||||
func (m *mockExchange) UpdateAccountBalances(context.Context, asset.Item) (accounts.SubAccounts, error) {
|
||||
return nil, m.err
|
||||
}
|
||||
|
||||
func (m *mockExchange) GetBase() *exchange.Base {
|
||||
return &exchange.Base{Name: "mocky", Accounts: m.accounts}
|
||||
}
|
||||
|
||||
func (m *mockExchange) GetCredentials(context.Context) (*accounts.Credentials, error) {
|
||||
return &accounts.Credentials{Key: m.GetName()}, nil
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/database/repository/audit"
|
||||
exchangeDB "github.com/thrasher-corp/gocryptotrader/database/repository/exchange"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchange/accounts"
|
||||
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/fundingrate"
|
||||
@@ -73,7 +73,6 @@ var (
|
||||
errCurrencyPairInvalid = errors.New("currency provided is not found in the available pairs list")
|
||||
errNoTrades = errors.New("no trades returned from supplied params")
|
||||
errNilRequestData = errors.New("nil request data received, cannot continue")
|
||||
errNoAccountInformation = errors.New("account information does not exist")
|
||||
errShutdownNotAllowed = errors.New("shutting down this bot instance is not allowed via gRPC, please enable by command line flag --grpcshutdown or config.json field grpcAllowBotShutdown")
|
||||
errGRPCShutdownSignalIsNil = errors.New("cannot shutdown, gRPC shutdown channel is nil")
|
||||
errInvalidStrategy = errors.New("invalid strategy")
|
||||
@@ -114,7 +113,7 @@ func (s *RPCServer) authenticateClient(ctx context.Context) (context.Context, er
|
||||
password != s.Config.RemoteControl.Password {
|
||||
return ctx, errors.New("username/password mismatch")
|
||||
}
|
||||
ctx, err = account.ParseCredentialsMetadata(ctx, md)
|
||||
ctx, err = accounts.ParseCredentialsMetadata(ctx, md)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
@@ -556,87 +555,80 @@ func (s *RPCServer) GetOrderbooks(_ context.Context, _ *gctrpc.GetOrderbooksRequ
|
||||
return &gctrpc.GetOrderbooksResponse{Orderbooks: obResponse}, nil
|
||||
}
|
||||
|
||||
// GetAccountInfo returns an account balance for a specific exchange
|
||||
func (s *RPCServer) GetAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfoRequest) (*gctrpc.GetAccountInfoResponse, error) {
|
||||
// GetAccountBalances returns an account balance for a specific exchange.
|
||||
func (s *RPCServer) GetAccountBalances(ctx context.Context, r *gctrpc.GetAccountBalancesRequest) (*gctrpc.GetAccountBalancesResponse, error) {
|
||||
assetType, err := asset.New(r.AssetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
e, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkParams(r.Exchange, exch, assetType, currency.EMPTYPAIR)
|
||||
if err := checkParams(r.Exchange, e, assetType, currency.EMPTYPAIR); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := e.GetCachedSubAccounts(ctx, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := exch.GetCachedAccountInfo(ctx, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createAccountInfoRequest(resp)
|
||||
return accountBalanceResp(r.Exchange, resp), nil
|
||||
}
|
||||
|
||||
// UpdateAccountInfo forces an update of the account info
|
||||
func (s *RPCServer) UpdateAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfoRequest) (*gctrpc.GetAccountInfoResponse, error) {
|
||||
// UpdateAccountBalances forces an update of the account balances.
|
||||
func (s *RPCServer) UpdateAccountBalances(ctx context.Context, r *gctrpc.GetAccountBalancesRequest) (*gctrpc.GetAccountBalancesResponse, error) {
|
||||
assetType, err := asset.New(r.AssetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
e, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkParams(r.Exchange, exch, assetType, currency.EMPTYPAIR)
|
||||
if err := checkParams(r.Exchange, e, assetType, currency.EMPTYPAIR); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := e.UpdateAccountBalances(ctx, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := exch.UpdateAccountInfo(ctx, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createAccountInfoRequest(resp)
|
||||
return accountBalanceResp(r.Exchange, resp), nil
|
||||
}
|
||||
|
||||
func createAccountInfoRequest(h account.Holdings) (*gctrpc.GetAccountInfoResponse, error) {
|
||||
accounts := make([]*gctrpc.Account, len(h.Accounts))
|
||||
for x := range h.Accounts {
|
||||
var a gctrpc.Account
|
||||
a.Id = h.Accounts[x].Credentials.String()
|
||||
for _, y := range h.Accounts[x].Currencies {
|
||||
if y.Total == 0 &&
|
||||
y.Hold == 0 &&
|
||||
y.Free == 0 &&
|
||||
y.AvailableWithoutBorrow == 0 &&
|
||||
y.Borrowed == 0 {
|
||||
continue
|
||||
}
|
||||
a.Currencies = append(a.Currencies, &gctrpc.AccountCurrencyInfo{
|
||||
Currency: y.Currency.String(),
|
||||
TotalValue: y.Total,
|
||||
Hold: y.Hold,
|
||||
Free: y.Free,
|
||||
FreeWithoutBorrow: y.AvailableWithoutBorrow,
|
||||
Borrowed: y.Borrowed,
|
||||
UpdatedAt: timestamppb.New(y.UpdatedAt),
|
||||
func accountBalanceResp(eName string, s accounts.SubAccounts) *gctrpc.GetAccountBalancesResponse {
|
||||
subAccts := make([]*gctrpc.Account, len(s))
|
||||
for i, sa := range s {
|
||||
subAccts[i] = &gctrpc.Account{
|
||||
Id: sa.ID,
|
||||
}
|
||||
for curr, bal := range sa.Balances {
|
||||
subAccts[i].Currencies = append(subAccts[i].Currencies, &gctrpc.AccountCurrencyInfo{
|
||||
Currency: curr.String(),
|
||||
TotalValue: bal.Total,
|
||||
Hold: bal.Hold,
|
||||
Free: bal.Free,
|
||||
FreeWithoutBorrow: bal.AvailableWithoutBorrow,
|
||||
Borrowed: bal.Borrowed,
|
||||
UpdatedAt: timestamppb.New(bal.UpdatedAt),
|
||||
})
|
||||
}
|
||||
accounts[x] = &a
|
||||
}
|
||||
|
||||
return &gctrpc.GetAccountInfoResponse{Exchange: h.Exchange, Accounts: accounts}, nil
|
||||
return &gctrpc.GetAccountBalancesResponse{
|
||||
Exchange: eName,
|
||||
Accounts: subAccts,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAccountInfoStream streams an account balance for a specific exchange
|
||||
func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream gctrpc.GoCryptoTraderService_GetAccountInfoStreamServer) error {
|
||||
// GetAccountBalancesStream streams an account balance for a specific exchange
|
||||
func (s *RPCServer) GetAccountBalancesStream(r *gctrpc.GetAccountBalancesRequest, stream gctrpc.GoCryptoTraderService_GetAccountBalancesStreamServer) error {
|
||||
assetType, err := asset.New(r.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -652,7 +644,7 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream
|
||||
return err
|
||||
}
|
||||
|
||||
pipe, err := account.SubscribeToExchangeAccount(r.Exchange)
|
||||
pipe, err := exch.SubscribeAccountBalances()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -677,32 +669,12 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream
|
||||
case <-init:
|
||||
}
|
||||
|
||||
holdings, err := exch.GetCachedAccountInfo(stream.Context(), assetType)
|
||||
subAccts, err := exch.GetCachedSubAccounts(stream.Context(), assetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accounts := make([]*gctrpc.Account, len(holdings.Accounts))
|
||||
for x := range holdings.Accounts {
|
||||
subAccounts := make([]*gctrpc.AccountCurrencyInfo, len(holdings.Accounts[x].Currencies))
|
||||
for y := range holdings.Accounts[x].Currencies {
|
||||
subAccounts[y] = &gctrpc.AccountCurrencyInfo{
|
||||
Currency: holdings.Accounts[x].Currencies[y].Currency.String(),
|
||||
TotalValue: holdings.Accounts[x].Currencies[y].Total,
|
||||
Hold: holdings.Accounts[x].Currencies[y].Hold,
|
||||
UpdatedAt: timestamppb.New(holdings.Accounts[x].Currencies[y].UpdatedAt),
|
||||
}
|
||||
}
|
||||
accounts[x] = &gctrpc.Account{
|
||||
Id: holdings.Accounts[x].ID,
|
||||
Currencies: subAccounts,
|
||||
}
|
||||
}
|
||||
|
||||
if err := stream.Send(&gctrpc.GetAccountInfoResponse{
|
||||
Exchange: holdings.Exchange,
|
||||
Accounts: accounts,
|
||||
}); err != nil {
|
||||
if err := stream.Send(accountBalanceResp(r.Exchange, subAccts)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -4756,8 +4728,7 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
feat := exch.GetSupportedFeatures()
|
||||
if !feat.FuturesCapabilities.Collateral {
|
||||
if f := exch.GetSupportedFeatures(); !f.FuturesCapabilities.Collateral {
|
||||
return nil, fmt.Errorf("%w Get Collateral for exchange %v", common.ErrFunctionNotSupported, exch.GetName())
|
||||
}
|
||||
|
||||
@@ -4766,42 +4737,16 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkParams(r.Exchange, exch, a, currency.EMPTYPAIR)
|
||||
if err != nil {
|
||||
if err := checkParams(r.Exchange, exch, a, currency.EMPTYPAIR); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !a.IsFutures() {
|
||||
return nil, fmt.Errorf("%s %w", a, futures.ErrNotFuturesAsset)
|
||||
}
|
||||
ai, err := exch.GetCachedAccountInfo(ctx, a)
|
||||
currBalances, err := exch.GetCachedCurrencyBalances(ctx, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds, err := exch.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subAccounts := make([]string, len(ai.Accounts))
|
||||
var acc *account.SubAccount
|
||||
for i := range ai.Accounts {
|
||||
subAccounts[i] = ai.Accounts[i].ID
|
||||
if ai.Accounts[i].ID == "main" && creds.SubAccount == "" {
|
||||
acc = &ai.Accounts[i]
|
||||
break
|
||||
}
|
||||
if strings.EqualFold(creds.SubAccount, ai.Accounts[i].ID) {
|
||||
acc = &ai.Accounts[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if acc == nil {
|
||||
return nil, fmt.Errorf("%w for %s %s and stored credentials - available subaccounts: %s",
|
||||
errNoAccountInformation,
|
||||
exch.GetName(),
|
||||
creds.SubAccount,
|
||||
strings.Join(subAccounts, ","))
|
||||
}
|
||||
var spotPairs currency.Pairs
|
||||
if r.CalculateOffline {
|
||||
spotPairs, err = exch.GetAvailablePairs(asset.Spot)
|
||||
@@ -4810,24 +4755,22 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe
|
||||
}
|
||||
}
|
||||
|
||||
calculators := make([]futures.CollateralCalculator, 0, len(acc.Currencies))
|
||||
for i := range acc.Currencies {
|
||||
total := decimal.NewFromFloat(acc.Currencies[i].Total)
|
||||
free := decimal.NewFromFloat(acc.Currencies[i].AvailableWithoutBorrow)
|
||||
calculators := make([]futures.CollateralCalculator, 0, len(currBalances))
|
||||
for curr, balance := range currBalances {
|
||||
total := decimal.NewFromFloat(balance.Total)
|
||||
free := decimal.NewFromFloat(balance.AvailableWithoutBorrow)
|
||||
cal := futures.CollateralCalculator{
|
||||
CalculateOffline: r.CalculateOffline,
|
||||
CollateralCurrency: acc.Currencies[i].Currency,
|
||||
CollateralCurrency: curr,
|
||||
Asset: a,
|
||||
FreeCollateral: free,
|
||||
LockedCollateral: total.Sub(free),
|
||||
}
|
||||
if r.CalculateOffline &&
|
||||
!acc.Currencies[i].Currency.Equal(currency.USD) {
|
||||
if r.CalculateOffline && !curr.Equal(currency.USD) {
|
||||
var tick *ticker.Price
|
||||
tickerCurr := currency.NewPair(acc.Currencies[i].Currency, currency.USD)
|
||||
tickerCurr := currency.NewPair(curr, currency.USD)
|
||||
if !spotPairs.Contains(tickerCurr, true) {
|
||||
// cannot price currency to calculate collateral
|
||||
continue
|
||||
continue // cannot price currency to calculate collateral
|
||||
}
|
||||
tick, err = exch.GetCachedTicker(tickerCurr, asset.Spot)
|
||||
if err != nil {
|
||||
@@ -4855,7 +4798,6 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe
|
||||
|
||||
collateralDisplayCurrency := " " + c.CollateralCurrency.String()
|
||||
result := &gctrpc.GetCollateralResponse{
|
||||
SubAccount: creds.SubAccount,
|
||||
CollateralCurrency: c.CollateralCurrency.String(),
|
||||
AvailableCollateral: c.AvailableCollateral.String() + collateralDisplayCurrency,
|
||||
UsedCollateral: c.UsedCollateral.String() + collateralDisplayCurrency,
|
||||
|
||||
@@ -34,8 +34,8 @@ import (
|
||||
dbexchange "github.com/thrasher-corp/gocryptotrader/database/repository/exchange"
|
||||
sqltrade "github.com/thrasher-corp/gocryptotrader/database/repository/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchange/accounts"
|
||||
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/binance"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/collateral"
|
||||
@@ -316,28 +316,33 @@ func (f fExchange) GetCachedTicker(p currency.Pair, a asset.Item) (*ticker.Price
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCachedAccountInfo overrides testExchange's fetch account info function
|
||||
// to do the bare minimum required with no API calls or credentials required
|
||||
func (f fExchange) GetCachedAccountInfo(_ context.Context, a asset.Item) (account.Holdings, error) {
|
||||
return account.Holdings{
|
||||
Exchange: f.GetName(),
|
||||
Accounts: []account.SubAccount{
|
||||
{
|
||||
ID: "1337",
|
||||
AssetType: a,
|
||||
Currencies: []account.Balance{
|
||||
{
|
||||
Currency: currency.USD,
|
||||
Total: 1337,
|
||||
},
|
||||
{
|
||||
Currency: currency.BTC,
|
||||
Total: 13337,
|
||||
},
|
||||
},
|
||||
},
|
||||
// GetCachedSubAccounts overrides testExchange's fetch account info function to do the bare minimum required with no API calls or credentials required
|
||||
// Only returns balances for creds with a SubAccount populated
|
||||
func (f fExchange) GetCachedSubAccounts(ctx context.Context, a asset.Item) (accounts.SubAccounts, error) {
|
||||
creds, err := f.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if creds.SubAccount == "" {
|
||||
return nil, fmt.Errorf("%w for %s credentials %s asset %s", accounts.ErrNoBalances, f.GetName(), creds, a)
|
||||
}
|
||||
return accounts.SubAccounts{{
|
||||
ID: creds.SubAccount,
|
||||
Balances: accounts.CurrencyBalances{
|
||||
currency.USD: {Currency: currency.USD, Total: 1337},
|
||||
currency.BTC: {Currency: currency.BTC, Total: 13337},
|
||||
},
|
||||
}, nil
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// GetCachedCurrencyBalances overrides testExchange's fetch account info function to do the bare minimum required with no API calls or credentials required
|
||||
// Only returns balances for creds with a SubAccount populated
|
||||
func (f fExchange) GetCachedCurrencyBalances(ctx context.Context, a asset.Item) (accounts.CurrencyBalances, error) {
|
||||
subAccts, err := f.GetCachedSubAccounts(ctx, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return subAccts[0].Balances, nil
|
||||
}
|
||||
|
||||
// CalculateTotalCollateral overrides testExchange's CalculateTotalCollateral function
|
||||
@@ -386,22 +391,13 @@ func (f fExchange) CalculateTotalCollateral(context.Context, *futures.TotalColla
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateAccountInfo overrides testExchange's update account info function
|
||||
// UpdateAccountBalances overrides testExchange's update account info function
|
||||
// to do the bare minimum required with no API calls or credentials required
|
||||
func (f fExchange) UpdateAccountInfo(_ context.Context, a asset.Item) (account.Holdings, error) {
|
||||
func (f fExchange) UpdateAccountBalances(_ context.Context, a asset.Item) (accounts.SubAccounts, error) {
|
||||
if a == asset.Futures {
|
||||
return account.Holdings{}, asset.ErrNotSupported
|
||||
return accounts.SubAccounts{}, asset.ErrNotSupported
|
||||
}
|
||||
return account.Holdings{
|
||||
Exchange: f.GetName(),
|
||||
Accounts: []account.SubAccount{
|
||||
{
|
||||
ID: "1337",
|
||||
AssetType: a,
|
||||
Currencies: nil,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
return accounts.SubAccounts{accounts.NewSubAccount(a, "1337")}, nil
|
||||
}
|
||||
|
||||
// GetCurrencyStateSnapshot overrides interface function
|
||||
@@ -1216,63 +1212,48 @@ func TestGetHistoricTrades(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccountInfo(t *testing.T) {
|
||||
func TestGetAccountBalances(t *testing.T) {
|
||||
t.Parallel()
|
||||
em := NewExchangeManager()
|
||||
exch, err := em.NewExchangeByName(testExchange)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: true,
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{AssetEnabled: true}
|
||||
fakeExchange := fExchange{IBotExchange: exch}
|
||||
err = em.Add(fakeExchange)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := accounts.DeployCredentialsToContext(t.Context(), &accounts.Credentials{Key: "fakerino", Secret: "supafake", SubAccount: "42"})
|
||||
s := RPCServer{Engine: &Engine{ExchangeManager: em}}
|
||||
_, err = s.GetAccountInfo(t.Context(), &gctrpc.GetAccountInfoRequest{Exchange: fakeExchangeName, AssetType: asset.Spot.String()})
|
||||
_, err = s.GetAccountBalances(ctx, &gctrpc.GetAccountBalancesRequest{Exchange: fakeExchangeName, AssetType: asset.Spot.String()})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateAccountInfo(t *testing.T) {
|
||||
func TestUpdateAccountBalances(t *testing.T) {
|
||||
t.Parallel()
|
||||
em := NewExchangeManager()
|
||||
exch, err := em.NewExchangeByName(testExchange)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: true,
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{AssetEnabled: true}
|
||||
fakeExchange := fExchange{IBotExchange: exch}
|
||||
err = em.Add(fakeExchange)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := RPCServer{Engine: &Engine{ExchangeManager: em}}
|
||||
|
||||
_, err = s.GetAccountInfo(t.Context(), &gctrpc.GetAccountInfoRequest{Exchange: fakeExchangeName, AssetType: asset.Spot.String()})
|
||||
ctx := accounts.DeployCredentialsToContext(t.Context(), &accounts.Credentials{Key: "fakerino", Secret: "supafake", SubAccount: "42"})
|
||||
_, err = s.GetAccountBalances(ctx, &gctrpc.GetAccountBalancesRequest{Exchange: fakeExchangeName, AssetType: asset.Spot.String()})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = s.UpdateAccountInfo(t.Context(), &gctrpc.GetAccountInfoRequest{Exchange: fakeExchangeName, AssetType: asset.Futures.String()})
|
||||
_, err = s.UpdateAccountBalances(ctx, &gctrpc.GetAccountBalancesRequest{Exchange: fakeExchangeName, AssetType: asset.Futures.String()})
|
||||
assert.ErrorIs(t, err, currency.ErrAssetNotFound)
|
||||
|
||||
_, err = s.UpdateAccountInfo(t.Context(), &gctrpc.GetAccountInfoRequest{
|
||||
Exchange: fakeExchangeName,
|
||||
AssetType: asset.Spot.String(),
|
||||
})
|
||||
_, err = s.UpdateAccountBalances(ctx, &gctrpc.GetAccountBalancesRequest{Exchange: fakeExchangeName, AssetType: asset.Spot.String()})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -2196,6 +2177,8 @@ func TestGetCollateral(t *testing.T) {
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
b.Accounts, err = accounts.GetStore().GetExchangeAccounts(b)
|
||||
require.NoError(t, err, "GetExchangeAccounts must not error")
|
||||
|
||||
cp, err := currency.NewPairFromString("btc-usd")
|
||||
require.NoError(t, err)
|
||||
@@ -2235,17 +2218,15 @@ func TestGetCollateral(t *testing.T) {
|
||||
})
|
||||
require.ErrorIs(t, err, exchange.ErrCredentialsAreEmpty)
|
||||
|
||||
ctx := account.DeployCredentialsToContext(t.Context(),
|
||||
&account.Credentials{Key: "fakerino", Secret: "supafake"})
|
||||
ctx := accounts.DeployCredentialsToContext(t.Context(), &accounts.Credentials{Key: "fakerino", Secret: "supafake"})
|
||||
|
||||
_, err = s.GetCollateral(ctx, &gctrpc.GetCollateralRequest{
|
||||
Exchange: fakeExchangeName,
|
||||
Asset: asset.Futures.String(),
|
||||
})
|
||||
require.ErrorIs(t, err, errNoAccountInformation)
|
||||
require.ErrorIs(t, err, accounts.ErrNoBalances)
|
||||
|
||||
ctx = account.DeployCredentialsToContext(t.Context(),
|
||||
&account.Credentials{Key: "fakerino", Secret: "supafake", SubAccount: "1337"})
|
||||
ctx = accounts.DeployCredentialsToContext(t.Context(), &accounts.Credentials{Key: "fakerino", Secret: "supafake", SubAccount: "1337"})
|
||||
|
||||
r, err := s.GetCollateral(ctx, &gctrpc.GetCollateralRequest{
|
||||
Exchange: fakeExchangeName,
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"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/fill"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
@@ -329,15 +329,9 @@ func (m *WebsocketRoutineManager) websocketDataHandler(exchName string, data any
|
||||
return fmt.Errorf("%w %s", d.Err, d.Error())
|
||||
case websocket.UnhandledMessageWarning:
|
||||
log.Warnf(log.WebsocketMgr, "%s unhandled message - %s", exchName, d.Message)
|
||||
case account.Change:
|
||||
case []accounts.Change, accounts.Change:
|
||||
if m.verbose {
|
||||
m.printAccountHoldingsChangeSummary(exchName, d)
|
||||
}
|
||||
case []account.Change:
|
||||
if m.verbose {
|
||||
for x := range d {
|
||||
m.printAccountHoldingsChangeSummary(exchName, d[x])
|
||||
}
|
||||
log.Debugf(log.WebsocketMgr, "%s %+v", exchName, d)
|
||||
}
|
||||
case []trade.Data, trade.Data:
|
||||
if m.verbose {
|
||||
@@ -349,10 +343,7 @@ func (m *WebsocketRoutineManager) websocketDataHandler(exchName string, data any
|
||||
}
|
||||
default:
|
||||
if m.verbose {
|
||||
log.Warnf(log.WebsocketMgr,
|
||||
"%s websocket Unknown type: %+v",
|
||||
exchName,
|
||||
d)
|
||||
log.Warnf(log.WebsocketMgr, "%s websocket Unknown type: %+v", exchName, d)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -396,21 +387,6 @@ func (m *WebsocketRoutineManager) printOrderSummary(o *order.Detail, isUpdate bo
|
||||
o.RemainingAmount)
|
||||
}
|
||||
|
||||
// printAccountHoldingsChangeSummary this function will be deprecated when a
|
||||
// account holdings update is done.
|
||||
func (m *WebsocketRoutineManager) printAccountHoldingsChangeSummary(exch string, o account.Change) {
|
||||
if m == nil || atomic.LoadInt32(&m.state) == stoppedState || o.Balance == nil {
|
||||
return
|
||||
}
|
||||
log.Debugf(log.WebsocketMgr,
|
||||
"Account Holdings Balance Changed: %s %s %s has changed balance by %f for account: %s",
|
||||
exch,
|
||||
o.AssetType,
|
||||
o.Balance.Currency,
|
||||
o.Balance.Total,
|
||||
o.Account)
|
||||
}
|
||||
|
||||
// registerWebsocketDataHandler registers an externally (GCT Library) defined
|
||||
// dedicated filter specific data types for internal & external strategy use.
|
||||
// InterceptorOnly as true will purge all other registered handlers
|
||||
|
||||
Reference in New Issue
Block a user