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>
15 KiB
15 KiB
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 checkclean.
측정 방식 및 데이터 출처
- 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개
- 기준: OQ 목록에
- Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: Justin Ko 승인 후
confirm-discovery수행 - 결과: Justin Ko 승인, confirm-discovery 완료
- 기준: Justin Ko 승인 후
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
섹션 참조
- 범위: # 5. 요구사항 목록 > 범위 경계
- 리스크/오픈 질문: # 6. 리스크/가정 목록
- 성공 기준: # 7. 초기 성공 기준
- 인계 전 확인: # 8. REQ로 넘기기 전 확인 체크
- 사용자 결정 필요: # 10. 사용자 결정 필요 항목 요약
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