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 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)
|
||||
Reference in New Issue
Block a user