"""ledger pending 단위 테스트.""" from __future__ import annotations from bithumb.operations.runner import ( _advance_cursor_from_ledger, _apply_backlog_limit, _is_settled, _is_signal_api_executable, _ledger_pending_signals, _merge_pending_signals, _settle_expired_backlog, ) from datetime import datetime def test_is_settled_executed_or_expected_skip() -> None: """executed 또는 expected_skip이면 settled.""" assert _is_settled({"executed": True, "expected_skip": False}) assert _is_settled({"executed": False, "expected_skip": True}) assert not _is_settled({"executed": False, "expected_skip": False}) def test_ledger_pending_skips_settled_and_includes_missing() -> None: """history settled 신호는 제외, 미기록 신호는 pending.""" kept = [ {"datetime": "2026-06-14 14:09:00", "side": "buy", "bar_index": 10}, {"datetime": "2026-06-14 14:15:00", "side": "sell", "bar_index": 11}, {"datetime": "2026-06-14 14:51:00", "side": "sell", "bar_index": 12}, ] history = [ { "datetime": "2026-06-14 14:09:00", "side": "buy", "trade": {"executed": True, "expected_skip": False}, }, { "datetime": "2026-06-14 14:51:00", "side": "sell", "trade": {"executed": False, "expected_skip": True}, }, ] pending = _ledger_pending_signals( kept, history, latest_bar_index=12, lookback_days=3, ) assert len(pending) == 1 assert pending[0]["datetime"] == "2026-06-14 14:15:00" def test_ledger_pending_includes_failed_api() -> None: """API 실패(expected_skip false)는 재시도 대상.""" kept = [ {"datetime": "2026-06-14 15:00:00", "side": "buy", "bar_index": 20}, ] history = [ { "datetime": "2026-06-14 15:00:00", "side": "buy", "trade": {"executed": False, "expected_skip": False}, }, ] pending = _ledger_pending_signals( kept, history, latest_bar_index=20, lookback_days=3, ) assert len(pending) == 1 def test_apply_backlog_limit() -> None: """tick당 backlog 상한.""" signals = [ {"datetime": f"2026-06-14 10:{i:02d}:00", "side": "buy", "bar_index": i} for i in range(5) ] limited, dropped = _apply_backlog_limit(signals, 2) assert len(limited) == 2 assert dropped == 3 def test_advance_cursor_from_ledger() -> None: """settled 신호까지만 커서 전진.""" kept = [ {"datetime": "2026-06-14 14:09:00", "side": "buy", "bar_index": 10}, {"datetime": "2026-06-14 14:15:00", "side": "sell", "bar_index": 11}, ] history = [ { "datetime": "2026-06-14 14:09:00", "side": "buy", "trade": {"executed": True}, }, ] state: dict = { "last_processed_datetime": "2026-06-14 13:00:00", "last_processed_bar_index": 5, } _advance_cursor_from_ledger(state, kept, history) assert state["last_processed_datetime"] == "2026-06-14 14:09:00" assert state["last_processed_bar_index"] == 10 def test_settle_expired_backlog() -> None: """만료 backlog는 API 없이 expected_skip 정산.""" from datetime import timedelta now = datetime.now() old_dt = (now - timedelta(hours=2)).strftime("%Y-%m-%d %H:%M:%S") kept = [{"datetime": old_dt, "side": "buy", "bar_index": 1, "price": 1.0}] settled = _settle_expired_backlog( kept, [], max_age_minutes=45, live_since=None, ) assert len(settled) == 1 assert settled[0]["trade"]["expected_skip"] is True assert "backlog 만료" in settled[0]["trade"]["skip_reason"] def test_is_signal_api_executable_recent() -> None: """최근 신호는 API 체결 가능.""" from datetime import timedelta recent = (datetime.now() - timedelta(minutes=10)).strftime("%Y-%m-%d %H:%M:%S") ok, _ = _is_signal_api_executable( {"datetime": recent, "side": "buy"}, max_age_minutes=45, live_since=None, ) assert ok def test_merge_pending_dedupes() -> None: """ledger·catchup 병합 시 datetime·side 중복 제거.""" a = [{"datetime": "2026-06-14 14:09:00", "side": "buy", "bar_index": 1}] b = [{"datetime": "2026-06-14 14:09:00", "side": "buy", "bar_index": 1}] merged = _merge_pending_signals(a, b) assert len(merged) == 1