정직 검증 · 매매 비용

같은 코스피, 같은 10년.
다른 건 매매 횟수 하나뿐입니다.

2016년 8월부터 2026년 8월까지, 코스피에 1000만원을 넣었습니다. 한 번도 안 판 계좌에는 3393만원이, 매일 사고판 계좌에는 12만원이 남았습니다. 종목도 기간도 같습니다. 매매를 잘해서 수익률이 달라지는 부분은 계산에 넣지 않았습니다 — 같은 수익률을 냈다고 쳤을 때 비용만으로 얼마나 갈리는지를 본 것입니다.

재현 코드 받기 이 계산의 한계 →
3393만원
한 번도 안 판 계좌
12만원
매일 사고판 계좌
2300원
100만원 한 번 사고팔 때

1. 한 번 사고팔면 얼마가 나가나

증권거래세는 팔 때만 냅니다. 수익이 났든 손해가 났든 상관없이 판 금액에 붙습니다.

구분2025년까지2026년부터
코스피 (거래세 0.05% + 농특세 0.15%)0.15%0.20%
코스닥 (농특세 없음)0.15%0.20%
위탁수수료 (MTS 기준 · 매수·매도 각각)0.015%
왕복 합계0.18%0.23%

100만원어치라면 — 살 때 수수료 150원, 팔 때 수수료 150원에 세금 2000원. 한 번 사고팔면 2300원입니다. 커피 한 잔 값도 안 됩니다. 문제는 이걸 몇 번 반복하느냐입니다.

2. 10년을 돌려 봤습니다

여기서 사고판다는 건 전부 팔고 곧바로 같은 걸 다시 사는 것입니다. 현금으로 들고 있거나 다른 종목으로 갈아타는 게 아닙니다. 그래야 수익률은 그대로 두고 비용만 볼 수 있습니다. 코스피는 2016년 8월 2000선에서 2026년 8월 6900선 — 10년 동안 3.4배가 된 역대급 상승장이었습니다.

매매 주기연 왕복 횟수남은 돈안 판 것 대비
안 판다0회3,393만원100.0%
연 1회1회3,315만원97.7%
분기 1회4회3,094만원91.2%
월 1회12회2,574만원75.9%
주 1회52회1,029만원30.3%
매일246회12만원0.4%

눈여겨볼 곳은 위쪽입니다. 분기에 한 번까지는 3094만원이 남습니다 — 횟수가 적을 때는 비용이 결과를 크게 흔들지 못합니다. 차이가 벌어지는 건 월 1회를 넘어서면서부터입니다. 주 1회는 일주일에 한 번 정도 사고파는 흔한 습관인데, 안 판 계좌와 2363만원이 벌어졌습니다.

3. 왜 이렇게까지 벌어지나

주가가 전혀 움직이지 않았다고 쳐 보면 비용만 남습니다.

매매 주기연 왕복1년 뒤10년 뒤
연 1회1회998만원977.2만원
분기 1회4회991만원912.0만원
월 1회12회973만원758.6만원
주 1회52회887만원302.0만원
매일246회568만원3.5만원

1000만원으로 매일 사고팔면 1년 뒤에 568만원이 남습니다. 주가는 그대로인데 절반 가까이가 사라진 것입니다. 비용은 뗄 때마다 남은 돈에서 다시 떼어 가기 때문입니다. 줄어든 원금으로 다음 매매를 하고 거기서 또 뗍니다. 수익이 복리로 불어나듯 비용도 복리로 깎아 냅니다. 방향만 반대입니다.

4. 국내 주식형 ETF에는 이 세금이 없습니다

국내 주식형 ETF는 증권거래세가 면제됩니다. 팔 때 붙던 2000원이 아예 없습니다. 수수료만 내면 되니 100만원을 사고팔 때 300원 — 개별 주식의 8분의 1입니다.

매매 주기개별 주식국내 주식형 ETF차이
월 1회2,574만원3,161만원+587만원
주 1회1,029만원2,904만원+1,875만원
매일12만원1,628만원+1,616만원

