미국 주식 · 익절 · 원금 회수 · 실측 재현 페이지

미국 주식 두 배 되면,
뺀 원금은 어디에 둘까

2005년 무렵 미국의 큰 회사 30곳을 골라, 2005년부터 매달 첫 거래일에 1,000만원어치를 샀다고 따로따로 쳤습니다. 산 뒤 5년 안에 처음 2,000만원(두 배)이 된 날 — 모두 1,278번 — 계좌를 셋으로 나눴습니다: ① 그대로 ② 원금 1,000만원어치를 팔아 통장(이자 0) ③ 원금 1,000만원어치를 팔아 S&P500 ETF(SPY). 그리고 3년 뒤를 비교했습니다.

65번원금을 S&P500 ETF로 옮긴 계좌가 3년 뒤 두 배 된 날보다 줄어든 경우 (그대로 255번 · 통장 256번)
775번1,278번 중 원금을 옮긴 쪽이 그대로 둔 쪽보다 더 많이 번 경우 (그대로가 더 번 경우 503번)
32번1,278번 중 S&P500 이 3년 뒤 떨어져 있던 때 (두 배 된 달로는 15개 달) — 옮긴 원금이 계좌를 받친 이유

오늘 드리는 것 — 기준 한 줄

상황기준
산 미국 주식이 5년 안에 두 배가 된 날적어도 원금만큼은 팔아 S&P500 ETF로 옮긴다 — 줄어든 경우 255번 → 65번
원금을 통장에 빼 둔다면줄어든 경우가 256번 — 그대로 둔 것(255번)과 거의 같았습니다. 옮긴 쪽이 통장 쪽보다 더 번 경우 1,246번
전부 옮긴다면줄어든 경우 32번으로 더 적고, 원금만 옮긴 쪽보다 더 번 경우도 775번 — 원금만 옮기면 그 회사가 S&P500 보다 더 오를 몫이 남습니다

한계 — 먼저 읽어 주세요

받아가기

돌리는 법: zip 을 풀고 pip install numpy 뒤 python run_doubled.py (1분 안팎).

그대로 돌린 출력

[미국 대형주 30곳 · S&P500(SPY)] 두 배 된 날 1,278번 · 30종목 · 서로 다른 달 170개 · 두 배 된 날 2005-10-31 ~ 2023-09-27 · 3년 뒤 끝 2026-09-24
  3년 뒤 두 배 된 날의 금액보다 줄어든 경우: 그대로 255번 · 원금만큼 S&P500 로 옮기기 65번 · 원금만큼 현금으로 256번
  더 많이 남은 쪽: 옮기기 775번 · 그대로 503번 · (반론) 현금이 그대로보다 더 남은 255번 · 옮기기가 현금보다 더 남은 1,246번
  옮긴 곳(지수)이 3년 뒤 두 배 된 날보다 떨어져 있던 경우: 32번 (두 배 된 달 15개)
  (반론) 전부 지수로 옮기기: 줄어든 경우 32번 · 원금만 옮기기보다 더 남은 775번 · 그대로보다 더 남은 775번
  한가운데 경우(640/1278): PFE 2009-07-01 매수 → 2013-01-02 두 배(2,001만원) → 2015-12-23 그대로 2,780만원 · 옮기기 2,885만원 · 현금 2,389만원
  (참고) 평균 로그수익: 그대로 +0.245 · 절반 옮기기 +0.301
  두 배 된 날 평가액: 2,000만원 이상 1278/1278번 · 가장 큰 날 2,286만원 · 2,100만원 넘은 날 20번

[미국 · 세금 22% 넣은 판] 더 많이 남은 쪽: 옮기기 840번 · 그대로 438번
[미국 · 세금판 · 옮기는 쪽도 공제 한 번만] 더 많이 남은 쪽: 옮기기 710번 · 그대로 568번
[미국 · 1.5배 된 날 원금만큼] 2,419번 · 줄어든 경우 그대로 474 · 옮기기 181 · 옮기기가 더 남음 1,453
[미국 · 3.0배 된 날 원금만큼] 292번 · 줄어든 경우 그대로 50 · 옮기기 15 · 옮기기가 더 남음 120

