108 lines
4.7 KiB
Python
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)
|