다만 ETF가 세금을 하나도 안 낸다는 뜻은 아닙니다. 면제되는 건 파는 순간의 증권거래세뿐이고, 분배금에는 배당소득세 15.4%가 붙습니다. 해외 지수를 담는 ETF는 세금 구조가 또 달라서 이 계산과 다르게 나옵니다.

이 계산의 한계

그래도 남는 게 둘 있습니다. 매매 횟수를 줄이면 줄인 만큼 그대로 남습니다 (주 1회에서 월 1회로만 바꿔도 1029만원이 2574만원이 됐습니다). 그리고 국내 주식형 ETF에는 증권거래세가 없습니다. 시장이 오를지 내릴지는 아무도 모르지만, 한 번 사고팔 때 100만원당 2300원이 나가는 건 이미 정해져 있습니다.

재현 코드 — 믿지 말고 직접 돌려보세요

Python 3.8+ 와 numpy 만 있으면 됩니다. 데이터는 실행할 때 자동으로 받습니다(API 키 불필요). 위 표가 그대로 나옵니다. 세율·수수료·기간을 바꿔 여러분의 조건으로 실험해도 됩니다.

trading_cost_backtest.py · Python 3 + numpy 파일 다운로드
# -*- coding: utf-8 -*-
# =============================================================================
# AlgoLab · 매매 비용 백테스트 — 공개용
#   영상 「같은 코스피, 같은 10년 — 3393만원 vs 12만원」의 숫자를 그대로 재현합니다.
#   믿지 말고 직접 돌려보세요. 세율·수수료·기간을 바꿔 여러분의 조건으로 실험해도 됩니다.
#
# 실행:  python trading_cost_backtest.py
# 필요:  Python 3.8+ / numpy  (pip install numpy)
#        데이터는 Yahoo Finance 공개 API 에서 자동으로 받습니다(API 키 불필요).
#
# ── 무엇을 계산하나 ──────────────────────────────────────────────────────────
# "매매를 잘해서 수익률이 달라지는 부분"은 계산에 넣지 않습니다.
# 같은 수익률(코스피 지수)을 냈다고 쳤을 때, **매매 횟수만 다를 때 비용으로 얼마나
# 갈리는가**를 봅니다. 사고판다 = 전부 팔고 곧바로 같은 걸 다시 산다(왕복 1회).
#
# ── 세율 근거 (2026-08-30 확인, 두 곳 교차) ──────────────────────────────────
#   · 코스피: 증권거래세 0.05% + 농어촌특별세 0.15% = 0.20% (2026년 1월 양도분부터)
#   · 코스닥: 0.20% (농특세 없음).  2025년까지는 둘 다 0.15%
#   · 위탁수수료: MTS 기준 0.014~0.015% → 여기서는 0.015% (매수·매도 각각)
#   · 국내 주식형 ETF: 증권거래세 면제 → 왕복 비용은 수수료뿐(0.03%)
#
# ── 이 계산의 한계 (영상에서도 밝힙니다) ─────────────────────────────────────
#   · 코스피 '지수'를 그대로 사고판다고 가정합니다. 지수에는 배당이 빠져 있습니다.
#   · 단타로 시장보다 더 벌 수 있다면 결과는 달라집니다. 그건 이 계산의 범위 밖입니다.
#   · 세금은 매도 금액에, 수수료는 매수·매도 양쪽에 물립니다.
#   · 호가 스프레드·시장충격은 넣지 않았습니다(넣으면 격차는 더 벌어집니다).
# =============================================================================
import datetime, json, os, urllib.request

import numpy as np

FEE = 0.00015          # 위탁수수료 — 매수·매도 각각
TAX_2026 = 0.0020      # 증권거래세 2026년 (코스피 0.05 + 농특세 0.15)
TAX_2025 = 0.0015      # 2025년까지
SEED = 1000            # 만원 = 1000만원

# ★ 기준일 — 영상을 만든 날까지만 씁니다.
#   이걸 안 고정하면 시장이 움직인 만큼 결과가 달라져 **영상의 숫자와 안 맞습니다**
#   (실측: 고정 없이 돌리면 3393만원이 3277만원으로 나옵니다. 코스피가 그새 내렸습니다).
#   오늘까지로 보고 싶으면 None 으로 바꾸세요. 결론(횟수가 늘수록 준다)은 그대로입니다.
ASOF = datetime.date(2026, 8, 27)

