Skip to content

Commit 43e0cf6

Browse files
authored
Fix Bybit WebSocket balance precision errors (#2904)
Extract string-to-raw conversion to centralized parsing utility. Fixes MOG and other high-value token balance errors from WebSocket updates by avoiding float conversion entirely.
1 parent 9a55aa2 commit 43e0cf6

File tree

3 files changed

+27
-24
lines changed

3 files changed

+27
-24
lines changed

nautilus_trader/adapters/bybit/common/parsing.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from __future__ import annotations
1717

18+
from decimal import Decimal
1819
from typing import TYPE_CHECKING
1920

2021
from nautilus_trader.adapters.bybit.common.constants import BYBIT_HOUR_INTERVALS
@@ -27,6 +28,7 @@
2728
from nautilus_trader.model.enums import BookAction
2829
from nautilus_trader.model.enums import OrderSide
2930
from nautilus_trader.model.enums import bar_aggregation_to_str
31+
from nautilus_trader.model.objects import FIXED_SCALAR
3032

3133

3234
if TYPE_CHECKING:
@@ -35,6 +37,12 @@
3537
from nautilus_trader.model.objects import Quantity
3638

3739

40+
def parse_str_to_raw(value: str) -> int:
41+
if not value or value == "":
42+
return 0
43+
return int(Decimal(value) * Decimal(int(FIXED_SCALAR)))
44+
45+
3846
def parse_aggressor_side(value: str) -> AggressorSide:
3947
match value:
4048
case "Buy":

nautilus_trader/adapters/bybit/schemas/account/balance.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@
1313
# limitations under the License.
1414
# -------------------------------------------------------------------------------------------------
1515

16-
from decimal import Decimal
17-
1816
import msgspec
1917

18+
from nautilus_trader.adapters.bybit.common.parsing import parse_str_to_raw
2019
from nautilus_trader.adapters.bybit.schemas.common import BybitListResult
21-
from nautilus_trader.model.objects import FIXED_SCALAR
2220
from nautilus_trader.model.objects import AccountBalance
2321
from nautilus_trader.model.objects import Currency
2422
from nautilus_trader.model.objects import MarginBalance
@@ -50,17 +48,11 @@ class BybitCoinBalance(msgspec.Struct):
5048
marginCollateral: bool
5149
coin: str
5250

53-
@staticmethod
54-
def _str_to_raw(value: str) -> int:
55-
if not value or value == "":
56-
return 0
57-
return int(Decimal(value) * Decimal(int(FIXED_SCALAR)))
58-
5951
def parse_to_account_balance(self) -> AccountBalance:
6052
currency = Currency.from_str(self.coin)
6153

62-
total_raw = self._str_to_raw(self.walletBalance)
63-
locked_raw = self._str_to_raw(self.locked) # TODO: Locked only valid for Spot
54+
total_raw = parse_str_to_raw(self.walletBalance)
55+
locked_raw = parse_str_to_raw(self.locked) # TODO: Locked only valid for Spot
6456
free_raw = total_raw - locked_raw
6557

6658
return AccountBalance(
@@ -72,8 +64,8 @@ def parse_to_account_balance(self) -> AccountBalance:
7264
def parse_to_margin_balance(self) -> MarginBalance:
7365
currency = Currency.from_str(self.coin)
7466

75-
initial_raw = self._str_to_raw(self.totalPositionIM)
76-
maintenance_raw = self._str_to_raw(self.totalPositionMM)
67+
initial_raw = parse_str_to_raw(self.totalPositionIM)
68+
maintenance_raw = parse_str_to_raw(self.totalPositionMM)
7769

7870
return MarginBalance(
7971
initial=Money.from_raw(initial_raw, currency),

nautilus_trader/adapters/bybit/schemas/ws.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from nautilus_trader.adapters.bybit.common.enums import BybitTriggerType
3737
from nautilus_trader.adapters.bybit.common.enums import BybitWsOrderRequestMsgOP
3838
from nautilus_trader.adapters.bybit.common.parsing import parse_bybit_delta
39+
from nautilus_trader.adapters.bybit.common.parsing import parse_str_to_raw
3940
from nautilus_trader.adapters.bybit.endpoints.trade.amend_order import BybitAmendOrderPostParams
4041
from nautilus_trader.adapters.bybit.endpoints.trade.batch_amend_order import BybitBatchAmendOrderPostParams
4142
from nautilus_trader.adapters.bybit.endpoints.trade.batch_cancel_order import BybitBatchCancelOrderPostParams
@@ -801,24 +802,26 @@ class BybitWsAccountWalletCoin(msgspec.Struct):
801802

802803
def parse_to_account_balance(self) -> AccountBalance:
803804
currency = Currency.from_str(self.coin)
804-
total = Decimal(self.walletBalance)
805-
locked = Decimal(self.locked) # TODO: Locked only valid for Spot
806-
free = total - locked
805+
806+
total_raw = parse_str_to_raw(self.walletBalance)
807+
locked_raw = parse_str_to_raw(self.locked) # TODO: Locked only valid for Spot
808+
free_raw = total_raw - locked_raw
807809

808810
return AccountBalance(
809-
total=Money(total, currency),
810-
locked=Money(locked, currency),
811-
free=Money(free, currency),
811+
total=Money.from_raw(total_raw, currency),
812+
locked=Money.from_raw(locked_raw, currency),
813+
free=Money.from_raw(free_raw, currency),
812814
)
813815

814816
def parse_to_margin_balance(self) -> MarginBalance:
815-
self.totalPositionIM = self.totalPositionIM if self.totalPositionIM != "" else "0"
816-
self.totalPositionMM = self.totalPositionMM if self.totalPositionMM != "" else "0"
817-
currency: Currency = Currency.from_str(self.coin)
817+
currency = Currency.from_str(self.coin)
818+
819+
initial_raw = parse_str_to_raw(self.totalPositionIM)
820+
maintenance_raw = parse_str_to_raw(self.totalPositionMM)
818821

819822
return MarginBalance(
820-
initial=Money(Decimal(self.totalPositionIM), currency),
821-
maintenance=Money(Decimal(self.totalPositionMM), currency),
823+
initial=Money.from_raw(initial_raw, currency),
824+
maintenance=Money.from_raw(maintenance_raw, currency),
822825
)
823826

824827

0 commit comments

Comments
 (0)