Initial TradeBot implementation
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from crypto_spot_bot.bybit import Instrument
|
||||
from crypto_spot_bot.execution import PaperBroker
|
||||
from crypto_spot_bot.models import Signal, Ticker
|
||||
from crypto_spot_bot.storage import Storage
|
||||
|
||||
|
||||
def test_paper_broker_buy_and_sell_records_trade(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(tmp_path)
|
||||
storage = Storage(settings.database_path)
|
||||
broker = PaperBroker(settings, storage)
|
||||
ticker = Ticker("BTCUSDT", 100, 99.9, 100.1, 10_000_000, 100, 0)
|
||||
instrument = Instrument("BTCUSDT", "BTC", "USDT", "Trading", 0.01, 0.000001, 0.000001, 5)
|
||||
signal = Signal("BTCUSDT", "BUY", 0.8, "test")
|
||||
|
||||
position = broker.buy(signal, ticker, instrument, {"BTCUSDT": 100})
|
||||
assert position is not None
|
||||
assert broker.cash < settings.starting_balance_usdt
|
||||
assert len(broker.open_positions()) == 1
|
||||
|
||||
trade = broker.sell(position, ticker, "test exit")
|
||||
assert trade.side == "SELL"
|
||||
assert len(broker.open_positions()) == 0
|
||||
assert storage.recent_trades(limit=10)
|
||||
|
||||
|
||||
def test_paper_broker_limits_fast_entries_per_minute(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(
|
||||
tmp_path,
|
||||
max_entries_per_minute=1,
|
||||
max_open_positions=3,
|
||||
max_positions_per_symbol=3,
|
||||
max_total_exposure_usdt=90,
|
||||
)
|
||||
storage = Storage(settings.database_path)
|
||||
broker = PaperBroker(settings, storage)
|
||||
ticker = Ticker("BTCUSDT", 100, 99.9, 100.1, 10_000_000, 100, 0)
|
||||
instrument = Instrument("BTCUSDT", "BTC", "USDT", "Trading", 0.01, 0.000001, 0.000001, 5)
|
||||
|
||||
first = broker.buy(Signal("BTCUSDT", "BUY", 0.8, "first"), ticker, instrument, {"BTCUSDT": 100})
|
||||
second = broker.buy(Signal("BTCUSDT", "BUY", 0.8, "second"), ticker, instrument, {"BTCUSDT": 100})
|
||||
|
||||
assert first is not None
|
||||
assert second is None
|
||||
assert len(broker.open_positions()) == 1
|
||||
assert "лимит новых входов" in storage.recent_events(limit=1)[0]["message"]
|
||||
|
||||
|
||||
def test_paper_broker_uses_signal_notional_and_pair_exposure(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(
|
||||
tmp_path,
|
||||
min_position_usdt=1,
|
||||
max_position_usdt=20,
|
||||
max_symbol_exposure_usdt=6,
|
||||
max_total_exposure_usdt=50,
|
||||
max_open_positions=20,
|
||||
max_positions_per_symbol=1,
|
||||
max_entries_per_minute=0,
|
||||
)
|
||||
storage = Storage(settings.database_path)
|
||||
broker = PaperBroker(settings, storage)
|
||||
ticker = Ticker("BTCUSDT", 100, 99.9, 100.1, 10_000_000, 100, 0)
|
||||
instrument = Instrument("BTCUSDT", "BTC", "USDT", "Trading", 0.01, 0.000001, 0.000001, 1)
|
||||
|
||||
first = broker.buy(
|
||||
Signal("BTCUSDT", "BUY", 0.8, "first", {"position_notional_usdt": 2}),
|
||||
ticker,
|
||||
instrument,
|
||||
{"BTCUSDT": 100},
|
||||
)
|
||||
second = broker.buy(
|
||||
Signal("BTCUSDT", "BUY", 0.8, "second", {"position_notional_usdt": 2}),
|
||||
ticker,
|
||||
instrument,
|
||||
{"BTCUSDT": 100},
|
||||
)
|
||||
third = broker.buy(
|
||||
Signal("BTCUSDT", "BUY", 0.8, "third", {"position_notional_usdt": 2}),
|
||||
ticker,
|
||||
instrument,
|
||||
{"BTCUSDT": 100},
|
||||
)
|
||||
fourth = broker.buy(
|
||||
Signal("BTCUSDT", "BUY", 0.8, "fourth", {"position_notional_usdt": 2}),
|
||||
ticker,
|
||||
instrument,
|
||||
{"BTCUSDT": 100},
|
||||
)
|
||||
|
||||
assert first is not None
|
||||
assert second is not None
|
||||
assert third is not None
|
||||
assert fourth is None
|
||||
assert len(broker.open_positions()) == 3
|
||||
assert 5.5 <= broker.symbol_exposure("BTCUSDT") <= 6.0
|
||||
|
||||
|
||||
def test_paper_broker_respects_adaptive_exposure_target(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(
|
||||
tmp_path,
|
||||
min_position_usdt=1,
|
||||
max_position_usdt=20,
|
||||
max_symbol_exposure_usdt=20,
|
||||
max_total_exposure_usdt=80,
|
||||
max_open_positions=20,
|
||||
max_positions_per_symbol=20,
|
||||
max_entries_per_minute=0,
|
||||
)
|
||||
storage = Storage(settings.database_path)
|
||||
broker = PaperBroker(settings, storage)
|
||||
ticker = Ticker("BTCUSDT", 100, 99.9, 100.1, 10_000_000, 100, 0)
|
||||
instrument = Instrument("BTCUSDT", "BTC", "USDT", "Trading", 0.01, 0.000001, 0.000001, 1)
|
||||
capped_signal = Signal(
|
||||
"BTCUSDT",
|
||||
"BUY",
|
||||
0.8,
|
||||
"adaptive cap",
|
||||
{
|
||||
"position_notional_usdt": 10,
|
||||
"adaptive_rules": {
|
||||
"target_total_exposure_usdt": 0,
|
||||
"target_symbol_exposure_usdt": 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
position = broker.buy(capped_signal, ticker, instrument, {"BTCUSDT": 100})
|
||||
|
||||
assert position is None
|
||||
assert broker.open_positions() == []
|
||||
Reference in New Issue
Block a user