Files
trex-core/docs/batches/bat-038_20260612_sell-transaction-tax-fee/design.md
T
genichinandClaude Opus 4.8 1cf13b060f feat(broker): BAT-038 — 매도 증권거래세 누락 정정 + fee/tax 단일 정책 지점 (dcy-026/REQ-018, issue #24)
dcy-026 confirm-discovery + REQ-018 change-req(CHG-20260612-01)→구현→
confirm-req-implemented(CHG-20260612-02) 전체 delivery.

- broker_port.py: calculate_tax(side, amount) 신규 + fee/tax 율 env 상수화
  (TREX_KIS_FEE_BPS=1.5, TREX_KRX_TAX_SELL_BPS=20.0). 매도 0.2%/매수 0.
- paper.py / kis.py: tax=0.0 하드코딩 3곳 제거 → 정책 함수 호출.
  KIS daily-ccld 응답에 fee/tax 필드 부재 → 정책 폴백(향후 정본 우선 훅).
- runtime-flows.md: 체결→ledger fee/tax 산출 흐름 갱신.
- tests: fee/tax 정책 단위 + issue #24 4건 회귀(fee+tax=1,376.9원).
  429 passed (+6), ruff clean.

REQ-018 Approved→Implemented, BAT-038 release-candidate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 17:57:15 +09:00

4.2 KiB
Raw Blame History

BAT-038 Design

  • Status: confirmed
  • Batch: BAT-038

Architecture Summary

  • broker_port.py 의 fee 단일 정책 지점을 fee+tax 로 확장한다. 기존 calculate_fee(side, amount) 옆에 calculate_tax(side, amount) 를 추가하고, 수수료율·세율을 env 로딩 상수로 통일한다(_FEE_BPS_* 하드코딩 → env 기본값).
  • paper/kis 어댑터의 3개 tax=0.0 사이트(paper.py:90, kis.py:213·230)를 calculate_tax(side, amount) 호출로 교체한다.
  • KIS daily-ccld 응답은 fee/tax 필드를 제공하지 않으므로(아래 Key Decision 3) KIS 도 paper 와 동일하게 정책 추정값을 사용한다. 브로커 응답 정본 우선(FR-3)은 향후 확장 가능한 guarded 파싱 훅으로 표현하되 현재 endpoint 에서는 폴백 경로만 활성.
  • fee/tax 전달 경로(execution.py:_record_ledger → LedgerTrade)는 이미 fs.tax 를 운반하므로 계약 변경 없음(planning R-3 점검 완료).

Changed Areas

  • Modules:
    • trex_core/application/broker_port.py:
      • _FEE_BPS_BUY/_FEE_BPS_SELL → env(TREX_KIS_FEE_BPS, 기본 1.5) 로딩 상수로 통일
      • _TAX_BPS_SELL 신규 → env(TREX_KRX_TAX_SELL_BPS, 기본 20.0)
      • calculate_tax(side, amount) -> float 신규: 매도 amount × _TAX_BPS_SELL / 10_000, 매수 0
      • FillStatus.tax 주석 갱신(0 고정 → 매도 정책 계산, dcy-026)
    • trex_core/adapters/broker/paper.py: line 90 tax=0.0 → tax=calculate_tax(o.side, o.filled * o.price)
    • trex_core/adapters/broker/kis.py:
      • line 213(전량)·230(부분) tax=0.0 → calculate_tax(side, ccld_qty * fill_price)
      • calculate_tax import 추가
      • (선택) _parse_broker_cost(row) guarded 훅 — fee/tax 필드 부재 시 None → 정책 폴백(FR-3 future-proof)
  • Tests:
    • tests/application/ 또는 tests/adapters/broker/: 매수/매도 fee·tax 단위, issue #24 4건 회귀(1,376.9원), paper·KIS 동일 정책, KIS 폴백 경로
  • Docs: docs/runtime-flows.md line 254

Key Decisions

  1. calculate_tax 신규 함수(통합 아님): calculate_fee 와 대칭으로 별도 함수. side 분기 단순(매수 0/매도 세율), 호출부에서 fee=calculate_fee(...), tax=calculate_tax(...) 명시적 페어. 단일 정책 지점 원칙 충족(AC-7).
  2. env 로딩, 기본값 1.5 / 20.0: 기존 TREX_* os.environ 패턴(config.py 사례) 준용. 모듈 로드 시 float(os.environ.get("TREX_KIS_FEE_BPS", "1.5")), float(os.environ.get("TREX_KRX_TAX_SELL_BPS", "20.0")). paper·KIS 가 동일 모듈 상수 공유 → 동일 정책 보장(NFR-2).
  3. KIS 응답 fee/tax 필드 부재 → 항상 정책 폴백 (R-2 해소): 어댑터가 쓰는 주식일별주문체결조회(inquire-daily-ccld, output1)는 per-fill 수수료/증권거래세 필드를 제공하지 않는다(tot_ccld_amt 만 존재, docs/broker/kis/rest/국내주식/주문/계좌/021 확인). 따라서 KIS 도 calculate_fee/calculate_tax 정책값을 사용한다. FR-3 "브로커 정본 우선" 은 필드가 없어 구조적으로 폴백 경로만 활성 — 오파싱 리스크 자체가 제거됨. 향후 fee/tax 노출 endpoint 도입 시 guarded 훅 지점에 파싱 추가.
  4. 소급 보정 없음(dcy-026 OQ-3): 과거 행 미변경, 신규 체결부터 정확값.

Edge Cases

  • amount <= 0: calculate_tax 도 calculate_fee 처럼 0 반환(0/음수 가드).
  • 매수 체결: calculate_tax("buy", amount) == 0 (AC-6 매수 무세).
  • 부분 체결(kis.py:230): 체결분 ccld_qty * fill_price 기준으로 fee/tax 계산(전량과 동일 공식, 체결분만).
  • fill_price == 0(avg 부재): amount=0 → fee/tax 0(기존 fee 동작과 일관).
  • KIS 응답 fee/tax 필드 부재(현재 항상): 정책 폴백, 로그 불필요(설계상 기본 경로).

Architecture Impact

  • runtime-flows

Reference Doc Update Plan

  • project-structure.md: none (모듈 구조 불변, 함수 1개 추가만)
  • runtime-flows.md: line 254 체결 → ledger outbox flow — "계산된 fee(...), tax=0 을 운반" → "계산된 fee(calculate_fee)·tax(calculate_tax, 매도 정책 세율 0.2%/매수 0) 를 운반" 으로 갱신. fee/tax 단일 정책 지점(env TREX_KIS_FEE_BPS/TREX_KRX_TAX_SELL_BPS) 주석 추가.