From 0a91af0f2e2b0a93e7e31803a52eec6fbb7b72a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Rasc=C3=A3o?= Date: Wed, 13 Oct 2021 01:05:16 +0100 Subject: [PATCH] bitmex: normalize account info currencies (#799) * bitmex: normalize account info currencies Bitmex has an interesting way of returning BTC balances, it returns them as XBt and denominated in Satoshis instead. Normalize that still in the bitmex wrapper by performing the conversion and returning the normal use BTC currency. * Change NW contract to NQ Co-authored-by: Adrian Gallagher --- exchanges/bitmex/bitmex.go | 29 ++++++++++++++++++++--- exchanges/bitmex/bitmex_test.go | 37 ++++++++++++++++++++++++++---- exchanges/bitmex/bitmex_types.go | 2 +- exchanges/bitmex/bitmex_wrapper.go | 31 +++++++++++++++++-------- exchanges/huobi/huobi_test.go | 4 ++-- 5 files changed, 82 insertions(+), 21 deletions(-) diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index bb081686..c8a35f90 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -93,6 +93,8 @@ const ( bitmexEndpointUserWalletSummary = "/user/walletSummary" bitmexEndpointUserRequestWithdraw = "/user/requestWithdrawal" + constSatoshiBTC = 1e-08 + // ContractPerpetual perpetual contract type ContractPerpetual = iota // ContractFutures futures contract type @@ -772,10 +774,21 @@ func (b *Bitmex) UserRequestWithdrawal(ctx context.Context, params UserRequestWi func (b *Bitmex) GetWalletInfo(ctx context.Context, currency string) (WalletInfo, error) { var info WalletInfo - return info, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + if err := b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, bitmexEndpointUserWallet, UserCurrencyParams{Currency: currency}, - &info) + &info); err != nil { + return info, err + } + + // Bitmex has an "interesting" of dealing with currencies, + // for instance XBt is actually BTC but in Satoshi units, + // for sanity purposes apply here a conversion to normalize + // this + // avoid a copy here since this is a big struct + normalizeWalletInfo(&info) + + return info, nil } // GetWalletHistory returns user wallet history transaction data @@ -908,7 +921,7 @@ func (b *Bitmex) CaptureError(resp, reType interface{}) error { } err = json.Unmarshal(marshalled, &Error) - if err == nil { + if err == nil && Error.Error.Name != "" { return fmt.Errorf("bitmex error %s: %s", Error.Error.Name, Error.Error.Message) @@ -947,3 +960,13 @@ func calculateTradingFee(purchasePrice, amount float64, isMaker bool) float64 { return fee * purchasePrice * amount } + +// normalizeWalletInfo converts any non-standard currencies (eg. XBt -> BTC) +func normalizeWalletInfo(w *WalletInfo) { + if w.Currency != "XBt" { + return + } + + w.Currency = "BTC" + w.Amount *= constSatoshiBTC +} diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 589be78a..fabf4012 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -163,8 +163,8 @@ func TestGetTrollboxChannels(t *testing.T) { func TestGetTrollboxConnectedUsers(t *testing.T) { t.Parallel() _, err := b.GetTrollboxConnectedUsers(context.Background()) - if err == nil { - t.Error("GetTrollboxConnectedUsers() Expected error") + if err != nil { + t.Error("GetTrollboxConnectedUsers() error", err) } } @@ -231,8 +231,8 @@ func TestGetActiveAndIndexInstruments(t *testing.T) { func TestGetActiveIntervals(t *testing.T) { t.Parallel() _, err := b.GetActiveIntervals(context.Background()) - if err == nil { - t.Error("GetActiveIntervals() Expected error") + if err != nil { + t.Error("GetActiveIntervals() error", err) } } @@ -731,13 +731,23 @@ func TestGetAccountInfo(t *testing.T) { if areTestAPIKeysSet() { _, err := b.UpdateAccountInfo(context.Background(), asset.Spot) if err != nil { - t.Error("GetAccountInfo() error", err) + t.Error("GetAccountInfo(spot) error", err) + } + + _, err = b.UpdateAccountInfo(context.Background(), asset.Futures) + if err != nil { + t.Error("GetAccountInfo(futures) error", err) } } else { _, err := b.UpdateAccountInfo(context.Background(), asset.Spot) if err == nil { t.Error("GetAccountInfo() error") } + + _, err = b.UpdateAccountInfo(context.Background(), asset.Futures) + if err == nil { + t.Error("GetAccountInfo(futures) error") + } } } @@ -1134,3 +1144,20 @@ func TestUpdateTickers(t *testing.T) { t.Fatal(err) } } + +func TestCurrencyNormalization(t *testing.T) { + w := &WalletInfo{ + Currency: "XBt", + Amount: 1e+08, + } + + normalizeWalletInfo(w) + + if w.Currency != "BTC" { + t.Errorf("currency mismatch, expected BTC, got %s", w.Currency) + } + + if w.Amount != 1.0 { + t.Errorf("amount mismatch, expected 1.0, got %f", w.Amount) + } +} diff --git a/exchanges/bitmex/bitmex_types.go b/exchanges/bitmex/bitmex_types.go index 4bcb98e9..1877a6ee 100644 --- a/exchanges/bitmex/bitmex_types.go +++ b/exchanges/bitmex/bitmex_types.go @@ -649,7 +649,7 @@ type MinWithdrawalFee struct { type WalletInfo struct { Account int64 `json:"account"` Addr string `json:"addr"` - Amount int64 `json:"amount"` + Amount float64 `json:"amount"` ConfirmedDebit int64 `json:"confirmedDebit"` Currency string `json:"currency"` DeltaAmount int64 `json:"deltaAmount"` diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 8429878e..6980d92c 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "sort" + "strconv" "strings" "sync" "time" @@ -179,6 +180,7 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error { return b.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + URL: bitmexWSURL, }) } @@ -415,27 +417,36 @@ func (b *Bitmex) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType func (b *Bitmex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) { var info account.Holdings - bal, err := b.GetAllUserMargin(ctx) + userMargins, err := b.GetAllUserMargin(ctx) if err != nil { return info, err } - // Need to update to add Margin/Liquidity availibilty + var accountID string var balances []account.Balance - for i := range bal { + // Need to update to add Margin/Liquidity availability + for i := range userMargins { + accountID = strconv.FormatInt(userMargins[i].Account, 10) + + wallet, err := b.GetWalletInfo(ctx, userMargins[i].Currency) + if err != nil { + continue + } + balances = append(balances, account.Balance{ - CurrencyName: currency.NewCode(bal[i].Currency), - TotalValue: float64(bal[i].WalletBalance), + CurrencyName: currency.NewCode(wallet.Currency), + TotalValue: wallet.Amount, }) } + info.Accounts = append(info.Accounts, + account.SubAccount{ + ID: accountID, + Currencies: balances, + }) info.Exchange = b.Name - info.Accounts = append(info.Accounts, account.SubAccount{ - Currencies: balances, - }) - err = account.Process(&info) - if err != nil { + if err := account.Process(&info); err != nil { return account.Holdings{}, err } diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index f233f49b..37a208f7 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -171,7 +171,7 @@ func TestFLastTradeData(t *testing.T) { func TestFRequestPublicBatchTrades(t *testing.T) { t.Parallel() - cp, err := currency.NewPairFromString("BTC_NW") + cp, err := currency.NewPairFromString("BTC_NQ") if err != nil { t.Error(err) } @@ -211,7 +211,7 @@ func TestFQueryTieredAdjustmentFactor(t *testing.T) { func TestFQueryHisOpenInterest(t *testing.T) { t.Parallel() _, err := h.FQueryHisOpenInterest(context.Background(), - "BTC", "next_week", "60min", "cont", 3) + "BTC", "next_quarter", "60min", "cont", 3) if err != nil { t.Error(err) }