From c153716e897e397f8c24a581dc96f7876a44ceef Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Mon, 15 Sep 2025 16:05:52 +1000 Subject: [PATCH] bybit: Fix UpdateAccountInfo free and hold calculations (#1980) * fix free and hold calculations * rm verbosity * gk/glorious: nit truncate loaded value from http mock recording to adhere to test * gk: readability nit * fix test issue * regig calculation and confirmed upstream * thrasher: nits * update tests --------- Co-authored-by: shazbert --- exchanges/bybit/bybit_test.go | 21 +++++++++++-------- exchanges/bybit/bybit_types.go | 2 +- exchanges/bybit/bybit_wrapper.go | 35 +++++++++++++++++++------------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/exchanges/bybit/bybit_test.go b/exchanges/bybit/bybit_test.go index 1537fd0e..2fb440f4 100644 --- a/exchanges/bybit/bybit_test.go +++ b/exchanges/bybit/bybit_test.go @@ -1636,7 +1636,7 @@ func TestGetWalletBalance(t *testing.T) { assert.Equal(t, types.Number(0), r.List[0].Coin[x].AvailableToBorrow, "AvailableToBorrow should be correct") assert.Equal(t, types.Number(0), r.List[0].Coin[x].AvailableToWithdraw, "AvailableToWithdraw should be correct") assert.Equal(t, types.Number(0), r.List[0].Coin[x].Bonus, "Bonus should be correct") - assert.Equal(t, types.Number(30723.630216383711792744), r.List[0].Coin[x].BorrowAmount, "BorrowAmount should be correct") + assert.Equal(t, types.Number(30723.630216383714), r.List[0].Coin[x].BorrowAmount, "BorrowAmount should be correct") assert.Equal(t, currency.USDC, r.List[0].Coin[x].Coin, "Coin should be correct") assert.True(t, r.List[0].Coin[x].CollateralSwitch, "CollateralSwitch should match") assert.Equal(t, types.Number(0), r.List[0].Coin[x].CumulativeRealisedPNL, "CumulativeRealisedPNL should be correct") @@ -2879,21 +2879,24 @@ func TestUpdateAccountInfo(t *testing.T) { 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.Equal(t, -30723.63021638, r.Accounts[0].Currencies[x].Hold, "Hold amount should be correct") - assert.Equal(t, 30723.630216383714, r.Accounts[0].Currencies[x].Borrowed, "Borrowed amount should be correct") - assert.Equal(t, 0.0, r.Accounts[0].Currencies[x].Free, "Free 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.Equal(t, 1468.10808813, r.Accounts[0].Currencies[x].Hold, "Hold amount should be correct") - assert.Equal(t, 0.0, r.Accounts[0].Currencies[x].Borrowed, "Borrowed amount should be correct") - assert.Equal(t, 1005.79191187, r.Accounts[0].Currencies[x].Free, "Free 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.Equal(t, 0.0, r.Accounts[0].Currencies[x].Borrowed, "Borrowed amount should be correct") - assert.Equal(t, 0.0, r.Accounts[0].Currencies[x].Hold, "Hold 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") } } } diff --git a/exchanges/bybit/bybit_types.go b/exchanges/bybit/bybit_types.go index a8eafb13..ac722d4a 100644 --- a/exchanges/bybit/bybit_types.go +++ b/exchanges/bybit/bybit_types.go @@ -895,7 +895,7 @@ type WalletBalance struct { TotalWalletBalance types.Number `json:"totalWalletBalance"` AccountLTV types.Number `json:"accountLTV"` // Account LTV: account total borrowed size / (account total equity + account total borrowed size). TotalMaintenanceMargin types.Number `json:"totalMaintenanceMargin"` - Coin []struct { + Coin []*struct { AvailableToBorrow types.Number `json:"availableToBorrow"` Bonus types.Number `json:"bonus"` AccruedInterest types.Number `json:"accruedInterest"` diff --git a/exchanges/bybit/bybit_wrapper.go b/exchanges/bybit/bybit_wrapper.go index ca99aad5..583863f0 100644 --- a/exchanges/bybit/bybit_wrapper.go +++ b/exchanges/bybit/bybit_wrapper.go @@ -656,9 +656,7 @@ func (e *Exchange) UpdateAccountInfo(ctx context.Context, assetType asset.Item) return info, err } switch assetType { - case asset.Spot, asset.Options, - asset.USDCMarginedFutures, - asset.USDTMarginedFutures: + case asset.Spot, asset.Options, asset.USDCMarginedFutures, asset.USDTMarginedFutures: switch at { case accountTypeUnified: accountType = "UNIFIED" @@ -680,18 +678,27 @@ func (e *Exchange) UpdateAccountInfo(ctx context.Context, assetType asset.Item) } currencyBalance := []account.Balance{} for i := range balances.List { - for c := range balances.List[i].Coin { - balance := account.Balance{ - Currency: balances.List[i].Coin[c].Coin, - Total: balances.List[i].Coin[c].WalletBalance.Float64(), - Free: balances.List[i].Coin[c].AvailableToWithdraw.Float64(), - Borrowed: balances.List[i].Coin[c].BorrowAmount.Float64(), - Hold: balances.List[i].Coin[c].WalletBalance.Float64() - balances.List[i].Coin[c].AvailableToWithdraw.Float64(), + for _, c := range balances.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() + + // wallet balance can be negative when borrow is present, and wallet balance will be offset with spot holdings + // e.g. borrow $10,000, wallet balance will be -$9,900 ∴ spot holding $100 + balanceDiff := truncBorrow + c.WalletBalance.Float64() + + freeBalance := balanceDiff - c.Locked.Float64() + if assetType == asset.Spot && c.AvailableBalanceForSpot.Float64() != 0 { + freeBalance = c.AvailableBalanceForSpot.Float64() } - if assetType == asset.Spot && balances.List[i].Coin[c].AvailableBalanceForSpot.Float64() != 0 { - balance.Free = balances.List[i].Coin[c].AvailableBalanceForSpot.Float64() - } - currencyBalance = append(currencyBalance, balance) + + currencyBalance = append(currencyBalance, account.Balance{ + Currency: c.Coin, + Total: c.WalletBalance.Float64(), + Free: freeBalance, + Borrowed: c.BorrowAmount.Float64(), + Hold: c.Locked.Float64(), + AvailableWithoutBorrow: c.AvailableToWithdraw.Float64(), + }) } } acc.Currencies = currencyBalance