[국내 대형주 36종 · KODEX200] 두 배 된 날 1,191번 · 36종목 · 서로 다른 달 153개 · 두 배 된 날 2005-07-28 ~ 2023-08-21 · 3년 뒤 끝 2026-09-22
  3년 뒤 두 배 된 날의 금액보다 줄어든 경우: 그대로 568번 · 원금만큼 KODEX200 로 옮기기 452번 · 원금만큼 현금으로 569번
  더 많이 남은 쪽: 옮기기 655번 · 그대로 536번 · (반론) 현금이 그대로보다 더 남은 567번 · 옮기기가 현금보다 더 남은 819번
  옮긴 곳(지수)이 3년 뒤 두 배 된 날보다 떨어져 있던 경우: 366번 (두 배 된 달 45개)
  (반론) 전부 지수로 옮기기: 줄어든 경우 380번 · 원금만 옮기기보다 더 남은 655번 · 그대로보다 더 남은 655번
  한가운데 경우(596/1191): 055550 2009-04-01 매수 → 2010-12-17 두 배(2,026만원) → 2013-12-26 그대로 1,959만원 · 옮기기 2,007만원 · 현금 1,990만원

끝.

코드 전문

# -*- coding: utf-8 -*-
"""AlgoLab 재현 코드 — 두 배 된 미국 주식, 그대로 둘까 원금만큼 S&P500 ETF 로 옮길까 (algolab.co.kr/doubled)
ASOF = 2026-10-02 (얼린 자료의 마지막 날). 돌리는 법: pip install numpy → python run_doubled.py

설계(사전 등록한 정의 그대로):
- 미국: 2005년 무렵 큰 회사 30곳(아래 US) · 야후 수정종가(배당 포함) · 비교 지수 = SPY(수정종가)
- 산 날: 2005-01 부터 매달 첫 거래일. 산 뒤 5년(1,250거래일) 안에 처음 종가가 산 값의 2배 이상이 된 날 = '두 배 된 날'
  (같은 종목·같은 날은 한 번만 센다)
- 1,000만원 어치를 샀다고 치면 두 배 된 날 2,000만원.
  ① 그대로: 계속 들고 간다
  ② 원금만큼 옮기기: 그날 1,000만원어치(=절반)를 팔아 SPY 로 옮긴다(옮기는 몫에 비용 0.2%)
  ③ (반론) 원금만큼 현금으로: 그날 1,000만원어치를 팔아 현금으로 둔다(이자 0 · 비용 0.1%)
- 3년(750거래일) 뒤 계좌를 비교한다. 달러 기준 비율을 만원으로 환산(환율은 넣지 않는다).
- 홀드아웃: 국내 대형주 36종 vs KODEX200 이음본(가격제한폭 밖 봉은 수익 0 처리) · 비용 0.4%
- 세금판: 미국 양도세 22% · 연 250만원 공제 · 3년 뒤 전부 판다고 치고(통산)
"""
import csv
import json
import os
import sys

import numpy as np

sys.stdout.reconfigure(encoding="utf-8", errors="replace")
HERE = os.path.dirname(os.path.abspath(__file__))
ASOF = "2026-10-02"
D_US = os.path.join(HERE, "data_doubled", "us")
D_KR = os.path.join(HERE, "data_doubled", "kr")
KODEX = os.path.join(HERE, "data_doubled", "kodex_splice.csv")
US = "XOM GE MSFT C BAC WMT JNJ PFE AIG MO PG JPM IBM INTC CVX WFC KO CSCO GOOG VZ AMGN PEP HD COP HPQ UPS T ORCL QCOM MRK".split()
H3 = 750
WIN = 1250
BUY0 = "2005-01-01"
UP = 2.0
TAX, DED = 0.22, 250.0


