S&P500 · 적립식 · 매수 시점 · 실측 재현 페이지
S&P500 지수 펀드(VFINX)의 1985년부터 실제 가격(배당 재투자)으로, 매달 첫 거래일에 50만원씩 10년 동안 넣는 계좌를 둘 만들었습니다. ① 바로 — 그날 종가에 전부 산다 ② 기다리기 — 그 돈을 현금(미국 3개월 국채 이자)으로 모아 두다가, 전날 종가가 그때까지 가장 높았던 종가보다 20% 넘게 낮은 첫날 모은 돈을 전부 산다(기다리는 기간 상한 없음). 시작 달을 한 달씩 밀어 이런 10년짜리 계좌를 382번 만들었습니다(서로 많이 겹칩니다).
| 상황 | 기준 |
|---|---|
| S&P500 에 매달 적립한다 | 떨어지길 기다리지 말고 월급날(매달 첫 거래일) 바로 산다 — 382번 중 290번 더 남음 |
| 기다린 쪽이 이긴 때 · 진 때 | 기다린 쪽이 이긴 92번 중 가장 크게 이긴 계좌 +553만원 · 진 290번 중 가장 크게 진 계좌 -1억 1,130만원 (10년 동안 넣은 돈은 똑같이 6,000만원) — 영상의 「넷 중 셋」은 382번 중 290번입니다 |
| 지키는 법 | 증권사 앱의 자동 모으기(적립식 자동 매수) 메뉴에서 날짜는 월급날, 금액은 정해 둔 만큼, 종목은 S&P500 ETF 로 걸어 둡니다 — 무서운 뉴스가 나온 날에도 그날 그대로 삽니다 |
| 조금만 떨어져도 사고 오래는 안 기다리면(−10% · 최대 12개월) | 바로 302번 · 기다리기 80번 — 기다리는 방법 50가지 중 절반 넘게 이긴 것 0가지 |
| 왜 | 기다리는 동안에도 대개 올라, 떨어진 값이 월급날 값보다 높은 때가 많았습니다 — 1985년부터 기다렸다 산 455달 중 249달이 월급날보다 비쌌습니다(싸게 131달 · 같은 날 75달). 가운데 계좌는 2014년 4월부터 모은 72달치를 2020-03-13(코로나 폭락) 한꺼번에 샀는데 그중 55달은 월급날 값이 더 쌌고, 마지막 16달치는 끝날까지 현금이었습니다. 10년 내내 한 번도 못 산 계좌 30개 |
돌리는 법: zip 을 풀고 pip install numpy pandas 뒤 python run_dipwait.py (몇 분 걸립니다).
자료: VFINX 1985-01-02~2026-10-09 · ^IRX 끝 2026-10-12 · ASOF 2026-10-12 [S&P500 · 매달 50만원 · 20% 떨어지면 사기(상한 없음)] 10년짜리 계좌 382개 (1985-01-02~1994-12-30 … 2016-10-03~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 290번 · 기다리기 92번 · 같음 0번 가운데 계좌(192/382): 2014-04-01~2024-03-28 · 넣은 돈 6,000만원 → 바로 1억 2,343만원 · 기다리기 1억 888만원 기다린 돈이 산 날까지 평균 809.7거래일 · 1년 넘게 기다린 돈 65.7% 10년 내내 한 번도 못 산 계좌 30개 · 끝날 현금이 남은 계좌 302개 기다리기가 가장 크게 이긴 계좌 +553만원 · 가장 크게 진 계좌 -1억 1,130만원 1985년부터 넣은 달: 기다렸다 산 달 455개 중 월급날 값보다 비싸게 산 달 249개 (54.7%) · 싸게 산 달 131개 · 같은 날 산 달 75개 · 아직 못 산 달 47개 [가운데 계좌 리플레이] 2014-04-01~2024-03-28 2020-03-13 샀다(전날 최고점 대비 -26.7%) — 2014-04-01~2020-03-02 에 모은 72달치 · 그중 월급날 값이 더 쌌던 달 55개 2020-04-01 샀다(전날 최고점 대비 -23.5%) — 2020-04-01~2020-04-01 에 모은 1달치 · 그중 월급날 값이 더 쌌던 달 0개 2022-06-14 샀다(전날 최고점 대비 -21.3%) — 2020-05-01~2022-06-01 에 모은 26달치 · 그중 월급날 값이 더 쌌던 달 10개 2022-07-01 샀다(전날 최고점 대비 -20.5%) — 2022-07-01~2022-07-01 에 모은 1달치 · 그중 월급날 값이 더 쌌던 달 0개 2022-09-22 샀다(전날 최고점 대비 -20.2%) — 2022-08-01~2022-09-01 에 모은 2달치 · 그중 월급날 값이 더 쌌던 달 0개 2022-10-03 샀다(전날 최고점 대비 -24.4%) — 2022-10-03~2022-10-03 에 모은 1달치 · 그중 월급날 값이 더 쌌던 달 0개 2022-11-03 샀다(전날 최고점 대비 -20.7%) — 2022-11-01~2022-11-01 에 모은 1달치 · 그중 월급날 값이 더 쌌던 달 0개 끝까지 현금: 2022-12-01~2024-03-01 에 모은 16달치 산 달 104개 중 월급날보다 비싸게 산 달 65개 [반론 · 10% 떨어지면 사기 · 최대 12개월] 10년짜리 계좌 382개 (1985-01-02~1994-12-30 … 2016-10-03~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 302번 · 기다리기 80번 · 같음 0번 가운데 계좌(192/382): 1987-07-01~1997-06-30 · 넣은 돈 6,000만원 → 바로 1억 5,593만원 · 기다리기 1억 5,069만원 [반론 · 기다리는 방법 50가지] 382개 계좌 중 절반 넘게 이긴 방법 0가지 · 가장 많이 이긴 방법 155번(−5% · 최대 24개월 · 전부) − 5% · 최대 3개월 · 전부: 기다리기가 더 남은 113/382 · 가운데 비율 0.9973 − 5% · 최대 3개월 · 절반: 기다리기가 더 남은 113/382 · 가운데 비율 0.9986 − 5% · 최대 6개월 · 전부: 기다리기가 더 남은 117/382 · 가운데 비율 0.9973 − 5% · 최대 6개월 · 절반: 기다리기가 더 남은 117/382 · 가운데 비율 0.9986 − 5% · 최대 12개월 · 전부: 기다리기가 더 남은 124/382 · 가운데 비율 0.9965 − 5% · 최대 12개월 · 절반: 기다리기가 더 남은 124/382 · 가운데 비율 0.9983 − 5% · 최대 24개월 · 전부: 기다리기가 더 남은 155/382 · 가운데 비율 0.9988 − 5% · 최대 24개월 · 절반: 기다리기가 더 남은 155/382 · 가운데 비율 0.9994 − 5% · 최대 inf개월 · 전부: 기다리기가 더 남은 155/382 · 가운데 비율 0.9988 − 5% · 최대 inf개월 · 절반: 기다리기가 더 남은 155/382 · 가운데 비율 0.9994 −10% · 최대 3개월 · 전부: 기다리기가 더 남은 60/382 · 가운데 비율 0.9896 −10% · 최대 3개월 · 절반: 기다리기가 더 남은 60/382 · 가운데 비율 0.9948 −10% · 최대 6개월 · 전부: 기다리기가 더 남은 70/382 · 가운데 비율 0.9799 −10% · 최대 6개월 · 절반: 기다리기가 더 남은 70/382 · 가운데 비율 0.9900 −10% · 최대 12개월 · 전부: 기다리기가 더 남은 80/382 · 가운데 비율 0.9664 −10% · 최대 12개월 · 절반: 기다리기가 더 남은 80/382 · 가운데 비율 0.9832 −10% · 최대 24개월 · 전부: 기다리기가 더 남은 73/382 · 가운데 비율 0.9586 −10% · 최대 24개월 · 절반: 기다리기가 더 남은 73/382 · 가운데 비율 0.9793 −10% · 최대 inf개월 · 전부: 기다리기가 더 남은 73/382 · 가운데 비율 0.9545 −10% · 최대 inf개월 · 절반: 기다리기가 더 남은 73/382 · 가운데 비율 0.9772 −15% · 최대 3개월 · 전부: 기다리기가 더 남은 66/382 · 가운데 비율 0.9860 −15% · 최대 3개월 · 절반: 기다리기가 더 남은 66/382 · 가운데 비율 0.9930 −15% · 최대 6개월 · 전부: 기다리기가 더 남은 68/382 · 가운데 비율 0.9744 −15% · 최대 6개월 · 절반: 기다리기가 더 남은 68/382 · 가운데 비율 0.9872 −15% · 최대 12개월 · 전부: 기다리기가 더 남은 83/382 · 가운데 비율 0.9564 −15% · 최대 12개월 · 절반: 기다리기가 더 남은 83/382 · 가운데 비율 0.9782 −15% · 최대 24개월 · 전부: 기다리기가 더 남은 104/382 · 가운데 비율 0.9374 −15% · 최대 24개월 · 절반: 기다리기가 더 남은 104/382 · 가운데 비율 0.9687 −15% · 최대 inf개월 · 전부: 기다리기가 더 남은 101/382 · 가운데 비율 0.9319 −15% · 최대 inf개월 · 절반: 기다리기가 더 남은 101/382 · 가운데 비율 0.9660 −20% · 최대 3개월 · 전부: 기다리기가 더 남은 49/382 · 가운데 비율 0.9832 −20% · 최대 3개월 · 절반: 기다리기가 더 남은 49/382 · 가운데 비율 0.9916 −20% · 최대 6개월 · 전부: 기다리기가 더 남은 54/382 · 가운데 비율 0.9685 −20% · 최대 6개월 · 절반: 기다리기가 더 남은 54/382 · 가운데 비율 0.9843 −20% · 최대 12개월 · 전부: 기다리기가 더 남은 57/382 · 가운데 비율 0.9410 −20% · 최대 12개월 · 절반: 기다리기가 더 남은 57/382 · 가운데 비율 0.9705 −20% · 최대 24개월 · 전부: 기다리기가 더 남은 67/382 · 가운데 비율 0.9099 −20% · 최대 24개월 · 절반: 기다리기가 더 남은 67/382 · 가운데 비율 0.9550 −20% · 최대 inf개월 · 전부: 기다리기가 더 남은 92/382 · 가운데 비율 0.8811 −20% · 최대 inf개월 · 절반: 기다리기가 더 남은 92/382 · 가운데 비율 0.9406 −30% · 최대 3개월 · 전부: 기다리기가 더 남은 52/382 · 가운데 비율 0.9834 −30% · 최대 3개월 · 절반: 기다리기가 더 남은 52/382 · 가운데 비율 0.9917 −30% · 최대 6개월 · 전부: 기다리기가 더 남은 63/382 · 가운데 비율 0.9691 −30% · 최대 6개월 · 절반: 기다리기가 더 남은 63/382 · 가운데 비율 0.9846 −30% · 최대 12개월 · 전부: 기다리기가 더 남은 66/382 · 가운데 비율 0.9471 −30% · 최대 12개월 · 절반: 기다리기가 더 남은 66/382 · 가운데 비율 0.9736 −30% · 최대 24개월 · 전부: 기다리기가 더 남은 80/382 · 가운데 비율 0.9229 −30% · 최대 24개월 · 절반: 기다리기가 더 남은 80/382 · 가운데 비율 0.9614 −30% · 최대 inf개월 · 전부: 기다리기가 더 남은 128/382 · 가운데 비율 0.9372 −30% · 최대 inf개월 · 절반: 기다리기가 더 남은 128/382 · 가운데 비율 0.9686 [반론 · 기간을 5년으로 · 20% 떨어지면 사기] 5년짜리 계좌 442개 (1985-01-02~1989-12-29 … 2021-10-01~2026-09-30) 5년 뒤 더 많이 남은 쪽: 월급날 바로 286번 · 기다리기 156번 · 같음 0번 가운데 계좌(222/442): 2009-02-02~2014-01-31 · 넣은 돈 3,000만원 → 바로 4,600만원 · 기다리기 4,351만원 [반론 · 기간을 15년으로 · 20% 떨어지면 사기] 15년짜리 계좌 322개 (1985-01-02~1999-12-31 … 2011-10-03~2026-09-30) 15년 뒤 더 많이 남은 쪽: 월급날 바로 266번 · 기다리기 56번 · 같음 0번 가운데 계좌(162/322): 2007-11-01~2022-10-31 · 넣은 돈 9,000만원 → 바로 2억 3,163만원 · 기다리기 2억 442만원 [반론 · 기간을 20년으로 · 20% 떨어지면 사기] 20년짜리 계좌 262개 (1985-01-02~2004-12-31 … 2006-10-02~2026-09-30) 20년 뒤 더 많이 남은 쪽: 월급날 바로 262번 · 기다리기 0번 · 같음 0번 가운데 계좌(132/262): 2005-12-01~2025-11-28 · 넣은 돈 1억 2,000만원 → 바로 5억 3,376만원 · 기다리기 4억 9,103만원 [한계 · 수수료·세금 어림] VFINX 원래 종가 + 배당 기록으로 다시 지은 총수익 · 10년짜리 계좌 기준_원래종가재구성: 월급날 바로 290번 · 기다리기 92번 (계좌 382개) 수수료0.25: 월급날 바로 290번 · 기다리기 92번 (계좌 382개) 이자세15.4: 월급날 바로 300번 · 기다리기 82번 (계좌 382개) 배당세15: 월급날 바로 281번 · 기다리기 101번 (계좌 382개) 셋다: 월급날 바로 287번 · 기다리기 95번 (계좌 382개) (수수료 = 산 몫의 0.25% · 이자세 = 현금 이자의 15.4% · 배당세 = 배당의 15%) ※ 야후 069500.KS 는 2007-02-07 다음 봉이 2009-04-17 이다(2008년 전체 공백) — 코스피200 ETF 는 2009-05 부터만 쓴다 [한계 · 코스피200 ETF(KODEX200) · 2009-05~ · 원화 · 현금 이자 0%] 10년짜리 계좌 90개 (2009-05-04~2019-04-30 … 2016-10-04~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 0번 · 기다리기 90번 · 같음 0번 가운데 계좌(46/90): 2011-10-04~2021-09-30 · 넣은 돈 6,000만원 → 바로 9,661만원 · 기다리기 1억 193만원 기다렸다 산 달 210개 중 월급날 값보다 싸게 산 달 129개 · 비싸게 산 달 56개 · 같은 날 산 달 25개 [한계 · 코스피200 ETF(KODEX200) · 2009-05~ · 원화 · 현금 이자 3%] 10년짜리 계좌 90개 (2009-05-04~2019-04-30 … 2016-10-04~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 0번 · 기다리기 90번 · 같음 0번 가운데 계좌(46/90): 2011-10-04~2021-09-30 · 넣은 돈 6,000만원 → 바로 9,661만원 · 기다리기 1억 488만원 [한계 · 코스피200 ETF(TIGER200) · 2009-05~ · 원화 · 현금 이자 0%] 10년짜리 계좌 90개 (2009-05-04~2019-04-30 … 2016-10-04~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 0번 · 기다리기 90번 · 같음 0번 가운데 계좌(46/90): 2011-10-04~2021-09-30 · 넣은 돈 6,000만원 → 바로 9,743만원 · 기다리기 1억 306만원 [한계 · 코스피 지수(가격지수 · 배당 0%) · 1997~ · 현금 이자 0%] 10년짜리 계좌 238개 (1997-01-03~2006-12-28 … 2016-10-04~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 121번 · 기다리기 117번 · 같음 0번 가운데 계좌(120/238): 2005-08-01~2015-07-31 · 넣은 돈 6,000만원 → 바로 7,267만원 · 기다리기 7,237만원 [한계 · 코스피 지수(가격지수 · 배당 0%) · 1997~ · 현금 이자 3%] 10년짜리 계좌 238개 (1997-01-03~2006-12-28 … 2016-10-04~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 107번 · 기다리기 131번 · 같음 0번 가운데 계좌(120/238): 2008-09-01~2018-08-31 · 넣은 돈 6,000만원 → 바로 7,411만원 · 기다리기 7,582만원 [한계 · 코스피 지수 + 배당 연 1.8%(어림) · 1997~ · 현금 이자 0%] 10년짜리 계좌 238개 (1997-01-03~2006-12-28 … 2016-10-04~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 174번 · 기다리기 64번 · 같음 0번 가운데 계좌(120/238): 2011-10-05~2021-09-30 · 넣은 돈 6,000만원 → 바로 9,529만원 · 기다리기 9,225만원 [한계 · 코스피 지수 + 배당 연 1.8%(어림) · 1997~ · 현금 이자 3%] 10년짜리 계좌 238개 (1997-01-03~2006-12-28 … 2016-10-04~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 122번 · 기다리기 116번 · 같음 0번 가운데 계좌(120/238): 1997-03-03~2007-02-28 · 넣은 돈 6,000만원 → 바로 1억 3,435만원 · 기다리기 1억 3,346만원 [한계 · S&P500 원화로(SPY × 원달러) · 현금 이자 0%] 10년짜리 계좌 155개 (2003-12-01~2013-11-29 … 2016-10-03~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 155번 · 기다리기 0번 · 같음 0번 가운데 계좌(78/155): 2013-02-01~2023-01-31 · 넣은 돈 6,000만원 → 바로 1억 1,838만원 · 기다리기 8,665만원 [한계 · S&P500 원화로(SPY × 원달러) · 현금 이자 3%] 10년짜리 계좌 155개 (2003-12-01~2013-11-29 … 2016-10-03~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 155번 · 기다리기 0번 · 같음 0번 가운데 계좌(78/155): 2016-02-01~2026-01-30 · 넣은 돈 6,000만원 → 바로 1억 6,206만원 · 기다리기 1억 2,644만원 [한계 · 나스닥100(QQQ) · 미국 단기 국채 이자] 10년짜리 계좌 212개 (1999-03-10~2009-02-27 … 2016-10-03~2026-09-30) 10년 뒤 더 많이 남은 쪽: 월급날 바로 151번 · 기다리기 21번 · 같음 40번 가운데 계좌(107/212): 2007-03-01~2017-02-28 · 넣은 돈 6,000만원 → 바로 1억 4,012만원 · 기다리기 1억 3,644만원 끝.
# -*- coding: utf-8 -*-
"""AlgoLab 재현 코드 — S&P500 적립식, 월급날 바로 살까 떨어질 때까지 모았다 살까 (algolab.co.kr/dipwait)
ASOF = 2026-10-12 (얼린 자료의 마지막 날). 돌리는 법: pip install numpy pandas → python run_dipwait.py
설계:
- 자료: 야후 VFINX(뱅가드 S&P500 인덱스펀드) 수정종가(배당 재투자) 1985-01 ~ · 달러 기준
- 매달 첫 거래일에 50만원(달러 기준 비율을 만원으로 환산 · 환율 미반영)
① 바로: 그날 종가에 전부 산다
② 기다리기(−20%): 그달 돈을 현금으로 모아 둔다. 전날 종가가 그때까지의 최고 종가보다 20% 이상
낮은 첫 거래일 종가에 모아 둔 돈을 전부 산다. 기다리는 기간 상한 없음.
모아 둔 현금에는 미국 3개월 국채 금리(^IRX) 이자를 붙인다.
- 10년 창: 시작 달을 한 달씩 밀며 120번 넣는다. 끝 날 = 120번째 넣은 다음 달 첫 거래일 전날.
끝 날 계좌 = 주식 평가액 + 아직 안 쓴 현금(이자 포함).
- 반론 ① 짧게: −10% · 최대 12개월 / 반론 ② 기다리는 방법 50가지(−5·10·15·20·30% × 최대 3·6·12·24개월·무한 × 전부/절반)
- 반론 ③ 기간: 5·15·20년 계좌
- 한계: 코스피200 ETF(KODEX200·TIGER200 · 2009-05~ · 현금 이자 0%·3%) · 코스피 지수(1997~ · 배당 0%/연 1.8% 어림 × 이자 0%/3%)
· S&P500 원화(SPY × 원달러 · 2003-12~) · 나스닥100(QQQ)
· 수수료·세금 어림(수수료 0.25% · 현금 이자세 15.4% · 배당세 15%)
"""
import json
import os
import sys
import numpy as np
import pandas as pd
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
HERE = os.path.dirname(os.path.abspath(__file__))
DATA = os.path.join(HERE, "data_dipwait")
ASOF = "2026-10-12"
MONTHLY = 50 # 만원
H = 120 # 10년
def load(name):
df = pd.read_csv(os.path.join(DATA, name + ".csv"), parse_dates=["date"])
df = df[df["date"] <= ASOF]
s = df.set_index("date")["close"].astype(float)
return s[s > 0]
IRX = load("_IRX")
RAWNAME, DIVNAME, ASOF_RAW = "VFINX_raw.csv", "VFINX_div.csv", None
def cash_index(idx, mode):
if mode == "irx":
r = IRX.reindex(idx, method="ffill").bfill().values / 100.0
daily = (1 + r) ** (1 / 252.0)
else:
daily = np.full(len(idx), (1 + mode) ** (1 / 252.0))
daily[0] = 1.0
return np.cumprod(daily)
def month_starts(idx):
s = pd.Series(np.arange(len(idx)), index=idx)
return s.groupby([idx.year, idx.month]).first().values
def deploy_days(cond, contrib, K, n):
nxt = np.full(n + 1, n, dtype=np.int64)
for t in range(n - 1, -1, -1):
nxt[t] = t if cond[t] else nxt[t + 1]
out = []
for i, c in enumerate(contrib):
d = nxt[c]
if K is not None:
j = i + K
cap = contrib[j] if j < len(contrib) else n
d = min(d, cap)
out.append(d)
return np.array(out)
class Market:
def __init__(self, price, cash_mode, start="1985-01-01"):
p = price[price.index >= start]
self.idx = p.index
self.price = p.values
self.n = len(self.price)
self.C = cash_index(self.idx, cash_mode)
self.contrib = month_starts(self.idx)
runmax = np.maximum.accumulate(self.price)
dd = self.price / runmax - 1
self.dd_prev = np.concatenate([[0.0], dd[:-1]]) # 전날 종가 기준(룩어헤드 없음)
self.nwin = len(self.contrib) - H
self.E = np.array([self.contrib[w + H] - 1 for w in range(self.nwin)])
def values(self, d):
"""창마다 끝 날 계좌(1 단위 납입 기준) · 창마다 끝까지 안 쓴 현금 비중."""
n, C, price, contrib = self.n, self.C, self.price, self.contrib
vals = np.zeros(self.nwin)
cashfrac = np.zeros(self.nwin)
for w in range(self.nwin):
e = self.E[w]
cs = contrib[w:w + H]
ds = d[w:w + H]
bought = ds <= e
dd_ = np.minimum(ds, n - 1)
stock = np.where(bought, (C[dd_] / C[cs]) / price[dd_] * price[e], 0.0)
cash = np.where(bought, 0.0, C[e] / C[cs])
vals[w] = stock.sum() + cash.sum()
cashfrac[w] = cash.sum() / vals[w]
return vals, cashfrac
def arm(self, X, K):
if X is None:
return self.contrib.copy()
return deploy_days(self.dd_prev <= -X / 100.0, self.contrib, K, self.n)
def date(self, i):
return str(self.idx[min(i, self.n - 1)].date())
def compare(mk, X, K, half=False):
A, _ = mk.values(mk.arm(None, None))
d = mk.arm(X, K)
B, cf = mk.values(d)
if half:
B = 0.5 * A + 0.5 * B
r = B / A
order = np.argsort(r)
mid = int(order[len(order) // 2])
wait = np.minimum(d, mk.n - 1) - mk.contrib
return {
"창수": int(mk.nwin),
"첫창": mk.date(mk.contrib[0]) + "~" + mk.date(mk.E[0]),
"끝창": mk.date(mk.contrib[mk.nwin - 1]) + "~" + mk.date(mk.E[-1]),
"기다림이더남음": int((B > A).sum()),
"바로가더남음": int((B < A).sum()),
"같음": int((B == A).sum()),
"한가운데": {
"창": mk.date(mk.contrib[mid]) + "~" + mk.date(mk.E[mid]),
"순위": f"{len(order) // 2 + 1}/{len(order)}",
"넣은돈_만원": MONTHLY * H,
"바로_만원": int(round(A[mid] * MONTHLY)),
"기다림_만원": int(round(B[mid] * MONTHLY)),
"끝날현금비중": round(float(cf[mid]), 3),
},
"기다림_가장많이이김_만원": int(round(((B - A) * MONTHLY).max())),
"기다림_가장많이짐_만원": int(round(((B - A) * MONTHLY).min())),
"평균대기_거래일": round(float(wait.mean()), 1),
"1년넘게기다린달_비율": round(float((wait > 252).mean()), 3),
"끝까지한번도안산창": int(sum(1 for w in range(mk.nwin) if (d[w:w + H] > mk.E[w]).all())),
"끝날현금이남은창": int((cf > 0).sum()),
"_r": r, "_A": A * MONTHLY, "_B": B * MONTHLY,
}
def paid_more(mk, X=20):
"""기다렸다 산 달 가운데, 산 값이 그달 첫 거래일(월급날) 값보다 비쌌던 달 — 1985~ 전체 달 기준."""
d = mk.arm(X, None)
ok = d < mk.n
pc = mk.price[mk.contrib[ok]]
pb = mk.price[d[ok]]
return {"산달": int(ok.sum()), "아직못산달": int((~ok).sum()), "월급날보다비싸게산달": int((pb > pc).sum()),
"월급날보다싸게산달": int((pb < pc).sum()), "같은날산달": int((pb == pc).sum()),
"비율": round(float((pb > pc).mean()), 3)}
def replay(mk, start_ym, X=20):
"""한 계좌의 거래 리플레이 — 어느 달 돈이 언제 들어갔나, 들어간 값이 월급날 값보다 비쌌나."""
d = mk.arm(X, None)
w = next(i for i in range(mk.nwin) if mk.date(mk.contrib[i]).startswith(start_ym))
e = mk.E[w]
cs, ds = mk.contrib[w:w + H], d[w:w + H]
buys = []
for day in sorted(set(int(x) for x in ds if x <= e)):
months = [int(ci) for ci, di in zip(cs, ds) if di == day]
p = mk.price[day]
buys.append({"산날": mk.date(day), "달수": len(months), "첫달": mk.date(months[0]), "끝달": mk.date(months[-1]),
"전날낙폭": round(float(mk.dd_prev[day]), 3),
"월급날값이더쌌던달": sum(1 for ci in months if mk.price[ci] < p)})
left = [int(ci) for ci, di in zip(cs, ds) if di > e]
return {"창": mk.date(cs[0]) + "~" + mk.date(e), "산날들": buys,
"끝까지현금": {"달수": len(left), "첫달": mk.date(left[0]) if left else None,
"끝달": mk.date(left[-1]) if left else None},
"산달": int(sum(b["달수"] for b in buys)),
"월급날보다비싸게산달": int(sum(1 for ci, di in zip(cs, ds) if di <= e and mk.price[di] > mk.price[ci]))}
def compare_cost(price_tr, irx_scale, fee, X=20):
"""수수료(산 몫에 fee) · 현금 이자세(이자 × irx_scale) · 배당세(price_tr 로) 를 넣은 판 — 바로/기다리기 승수만."""
global IRX
keep = IRX
IRX = keep * irx_scale
try:
mk = Market(price_tr, "irx")
finally:
IRX = keep
res = []
for d in (mk.arm(None, None), mk.arm(X, None)):
vals = np.zeros(mk.nwin)
for w in range(mk.nwin):
e = mk.E[w]
cs, ds = mk.contrib[w:w + H], d[w:w + H]
b = ds <= e
dd = np.minimum(ds, mk.n - 1)
st = np.where(b, (mk.C[dd] / mk.C[cs]) / mk.price[dd] * mk.price[e] * (1 - fee), 0.0)
ca = np.where(b, 0.0, mk.C[e] / mk.C[cs])
vals[w] = st.sum() + ca.sum()
res.append(vals)
A, B = res
return {"창수": int(mk.nwin), "바로가더남음": int((A > B).sum()), "기다림이더남음": int((B > A).sum())}
def after_tax_tr(tax):
"""VFINX 원래 종가 + 배당(야후 events) 로 배당세를 뗀 총수익 지수를 짓는다."""
raw = pd.read_csv(os.path.join(DATA, RAWNAME), parse_dates=["date"]).set_index("date")
raw = raw[raw.index <= ASOF_RAW] if ASOF_RAW else raw
dv = pd.read_csv(os.path.join(DATA, DIVNAME), parse_dates=["date"]).set_index("date")["amount"]
P = raw["close"].astype(float)
D = dv.reindex(P.index).fillna(0.0)
r = (P + D * (1 - tax)) / P.shift(1)
r.iloc[0] = 1.0
return pd.Series(np.cumprod(r.values), index=P.index)
def strip(x):
return {k: v for k, v in x.items() if not k.startswith("_")}
def main():
global H
out = {"입력": {"매달_만원": MONTHLY, "기간_년": H // 12, "넣은돈_만원": MONTHLY * H,
"기다림_문턱": 20, "짧은기다림_문턱": 10, "짧은기다림_최대개월": 12}}
vf = Market(load("VFINX"), "irx")
out["자료"] = {"VFINX": f"{vf.date(0)}~{vf.date(vf.n - 1)}", "IRX끝": str(IRX.index[-1].date())}
main_ = compare(vf, 20, None)
out["본편_SP500_기다림20"] = strip(main_)
short = compare(vf, 10, 12)
out["반론_짧게_10_12개월"] = strip(short)
# 반론 ③ 10년이 아니면? — 같은 규칙을 5·15·20년 계좌로
for yrs in (5, 15, 20):
H = yrs * 12
out[f"반론_기간_{yrs}년"] = strip(compare(Market(load("VFINX"), "irx"), 20, None))
H = 120
out["왜_비싸게산달_SP500"] = paid_more(vf)
out["리플레이_한가운데"] = replay(vf, main_["한가운데"]["창"][:7])
# 한계 ④ 수수료·세금 어림 — 수수료 0.25%(산 몫) · 현금 이자세 15.4% · 미국 배당세 15%
tr15 = after_tax_tr(0.15)
tr0 = after_tax_tr(0.0)
out["한계_비용"] = {
"기준_원래종가재구성": compare_cost(tr0[tr0.index >= "1985-01-01"], 1.0, 0.0),
"수수료0.25": compare_cost(tr0[tr0.index >= "1985-01-01"], 1.0, 0.0025),
"이자세15.4": compare_cost(tr0[tr0.index >= "1985-01-01"], 0.846, 0.0),
"배당세15": compare_cost(tr15[tr15.index >= "1985-01-01"], 1.0, 0.0),
"셋다": compare_cost(tr15[tr15.index >= "1985-01-01"], 0.846, 0.0025),
}
# 50가지
cells = []
for X in (5, 10, 15, 20, 30):
for K in (3, 6, 12, 24, None):
for half in (False, True):
c = compare(vf, X, K, half)
cells.append({"X": X, "K": K if K else "inf", "절반": half,
"기다림이더남음": c["기다림이더남음"], "창수": c["창수"],
"중앙비율": round(float(np.median(c["_r"])), 4)})
out["반론_50가지"] = {
"가지수": len(cells),
"절반넘게이긴가지": sum(1 for c in cells if c["기다림이더남음"] > c["창수"] / 2),
"가장많이이긴가지": max(cells, key=lambda c: c["기다림이더남음"]),
"중앙비율1넘은가지": sum(1 for c in cells if c["중앙비율"] > 1),
"cells": cells,
}
# 한계: 코스피200 ETF · 원화 S&P500 · 나스닥100
# ⚠ 야후 069500.KS 는 2007-02-07 다음 봉이 2009-04-17 이다(2008년 전체 공백) — 2009-05 부터만 쓴다(261013 심사 지적)
kx = load("069500_KS")
out["한계_KODEX200_이자0"] = strip(compare(Market(kx, 0.0, "2009-05-01"), 20, None))
out["왜_비싸게산달_KODEX200"] = paid_more(Market(kx, 0.0, "2009-05-01"))
out["한계_KODEX200_이자3"] = strip(compare(Market(kx, 0.03, "2009-05-01"), 20, None))
out["한계_TIGER200_이자0"] = strip(compare(Market(load("102110_KS"), 0.0, "2009-05-01"), 20, None))
# 길게: 코스피 지수(^KS11 · 가격지수라 배당 없음) 1997~ — 배당 0%·연 1.8%(어림) × 현금 이자 0%·3%
k11 = load("_KS11")
yrs = np.arange(len(k11)) / 252.0
k11d = pd.Series(k11.values * np.exp(np.log1p(0.018) * yrs), index=k11.index)
out["한계_코스피지수_배당0_이자0"] = strip(compare(Market(k11, 0.0, "1997-01-01"), 20, None))
out["한계_코스피지수_배당0_이자3"] = strip(compare(Market(k11, 0.03, "1997-01-01"), 20, None))
out["한계_코스피지수_배당18_이자0"] = strip(compare(Market(k11d, 0.0, "1997-01-01"), 20, None))
out["한계_코스피지수_배당18_이자3"] = strip(compare(Market(k11d, 0.03, "1997-01-01"), 20, None))
spy = load("SPY")
fx = load("USDKRW_X")
j = spy.index.intersection(fx.index)
spy_krw = (spy.reindex(j) * fx.reindex(j)).dropna()
out["한계_SP500원화_이자0"] = strip(compare(Market(spy_krw, 0.0, "2003-12-01"), 20, None))
out["한계_SP500원화_이자3"] = strip(compare(Market(spy_krw, 0.03, "2003-12-01"), 20, None))
out["한계_나스닥100"] = strip(compare(Market(load("QQQ"), "irx", "1999-01-01"), 20, None))
def won(v):
v = int(v)
if abs(v) >= 10000:
r = abs(v) % 10000
s = f"{abs(v) // 10000}억" + (f" {r:,}만원" if r else "원")
return ("-" if v < 0 else "") + s
return f"{v:,}만원"
def line(name, v, yrs=10):
g = v["한가운데"]
print(f"[{name}] {yrs}년짜리 계좌 {v['창수']:,}개 ({v['첫창']} … {v['끝창']})")
print(f" {yrs}년 뒤 더 많이 남은 쪽: 월급날 바로 {v['바로가더남음']:,}번 · 기다리기 {v['기다림이더남음']:,}번 · 같음 {v['같음']}번")
print(f" 가운데 계좌({g['순위']}): {g['창']} · 넣은 돈 {won(g['넣은돈_만원'])} → 바로 {won(g['바로_만원'])} · 기다리기 {won(g['기다림_만원'])}")
print(f"자료: VFINX {out['자료']['VFINX']} · ^IRX 끝 {out['자료']['IRX끝']} · ASOF {ASOF}\n")
m = out["본편_SP500_기다림20"]
line("S&P500 · 매달 50만원 · 20% 떨어지면 사기(상한 없음)", m)
print(f" 기다린 돈이 산 날까지 평균 {m['평균대기_거래일']}거래일 · 1년 넘게 기다린 돈 {m['1년넘게기다린달_비율'] * 100:.1f}%")
print(f" 10년 내내 한 번도 못 산 계좌 {m['끝까지한번도안산창']}개 · 끝날 현금이 남은 계좌 {m['끝날현금이남은창']}개")
print(f" 기다리기가 가장 크게 이긴 계좌 +{won(m['기다림_가장많이이김_만원'])} · 가장 크게 진 계좌 {won(m['기다림_가장많이짐_만원'])}")
pm = out["왜_비싸게산달_SP500"]
print(f" 1985년부터 넣은 달: 기다렸다 산 달 {pm['산달']}개 중 월급날 값보다 비싸게 산 달 {pm['월급날보다비싸게산달']}개 ({pm['비율'] * 100:.1f}%)"
f" · 싸게 산 달 {pm['월급날보다싸게산달']}개 · 같은 날 산 달 {pm['같은날산달']}개 · 아직 못 산 달 {pm['아직못산달']}개")
rp = out["리플레이_한가운데"]
print(f"\n[가운데 계좌 리플레이] {rp['창']}")
for b in rp["산날들"]:
print(f" {b['산날']} 샀다(전날 최고점 대비 {b['전날낙폭'] * 100:.1f}%) — {b['첫달']}~{b['끝달']} 에 모은 {b['달수']}달치 · 그중 월급날 값이 더 쌌던 달 {b['월급날값이더쌌던달']}개")
lf = rp["끝까지현금"]
print(f" 끝까지 현금: {lf['첫달']}~{lf['끝달']} 에 모은 {lf['달수']}달치")
print(f" 산 달 {rp['산달']}개 중 월급날보다 비싸게 산 달 {rp['월급날보다비싸게산달']}개")
s = out["반론_짧게_10_12개월"]
print()
line("반론 · 10% 떨어지면 사기 · 최대 12개월", s)
f = out["반론_50가지"]
t = f["가장많이이긴가지"]
print(f"\n[반론 · 기다리는 방법 {f['가지수']}가지] 382개 계좌 중 절반 넘게 이긴 방법 {f['절반넘게이긴가지']}가지 · "
f"가장 많이 이긴 방법 {t['기다림이더남음']}번(−{t['X']}% · 최대 {t['K']}개월 · {'절반' if t['절반'] else '전부'})")
for c in f["cells"]:
print(f" −{c['X']:>2}% · 최대 {str(c['K']):>3}개월 · {'절반' if c['절반'] else '전부'}: 기다리기가 더 남은 {c['기다림이더남음']:>3}/{c['창수']} · 가운데 비율 {c['중앙비율']:.4f}")
print()
for yrs in (5, 15, 20):
line(f"반론 · 기간을 {yrs}년으로 · 20% 떨어지면 사기", out[f"반론_기간_{yrs}년"], yrs)
print("\n[한계 · 수수료·세금 어림] VFINX 원래 종가 + 배당 기록으로 다시 지은 총수익 · 10년짜리 계좌")
for k, v in out["한계_비용"].items():
print(f" {k}: 월급날 바로 {v['바로가더남음']}번 · 기다리기 {v['기다림이더남음']}번 (계좌 {v['창수']}개)")
print(" (수수료 = 산 몫의 0.25% · 이자세 = 현금 이자의 15.4% · 배당세 = 배당의 15%)")
print("\n※ 야후 069500.KS 는 2007-02-07 다음 봉이 2009-04-17 이다(2008년 전체 공백) — 코스피200 ETF 는 2009-05 부터만 쓴다")
line("한계 · 코스피200 ETF(KODEX200) · 2009-05~ · 원화 · 현금 이자 0%", out["한계_KODEX200_이자0"])
pk = out["왜_비싸게산달_KODEX200"]
print(f" 기다렸다 산 달 {pk['산달']}개 중 월급날 값보다 싸게 산 달 {pk['월급날보다싸게산달']}개 · 비싸게 산 달 {pk['월급날보다비싸게산달']}개 · 같은 날 산 달 {pk['같은날산달']}개")
line("한계 · 코스피200 ETF(KODEX200) · 2009-05~ · 원화 · 현금 이자 3%", out["한계_KODEX200_이자3"])
line("한계 · 코스피200 ETF(TIGER200) · 2009-05~ · 원화 · 현금 이자 0%", out["한계_TIGER200_이자0"])
line("한계 · 코스피 지수(가격지수 · 배당 0%) · 1997~ · 현금 이자 0%", out["한계_코스피지수_배당0_이자0"])
line("한계 · 코스피 지수(가격지수 · 배당 0%) · 1997~ · 현금 이자 3%", out["한계_코스피지수_배당0_이자3"])
line("한계 · 코스피 지수 + 배당 연 1.8%(어림) · 1997~ · 현금 이자 0%", out["한계_코스피지수_배당18_이자0"])
line("한계 · 코스피 지수 + 배당 연 1.8%(어림) · 1997~ · 현금 이자 3%", out["한계_코스피지수_배당18_이자3"])
line("한계 · S&P500 원화로(SPY × 원달러) · 현금 이자 0%", out["한계_SP500원화_이자0"])
line("한계 · S&P500 원화로(SPY × 원달러) · 현금 이자 3%", out["한계_SP500원화_이자3"])
line("한계 · 나스닥100(QQQ) · 미국 단기 국채 이자", out["한계_나스닥100"])
print("\n끝.")
if __name__ == "__main__":
main()
알고랩 Strategy Lab — 전략을 검증하고, 검증한 것만 자동매매로 옮깁니다. 무서운 뉴스가 나온 날에도 정해 둔 날에 사는 일은 사람보다 봇이 잘합니다. 알고랩 자동매매 살펴보기 · 전략 검증 전체 보기
과거 데이터를 돌려 본 결과이며 미래 수익을 보장하지 않습니다. 투자 판단과 책임은 본인에게 있습니다.