65ed73977e
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.
84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
import numpy as np
|
|
|
|
from trademind.features import (
|
|
FEATURE_NAMES,
|
|
N_FEATURES,
|
|
build_feature_matrix,
|
|
compute_features,
|
|
required_bars,
|
|
)
|
|
|
|
from .conftest import make_candles
|
|
|
|
|
|
def test_matrix_has_expected_shape(candles, rules):
|
|
matrix = build_feature_matrix(candles, rules)
|
|
assert matrix is not None
|
|
assert matrix.values.shape == (len(candles), N_FEATURES)
|
|
assert matrix.first_valid == required_bars(rules) - 1
|
|
|
|
|
|
def test_all_feature_values_are_finite_and_bounded(candles, rules):
|
|
matrix = build_feature_matrix(candles, rules)
|
|
valid = matrix.values[matrix.first_valid :]
|
|
assert np.isfinite(valid).all()
|
|
assert np.abs(valid).max() <= 8.0
|
|
|
|
|
|
def test_too_short_history_returns_none(rules):
|
|
short = make_candles(n=50)
|
|
assert build_feature_matrix(short, rules) is None
|
|
assert compute_features(short, rules) is None
|
|
|
|
|
|
def test_snapshot_exposes_raw_indicators(candles, rules):
|
|
snapshot = compute_features(candles, rules)
|
|
assert snapshot is not None
|
|
assert snapshot.price == float(candles.close[-1])
|
|
assert snapshot.atr > 0
|
|
assert 0.0 <= snapshot.rsi <= 100.0
|
|
assert len(snapshot.values) == len(FEATURE_NAMES)
|
|
assert set(snapshot.as_dict()) == set(FEATURE_NAMES)
|
|
|
|
|
|
def test_snapshot_before_warmup_is_none(candles, rules):
|
|
matrix = build_feature_matrix(candles, rules)
|
|
assert matrix.snapshot(matrix.first_valid - 1) is None
|
|
assert matrix.snapshot(matrix.first_valid) is not None
|
|
|
|
|
|
def test_uptrend_produces_positive_trend_distance(rules):
|
|
up = make_candles(n=500, trend=0.001, noise=0.0005, seed=11)
|
|
snapshot = compute_features(up, rules)
|
|
features = snapshot.as_dict()
|
|
assert features["trend_dist"] > 0
|
|
assert features["ema_spread"] > 0
|
|
|
|
|
|
def test_downtrend_produces_negative_trend_distance(rules):
|
|
down = make_candles(n=500, trend=-0.001, noise=0.0005, seed=12)
|
|
features = compute_features(down, rules).as_dict()
|
|
assert features["trend_dist"] < 0
|
|
assert features["ema_spread"] < 0
|
|
|
|
|
|
def test_features_are_scale_invariant(rules):
|
|
"""Ein zehnfach höherer Kurs darf die normierten Merkmale kaum verändern."""
|
|
cheap = make_candles(n=400, start_price=100.0, seed=5)
|
|
expensive = make_candles(n=400, start_price=1_000.0, seed=5)
|
|
a = compute_features(cheap, rules).values
|
|
b = compute_features(expensive, rules).values
|
|
assert np.allclose(a, b, atol=1e-8)
|
|
|
|
|
|
def test_time_features_are_on_the_unit_circle(candles, rules):
|
|
snapshot = compute_features(candles, rules).as_dict()
|
|
radius = snapshot["time_sin"] ** 2 + snapshot["time_cos"] ** 2
|
|
assert radius == 1.0 or abs(radius - 1.0) < 1e-9
|
|
|
|
|
|
def test_matrix_rows_match_pointwise_snapshots(candles, rules):
|
|
matrix = build_feature_matrix(candles, rules)
|
|
index = matrix.first_valid + 25
|
|
assert np.allclose(matrix.snapshot(index).values, matrix.values[index])
|