Files
Trademind/tests/test_indicators.py
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
2.7 KiB
Python

import numpy as np
import pytest
from trademind.indicators import atr, bollinger, donchian_position, ema, macd, roc, rsi, sma, true_range
def test_ema_of_constant_series_is_constant():
values = np.full(50, 42.0)
assert np.allclose(ema(values, 10), 42.0)
def test_ema_reacts_faster_than_sma():
values = np.concatenate([np.full(30, 100.0), np.full(30, 110.0)])
fast = ema(values, 10)[35]
slow = sma(values, 10)[35]
assert fast > slow # EMA hat den Sprung stärker eingepreist
def test_sma_matches_manual_mean():
values = np.arange(1.0, 11.0)
result = sma(values, 3)
assert np.isnan(result[:2]).all()
assert result[2] == pytest.approx(2.0)
assert result[-1] == pytest.approx(9.0)
def test_rsi_bounds_and_extremes():
rising = np.arange(1.0, 60.0)
values = rsi(rising, 14)
finite = values[np.isfinite(values)]
assert finite.min() >= 0.0 and finite.max() <= 100.0
assert finite[-1] == pytest.approx(100.0) # nur Gewinne
falling = rising[::-1].copy()
assert rsi(falling, 14)[-1] == pytest.approx(0.0)
def test_rsi_of_flat_series_is_neutral():
values = rsi(np.full(60, 25.0), 14)
assert values[-1] == pytest.approx(50.0)
def test_true_range_covers_gaps():
high = np.array([10.0, 20.0])
low = np.array([9.0, 19.0])
close = np.array([9.5, 19.5])
tr = true_range(high, low, close)
assert tr[0] == pytest.approx(1.0)
assert tr[1] == pytest.approx(10.5) # Lücke gegenüber dem Vortagesschluss
def test_atr_is_positive_and_warm():
n = 100
rng = np.random.default_rng(3)
close = 100 + np.cumsum(rng.normal(0, 1, n))
high = close + 1.0
low = close - 1.0
values = atr(high, low, close, 14)
assert np.isnan(values[:13]).all()
assert (values[13:] > 0).all()
def test_macd_histogram_is_difference():
values = 100 + np.cumsum(np.random.default_rng(1).normal(0, 1, 200))
line, signal, hist = macd(values)
assert np.allclose(hist, line - signal)
def test_bollinger_bands_are_ordered():
values = 100 + np.cumsum(np.random.default_rng(2).normal(0, 1, 200))
lower, mid, upper = bollinger(values, 20, 2.0)
valid = ~np.isnan(mid)
assert (lower[valid] <= mid[valid]).all()
assert (mid[valid] <= upper[valid]).all()
def test_roc_is_relative_change():
values = np.array([100.0] * 10 + [110.0])
assert roc(values, 10)[-1] == pytest.approx(0.10)
def test_donchian_position_hits_extremes():
close = np.array([float(i) for i in range(1, 41)])
high = close + 0.0
low = close - 0.0
pos = donchian_position(high, low, close, 20)
assert pos[-1] == pytest.approx(1.0) # Schluss auf dem Hoch der Range
def test_indicators_reject_wrong_dimensions():
with pytest.raises(ValueError):
ema(np.zeros((5, 2)), 3)