FREQ = [("never", "안 판다"), ("yearly", "연 1회"), ("quarterly", "분기 1회"),
        ("monthly", "월 1회"), ("weekly", "주 1회"), ("daily", "매일")]


def load_kospi(cache="kospi_daily.csv"):
    """코스피 일봉. 한 번 받으면 옆에 csv 로 저장해 두고 다음부터는 그걸 씁니다."""
    if os.path.exists(cache):
        import csv
        d, c = [], []
        for r in csv.DictReader(open(cache, encoding="utf-8")):
            d.append(datetime.date(*map(int, r["date"].split("-"))))
            c.append(float(r["close"]))
    else:
        url = ("https://query1.finance.yahoo.com/v8/finance/chart/%5EKS11"
               "?range=30y&interval=1d")
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        j = json.loads(urllib.request.urlopen(req, timeout=30).read())
        r = j["chart"]["result"][0]
        q = r["indicators"]["quote"][0]
        d, c = [], []
        for i, ts in enumerate(r["timestamp"]):
            if q["close"][i] is None:
                continue
            d.append(datetime.datetime.utcfromtimestamp(ts).date())
            c.append(q["close"][i])
        with open(cache, "w", encoding="utf-8", newline="") as f:
            f.write("date,close\n")
            for x, y in zip(d, c):
                f.write("{},{}\n".format(x, round(y, 4)))
        print("  데이터를 받아 {} 에 저장했습니다 ({}일)".format(cache, len(d)))

    # ⚠ 기준일 자르기는 **캐시를 쓰든 새로 받든 똑같이** 적용해야 한다.
    #   한쪽에만 걸면 두 번째 실행부터 결과가 달라진다.
    if ASOF:
        keep = [i for i, x in enumerate(d) if x <= ASOF]
        d = [d[i] for i in keep]
        c = [c[i] for i in keep]
    return d, np.array(c)


def sell_days(dates, freq):
    """회전 주기마다 True. 그날 팔고 곧바로 다시 삽니다(왕복 1회)."""
    n = len(dates)
    m = np.zeros(n, dtype=bool)
    if freq == "never":
        return m
    if freq == "daily":
        m[1:] = True
        return m
    for i in range(1, n):
        a, b = dates[i - 1], dates[i]
        if freq == "weekly":
            m[i] = b.weekday() < a.weekday()          # 주가 바뀌는 첫 거래일
        elif freq == "monthly":
            m[i] = b.month != a.month
        elif freq == "quarterly":
            m[i] = (b.month - 1) // 3 != (a.month - 1) // 3
        elif freq == "yearly":
            m[i] = b.year != a.year
    return m


def run(close, dates, freq, roundtrip):
    """지수 수익률을 그대로 따라가되, 회전할 때마다 왕복 비용을 뺍니다."""
    ret = np.zeros(len(close))
    ret[1:] = close[1:] / close[:-1] - 1
    sd = sell_days(dates, freq)
    eq, out, trades = 1.0, np.empty(len(close)), 0
    for i in range(len(close)):
        eq *= (1 + ret[i])
        if sd[i]:
            eq *= (1 - roundtrip)
            trades += 1
        out[i] = eq
    return out, trades


def table(title, header, rows):
    print("\n■ " + title + "\n")
    print("| " + " | ".join(header) + " |")
    print("|" + "|".join(["---"] * len(header)) + "|")
    for r in rows:
        print("| " + " | ".join(str(x) for x in r) + " |")


