Initial release: TradeMind crypto trading bot with paper/live modes and strategy training
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""Smoke-Tests für TradeMind (ohne Netzwerk & ohne echte Keys)."""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from trademind.config import Config, StrategyConfig, TradingConfig, TrainingConfig
|
||||
from trademind.strategy import Strategy
|
||||
from trademind.engine import Engine
|
||||
from trademind.trader import Trader
|
||||
from trademind.exchange import MockBroker, CcxtBroker
|
||||
from trademind.trainer import Trainer, eval_params, random_params
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cfg() -> Config:
|
||||
return Config(
|
||||
trading=TradingConfig(),
|
||||
strategy=StrategyConfig(),
|
||||
training=TrainingConfig(generations=3, population=6),
|
||||
exchanges={},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def candles() -> pd.DataFrame:
|
||||
rng = np.random.default_rng(0)
|
||||
n = 300
|
||||
close = 50_000 + np.cumsum(rng.standard_normal(n) * 500)
|
||||
open_ = np.roll(close, 1)
|
||||
open_[0] = 50_000
|
||||
spread = np.abs(rng.standard_normal(n)) * 100 + 5
|
||||
high = np.maximum(open_, close) + spread
|
||||
low = np.minimum(open_, close) - spread
|
||||
idx = pd.date_range(end=pd.Timestamp.utcnow().floor("h"), periods=n, freq="h")
|
||||
return pd.DataFrame(
|
||||
{"time": idx, "open": open_, "high": high, "low": low, "close": close,
|
||||
"volume": rng.uniform(10, 100, n)}
|
||||
)
|
||||
|
||||
|
||||
def test_strategy_prepare_and_decide(cfg, candles):
|
||||
st = Strategy(cfg.strategy)
|
||||
prep = st.prepare(candles)
|
||||
assert all(k in prep for k in ("ema_fast", "ema_slow", "rsi", "atr"))
|
||||
sigs = st.decide(prep)
|
||||
assert len(sigs) == len(candles)
|
||||
assert sigs[-1].price > 0
|
||||
|
||||
|
||||
def test_engine_run_produces_result(cfg, candles):
|
||||
st = Strategy(cfg.strategy)
|
||||
eng = Engine(cfg.trading, st)
|
||||
res = eng.run(candles)
|
||||
assert res.final_equity > 0
|
||||
assert isinstance(res.summary().get("num_trades"), int)
|
||||
assert res.equity_curve[0] > 0
|
||||
|
||||
|
||||
def test_paper_trader_roundtrip(cfg, candles):
|
||||
broker = MockBroker(seed=1)
|
||||
st = Strategy(cfg.strategy)
|
||||
tr = Trader(cfg, broker, st)
|
||||
res = tr.simulate()
|
||||
assert res.final_equity >= 0
|
||||
assert res.num_trades >= 0
|
||||
|
||||
|
||||
def test_trainer_improves_and_sets_weights(cfg, candles):
|
||||
base = Strategy(cfg.strategy)
|
||||
trainer = Trainer(cfg.training, base)
|
||||
p0 = random_params(__import__("random").Random(1))
|
||||
before = eval_params(p0, candles, cfg.trading, base, cfg.training)
|
||||
best = trainer.train(candles, cfg.trading, base)
|
||||
# after: best should be >= before (elitism guarantees)
|
||||
after = eval_params(best, candles, cfg.trading, base, cfg.training)
|
||||
assert after >= before - 1e-6
|
||||
|
||||
|
||||
def test_data_broker_falls_back_to_mock(cfg):
|
||||
"""Ohne Netzwerk/Exchange muss der Fallback auf Mock-Daten greifen."""
|
||||
from trademind.cli import _data_broker
|
||||
|
||||
broker = _data_broker(cfg, data="auto", exchange="binance", seed=3)
|
||||
df = broker.fetch_ohlcv("BTC/USDT", "1h", 50)
|
||||
assert len(df) == 50
|
||||
for col in ("open", "high", "low", "close"):
|
||||
assert (df[col] > 0).all()
|
||||
|
||||
|
||||
def test_mock_broker_deterministic():
|
||||
b1 = MockBroker(seed=3).fetch_ohlcv("BTC/USDT", "1h", 100)
|
||||
b2 = MockBroker(seed=3).fetch_ohlcv("BTC/USDT", "1h", 100)
|
||||
assert b1.equals(b2)
|
||||
|
||||
|
||||
def test_strategy_set_parameter(cfg):
|
||||
st = Strategy(cfg.strategy)
|
||||
p = st.parameters()
|
||||
assert "fast_period" in p and "w_ema_cross" in p
|
||||
st.set_parameter("fast_period", 20)
|
||||
assert st.cfg.fast_period == 20
|
||||
st.set_parameter("w_ema_cross", 1.5)
|
||||
assert st.weights["ema_cross"] == pytest.approx(1.5)
|
||||
Reference in New Issue
Block a user