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:
Tobias Zimmermann
2026-08-22 08:53:04 +02:00
commit 65ed73977e
40 changed files with 7033 additions and 0 deletions
+92
View File
@@ -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)