Files
Tobias Zimmermann 65ed73977e 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.
2026-08-22 08:53:04 +02:00

93 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)