feat: implement autonomous Bybit demo trading agent
This commit is contained in:
102
tests/test_agent_tools.py
Normal file
102
tests/test_agent_tools.py
Normal file
@@ -0,0 +1,102 @@
|
||||
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,
|
||||
DeepSeekConfig,
|
||||
RiskConfig,
|
||||
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(
|
||||
DeepSeekConfig("key"),
|
||||
BybitConfig("key", "secret"),
|
||||
TelegramConfig(1, "hash", "token", frozenset({1})),
|
||||
BraveConfig("key"),
|
||||
ScheduleConfig(ZoneInfo("Europe/Warsaw"), (time(9), time(21))),
|
||||
ScannerConfig(),
|
||||
RiskConfig(),
|
||||
StorageConfig(database_path),
|
||||
)
|
||||
|
||||
|
||||
async def test_open_trade_is_risk_sized_and_deduplicated_within_run(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, "candidate_keys": {"linear:BTCUSDT"}}
|
||||
try:
|
||||
first = await tools.open_trade(
|
||||
"linear", "BTCUSDT", "Buy", "Market", 90, 2, "test", take_profit=120, leverage=3, context=context
|
||||
)
|
||||
second = await tools.open_trade(
|
||||
"linear", "BTCUSDT", "Buy", "Market", 90, 2, "test", take_profit=120, leverage=3, context=context
|
||||
)
|
||||
assert first["quantity"] == "20"
|
||||
assert second["deduplicated"] is True
|
||||
assert len(bybit.orders) == 1
|
||||
assert bybit.orders[0]["stopLoss"] == "90"
|
||||
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,
|
||||
2,
|
||||
"test",
|
||||
take_profit=120,
|
||||
context={"run_id": 1, "candidate_keys": {"linear:BTCUSDT"}},
|
||||
)
|
||||
finally:
|
||||
await database.close()
|
||||
Reference in New Issue
Block a user