Fix Bybit isolated balance sizing
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from llm_trade_bot.bybit_gateway import BybitGateway
|
||||
@@ -7,9 +8,17 @@ from llm_trade_bot.config import BybitConfig, ScannerConfig
|
||||
|
||||
|
||||
class FakeBybitHTTP:
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, wallet: dict[str, Any] | None = None) -> None:
|
||||
self.wallet = wallet
|
||||
self.open_order_requests: list[dict[str, Any]] = []
|
||||
|
||||
def get_wallet_balance(self, **parameters: Any) -> dict[str, Any]:
|
||||
assert parameters == {"accountType": "UNIFIED"}
|
||||
return self.wallet or {"retCode": 0, "result": {"list": []}}
|
||||
|
||||
def get_positions(self, **parameters: Any) -> dict[str, Any]:
|
||||
return {"retCode": 0, "result": {"list": []}}
|
||||
|
||||
def get_open_orders(self, **parameters: Any) -> dict[str, Any]:
|
||||
self.open_order_requests.append(parameters)
|
||||
return {"retCode": 0, "result": {"list": []}}
|
||||
@@ -26,3 +35,38 @@ async def test_open_orders_scopes_linear_requests_by_settle_coin() -> None:
|
||||
{"category": "spot", "limit": 50},
|
||||
{"category": "linear", "limit": 50, "settleCoin": "USDC"},
|
||||
]
|
||||
|
||||
|
||||
async def test_account_snapshot_uses_quote_coin_balance_when_account_available_is_empty() -> None:
|
||||
wallet = {
|
||||
"retCode": 0,
|
||||
"result": {
|
||||
"list": [
|
||||
{
|
||||
"totalEquity": "100000",
|
||||
"totalAvailableBalance": "",
|
||||
"totalMarginBalance": "100000",
|
||||
"coin": [
|
||||
{
|
||||
"coin": "USDT",
|
||||
"walletBalance": "50000",
|
||||
"availableToWithdraw": "",
|
||||
"totalPositionIM": "100",
|
||||
"totalOrderIM": "200",
|
||||
"locked": "300",
|
||||
"bonus": "400",
|
||||
},
|
||||
{"coin": "USDC", "walletBalance": "50000", "availableToWithdraw": ""},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
gateway = BybitGateway(BybitConfig("key", "secret", "USDC"), ScannerConfig(quote_coin="USDT"))
|
||||
gateway._http = FakeBybitHTTP(wallet) # type: ignore[assignment]
|
||||
|
||||
spot = await gateway.account_snapshot("spot")
|
||||
linear = await gateway.account_snapshot("linear")
|
||||
|
||||
assert spot.available_balance == Decimal("49000")
|
||||
assert linear.available_balance == Decimal("50000")
|
||||
|
||||
@@ -59,6 +59,16 @@ def test_existing_exposure_does_not_cap_position(engine, rules):
|
||||
assert result.notional == Decimal(30000)
|
||||
|
||||
|
||||
def test_minimum_quantity_error_reports_sizing_inputs(engine, rules):
|
||||
empty_account = AccountSnapshot(Decimal(10000), Decimal(0), Decimal(0), 0)
|
||||
|
||||
with pytest.raises(
|
||||
OrderRejected,
|
||||
match=r"quantity 0.*minimum 0\.001.*available_balance=0.*leverage=3.*entry_price=100",
|
||||
):
|
||||
engine.validate_open(request(), empty_account, rules)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"overrides, message",
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user