if __name__ == "__main__":
    d, c = load_kospi()
    rt26 = TAX_2026 + FEE * 2      # 2026년 개별주식 왕복 0.23%
    rt25 = TAX_2025 + FEE * 2      # 2025년까지        0.18%
    rt_etf = FEE * 2               # 국내 주식형 ETF   0.03%

    # ── 최근 10년 (영상의 핵심 표) ────────────────────────────────────────
    i10 = next(i for i, x in enumerate(d) if x >= d[-1] - datetime.timedelta(days=3653))
    d10, c10 = d[i10:], c[i10:]
    print("■ 코스피 {} ~ {}".format(d10[0], d10[-1]))
    print("  지수 {:.0f} → {:.0f}  ({:.1f}배)".format(c10[0], c10[-1], c10[-1] / c10[0]))
    print("  왕복 비용 — 개별주식 {:.2f}% · 국내 주식형 ETF {:.2f}%"
          .format(rt26 * 100, rt_etf * 100))

    base = run(c10, d10, "never", rt26)[0][-1]
    rows = []
    for key, lab in FREQ:
        v = run(c10, d10, key, rt26)[0][-1]
        rows.append([lab, "{:,.0f}만원".format(v * SEED), "{:.1f}%".format(v / base * 100)])
    table("1000만원을 넣고 매매 횟수만 다르게 했을 때 (최근 10년)",
          ["매매 주기", "남은 돈", "안 판 것 대비"], rows)

    # ── 개별주식 vs 국내 주식형 ETF ───────────────────────────────────────
    rows = []
    for key, lab in FREQ:
        a = run(c10, d10, key, rt26)[0][-1]
        b = run(c10, d10, key, rt_etf)[0][-1]
        rows.append([lab, "{:,.0f}만원".format(a * SEED), "{:,.0f}만원".format(b * SEED),
                     "{:,.0f}만원".format((b - a) * SEED)])
    table("거래세가 붙느냐 안 붙느냐 (같은 10년, 같은 1000만원)",
          ["매매 주기", "개별 주식", "국내 주식형 ETF", "차이"], rows)

    # ── 순수 비용만 (지수가 제자리였다고 가정) ────────────────────────────
    rows = []
    for key, lab in FREQ:
        if key == "never":
            continue
        n = {"daily": 246, "weekly": 52, "monthly": 12, "quarterly": 4, "yearly": 1}[key]
        rows.append([lab, "{}회".format(n),
                     "{:,.0f}만원".format((1 - rt26) ** n * SEED),
                     "{:,.1f}만원".format((1 - rt26) ** (n * 10) * SEED)])
    table("주가가 전혀 움직이지 않았다고 쳤을 때 (비용만)",
          ["매매 주기", "연 왕복", "1년 뒤", "10년 뒤"], rows)

    # ── 100만원 한 번 사고팔 때 ───────────────────────────────────────────
    print("\n■ 100만원어치를 한 번 사고팔면\n")
    print("  살 때 수수료 {:,.0f}원 · 팔 때 수수료 {:,.0f}원 · 세금 {:,.0f}원 → 합계 {:,.0f}원"
          .format(1e6 * FEE, 1e6 * FEE, 1e6 * TAX_2026, 1e6 * rt26))
    print("  국내 주식형 ETF 는 세금이 없어 {:,.0f}원 — 개별 주식의 {:.0f}분의 1"
          .format(1e6 * rt_etf, rt26 / rt_etf))
    print("\n  ⚠ 이 계산은 매매 실력에 따른 수익률 차이를 넣지 않았습니다.")
    print("     지수에는 배당이 빠져 있고, 호가 스프레드·시장충격도 넣지 않았습니다.")
Strategy Lab · 이 검증 더 보기
내 전략도 같은 잣대로 검증해드립니다 — 제작 전 백테스트 상담 → 이 검증을 영상으로 보기4분 11초 폭락에 팔지 않는 규칙 — −55%를 −23%로덜 사고파는 규칙 24종목을 샀는데 실질 베팅 수는 1.06분산 실측 전략 검증 전체 보기 →

※ 본 페이지의 수치는 공개된 시장 데이터를 과거 기준으로 검증한 교육·검증 목적의 자료이며, 특정인에 대한 투자권유나 매수·매도 권유가 아닙니다. 과거 백테스트 성과가 미래 수익을 보장하지 않습니다. 세율·수수료는 2026-08-30 기준 공개 자료로 확인한 값이며, 증권사·상품·시점에 따라 다를 수 있으니 실제 거래 전에는 본인 계좌의 조건을 확인하시기 바랍니다. 모든 투자 판단과 그에 따른 책임은 이용자 본인에게 있습니다.