OQ-1: (a′) 기존 슬롯 도메인 유지 + 운영 프로파일/규칙 추가 (PositionBook 불필요) OQ-2: safety_sell_enabled=false → 코어 손절매 루틴 없음, 전략 모듈 전적 책임 OQ-3: per-instance watchdog_timeout_sec 연장 (1800s 이상 권장), 알림은 후속 Discovery OQ-4: 중장기 전략 폴링 주기 최소 1분·최대 10분 feat(bat-041): REQ-076/077/078 — watchdog timeout TOML + 중장기 운영 프로파일 + baseline Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
3.7 KiB
Python
90 lines
3.7 KiB
Python
"""Per-instance watchdog timeout TOML tests (REQ-076/077, dcy-027, BAT-041).
|
|
|
|
TOML `watchdog_timeout_sec` wins over env TREX_WATCHDOG_TIMEOUT; absent keeps
|
|
the env value (default 120) so intraday instances are byte-for-byte unaffected.
|
|
The long-term profile combines this with safety_sell_enabled=false.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from trex_core.runtime.config import DaemonConfig, DaemonInstanceConfig
|
|
|
|
_REPO = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def _write_toml(tmp_path, body: str, name: str = "inst.toml") -> str:
|
|
p = tmp_path / name
|
|
p.write_text(body)
|
|
return str(p)
|
|
|
|
|
|
# --- REQ-076 AC-1: TOML value applied -----------------------------------------
|
|
|
|
def test_toml_watchdog_timeout_applied(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
|
|
path = _write_toml(tmp_path, 'instance_key = "lt"\nwatchdog_timeout_sec = 1800\n')
|
|
cfg = DaemonConfig.from_config(path)
|
|
assert cfg.safety_sell.watchdog_timeout_sec == 1800
|
|
|
|
|
|
def test_toml_wins_over_env(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
|
|
monkeypatch.setenv("TREX_WATCHDOG_TIMEOUT", "300")
|
|
path = _write_toml(tmp_path, 'instance_key = "lt"\nwatchdog_timeout_sec = 1800\n')
|
|
assert DaemonConfig.from_config(path).safety_sell.watchdog_timeout_sec == 1800
|
|
|
|
|
|
# --- REQ-076 AC-2: absent -> env fallback (no regression) ---------------------
|
|
|
|
def test_absent_falls_back_to_env(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
|
|
monkeypatch.setenv("TREX_WATCHDOG_TIMEOUT", "300")
|
|
path = _write_toml(tmp_path, 'instance_key = "day"\n')
|
|
assert DaemonConfig.from_config(path).safety_sell.watchdog_timeout_sec == 300
|
|
|
|
|
|
def test_absent_and_no_env_keeps_default_120(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
|
|
monkeypatch.delenv("TREX_WATCHDOG_TIMEOUT", raising=False)
|
|
path = _write_toml(tmp_path, 'instance_key = "day"\n')
|
|
assert DaemonConfig.from_config(path).safety_sell.watchdog_timeout_sec == 120
|
|
|
|
|
|
# --- REQ-076 AC-3: per-instance independence ----------------------------------
|
|
|
|
def test_instances_get_independent_timeouts(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
|
|
monkeypatch.delenv("TREX_WATCHDOG_TIMEOUT", raising=False)
|
|
day = _write_toml(tmp_path, 'instance_key = "day"\n', name="day.toml")
|
|
lt = _write_toml(
|
|
tmp_path, 'instance_key = "lt"\nwatchdog_timeout_sec = 1800\n', name="lt.toml"
|
|
)
|
|
assert DaemonConfig.from_config(day).safety_sell.watchdog_timeout_sec == 120
|
|
assert DaemonConfig.from_config(lt).safety_sell.watchdog_timeout_sec == 1800
|
|
|
|
|
|
# --- from_toml validation (design: <= 0 rejected) ------------------------------
|
|
|
|
def test_nonpositive_timeout_rejected(tmp_path):
|
|
for bad in ("0", "-60"):
|
|
path = _write_toml(tmp_path, f'instance_key = "x"\nwatchdog_timeout_sec = {bad}\n')
|
|
with pytest.raises(RuntimeError):
|
|
DaemonInstanceConfig.from_toml(path)
|
|
|
|
|
|
# --- REQ-077 AC-1/AC-4: long-term profile recipe -------------------------------
|
|
|
|
def test_longterm_example_profile_loads_with_expected_combination(monkeypatch):
|
|
"""The shipped example profile yields: dedicated instance_key, safety-sell
|
|
worker OFF, watchdog 1800s, capacity cap set."""
|
|
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
|
|
example = _REPO / "config" / "longterm.example.toml"
|
|
cfg = DaemonConfig.from_config(str(example))
|
|
assert cfg.instance_key == "longterm" # A-2: separate instance scope
|
|
assert cfg.safety_sell_enabled is False # autonomous liquidation OFF
|
|
assert cfg.safety_sell.watchdog_timeout_sec == 1800
|
|
assert cfg.max_active_slots == 10
|