# 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로 넘기기 전 확인 체크 - [x] 범위(In Scope/Out of Scope) 확정 - 기준: 소급 보정(OQ-3) In/Out 확정 + 정책 상수 위치(OQ-2) 확정 - 결과: OQ-2 env 설정 확정, OQ-3 소급 보정 Out of Scope 확정 - [x] 성공 지표의 측정 방식/데이터 출처 확정 - 기준: §4 공식 기대값 1,376.9원 역산에 필요한 세율/수수료율(OQ-1) 확정 - 결과: OQ-1 매도세 0.2%, 수수료 0.015% 확정 — §4 역산 합계 1,376.9원 - [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - 기준: R-1(세율 오류) 회귀 테스트 기준값 확정 담당 Justin Ko - 결과: R-1 완화 방안 확정 — §4 기대 1,376.9원 회귀 테스트 기준 고정, Justin Ko 담당 - [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 - 결과: OQ-1·OQ-2·OQ-3 모두 Closed — Open 항목 0개 - [x] 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 ## 섹션 참조 - 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) - 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) - 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) - 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) - 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#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