"""vol_live 모니터 에쿼티·B&H 테스트.""" from __future__ import annotations import sys from pathlib import Path import pandas as pd import pytest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")) from bithumb.operations.vol_live_monitor import ( build_multi_buyhold_series, build_spot_strategy_equity_series, ) def test_multi_buyhold_thirds() -> None: """3종목 1/3씩 B&H — 한 종목만 10% 상승 시 포트폴리오 +3.33% 근사.""" panel = pd.DataFrame({ "datetime": pd.to_datetime(["2026-06-01 10:00:00", "2026-06-01 10:15:00"]), "TRX": [100.0, 110.0], "NEAR": [200.0, 200.0], "WLD": [300.0, 300.0], }) bh = build_multi_buyhold_series(panel, ["TRX", "NEAR", "WLD"], seed_krw=300_000.0) assert bh[0]["value"] == 0.0 assert bh[1]["value"] == pytest.approx(3.3333, rel=1e-3) def test_strategy_replay_buy_sell() -> None: panel = pd.DataFrame({ "datetime": pd.to_datetime(["2026-06-01 10:00:00", "2026-06-01 10:15:00"]), "TRX": [100.0, 110.0], "NEAR": [200.0, 200.0], "WLD": [300.0, 300.0], }) trades = [ { "symbol": "TRX", "side": "buy", "ts": "2026-06-01 10:05:00", "order_krw": 100_000.0, "order_coin": 1000.0, "price": 100.0, }, ] curve = build_spot_strategy_equity_series( panel, ["TRX", "NEAR", "WLD"], trades, seed_krw=300_000.0, current_equity=310_000.0, window_start=pd.Timestamp("2026-06-01 10:00:00"), ) assert curve[0]["value"] == 0.0 assert curve[-1]["value"] == pytest.approx(3.3333, rel=1e-2) def test_write_vol_monitor_html_no_format_error(tmp_path: Path) -> None: import re from bithumb.operations.vol_monitor_chart import write_vol_monitor_html out = tmp_path / "vol_live_monitor.html" write_vol_monitor_html(out) text = out.read_text(encoding="utf-8") assert "/api/chart" in text assert "equityChart" in text js = re.search(r"", text, re.S) assert js is not None js_path = tmp_path / "monitor.js" js_path.write_text(js.group(1), encoding="utf-8") import subprocess subprocess.run(["node", "--check", str(js_path)], check=True, capture_output=True) def test_candles_api_payload_filters_and_limits(monkeypatch) -> None: """/api/candles — 허용 종목·분봉 검증, max_bars 제한, 최신봉 순서.""" from types import SimpleNamespace from bithumb.operations import vol_live_monitor as m df = pd.DataFrame({ "datetime": pd.date_range("2026-06-01 10:00:00", periods=5, freq="5min"), "open": [1.0, 2.0, 3.0, 4.0, 5.0], "high": [1.5, 2.5, 3.5, 4.5, 5.5], "low": [0.5, 1.5, 2.5, 3.5, 4.5], "close": [1.2, 2.2, 3.2, 4.2, 5.2], "volume": [1, 1, 1, 1, 1], }) monkeypatch.setattr(m, "load_last_candles", lambda _db, _sym, _iv, n: df.tail(int(n)).reset_index(drop=True)) settings = SimpleNamespace( db_path="x.db", ops_symbols=["TRX"], vol_monitor_symbols=["XRP", "TRX"], vol_monitor_intervals=[1, 5, 15], vol_monitor_max_bars=3, ) out = m.build_candles_api_payload(settings, "trx", "5") assert out["ok"] and out["symbol"] == "TRX" and out["interval"] == 5 assert out["count"] == 3 and out["label"] == "5분" assert [c["close"] for c in out["candles"]] == [3.2, 4.2, 5.2] assert out["last"] == "2026-06-01 10:20:00" assert not m.build_candles_api_payload(settings, "BTC", 5)["ok"] assert not m.build_candles_api_payload(settings, "TRX", 7)["ok"] assert not m.build_candles_api_payload(settings, "TRX", "abc")["ok"]