Fix Binance linear market context
This commit is contained in:
@@ -465,25 +465,16 @@ def _has_existing_idea(state: dict[str, Any], category: str, symbol: str) -> boo
|
||||
for item in state.get("positions", [])
|
||||
):
|
||||
return True
|
||||
if category == "spot":
|
||||
base_coin = symbol.removesuffix("USDT")
|
||||
wallets = state.get("wallet", [])
|
||||
if wallets and any(
|
||||
item.get("coin") == base_coin and Decimal(str(item.get("usdValue") or 0)) > 0
|
||||
for item in wallets[0].get("coin", [])
|
||||
):
|
||||
return True
|
||||
spot_balances = state.get("wallets", {}).get("spot", {}).get("balances", [])
|
||||
if any(
|
||||
item.get("asset") == base_coin
|
||||
and Decimal(str(item.get("free") or 0)) + Decimal(str(item.get("locked") or 0)) > 0
|
||||
for item in spot_balances
|
||||
):
|
||||
return True
|
||||
return any(
|
||||
item.get("symbol") == symbol
|
||||
and item.get("category", category) == category
|
||||
and not bool(item.get("reduceOnly") or item.get("protective"))
|
||||
and (
|
||||
not bool(item.get("reduceOnly") or item.get("protective"))
|
||||
or (
|
||||
category == "spot"
|
||||
and str(item.get("client_id") or item.get("orderLinkId") or "").startswith("ltb-")
|
||||
)
|
||||
)
|
||||
for item in state.get("open_orders", [])
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ from llm_trade_bot.models import (
|
||||
)
|
||||
|
||||
_INTERVALS = {"60": "1h", "240": "4h", "D": "1d"}
|
||||
_STABLE_COINS = {"USDT", "USDC", "FDUSD", "BUSD", "DAI", "USDE"}
|
||||
|
||||
|
||||
class BinanceGateway:
|
||||
@@ -148,7 +147,7 @@ class BinanceGateway:
|
||||
|
||||
async def account_snapshot(self, category: str | None = None) -> AccountSnapshot:
|
||||
state = await self.account_state()
|
||||
active = _active_ideas(state, self.scanner.quote_coin)
|
||||
active = _active_ideas(state)
|
||||
if category == "linear":
|
||||
wallet = state["wallets"]["linear"]
|
||||
gross = sum(
|
||||
@@ -241,7 +240,8 @@ class BinanceGateway:
|
||||
self._known_symbols[category].add(symbol)
|
||||
ticker, book = await asyncio.gather(
|
||||
client.request("GET", f"{prefix}/ticker/24hr", {"symbol": symbol}),
|
||||
client.request("GET", f"{prefix}/depth", {"symbol": symbol, "limit": 25}),
|
||||
# Binance accepts a discrete set of depth limits; 25 is a Bybit-only value.
|
||||
client.request("GET", f"{prefix}/depth", {"symbol": symbol, "limit": 20}),
|
||||
)
|
||||
frames: dict[str, Any] = {}
|
||||
for requested in self.scanner.candle_intervals:
|
||||
@@ -994,18 +994,18 @@ def _normalize_futures_event(event: dict[str, Any]) -> dict[str, Any]:
|
||||
return {"provider": "binance", "category": "linear", "kind": "account", "event_type": event.get("e"), "raw": event}
|
||||
|
||||
|
||||
def _active_ideas(state: dict[str, Any], quote_coin: str) -> set[str]:
|
||||
def _active_ideas(state: dict[str, Any]) -> set[str]:
|
||||
result = {
|
||||
f"linear:{item['symbol']}" for item in state.get("positions", []) if _decimal(item.get("size")) > 0
|
||||
}
|
||||
for balance in state.get("wallets", {}).get("spot", {}).get("balances", []):
|
||||
asset = str(balance.get("asset"))
|
||||
if asset not in _STABLE_COINS and _decimal(balance.get("free")) + _decimal(balance.get("locked")) > 0:
|
||||
result.add(f"spot:{asset}{quote_coin}")
|
||||
result.update(
|
||||
f"{item['category']}:{item['symbol']}"
|
||||
for item in state.get("open_orders", [])
|
||||
if not item.get("protective")
|
||||
or (
|
||||
item.get("category") == "spot"
|
||||
and str(item.get("client_id") or "").startswith("ltb-")
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from zoneinfo import ZoneInfo
|
||||
|
||||
import pytest
|
||||
|
||||
from llm_trade_bot.agent import AgentTools
|
||||
from llm_trade_bot.agent import AgentTools, _has_existing_idea
|
||||
from llm_trade_bot.config import (
|
||||
AppConfig,
|
||||
BraveConfig,
|
||||
@@ -102,3 +102,22 @@ async def test_pause_blocks_entry_from_running_session(tmp_path):
|
||||
)
|
||||
finally:
|
||||
await database.close()
|
||||
|
||||
|
||||
def test_seeded_spot_balance_does_not_block_new_agent_entry():
|
||||
state = {
|
||||
"wallets": {"spot": {"balances": [{"asset": "SOL", "free": "4", "locked": "0"}]}},
|
||||
"positions": [],
|
||||
"open_orders": [],
|
||||
}
|
||||
assert _has_existing_idea(state, "spot", "SOLUSDT") is False
|
||||
|
||||
state["open_orders"] = [
|
||||
{
|
||||
"category": "spot",
|
||||
"symbol": "SOLUSDT",
|
||||
"protective": True,
|
||||
"client_id": "ltb-12-abcd-tp",
|
||||
}
|
||||
]
|
||||
assert _has_existing_idea(state, "spot", "SOLUSDT") is True
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from llm_trade_bot.binance_gateway import BinanceError, BinanceGateway
|
||||
from llm_trade_bot.binance_gateway import BinanceError, BinanceGateway, _active_ideas
|
||||
from llm_trade_bot.config import BinanceAccountConfig, BinanceConfig, ScannerConfig
|
||||
from llm_trade_bot.models import (
|
||||
Category,
|
||||
@@ -160,3 +160,36 @@ async def test_trailing_rate_outside_binance_range_rejects_before_opening():
|
||||
)
|
||||
assert not usdm.requests
|
||||
assert not usdm.mutations
|
||||
|
||||
|
||||
async def test_linear_market_context_uses_a_valid_binance_depth_limit():
|
||||
class MarketClient(FakeBinanceClient):
|
||||
async def request(self, method: str, path: str, parameters=None, **kwargs):
|
||||
self.requests.append((method, path, parameters, kwargs))
|
||||
if path.endswith("/ticker/24hr"):
|
||||
return {"lastPrice": "100"}
|
||||
if path.endswith("/depth"):
|
||||
return {"bids": [], "asks": []}
|
||||
if path.endswith("/klines"):
|
||||
return [[0, "1", "101", "99", "100", "10"] for _ in range(20)]
|
||||
return await super().request(method, path, parameters, **kwargs)
|
||||
|
||||
spot = MarketClient("/api/v3/exchangeInfo", [])
|
||||
usdm = MarketClient("/fapi/v1/exchangeInfo", [])
|
||||
context = await gateway(spot, usdm).market_context("linear", "BTCUSDT")
|
||||
depth = next(parameters for _, path, parameters, _ in usdm.requests if path == "/fapi/v1/depth")
|
||||
assert context["ticker"]["lastPrice"] == "100"
|
||||
assert depth == {"symbol": "BTCUSDT", "limit": 20}
|
||||
|
||||
|
||||
def test_seeded_spot_assets_are_exposure_but_not_active_ideas():
|
||||
state = {
|
||||
"wallets": {"spot": {"balances": [{"asset": "SOL", "free": "4", "locked": "0"}]}},
|
||||
"positions": [],
|
||||
"open_orders": [],
|
||||
}
|
||||
assert _active_ideas(state) == set()
|
||||
state["open_orders"] = [
|
||||
{"category": "spot", "symbol": "SOLUSDT", "protective": True, "client_id": "ltb-1-tp"}
|
||||
]
|
||||
assert _active_ideas(state) == {"spot:SOLUSDT"}
|
||||
|
||||
Reference in New Issue
Block a user