Files
llm-wiki/.claude/hooks/test_invest_ledger_check.py

108 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""invest_ledger_check.py 단위 테스트 (stdlib unittest)."""
import importlib.util
import sys
import tempfile
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
_SPEC = importlib.util.spec_from_file_location(
"ilc", str(Path(__file__).with_name("invest_ledger_check.py")))
ilc = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(ilc)
import datetime as dt
HEADER = ("| 날짜 | 매수/매도 | 종목 | 수량 | 단가 | 수수료 | 체결환율 | 금액(원) | 계좌 | 근거(링크) | 규칙체크 |\n"
"|---|---|---|---|---|---|---|---|---|---|---|\n")
def ledger(rows: str) -> str:
return ("# 원장\n## 현재 포지션 / Open Positions\n| a |\n|---|\n"
"## 거래 내역 / Trade Log\n" + HEADER + rows +
"\n## 규칙 위반 이력 / Rule-check Findings\n## 손익 요약 / P&L Summary\n")
TODAY = dt.date(2026, 6, 10)
def mk_evidence(root: Path, rel: str, date_str: str):
p = root / (rel + ".md")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(f"---\ntitle: x\ndate: {date_str}\n---\n# t\n", encoding="utf-8")
class TestCheck(unittest.TestCase):
def _run(self, rows, root, **kw):
return ilc.run_check(ledger(rows), root, kw.get("daily_max_h", 24),
kw.get("research_max_d", 90), kw.get("weekly_cap"), TODAY)
def test_clean_row_passes(self):
with tempfile.TemporaryDirectory() as d:
root = Path(d)
mk_evidence(root, "raw/invest-research/2026-06-08-x", "2026-06-08")
row = "| 2026-06-10 | 매수 | SCHD | 2 | 27.5 | 0.1 | 1380 | 76000 | ISA | [[raw/invest-research/2026-06-08-x]] | ✅ |"
self.assertEqual(self._run(row, root), 0)
def test_no_evidence_flags(self):
with tempfile.TemporaryDirectory() as d:
row = "| 2026-06-10 | 매수 | SCHD | 2 | 27.5 | 0.1 | 1380 | 76000 | ISA | 감으로 | ✅ |"
self.assertEqual(self._run(row, Path(d)), 1)
def test_missing_evidence_target_flags(self):
with tempfile.TemporaryDirectory() as d:
row = "| 2026-06-10 | 매수 | SCHD | 2 | 27.5 | 0.1 | 1380 | 76000 | ISA | [[raw/invest-research/ghost]] | ✅ |"
self.assertEqual(self._run(row, Path(d)), 1)
def test_stale_daily_evidence_flags(self):
with tempfile.TemporaryDirectory() as d:
root = Path(d)
mk_evidence(root, "raw/invest-daily/2026-06-06", "2026-06-06") # 4d > 24h
row = "| 2026-06-10 | 매도 | SCHD | 1 | 27.5 | 0.1 | 1380 | 38000 | ISA | [[raw/invest-daily/2026-06-06]] | ✅ |"
self.assertEqual(self._run(row, root), 1)
def test_fresh_research_passes_but_old_flags(self):
with tempfile.TemporaryDirectory() as d:
root = Path(d)
mk_evidence(root, "raw/invest-research/2026-01-01-x", "2026-01-01") # 160d > 90d
row = "| 2026-06-10 | 매수 | SCHD | 2 | 27.5 | 0.1 | 1380 | 76000 | ISA | [[raw/invest-research/2026-01-01-x]] | ✅ |"
self.assertEqual(self._run(row, root), 1)
def test_bad_schema_flags(self):
with tempfile.TemporaryDirectory() as d:
row = "| 2026-06-10 | 매수 | SCHD | 2 |"
self.assertEqual(self._run(row, Path(d)), 1)
def test_weekly_cap(self):
with tempfile.TemporaryDirectory() as d:
root = Path(d)
mk_evidence(root, "raw/invest-research/2026-06-08-x", "2026-06-08")
ev = "[[raw/invest-research/2026-06-08-x]]"
rows = "\n".join(
f"| 2026-06-{day} | 매수 | SCHD | 1 | 27.5 | 0.1 | 1380 | 38000 | ISA | {ev} | ✅ |"
for day in ("08", "09", "10")) # 같은 ISO 주 3건
self.assertEqual(self._run(rows, root, weekly_cap=2), 1)
self.assertEqual(self._run(rows, root, weekly_cap=3), 0)
class TestReport(unittest.TestCase):
def test_aggregates(self):
import io
import contextlib
rows = ("| 2026-06-10 | 매수 | SCHD | 2 | 27.5 | 100 | 1380 | 76000 | ISA | [[raw/invest-research/x]] | ✅ |\n"
"| 2026-06-09 | 매수 | SCHD | 1 | 30 | 50 | 1380 | 41400 | ISA | [[raw/invest-research/x]] | ✅ |")
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
rc = ilc.run_report(ledger(rows), TODAY)
out = buf.getvalue()
self.assertEqual(rc, 0)
self.assertIn("매수 합(원): 117,400", out)
self.assertIn("누적 수수료: 150", out)
self.assertIn("순수량 3", out) # SCHD 2+1
self.assertIn("28.33", out) # (2*27.5 + 1*30)/3 매수가중
if __name__ == "__main__":
unittest.main(verbosity=2)