feat: implement autonomous Bybit demo trading agent

This commit is contained in:
Azimkin
2026-09-19 16:10:11 +02:00
commit 110b9fee0b
26 changed files with 3391 additions and 0 deletions

83
tests/test_risk.py Normal file
View File

@@ -0,0 +1,83 @@
from decimal import Decimal
import pytest
from llm_trade_bot.config import RiskConfig
from llm_trade_bot.models import (
AccountSnapshot,
Category,
InstrumentRules,
OpenTradeRequest,
OrderType,
Side,
)
from llm_trade_bot.risk import RiskEngine, RiskRejected
@pytest.fixture
def engine() -> RiskEngine:
return RiskEngine(RiskConfig())
@pytest.fixture
def account() -> AccountSnapshot:
return AccountSnapshot(Decimal(10000), Decimal(10000), Decimal(0), 0)
@pytest.fixture
def rules() -> InstrumentRules:
return InstrumentRules(Decimal("0.1"), Decimal("0.001"), Decimal("0.001"), Decimal(5), Decimal(100))
def request(**overrides) -> OpenTradeRequest:
values = {
"category": Category.LINEAR,
"symbol": "BTCUSDT",
"side": Side.BUY,
"order_type": OrderType.MARKET,
"reference_price": Decimal(100),
"limit_price": None,
"stop_loss": Decimal(90),
"take_profit": Decimal(120),
"trailing_distance": None,
"risk_pct": Decimal(2),
"leverage": Decimal(3),
"rationale": "test",
}
values.update(overrides)
return OpenTradeRequest(**values)
def test_sizes_by_risk_and_exposure(engine, account, rules):
result = engine.validate_open(request(), account, rules)
assert result.quantity == Decimal(20)
assert result.notional == Decimal(2000)
def test_remaining_exposure_caps_position(engine, rules):
account = AccountSnapshot(Decimal(10000), Decimal(10000), Decimal(3900), 2)
result = engine.validate_open(request(), account, rules)
assert result.quantity == Decimal(1)
assert result.notional == Decimal(100)
@pytest.mark.parametrize(
"overrides, message",
[
({"risk_pct": Decimal("2.1")}, "risk_pct"),
({"leverage": Decimal(4)}, "Leverage"),
({"stop_loss": Decimal(101)}, "stop-loss"),
({"take_profit": None, "trailing_distance": None}, "take-profit or trailing"),
({"category": Category.SPOT, "order_type": OrderType.MARKET}, "Spot entries"),
({"category": Category.SPOT, "order_type": OrderType.LIMIT, "limit_price": Decimal(100), "take_profit": None}, "Spot entry requires"),
],
)
def test_rejects_unsafe_requests(engine, account, rules, overrides, message):
with pytest.raises(RiskRejected, match=message):
engine.validate_open(request(**overrides), account, rules)
def test_rejects_max_positions(engine, rules):
account = AccountSnapshot(Decimal(10000), Decimal(10000), Decimal(0), 5)
with pytest.raises(RiskRejected, match="Maximum"):
engine.validate_open(request(), account, rules)