def load(path, clean=False):
    rows = [r for r in csv.reader(open(path, encoding="utf-8")) if r and r[0][:1].isdigit()]
    d = np.array([r[0] for r in rows]); c = np.array([float(r[1]) for r in rows])
    if clean:
        k = c > 0
        d, c = d[k], c[k]
        r = c[1:] / c[:-1] - 1
        r[np.abs(r) > 0.31] = 0.0
        c = c[0] * np.concatenate([[1.0], np.cumprod(1 + r)])
    assert (c > 0).all(), path
    return d, c


def events(names, path_of, idx_path, cost, clean, k=UP):
    xd, xc = load(idx_path)
    xmap = {d: i for i, d in enumerate(xd)}
    out = []
    for nm in names:
        d, c = load(path_of(nm), clean=clean)
        months = {}
        for i, di in enumerate(d):
            if di >= BUY0 and di[:7] not in months:
                months[di[:7]] = i
        seen = set()
        for ym, b in months.items():
            lim = min(len(c), b + WIN)
            hit = np.where(c[b + 1:lim] >= k * c[b])[0]
            if not len(hit):
                continue
            t = b + 1 + hit[0]
            if (nm, t) in seen or d[t] not in xmap:
                continue
            seen.add((nm, t))
            if not (t + H3 < len(c) and d[t + H3] in xmap):
                continue
            h = c[t + H3] / c[t]
            s = xc[xmap[d[t + H3]]] / xc[xmap[d[t]]]
            m = c[t] / c[b]
            out.append({"code": nm, "buy": str(d[b]), "t": str(d[t]), "end": str(d[t + H3]), "m": float(m),
                        "h": float(h), "s": float(s), "cost": cost})
    return out


def arms(ev, k=UP):
    """두 배 된 날 평가액 = 1,000만원 × m. 원금(1,000만원)만큼 옮긴다 → 옮기는 비중 f = 1/m."""
    hold, move, cash = [], [], []
    for e in ev:
        V = 1000.0 * e["m"]
        f = 1000.0 / V
        hold.append(V * e["h"])
        move.append(V * ((1 - f) * e["h"] + f * e["s"] * (1 - e["cost"])))
        cash.append(V * ((1 - f) * e["h"] + f * (1 - e["cost"] / 2)))
    return np.array(hold), np.array(move), np.array(cash)


def tax_amounts(ev, P=1000.0, ded0=DED):
    """원금 1,000만원 · 두 배 된 날 절반을 옮김 — 3년 뒤 전부 판 세후 금액(만원)."""
    ha, fa = [], []
    for e in ev:
        Vt = P * e["m"]
        VH = Vt * e["h"]
        ha.append(VH - TAX * max(0.0, VH - P - DED))
        S_ = 0.5 * Vt; basis_s = 0.5 * P
        tx0 = TAX * max(0.0, S_ - basis_s - ded0)   # ded0=DED: 두 배 된 날 판 해에도 공제(해가 달라 실제로 가능) · 0: 공제 한 번만
        I = (S_ - tx0) * (1 - e["cost"])
        stockH = 0.5 * Vt * e["h"]; spyH = I * e["s"]
        gH = (stockH - 0.5 * P) + (spyH - I)
        fa.append(stockH + spyH - TAX * max(0.0, gH - DED))
    return np.array(ha), np.array(fa)


