import pytest from pydantic import ValidationError from trademind.config import LIVE_CONFIRMATION_PHRASE, Config, Mode, load_config MINIMAL = """ mode: paper market: symbols: [btc/usdt] """ def write(tmp_path, text: str): path = tmp_path / "config.yaml" path.write_text(text, encoding="utf-8") return path def test_minimal_config_uses_defaults(tmp_path): config = load_config(write(tmp_path, MINIMAL)) assert config.mode is Mode.PAPER assert config.market.symbols == ["BTC/USDT"] # normalisiert assert config.exchange.id == "binance" assert config.risk.max_open_positions == 3 def test_env_placeholders_are_resolved(tmp_path, monkeypatch): monkeypatch.setenv("MY_KEY", "abc123") config = load_config( write(tmp_path, "mode: paper\nexchange:\n api_key: ${MY_KEY}\n api_secret: ${MISSING}\n") ) assert config.exchange.api_key == "abc123" assert config.exchange.api_secret is None def test_env_placeholder_default_value(tmp_path, monkeypatch): monkeypatch.delenv("TRADEMIND_EXCHANGE", raising=False) config = load_config(write(tmp_path, "exchange:\n id: ${TRADEMIND_EXCHANGE:-kraken}\n")) assert config.exchange.id == "kraken" def test_double_underscore_env_override(tmp_path, monkeypatch): monkeypatch.setenv("TRADEMIND__RISK__MAX_OPEN_POSITIONS", "7") monkeypatch.setenv("TRADEMIND__MARKET__SYMBOLS", "BTC/USDT,ETH/USDT") monkeypatch.setenv("TRADEMIND__EXCHANGE__SANDBOX", "false") config = load_config(write(tmp_path, MINIMAL)) assert config.risk.max_open_positions == 7 assert config.market.symbols == ["BTC/USDT", "ETH/USDT"] assert config.exchange.sandbox is False def test_live_mode_requires_confirmation(tmp_path): text = "mode: live\nexchange:\n api_key: k\n api_secret: s\n" with pytest.raises(ValueError, match="live_confirmation"): load_config(write(tmp_path, text)) def test_live_mode_requires_credentials(tmp_path): text = f"mode: live\nlive_confirmation: {LIVE_CONFIRMATION_PHRASE}\n" with pytest.raises(ValueError, match="api_key"): load_config(write(tmp_path, text)) def test_live_mode_accepted_when_complete(tmp_path): text = ( f"mode: live\nlive_confirmation: {LIVE_CONFIRMATION_PHRASE}\n" "exchange:\n api_key: k\n api_secret: s\n" ) config = load_config(write(tmp_path, text)) assert config.mode is Mode.LIVE assert config.is_simulated is False def test_unknown_key_is_rejected(tmp_path): with pytest.raises(ValidationError): load_config(write(tmp_path, "mode: paper\nrisk:\n typo_here: 5\n")) def test_ema_periods_must_be_ordered(tmp_path): with pytest.raises(ValueError, match="fast_ema"): load_config(write(tmp_path, "strategy:\n rules:\n fast_ema: 30\n slow_ema: 10\n")) def test_missing_file_raises(tmp_path): with pytest.raises(FileNotFoundError): load_config(tmp_path / "nope.yaml") def test_empty_symbols_rejected(tmp_path): with pytest.raises(ValidationError): load_config(write(tmp_path, "market:\n symbols: []\n")) def test_config_is_simulated_flag(): assert Config(mode=Mode.PAPER).is_simulated assert Config(mode=Mode.BACKTEST).is_simulated