from datetime import time from decimal import Decimal from pathlib import Path from zoneinfo import ZoneInfo import pytest from llm_trade_bot.agent import AgentTools from llm_trade_bot.config import ( AppConfig, BraveConfig, BybitConfig, ZaiConfig, PromptsConfig, ScannerConfig, ScheduleConfig, StorageConfig, TelegramConfig, ) from llm_trade_bot.database import Database from llm_trade_bot.models import AccountSnapshot, InstrumentRules class FakeBybit: def __init__(self): self.orders = [] async def account_state(self): return {"wallet": [{"coin": []}], "positions": [], "open_orders": []} async def market_context(self, category, symbol): return {"ticker": {"lastPrice": "100"}} async def account_snapshot(self): return AccountSnapshot(Decimal(10000), Decimal(10000), Decimal(0), 0) async def instrument_rules(self, category, symbol): return InstrumentRules(Decimal("0.1"), Decimal("0.001"), Decimal("0.001"), Decimal(5), Decimal(100)) async def set_leverage(self, symbol, leverage): return {"retCode": 0} async def place_order(self, **parameters): self.orders.append(parameters) return {"retCode": 0, "result": {"orderId": f"order-{len(self.orders)}"}} def make_config(database_path: Path) -> AppConfig: return AppConfig( ZaiConfig("key"), BybitConfig("key", "secret"), TelegramConfig(1, "hash", "token", frozenset({1})), BraveConfig("key"), PromptsConfig(database_path.parent / "prompts.md"), ScheduleConfig(ZoneInfo("Europe/Warsaw"), (time(9), time(21))), ScannerConfig(), StorageConfig(database_path), ) async def test_open_trade_uses_all_available_buying_power_and_allows_repeated_entries(tmp_path): database = Database(tmp_path / "state.db") await database.open() bybit = FakeBybit() tools = AgentTools(make_config(database.path), database, bybit) # type: ignore[arg-type] run_id = await database.start_run("test") context = {"run_id": run_id} try: first = await tools.open_trade( "linear", "BTCUSDT", "Buy", "Market", 90, "test", take_profit=120, leverage=3, context=context ) second = await tools.open_trade( "linear", "BTCUSDT", "Buy", "Market", 90, "test", take_profit=120, leverage=3, context=context ) assert first["quantity"] == "300" assert second["quantity"] == "300" assert len(bybit.orders) == 2 assert bybit.orders[0]["stopLoss"] == "90" finally: await database.close() async def test_open_trade_accepts_explicit_quote_notional(tmp_path): database = Database(tmp_path / "state.db") await database.open() bybit = FakeBybit() tools = AgentTools(make_config(database.path), database, bybit) # type: ignore[arg-type] run_id = await database.start_run("test") try: result = await tools.open_trade( "linear", "BTCUSDT", "Buy", "Market", 90, "sized entry", take_profit=120, leverage=3, quote_notional=5000, context={"run_id": run_id}, ) assert result["quantity"] == "50" assert result["notional"] == "5000" assert bybit.orders[0]["qty"] == "50" finally: await database.close() async def test_pause_blocks_entry_from_running_session(tmp_path): database = Database(tmp_path / "state.db") await database.open() tools = AgentTools(make_config(database.path), database, FakeBybit()) # type: ignore[arg-type] await database.set_state("paused", "true") try: with pytest.raises(ValueError, match="paused"): await tools.open_trade( "linear", "BTCUSDT", "Buy", "Market", 90, "test", take_profit=120, context={"run_id": 1}, ) finally: await database.close()