Initial commit: TradeMind – Krypto-Trading-Bot mit Lernmodus
Per Podman deploybarer Bot, der Käufe und Verkäufe simuliert ausführt und sich aus den Ergebnissen weiter antrainiert. Aufbau - Einheitliche Bar-Verarbeitung für paper, backtest und live; ausgetauscht werden nur Datenquelle und Broker. - Börsenanbindung über ccxt: rund 100 Börsen allein über exchange.id erreichbar. Zugangsdaten kommen über ENV-Platzhalter, der Live-Modus ist doppelt abgesichert. - Paper-Broker mit Gebühren, Slippage, Börsenpräzision und Volumengrenzen. - Online trainierte logistische Regression bewertet jedes Einstiegssignal. Sie lernt aus realen Trade-Ergebnissen, aus Shadow-Labels aller Kandidaten – auch der abgelehnten – und aus Hintergrund-Stichproben; beim Kaltstart wird sie aus der Kurshistorie vorgelernt. - Risikomanagement: Positions- und Exposure-Grenzen, ATR-Stops, Cooldown sowie Tagesverlust- und Drawdown-Notbremsen. - SQLite-Persistenz, HTTP-Status mit Prometheus-Metriken und Dashboard, Webhooks. Deployment - Containerfile (zweistufig, non-root UID 10001), podman-compose, systemd-Quadlet. - Modell und Datenbank liegen im Volume /data und überleben Neustarts. 128 Tests, ruff sauber. Verifiziert gegen echte Marktdaten sowie im gebauten Container inklusive Healthcheck und Zustandswiederherstellung.
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import pytest
|
||||
|
||||
from trademind.broker import InsufficientFunds, OrderRejected, PaperBroker
|
||||
from trademind.config import PaperConfig
|
||||
from trademind.models import Side
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def broker(paper_config) -> PaperBroker:
|
||||
return PaperBroker(paper_config)
|
||||
|
||||
|
||||
async def test_buy_applies_slippage_and_fee(broker):
|
||||
fill = await broker.execute("BTC/USDT", Side.BUY, 0.1, 30_000.0)
|
||||
assert fill.price == pytest.approx(30_000.0 * 1.0005) # 5 bps Slippage
|
||||
assert fill.fee_quote == pytest.approx(fill.notional * 0.001)
|
||||
assert await broker.cash() == pytest.approx(10_000.0 - fill.notional - fill.fee_quote)
|
||||
assert await broker.holdings("BTC/USDT") == pytest.approx(0.1)
|
||||
|
||||
|
||||
async def test_sell_applies_slippage_in_the_other_direction(broker):
|
||||
await broker.execute("BTC/USDT", Side.BUY, 0.1, 30_000.0)
|
||||
fill = await broker.execute("BTC/USDT", Side.SELL, 0.1, 31_000.0)
|
||||
assert fill.price == pytest.approx(31_000.0 * 0.9995)
|
||||
assert await broker.holdings("BTC/USDT") == 0.0
|
||||
|
||||
|
||||
async def test_round_trip_at_constant_price_loses_exactly_the_costs(broker):
|
||||
price = 30_000.0
|
||||
await broker.execute("BTC/USDT", Side.BUY, 0.1, price)
|
||||
await broker.execute("BTC/USDT", Side.SELL, 0.1, price)
|
||||
# 2 × 0,1 % Gebühr + 2 × 5 bps Slippage auf ein Volumen von ~3000
|
||||
assert await broker.cash() == pytest.approx(10_000.0 - 9.0, abs=0.2)
|
||||
|
||||
|
||||
async def test_buy_is_scaled_down_to_available_cash(broker):
|
||||
fill = await broker.execute("BTC/USDT", Side.BUY, 10.0, 30_000.0) # 300k gewünscht
|
||||
assert fill.amount < 10.0
|
||||
assert await broker.cash() == pytest.approx(0.0, abs=1e-6)
|
||||
|
||||
|
||||
async def test_buy_without_any_cash_raises():
|
||||
broker = PaperBroker(PaperConfig(starting_balance=1.0, fee_rate=0.001))
|
||||
broker.market_info = {"BTC/USDT": {"amount_precision": 4}}
|
||||
with pytest.raises(InsufficientFunds):
|
||||
await broker.execute("BTC/USDT", Side.BUY, 1.0, 30_000.0)
|
||||
|
||||
|
||||
async def test_sell_without_position_raises(broker):
|
||||
with pytest.raises(OrderRejected):
|
||||
await broker.execute("BTC/USDT", Side.SELL, 1.0, 30_000.0)
|
||||
|
||||
|
||||
async def test_sell_is_capped_at_held_amount(broker):
|
||||
await broker.execute("BTC/USDT", Side.BUY, 0.1, 30_000.0)
|
||||
fill = await broker.execute("BTC/USDT", Side.SELL, 5.0, 30_000.0)
|
||||
assert fill.amount == pytest.approx(0.1)
|
||||
|
||||
|
||||
async def test_volume_participation_caps_the_order(broker):
|
||||
fill = await broker.execute("BTC/USDT", Side.BUY, 0.1, 30_000.0, bar_volume=0.2)
|
||||
assert fill.amount == pytest.approx(0.02) # 10 % von 0,2
|
||||
assert fill.requested_amount == pytest.approx(0.1)
|
||||
|
||||
|
||||
async def test_amount_precision_rounds_down(broker):
|
||||
broker.market_info = {"BTC/USDT": {"amount_precision": 3}}
|
||||
fill = await broker.execute("BTC/USDT", Side.BUY, 0.123456, 30_000.0)
|
||||
assert fill.amount == pytest.approx(0.123)
|
||||
|
||||
|
||||
async def test_step_size_precision_is_supported(broker):
|
||||
broker.market_info = {"BTC/USDT": {"amount_precision": 0.05}}
|
||||
fill = await broker.execute("BTC/USDT", Side.BUY, 0.17, 100.0)
|
||||
assert fill.amount == pytest.approx(0.15)
|
||||
|
||||
|
||||
async def test_invalid_inputs_are_rejected(broker):
|
||||
with pytest.raises(OrderRejected):
|
||||
await broker.execute("BTC/USDT", Side.BUY, 0.0, 30_000.0)
|
||||
with pytest.raises(OrderRejected):
|
||||
await broker.execute("BTC/USDT", Side.BUY, 1.0, 0.0)
|
||||
|
||||
|
||||
async def test_state_round_trip(broker):
|
||||
await broker.execute("BTC/USDT", Side.BUY, 0.05, 30_000.0)
|
||||
state = broker.state()
|
||||
|
||||
restored = PaperBroker(broker.config)
|
||||
restored.restore(state["cash"], state["holdings"], state["total_fees"])
|
||||
assert await restored.cash() == pytest.approx(await broker.cash())
|
||||
assert await restored.holdings("BTC/USDT") == pytest.approx(0.05)
|
||||
Reference in New Issue
Block a user