def report(name, ev, idx="S&P500"):
    hold, move, cash = arms(ev)
    n = len(ev)
    base = np.array([1000.0 * e["m"] for e in ev])
    allm = np.array([1000.0 * e["m"] * e["s"] * (1 - e["cost"]) for e in ev])   # (반론) 두 배 된 날 전부 팔아 지수로      # 두 배 된 날 평가액(2,000만원 남짓 — 종가가 2배를 넘긴 날)
    r = {"n": n, "종목수": len({e["code"] for e in ev}), "달수": len({e["t"][:7] for e in ev}),
         "첫두배날": min(e["t"] for e in ev), "마지막두배날": max(e["t"] for e in ev), "마지막끝날": max(e["end"] for e in ev),
         "그대로_두배날보다줄어든": int((hold < base).sum()),
         "옮기기_두배날보다줄어든": int((move < base).sum()),
         "현금_두배날보다줄어든": int((cash < base).sum()),
         "옮기기가더남음": int((move > hold).sum()), "그대로가더남음": int((hold > move).sum()),
         "현금이그대로보다더남음": int((cash > hold).sum()),
         "옮기기가현금보다더남음": int((move > cash).sum()),
         "지수가3년뒤떨어짐": int(sum(1 for e in ev if e["s"] < 1)),
         "전부옮기기_두배날보다줄어든": int((allm < base).sum()), "전부가원금보다더남음": int((allm > move).sum()),
         "전부가그대로보다더남음": int((allm > hold).sum()),
         "지수가3년뒤떨어짐_달수": len({e["t"][:7] for e in ev if e["s"] < 1})}
    # 한가운데 경우 — (옮기기 − 그대로) 차이가 n 개 중 한가운데인 사건 하나
    diff = move - hold
    order = np.argsort(diff)
    mid = int(order[n // 2])
    e = ev[mid]
    r["한가운데"] = {"종목": e["code"], "산날": e["buy"], "두배날": e["t"], "3년뒤": e["end"], "두배날금액": round(1000 * e["m"]),
                  "그대로": round(float(hold[mid])), "옮기기": round(float(move[mid])), "현금": round(float(cash[mid])),
                  "차이_순위": f"{n // 2 + 1}/{n}"}
    print(f"\n[{name}] 두 배 된 날 {n:,}번 · {r['종목수']:,}종목 · 서로 다른 달 {r['달수']:,}개 · 두 배 된 날 {r['첫두배날']} ~ {r['마지막두배날']} · 3년 뒤 끝 {r['마지막끝날']}")
    print(f"  3년 뒤 두 배 된 날의 금액보다 줄어든 경우: 그대로 {r['그대로_두배날보다줄어든']:,}번 · 원금만큼 {idx} 로 옮기기 {r['옮기기_두배날보다줄어든']:,}번 · 원금만큼 현금으로 {r['현금_두배날보다줄어든']:,}번")
    print(f"  더 많이 남은 쪽: 옮기기 {r['옮기기가더남음']:,}번 · 그대로 {r['그대로가더남음']:,}번 · (반론) 현금이 그대로보다 더 남은 {r['현금이그대로보다더남음']:,}번 · 옮기기가 현금보다 더 남은 {r['옮기기가현금보다더남음']:,}번")
    print(f"  옮긴 곳(지수)이 3년 뒤 두 배 된 날보다 떨어져 있던 경우: {r['지수가3년뒤떨어짐']:,}번 (두 배 된 달 {r['지수가3년뒤떨어짐_달수']:,}개)")
    print(f"  (반론) 전부 지수로 옮기기: 줄어든 경우 {r['전부옮기기_두배날보다줄어든']:,}번 · 원금만 옮기기보다 더 남은 {r['전부가원금보다더남음']:,}번 · 그대로보다 더 남은 {r['전부가그대로보다더남음']:,}번")
    g = r["한가운데"]
    print(f"  한가운데 경우({g['차이_순위']}): {g['종목']} {g['산날']} 매수 → {g['두배날']} 두 배({g['두배날금액']:,}만원) → {g['3년뒤']} 그대로 {g['그대로']:,}만원 · 옮기기 {g['옮기기']:,}만원 · 현금 {g['현금']:,}만원")
    return r


out = {}
us_ev = events(US, lambda s: os.path.join(D_US, s + ".csv"), os.path.join(D_US, "SPY.csv"), 0.002, clean=False)
out["미국"] = report("미국 대형주 30곳 · S&P500(SPY)", us_ev)

# 사건 수 고정 확인(같은 자료면 같은 수가 나와야 한다)
h = np.array([e["h"] for e in us_ev]); s = np.array([e["s"] for e in us_ev])
half = 0.5 * h + 0.5 * s * (1 - 0.002)
print(f"  (참고) 평균 로그수익: 그대로 {np.log(h).mean():+.3f} · 절반 옮기기 {np.log(half).mean():+.3f}")
assert len(us_ev) == 1278

# 두 배 된 날 평가액이 정확히 2,000만원이 아니라 '처음 2배 이상'인 날 종가 기준임을 보인다
mm = np.array([e["m"] for e in us_ev])
print(f"  두 배 된 날 평가액: 2,000만원 이상 {int((mm >= 2).sum())}/{len(mm)}번 · 가장 큰 날 {1000 * mm.max():,.0f}만원 · 2,100만원 넘은 날 {int((mm > 2.1).sum())}번")
out["미국"]["두배날평가액"] = {"최대": round(1000 * float(mm.max())), "2100넘음": int((mm > 2.1).sum())}

# 세금판(미국 양도세 22% · 250만원 공제 · 3년 뒤 전부 판다)
ha, fa = tax_amounts(us_ev)
base_t = np.array([1000.0 * e["m"] for e in us_ev])
out["미국_세후"] = {"옮기기가더남음": int((fa > ha).sum()), "그대로가더남음": int((ha > fa).sum()),
                 "그대로_두배날보다줄어든": int((ha < base_t).sum()), "옮기기_두배날보다줄어든": int((fa < base_t).sum())}
print(f"\n[미국 · 세금 22% 넣은 판] 더 많이 남은 쪽: 옮기기 {out['미국_세후']['옮기기가더남음']:,}번 · 그대로 {out['미국_세후']['그대로가더남음']:,}번")
ha1, fa1 = tax_amounts(us_ev, ded0=0.0)
out["미국_세후_공제한번"] = {"옮기기가더남음": int((fa1 > ha1).sum()), "그대로가더남음": int((ha1 > fa1).sum())}
print(f"[미국 · 세금판 · 옮기는 쪽도 공제 한 번만] 더 많이 남은 쪽: 옮기기 {out['미국_세후_공제한번']['옮기기가더남음']:,}번 · 그대로 {out['미국_세후_공제한번']['그대로가더남음']:,}번")

# 문턱 사다리(1.5배·3배에서 원금만큼 옮기기) — 방향만 본다
out["미국_문턱"] = {}
for k in (1.5, 3.0):
    evk = events(US, lambda s_: os.path.join(D_US, s_ + ".csv"), os.path.join(D_US, "SPY.csv"), 0.002, clean=False, k=k)
    hk, mk, _ = arms(evk)
    basek = np.array([1000.0 * e["m"] for e in evk])
    out["미국_문턱"][str(k)] = {"n": len(evk), "옮기기가더남음": int((mk > hk).sum()), "그대로_줄어든": int((hk < basek).sum()),
                             "옮기기_줄어든": int((mk < basek).sum())}
    print(f"[미국 · {k}배 된 날 원금만큼] {len(evk):,}번 · 줄어든 경우 그대로 {out['미국_문턱'][str(k)]['그대로_줄어든']:,} · 옮기기 {out['미국_문턱'][str(k)]['옮기기_줄어든']:,} · 옮기기가 더 남음 {out['미국_문턱'][str(k)]['옮기기가더남음']:,}")

# 홀드아웃 — 국내 대형주 36종 vs KODEX200
kr_codes = sorted(f[:-4] for f in os.listdir(D_KR) if f.endswith(".csv"))
kr_ev = events(kr_codes, lambda s_: os.path.join(D_KR, s_ + ".csv"), KODEX, 0.004, clean=True)
out["국내"] = report("국내 대형주 36종 · KODEX200", kr_ev, idx="KODEX200")
assert len(kr_ev) == 1191

print("\n끝.")

알고랩 Strategy Lab — 전략을 검증하고, 검증한 것만 자동매매로 옮깁니다. "조금만 더 오르면 팔까" 싶을 때 정한 규칙을 지키는 일은 사람보다 봇이 잘합니다. 알고랩 자동매매 살펴보기 · 전략 검증 전체 보기

과거 데이터를 돌려 본 결과이며 미래 수익을 보장하지 않습니다. 투자 판단과 책임은 본인에게 있습니다.