Etc

체결 47건을 역산해서야 드러난 4.1배짜리 수수료 오차

시작은 작은 지적이었다

  • 최초 지적: 손익 화면에서 수수료와 세금이 하나의 값으로 합산 표시
  • 첫 판단: “두 값을 나눠 보여주면 끝” — 넘어갈 뻔함
  • 확인 결과: 합산 구조만의 문제가 아님. 요율 자체가 틀려 있었다.

가정 대신 실측한 내역

  • 데이터: 실제 체결 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 이미지에 그대로 구워지는 경로
  • 호스트 마운트 파일만 갱신 시 → 이미지 내부 값과 버전 불일치
  • 결론: 재빌드 필수

  • 실거래 중인 시스템이므로 배포는 장 마감 후로 지정

남는 교훈

이 작업 전체를 관통하는 태도는 “가정하지 말고 실측하라”였다.

  • 틀린 건 수수료율 하나가 아니라 세 가지가 겹친 것 — 원인도 서로 다름
  • 오래된 상수를 갱신하지 않은 것
  • 법 개정을 놓친 것
  • 참조하던 데이터 소스 자체가 신뢰할 수 없었던 것
  • 공통점: 코드만 들여다봐서는 발견 불가
  • 발견 조건: 실제 체결 내역 — 시스템 바깥의 진실과 대조