미국주식 · 해외주식 양도세 · 250만원 공제 · 실측 재현 페이지
해외주식 양도세는 1년 동안 판 이익에서 250만원을 빼고 남은 만큼에 22%(지방세 포함)가 붙습니다. 이 250만원은 그해에 안 쓰면 사라집니다. 그래서 매년 12월, 그해 실현 이익이 250만원이 될 만큼만 팔고 그날 다시 사면 얼마나 달라지는지 실제 가격으로 계산했습니다. S&P500 ETF(SPY) 종가 × 원/달러 환율 · 2016년 1월 4일 1,000만원 · 2026년 10월 2일 전부 매도 · 사고팔 때마다 편도 0.25%.
| 순서 | 할 일 |
|---|---|
| 1 | 12월 15일이 지난 첫 거래일(결제일이 그해 안에 들어오는 날까지), 증권사 앱에서 올해 해외주식 실현 손익을 확인 |
| 2 | 그 실현 이익이 250만원이 될 만큼만 가진 미국주식을 판다 |
| 3 | 판 그날 같은 종목을 판 돈만큼 다시 산다 — 주식 수는 거의 그대로, 세금 계산에 쓰는 산 값만 올라간다 |
| 4 | 판 돈의 10분의 1도 이익이 아니면(1,000만원어치 팔아 이익이 100만원도 안 되면) 그해는 건너뛴다 — 왕복 수수료가 아낀 세금보다 커질 수 있다 |
2016-01-04 에 두 계좌 모두 1,000만원어치 삼 2016-12-15 리셋: 1,119.7만원어치 팔고 다시 삼 · 실현 이익 116.9만원 · 그해 양도세 0원 2017-12-15 건너뜀: 다 팔아도 이익 79.9만원 / 판 돈 1,199.8만원 (10분의 1 미만) 2018-12-17 건너뜀: 다 팔아도 이익 76.1만원 / 판 돈 1,196.0만원 (10분의 1 미만) 2019-12-16 리셋: 900.6만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2020-12-15 리셋: 975.2만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2021-12-15 리셋: 769.1만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2022-12-15 리셋: 1,198.3만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2023-12-15 리셋: 1,739.6만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2024-12-16 리셋: 603.6만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2025-12-15 리셋: 511.8만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원
| 계좌 | 판 돈 | 양도세 | 손에 쥔 돈 |
|---|---|---|---|
| 끝까지 그냥 둠 | 4,303만원 | 672만원 | 3,631만원 |
| 12월마다 팔고 다시 삼 | 4,216만원 | 242만원 | 3,975만원 |
판 돈이 조금 적은 것은 해마다 사고판 수수료 때문입니다. 그래도 양도세가 430만원 줄어 손에 쥔 돈은 343만원 더 많았습니다.
1993년부터 S&P500 10년 계좌를 시작 달만 바꿔 286번 계산했습니다. 12월마다 판 쪽이 더 남은 것 231번 · 똑같은 것 51번 · 진 것 4번입니다. 진 경우는 넷 다 이익이 작아 원래 양도세가 0원이던 경우이고, 잃은 돈은 수수료(가장 크게 5.3만원)였습니다. 나머지 조건은 아래 출력 전문에 있습니다.
돌리는 법: zip 을 풀고 python run_ustax.py
■ 사례 · S&P500(SPY) 2016년 1월 1,000만원 → 2026-10-02 전부 매도 · 편도 0.25% · 10분의 1 문턱 2016-01-04 에 두 계좌 모두 1,000만원어치 삼 2016-12-15 리셋: 1,119.7만원어치 팔고 다시 삼 · 실현 이익 116.9만원 · 그해 양도세 0원 2017-12-15 건너뜀: 다 팔아도 이익 79.9만원 / 판 돈 1,199.8만원 (10분의 1 미만) 2018-12-17 건너뜀: 다 팔아도 이익 76.1만원 / 판 돈 1,196.0만원 (10분의 1 미만) 2019-12-16 리셋: 900.6만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2020-12-15 리셋: 975.2만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2021-12-15 리셋: 769.1만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2022-12-15 리셋: 1,198.3만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2023-12-15 리셋: 1,739.6만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2024-12-16 리셋: 603.6만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 2025-12-15 리셋: 511.8만원어치 팔고 다시 삼 · 실현 이익 250.0만원 · 그해 양도세 0원 그냥 둔 계좌 판 돈 4,303만원 · 양도세 672만원 · 손에 쥔 돈 3,631만원 12월마다 리셋 판 돈 4,216만원 · 양도세 242만원 · 손에 쥔 돈 3,975만원 차이: 양도세 430만원 덜 냄 · 손에 쥔 돈 343만원 더 ■ 같은 사례, 조건을 바꾸면 편도 0.1% 손에 쥔 돈 차이 385만원 (양도세 674만원 → 256만원) 10분의 1 문턱 없이 해마다 손에 쥔 돈 차이 299만원 (양도세 672만원 → 244만원) ■ 나스닥100(QQQ) 같은 사례 그냥 둔 계좌 판 돈 7,693만원 · 양도세 1,418만원 · 손에 쥔 돈 6,276만원 12월마다 리셋 판 돈 7,623만원 · 양도세 962만원 · 손에 쥔 돈 6,661만원 차이: 양도세 456만원 덜 냄 · 손에 쥔 돈 385만원 더 ■ 시작 달만 바꿔 다시 (편도 0.25% · 10분의 1 문턱) — 리셋이 더 남음 / 똑같음 / 짐 · 가장 크게 진 금액 S&P500 1,000만원 10년 286번 중 더 남음 231 · 똑같음 51 · 짐 4 · 가장 크게 진 금액 -5.3만원 S&P500 3억원 20년 166번 중 더 남음 166 · 똑같음 0 · 짐 0 · 가장 크게 진 금액 248.8만원 S&P500 3억원 20년 · 문턱 없이 166번 중 더 남음 157 · 똑같음 0 · 짐 9 · 가장 크게 진 금액 -1,443.2만원 S&P500 매달 100만원 10년 286번 중 더 남음 236 · 똑같음 41 · 짐 9 · 가장 크게 진 금액 -22.0만원 나스닥100 1,000만원 10년 212번 중 더 남음 179 · 똑같음 19 · 짐 14 · 가장 크게 진 금액 -8.6만원 끝.
# -*- coding: utf-8 -*-
"""미국주식 양도세 — 매년 12월 이익 250만원어치만 팔고 그날 다시 사기(리셋) 대 끝까지 그냥 두기.
AlgoLab · 2026-11-01 · 표준 라이브러리만 씁니다. 돌리는 법: python run_ustax.py
가정(영상·설명란과 같음)
· 가격 = 미국 ETF 종가(배당 제외) × 그날 원/달러 환율(FRED DEXKOUS, 없는 날은 직전 값). 자료는 2026-10-02 까지 얼림.
· 수수료 편도 0.25%(살 때 값에 더하고, 팔 때 값에서 뺌). 소수점 수량.
· 양도세 = 22% × max(0, 그해 실현 이익 − 250만원). 취득가는 먼저 산 것부터(선입선출).
· 리셋: 해마다 12월 15일 이후 첫 거래일, 실현 이익이 250만원이 될 만큼만 먼저 산 주식부터 팔고 그날 판 돈으로 다시 산다.
판 돈의 10분의 1도 이익이 아니면 그해는 건너뛴다. 마지막으로 다 파는 해에는 리셋하지 않는다(공제를 마지막 매도가 쓴다).
· 마지막 날 두 계좌 모두 전부 팔고 양도세를 낸 뒤 손에 쥔 돈을 견준다.
"""
import bisect, csv, datetime as dt, os, sys
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
HERE = os.path.dirname(os.path.abspath(__file__))
TAX, DED = 0.22, 250.0 # 세율 · 공제(만원)
def load(sym):
def rd(f, col):
out = {}
with open(os.path.join(HERE, "data", f), encoding="utf-8") as fh:
r = csv.reader(fh); next(r)
for row in r:
try:
out[dt.date.fromisoformat(row[0])] = float(row[col])
except (ValueError, IndexError):
pass
return out
px, fx = rd(sym + ".csv", 1), rd("DEXKOUS.csv", 1)
fd = sorted(fx)
D, P = [], []
for d in sorted(px):
i = bisect.bisect_right(fd, d) - 1
if i >= 0:
D.append(d); P.append(px[d] * fx[fd[i]] / 1e4) # 1주 값(만원)
return D, P
class Acct:
def __init__(self, C, M):
self.C, self.M, self.lots, self.cash = C, M, [], 0.0
def buy(self, money, p):
money += self.cash
unit = p * (1 + self.C)
sh = money / unit
if sh > 0:
self.lots.append([sh, unit])
self.cash = money - sh * unit
def gain(self, p):
return sum(sh * (p * (1 - self.C) - b) for sh, b in self.lots)
def reset(self, p):
"""반환 (판 돈, 실현 이익, 했나)"""
if self.gain(p) <= 0:
return 0.0, 0.0, False
realized = proceeds = sold = 0.0
keep, lots = [], [l[:] for l in self.lots]
for i, (sh, b) in enumerate(lots):
if realized >= DED:
keep.extend(lots[i:]); break
g = p * (1 - self.C) - b
if g > 0 and realized + sh * g > DED:
q = (DED - realized) / g
realized += q * g; proceeds += q * p * (1 - self.C); sold += q
keep.append([sh - q, b]); keep.extend(lots[i + 1:]); break
realized += sh * g; proceeds += sh * p * (1 - self.C); sold += sh
if sold <= 0 or realized <= 0:
return 0.0, 0.0, False
if realized / (sold * p) < self.M:
return sold * p, realized, False
self.lots = keep
self.buy(proceeds, p)
return sold * p, realized, True
def settle(self, p):
val = sum(sh for sh, _ in self.lots) * p * (1 - self.C) + self.cash
tax = TAX * max(0.0, self.gain(p) - DED)
return val, tax
def reset_days(D):
out = {}
for y in sorted({d.year for d in D}):
k = bisect.bisect_left(D, dt.date(y, 12, 15))
if k < len(D) and D[k].year == y:
out[k] = y
return out
def case(D, P, start, amt=1000.0, C=0.0025, M=0.10, show=False):
a, z = bisect.bisect_left(D, start), len(D) - 1
H, R = Acct(C, M), Acct(C, M)
H.buy(amt, P[a]); R.buy(amt, P[a])
if show:
print(f" {D[a]} 에 두 계좌 모두 {amt:,.0f}만원어치 삼")
for y in range(D[a].year, D[z].year):
k = bisect.bisect_left(D, dt.date(y, 12, 15))
sold, real, did = R.reset(P[k])
if show:
if did:
print(f" {D[k]} 리셋: {sold:,.1f}만원어치 팔고 다시 삼 · 실현 이익 {real:,.1f}만원 · 그해 양도세 0원")
elif sold > 0:
print(f" {D[k]} 건너뜀: 다 팔아도 이익 {real:,.1f}만원 / 판 돈 {sold:,.1f}만원 (10분의 1 미만)")
else:
print(f" {D[k]} 건너뜀: 이익 없음")
vh, th = H.settle(P[z]); vr, tr = R.settle(P[z])
return dict(끝=D[z], 판값_보유=vh, 세금_보유=th, 세후_보유=vh - th, 판값_리셋=vr, 세금_리셋=tr, 세후_리셋=vr - tr)
def windows(D, P, mode, amt, years, C=0.0025, M=0.10):
first = {}
for i, d in enumerate(D):
first.setdefault((d.year, d.month), i)
ml = sorted(first)
rs = reset_days(D)
H = years * 12
res = []
for i0 in range(len(ml) - H):
(y0, m0), (y1, m1) = ml[i0], ml[i0 + H]
if (y1 * 12 + m1) - (y0 * 12 + m0) != H:
continue
a, z = first[ml[i0]], first[ml[i0 + H]]
zy = D[z].year
contrib = {a: amt} if mode == "일시금" else {first[ml[i0 + j]]: amt for j in range(H)}
ev = sorted(set(contrib) | {k for k in rs if a <= k < z and rs[k] != zy})
A, B = Acct(C, M), Acct(C, M)
for k in ev:
if k in contrib:
A.buy(contrib[k], P[k]); B.buy(contrib[k], P[k])
if k in rs and rs[k] != zy:
B.reset(P[k])
vh, th = A.settle(P[z]); vr, tr = B.settle(P[z])
res.append((vr - tr) - (vh - th))
win = sum(1 for d in res if d > 0); lose = sum(1 for d in res if d < 0)
return len(res), win, len(res) - win - lose, lose, min(res)
def man(x):
x = round(x)
return f"{x // 10000}억 {x % 10000:,}만원" if x >= 10000 else f"{x:,}만원"
def show_case(name, D, P, **kw):
r = case(D, P, dt.date(2016, 1, 1), **kw)
print(f" 그냥 둔 계좌 판 돈 {man(r['판값_보유'])} · 양도세 {man(r['세금_보유'])} · 손에 쥔 돈 {man(r['세후_보유'])}")
print(f" 12월마다 리셋 판 돈 {man(r['판값_리셋'])} · 양도세 {man(r['세금_리셋'])} · 손에 쥔 돈 {man(r['세후_리셋'])}")
print(f" 차이: 양도세 {man(r['세금_보유'] - r['세금_리셋'])} 덜 냄 · 손에 쥔 돈 {man(r['세후_리셋'] - r['세후_보유'])} 더")
return r
if __name__ == "__main__":
SPY, QQQ = load("SPY"), load("QQQ")
print("■ 사례 · S&P500(SPY) 2016년 1월 1,000만원 → 2026-10-02 전부 매도 · 편도 0.25% · 10분의 1 문턱")
case(*SPY, dt.date(2016, 1, 1), show=True)
show_case("SPY", *SPY)
print()
print("■ 같은 사례, 조건을 바꾸면")
for lab, kw in (("편도 0.1%", dict(C=0.001)), ("10분의 1 문턱 없이 해마다", dict(M=0.0))):
r = case(*SPY, dt.date(2016, 1, 1), **kw)
print(f" {lab:<16} 손에 쥔 돈 차이 {man(r['세후_리셋'] - r['세후_보유'])} (양도세 {man(r['세금_보유'])} → {man(r['세금_리셋'])})")
print()
print("■ 나스닥100(QQQ) 같은 사례")
show_case("QQQ", *QQQ)
print()
print("■ 시작 달만 바꿔 다시 (편도 0.25% · 10분의 1 문턱) — 리셋이 더 남음 / 똑같음 / 짐 · 가장 크게 진 금액")
for lab, S, mode, amt, yrs, kw in (
("S&P500 1,000만원 10년", SPY, "일시금", 1000, 10, {}),
("S&P500 3억원 20년", SPY, "일시금", 30000, 20, {}),
("S&P500 3억원 20년 · 문턱 없이", SPY, "일시금", 30000, 20, dict(M=0.0)),
("S&P500 매달 100만원 10년", SPY, "적립", 100, 10, {}),
("나스닥100 1,000만원 10년", QQQ, "일시금", 1000, 10, {})):
n, w, t, l, worst = windows(*S, mode, amt, yrs, **kw)
print(f" {lab:<26} {n}번 중 더 남음 {w} · 똑같음 {t} · 짐 {l} · 가장 크게 진 금액 {worst:,.1f}만원")
print()
print("끝.")
알고랩 Strategy Lab — 전략을 검증하고, 검증한 것만 자동매매로 옮깁니다. 해마다 12월 날짜와 금액을 챙기는 일은 사람보다 프로그램이 잘 지킵니다. 알고랩 자동매매 살펴보기 · 전략 검증 전체 보기
과거 데이터를 돌려 본 결과이며 미래 수익을 보장하지 않습니다. 투자 판단과 책임은 본인에게 있습니다.