시작은 작은 지적이었다
- 최초 지적: 손익 화면에서 수수료와 세금이 하나의 값으로 합산 표시
- 첫 판단: “두 값을 나눠 보여주면 끝” — 넘어갈 뻔함
- 확인 결과: 합산 구조만의 문제가 아님. 요율 자체가 틀려 있었다.
가정 대신 실측한 내역
- 데이터: 실제 체결 47건
- 출처: KIS API
TTTC8715R(기간별 매매손익 조회) - 방법: 실제 청구액과 코드 계산값 역산 대조
발견한 오차 세 가지
| 항목 | 기존 코드 | 실측 | 영향 |
|---|---|---|---|
| 수수료 | 0.015% | 0.0035427% | 4.1배 과대 |
| 거래세 | 0.18% | 0.20% | 과소 (2026-01-01 시행 반영 누락) |
| ETF 거래세 | 0.18% 부과 | 면제 | ETF 손익 과소계산 |
1. 위탁수수료는 지금 면제 상태였다
- 실측: 실제 징수된 수수료 합 ÷ 왕복거래대금 = 0.0035427%
-
잔여 항목: 유관기관 제비용 0.0036396%
- 잔여분의 정체: 거래소·예탁원 징수 몫 → 증권사 면제 대상 아님
- 면제 종료 시 대응: 뱅키스 기준 0.0140527% 가산 (주석으로 함께 기록)
2. 거래세는 2026년부터 0.20%로 바뀌었다
- 코스피: 거래세 0.05% + 농특세 0.15%
-
코스닥: 0.20%
- 구성 방식은 다르지만 합계 동일 → 단일 값 처리로 충분
- 실측 25종목 전부 0.1981~0.2000% 범위 안
3. ETF는 거래세가 아예 면제였다
- 실측: 운용사가 서로 다른 ETF 4종 모두 세금 0원
-
기존 코드: 여기에도 0.18% 부과
- 결과: ETF 종목의 실제 수익성을 체계적으로 과소계산
어떻게 고쳤나
- 함수 분리 — 기존
calc_sell_cost하나가 수수료·세금을 함께 계산 - 분리 후:
calc_sell_commission+calc_tax - 분리 근거는 성격 차이 — 수수료는 증권사 정책에 따라 면제 가능, 거래세는 면제 불가
- 묶어둘 때의 위험: 수수료 면제 종료 시 세금까지 같이 바뀌는 실수
나머지 조치:
- 요율 3종 실측값으로 교정 —
CommissionConfig,BacktestConfig양쪽 - ETF 거래세 면제 반영 —
KISClient.is_etf - 매도 알림에 수수료 · 거래세 개별 표시 → 면제 적용 여부를 눈으로 즉시 확인
ETF 판별을 왜 API 기준으로 바꿨나
- 최초 방식: 종목명 접두어(운용사 브랜드)로 판별
ETF 이름은 브랜드 + 테마 2단 구조라, 앞부분만 보면 ETF인지 알 수 있다고 봤다.
그런데 검증 도중에 로컬 종목명 테이블이 코드→이름을 잘못 반환한다는 걸 발견했다.
아래 표의 브랜드명은 가명(A운용·B운용…)으로 바꿨지만 구조는 실제와 같다.
| 코드 | 실제 종목명 | 로컬 조회 결과 |
|---|---|---|
| 종목1 | A운용 원자력TOP10 | 종목1 (조회 실패 — 코드가 그대로) |
| 종목2 | B운용 조선TOP3 | C운용 원자력 (다른 종목으로 오매칭) |
| 종목3 | C운용 AI반도체TOP2 | D운용 반도체TOP10 (다른 종목으로 오매칭) |
변경 후:
-
근거 필드: KIS API 응답의
rprs_mrkt_kor_name - 캐시 전략: ETF 여부는 종목의 불변 속성 → 프로세스 생존 동안 캐시 후 재사용
- 캐시 이유: API 호출 제한(rate limit, 에러코드 EGW00215) 회피
- 판별 실패·애매 시 처리: 과세로 fallback
- fallback 근거: 비용 과소 산정 → 손익 과대 표시로 이어지는 쪽이 더 위험
검증
- 대조 대상: 실계좌 체결 29건 vs 수정된 계산식
| 지표 | 수정 전 | 수정 후 |
|---|---|---|
| 유의한 편차 | 1건 (ETF 오탐) | 0건 |
| 세금 상대오차 | 1.250% | 0.068% |
- 잔여 오차: 건당 1~2원 — 원 단위 절사 규칙 차이에서 발생
-
판정: 요율 자체는 정확
- 테스트 275건 통과 —
test_commission,test_config,test_realtime_engine,test_notifier,test_api_client
배포할 때 남긴 주의사항
-
config.py특성: Docker 이미지에 그대로 구워지는 경로 - 호스트 마운트 파일만 갱신 시 → 이미지 내부 값과 버전 불일치
-
결론: 재빌드 필수
- 실거래 중인 시스템이므로 배포는 장 마감 후로 지정
남는 교훈
이 작업 전체를 관통하는 태도는 “가정하지 말고 실측하라”였다.
- 틀린 건 수수료율 하나가 아니라 세 가지가 겹친 것 — 원인도 서로 다름
- 오래된 상수를 갱신하지 않은 것
- 법 개정을 놓친 것
- 참조하던 데이터 소스 자체가 신뢰할 수 없었던 것
- 공통점: 코드만 들여다봐서는 발견 불가
- 발견 조건: 실제 체결 내역 — 시스템 바깥의 진실과 대조