Files
trex-core/docs/discovery/dcy-026_20260612_sell-transaction-tax-and-fee-policy.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

15 KiB
Raw Blame History

0. 문서 상태

  • 상태: confirmed
  • 문서 ID: dcy-026_20260612_sell-transaction-tax-and-fee-policy
  • 이슈명: 매도 증권거래세 누락 + 수수료 정책 정정 (issue #24)
  • 작성 시각(KST): 2026-06-12
  • 마지막 갱신 시각(KST): 2026-06-12 (confirm-discovery: Justin Ko 승인 — 상태 confirmed 전환, §8 체크 완료, Freeze 플래그 갱신)
  • 대체됨: 없음
  • 후속 Discovery 참조: 없음
  • 생성된 REQ 참조: 없음

1. 계획 상태 요약

  • 연계 문서 현황:
    • 관련 점수 파일: 없음
    • 관련 TODO/메모: GitHub issue #24 (bug 라벨, 2026-06-11 trex-admin-daily-trading 4건 체결 증거 포함)
  • 해석:
    • 매도 증권거래세가 전 체결에서 tax=0.0으로 저장되어 실현 손익·일일 수익률이 과대 계산되는 운영 버그. 코드상 dcy-004 OQ-3로 미뤄둔 "정밀 세금 모델"이 운영에서 표면화된 사안.
    • 단순 핫픽스가 아니라 수수료율/세율 정책(매수·매도 구분, bp 단위, 세금 산식)을 계약으로 확정해야 하므로 신규 Discovery로 정식화한다.

2. 요구사항 판정 결과

  • 판정: 신규
  • 근거
    • 기존 ledger 계열 Discovery(dcy-004 연동, dcy-015 outbox 백오프, dcy-018 cash write-back)는 fee/tax 정책 자체를 다루지 않음. fee/tax 정확성은 미해결 영역.
    • trex_core/application/broker_port.py:51 주석이 "tax defaults to 0 (precise tax model is a follow-up Discovery, dcy-004 OQ-3)"로 후속 Discovery 필요를 명시 — 그 후속을 본 Discovery로 개설.

3. 문제점의 요약

  • 현재 상태
    • trex_core/application/broker_port.py:118-119: _FEE_BPS_BUY = _FEE_BPS_SELL = 1.5(0.015%) — 수수료만 계산. calculate_fee(side, amount) 단일 함수.
    • trex_core/adapters/broker/paper.py:90·kis.py:213·230: 체결 시 tax=0.0 하드코딩 — 매도 증권거래세가 전혀 반영되지 않음. 한국 주식 매도세(증권거래세+농특세 약 0.18~0.20%)는 수수료(0.015%)보다 10배 이상 큰 비용인데 누락.
    • 결과(issue #24 증거): 2026-06-11 4건 체결의 저장 fee+tax 합계 177.9원 vs 실제 기대 1,357원 — 차액 대부분이 누락된 매도세.
  • 기대 상태
    • 매도 체결 시 증권거래세를 정책 기준으로 계산해 FillStatus.tax에 반영하고 ledger_outbox에 정확히 저장한다.
    • 수수료율·세율을 단일 정책 지점(calculate_fee/calculate_tax)에서 관리하고, paper·KIS 어댑터가 동일 정책을 사용한다.
  • 이해관계자
    • 운영자/시스템 관리자 (Justin Ko): 정확한 일일 손익·수익률 리포트 필요
    • portfolio-hub(ledger 수신측): fee_amount/tax_amount 정확값 수신
    • 전략 모듈(trex-orb-v1): 비용 반영된 실현 손익 기반 의사결정
  • 제약사항
    • KIS 실 체결 응답에 수수료/세금 필드가 포함되면 우선 사용하고, 없으면 정책 추정값으로 폴백해야 함(브로커 정본 우선 원칙).
    • 정책 상수는 코드 하드코딩 대신 설정 가능성을 검토(세율 변경 대응).

주요 사용자 시나리오

  • 매도 round-trip 체결 후 운영자가 일일 리포트를 확인 → 수수료+세금이 실제 부과액과 일치하고 실현 손익이 과대 계산되지 않음.
  • KIS 라이브 매도 체결 시 브로커 응답의 세금/수수료가 있으면 그 값이, 없으면 정책 추정값이 ledger에 기록됨.
  • paper 브로커 검증 시에도 동일 정책으로 세금이 계산되어 라이브와 비교 가능한 ledger 엔트리 생성.

4. 이번에 정의할 변경

  • 매도 증권거래세 산식 도입 — calculate_tax(side, amount)(매수 0, 매도 정책 세율) 추가 또는 calculate_fee를 fee+tax 동시 산출로 확장.
  • paper·KIS 어댑터의 tax=0.0 하드코딩 제거 — 정책 함수 호출로 교체, KIS는 브로커 응답값 우선·정책 폴백.
  • 수수료/세율 정책 상수를 env 설정으로 노출 (OQ-2 Closed) 및 baseline 문서(runtime-flows.md) fee/tax 흐름 갱신.

제비용 계산 공식 (OQ-1 Closed 기준)

정책 파라미터 (env 설정, 기본값):

TREX_KIS_FEE_BPS      = 1.5    # 수수료율 (매수·매도 공통, 1.5bps = 0.015%)
TREX_KRX_TAX_SELL_BPS = 20.0   # KRX(KOSPI·KOSDAQ) 매도 증권거래세율 (20.0bps = 0.2%)

변수명에 KIS/KRX 도메인 라벨이 붙지만, 이 값은 단일 정책 지점으로 paper·KIS 어댑터가 동일하게 공유한다(FR-2·NFR-2). paper 브로커는 KIS 제비용을 모사하므로 같은 상수를 사용한다.

체결 단위 제비용:

amount  = price × qty

fee     = amount × TREX_KIS_FEE_BPS / 10_000          # 매수·매도 동일
tax     = amount × TREX_KRX_TAX_SELL_BPS / 10_000     # 매도만 적용; 매수 = 0
total   = fee + tax

side별 전개:

side fee tax total
buy amount × 0.00015 0 amount × 0.00015
sell amount × 0.00015 amount × 0.002 amount × 0.00215

issue #24 역산 검증 (qty=1, OQ-1):

BUY  291,500원 → fee=43.73원, tax=0원,   total=43.73원
SELL 301,000원 → fee=45.15원, tax=602원, total=647.15원
BUY  295,000원 → fee=44.25원, tax=0원,   total=44.25원
SELL 298,500원 → fee=44.78원, tax=597원, total=641.78원
─────────────────────────────────────────────────────
합계           fee=177.9원, tax=1,199원, total=1,376.9원
(이슈 기재 1,357원과 차이 약 20원 — 이슈 원작성자 추정 오차)

영향 범위

  • 코드 영향:
    • trex_core/application/broker_port.py: calculate_fee 정정 + calculate_tax 신규(또는 통합), 세율/수수료율 상수
    • trex_core/adapters/broker/paper.py: tax=0.0 → calculate_tax(...) (line 90 주변)
    • trex_core/adapters/broker/kis.py: tax=0.0 → 브로커 응답 파싱 + 정책 폴백 (line 213·230 주변)
    • trex_core/application/ledger_outbox.py·execution.py: fee/tax 전달 경로(계약 불변 가능성 높음, 검증 필요)
  • 문서 영향:
    • REQ-018(체결→원장 기록) change-req: fee/tax 산출 정책 명시
    • REQ-011(BrokerPort + paper double) change-req 가능성: 수수료/세율 정책 함수 계약
    • docs/runtime-flows.md: 체결→ledger fee/tax 산출 흐름
  • 운영 영향:
    • 배포 후 신규 체결부터 정확한 fee/tax 저장. 과거 잘못 저장된 행(2026-06-11 등) 보정 여부는 별도 결정(OQ).
    • portfolio-hub 측 손익 재계산 영향 — 수신측 정합 확인 필요.

5. 요구사항 목록

기능 요구사항

  • FR-1: 매도 체결에 대해 정책 기준 증권거래세를 계산해 FillStatus.tax에 반영한다. 매수 체결의 세금은 0으로 한다(한국 주식 매수 무세 전제).
  • FR-2: 수수료율·세율을 단일 정책 지점에서 관리하고 paper·KIS 어댑터가 동일 정책을 사용한다. tax=0.0 하드코딩을 제거한다.
  • FR-3: KIS 어댑터는 브로커 체결 응답에 수수료/세금 필드가 있으면 그 값을 정본으로 사용하고, 없을 때만 정책 추정값으로 폴백한다.
  • FR-4: docs/runtime-flows.md에 체결→ledger fee/tax 산출·저장 흐름을 갱신한다.

비기능 요구사항

  • NFR-1: fee/tax 계산은 결정적이며 단위 테스트로 매수/매도 각 케이스의 기대값을 검증 가능해야 한다(issue #24 증거 수치 회귀 테스트 포함).
  • NFR-2: paper·KIS 어댑터가 동일 입력(side, amount)에 동일 정책 결과를 산출한다(브로커 응답 미존재 시).
  • NFR-3: 정책 변경(세율 조정) 시 코드 한 지점 수정으로 전 경로에 반영된다.

범위 경계

  • In Scope
    • 매도 증권거래세 산식 + 수수료율 정정
    • paper·KIS tax=0.0 하드코딩 제거 및 정책 함수 적용
    • KIS 브로커 응답 fee/tax 파싱(존재 시 정본)
    • REQ-018(+REQ-011) change-req + runtime-flows 갱신 + 회귀 테스트
  • Out of Scope
    • 과거 잘못 저장된 ledger 행 소급 보정: Out of Scope — 보정하지 않음 (OQ-3 Closed)
    • portfolio-hub 측 손익 재계산 로직 변경(수신측 저장소 책임)
    • 세율의 동적 설정 UI/운영 도구(필요 시 후속)

6. 리스크/가정 목록

리스크

  • R-1 (High): 세율 산식 오류 시 손익이 반대 방향으로 다시 왜곡됨 — 검증 부족하면 버그를 버그로 교체.
    • 완화 방안: §4 공식 기준 기대 합계(fee 177.9 + tax 1,199 = 1,376.9원)를 회귀 테스트 기준으로 고정. 매수/매도 각 케이스 단위 테스트. (이슈 기재 1,357원은 원작성자 추정 오차 약 20원 — baseline 아님.)
  • R-2 (Medium): KIS 실 체결 응답의 수수료/세금 필드명·단위(원 vs bp)를 잘못 파싱하면 라이브에서만 오류 발생(paper에서 미검출).
    • 완화 방안: KIS 응답 raw 필드 확인 후 파싱, 미확인 필드는 정책 폴백 + 로그. 라이브 스모크 시 실제 값 대조.
  • R-3 (Medium): 과거 저장 행 미보정 시 2026-06-11 이전 리포트는 계속 왜곡 상태로 남음.
    • 완화 방안: OQ-3에서 소급 보정 범위·방법 결정. 보정 안 하면 release 문서에 알려진 한계로 명시.

가정

  • A-1: 한국 주식 기준 매수는 무세, 매도에 증권거래세(+농특세)가 부과된다. 정확한 세율 값은 사용자 확인 대상(OQ-1).
  • A-2: 신규 REQ 없이 REQ-018(+필요 시 REQ-011) change-req로 닫힌다 — 계약 구조는 유지, 산식·값만 정정.
  • A-3: KIS 체결 응답에 수수료/세금이 포함될 수 있으나 항상 보장되지는 않아 정책 폴백이 필요하다.

오픈 질문

  • OQ-1: 적용할 정확한 매도 세율(증권거래세+농특세 합산 %)과 수수료율(매수/매도 bp)은?
    • 상태: Closed
    • 결정: 매도 증권거래세 0.2% (매수 무세), 수수료율 0.015% (1.5bps) 매수·매도 동일. issue #24 4건 체결 역산(qty=1 확인, 계산 기대 합계 1,376.9원)으로 현행 코드값 0.015% 정합 확인. 이슈 기재 1,357원은 약 20원 추정 오차.
    • 종료 조건: 충족
  • OQ-2: 정책 상수를 코드 상수로 둘지, 설정(env/TOML)으로 노출할지?
    • 상태: Closed
    • 결정: env 설정으로 노출한다. 확정 변수명:
      • TREX_KIS_FEE_BPS — 한국투자증권 수수료율 (bps, 기본 1.5 = 0.015%, 매수·매도 공통)
      • TREX_KRX_TAX_SELL_BPS — KRX(KOSPI·KOSDAQ) 매도 증권거래세율 (bps, 기본 20.0 = 0.2%)
      • 기존 패턴(TREX_WATCHDOG_TIMEOUT, TREX_PAPER_FILL_AFTER_POLLS 등) 준용 — TREX_{도메인}_{파라미터} 형식
    • 종료 조건: 충족
  • OQ-3: 과거 잘못 저장된 ledger 행(2026-06-11 등) 소급 보정 여부와 방법은?
    • 상태: Closed
    • 결정: 소급 보정하지 않는다 — 과거 행은 그대로 둔다. 배포 후 신규 체결부터 정확한 fee/tax 저장. Out of Scope 확정.
    • 종료 조건: 충족

7. 초기 성공 기준

  • S-1: 매도 체결의 tax가 정책 세율로 계산되어 0이 아닌 정확값으로 ledger_outbox에 저장된다. issue #24의 4건 시나리오 재현 시 fee+tax 합계가 §4 공식 기대값(fee 177.9 + tax 1,199 = 1,376.9원)에 정확히 수렴한다.
  • S-2: paper·KIS 어댑터가 동일 정책으로 fee/tax를 산출하며, KIS는 브로커 응답값이 있으면 그 값을 사용한다.
  • S-3: 기존 단위·통합 테스트 회귀 없음 + 신규 fee/tax 회귀 테스트 green, ruff check clean.

측정 방식 및 데이터 출처

  • S-1 측정 방식: 단위 테스트 — issue #24 4건 체결값(price/qty) 입력 후 fee+tax 합계가 §4 공식 기대 1,376.9원에 수렴 확인 / 데이터 출처: issue #24 Evidence + §4 공식 + tests/application/ 신규 테스트
  • S-2 측정 방식: paper·KIS 어댑터 단위 테스트로 동일 입력 동일 출력 확인 + KIS 응답값 우선 경로 테스트 / 데이터 출처: tests/adapters/broker/
  • S-3 측정 방식: pytest 전체 회귀 + ruff check trex_core tests / 데이터 출처: 로컬/CI 결과

8. REQ로 넘기기 전 확인 체크

  • 범위(In Scope/Out of Scope) 확정
    • 기준: 소급 보정(OQ-3) In/Out 확정 + 정책 상수 위치(OQ-2) 확정
    • 결과: OQ-2 env 설정 확정, OQ-3 소급 보정 Out of Scope 확정
  • 성공 지표의 측정 방식/데이터 출처 확정
    • 기준: §4 공식 기대값 1,376.9원 역산에 필요한 세율/수수료율(OQ-1) 확정
    • 결과: OQ-1 매도세 0.2%, 수수료 0.015% 확정 — §4 역산 합계 1,376.9원
  • High 영향 리스크 우선순위 및 해소 책임자 지정
    • 기준: R-1(세율 오류) 회귀 테스트 기준값 확정 담당 Justin Ko
    • 결과: R-1 완화 방안 확정 — §4 기대 1,376.9원 회귀 테스트 기준 고정, Justin Ko 담당
  • 모든 오픈 질문(OQ) Open 상태 없음 확인
    • 기준: OQ 목록에 Open 상태 항목이 0개이거나, 잔여 항목 전부 Deferred 처리 완료
    • 결과: OQ-1·OQ-2·OQ-3 모두 Closed — Open 항목 0개
  • Freeze 확정 담당자(CONFIRMED_BY) 지정
    • 기준: Justin Ko 승인 후 confirm-discovery 수행
    • 결과: Justin Ko 승인, confirm-discovery 완료

9. 파일 처리 결과

  • 처리 결과: 생성
  • 생성 경로: docs/discovery/dcy-026_20260612_sell-transaction-tax-and-fee-policy.md
  • 참조 문서:
    • GitHub issue #24 — fee/tax 저장 오류 증거(2026-06-11 4건 체결, 기대 1,357원 vs 저장 177.9원)
    • trex_core/application/broker_port.py:51·118-131 — fee 모델 + tax follow-up 주석(dcy-004 OQ-3)
    • trex_core/adapters/broker/paper.py:90, kis.py:213·230 — tax=0.0 하드코딩(변경 대상)
    • docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md — OQ-3 정밀 세금 모델 선행 참조

10. 사용자 결정 필요 항목 요약

DECIDE

  • 매도 세율: 0.2%, 수수료율: 0.015% (1.5bps) — 확정 (OQ-1 Closed)
  • 정책 상수 위치: env 설정 — TREX_KIS_FEE_BPS (기본 1.5), TREX_KRX_TAX_SELL_BPS (기본 20.0) — 확정 (OQ-2 Closed)
  • 과거 ledger 행 소급 보정: Out of Scope — 확정 (OQ-3 Closed)

CONFIRM

  • KIS 체결 응답에 수수료/세금 필드가 실제 포함되는지 확인 — 포함 시 정본 우선, 미포함 시 정책 폴백(FR-3·R-2 연결). delivery 단계 KIS raw 응답 확인으로 종료.

DATA

  • 없음 — issue #24 4건 체결의 기대 수수료/세금 분해값은 §4 "제비용 계산 공식"에서 체결별로 제공됨(answered).

11. Discovery Freeze

섹션 참조

Freeze 플래그

  • Handoff Decision: REQ Drafting 진행 가능
  • Handoff Rationale: OQ-1(세율/수수료율), OQ-2(정책 위치), OQ-3(소급 보정) 3건 모두 Closed. §8 체크 완료. 구현 부재는 정상(신규 작업 정의 Discovery).
  • Ready for REQ Drafting: true
  • Confirmed By: Justin Ko
  • Confirmed At (KST): 2026-06-12