# -*- coding: utf-8 -*-
"""확신 가는 대형주 하나, 계좌에 얼마나 넣을까 — 재현 코드 (AlgoLab · 2026-10-16)

무엇을 재나
  2009년 6월 1일에 1,000만원을 넣고 2026년 10월 15일까지 그대로 둔다(편도 수수료 0.1% 한 번).
  ① 코스피200 ETF(KODEX 200 · 069500) 만
  ② 국내 대형주 한 종목에 전부
  ③ 한 종목에 w(10% 등) + 나머지는 코스피200 ETF (처음 나눈 뒤 다시 맞추지 않는다)

가격 자료
  data_onestock/<코드>_KS.csv — 야후 일봉. close 는 배당·분할을 반영한 보정 종가(야후 adjclose 비율로 보정).
  2026-10-16 에 받아 얼렸다(ASOF 이후 봉은 쓰지 않는다).

종목 목록
  2007년 무렵 코스피 시가총액 상위권 38종목(사람이 정한 목록 — 정확한 순위표가 아니다).
  · 인적분할로 회사가 둘로 쪼개진 7종목은 뺀다: 야후 보정 종가가 떨어져 나간 회사 몫을 담지 못해
    한 종목 성적이 틀린다(SK텔레콤 2021-11 · 신세계 2011-06 · 대한항공 2013-08 · NAVER 2013-08 ·
    LG 2021-05 · 현대중공업 2017-04 · 롯데쇼핑 2017-10). 아래 [점프 점검] 에 그 흔적이 찍힌다.
  · 우리금융(구)·삼성물산(구)은 합병으로 사라져 야후 자료가 없다.
  → 사라진 회사가 빠진 만큼 '한 종목' 쪽이 실제보다 좋게 나왔을 수 있다.

돌리는 법: pip install numpy pandas → python run_onestock.py
"""
import os
import numpy as np
import pandas as pd

HERE = os.path.dirname(os.path.abspath(__file__))
D = os.path.join(HERE, "data_onestock")
ASOF = "2026-10-15"
START = "2009-06-01"
FEE = 0.001
WON = 1000  # 만원
U = {"005930": "삼성전자", "005490": "POSCO홀딩스", "009540": "HD한국조선해양", "015760": "한국전력", "105560": "KB금융",
     "055550": "신한지주", "017670": "SK텔레콤", "066570": "LG전자", "005380": "현대차", "000660": "SK하이닉스", "034220": "LG디스플레이",
     "053000": "우리금융(구)", "096770": "SK이노베이션", "034020": "두산에너빌리티", "030200": "KT", "010140": "삼성중공업",
     "012330": "현대모비스", "051910": "LG화학", "000810": "삼성화재", "033780": "KT&G", "010950": "S-Oil", "042660": "한화오션",
     "004020": "현대제철", "004170": "신세계", "000830": "삼성물산(구)", "003550": "LG", "000270": "기아", "086790": "하나금융지주",
     "024110": "기업은행", "006400": "삼성SDI", "009150": "삼성전기", "035420": "NAVER", "023530": "롯데쇼핑", "000720": "현대건설",
     "006360": "GS건설", "003490": "대한항공", "036460": "한국가스공사", "010130": "고려아연"}
SPINOFF = {"017670": "2021-11 SK스퀘어 인적분할", "004170": "2011-06 이마트 인적분할", "003490": "2013-08 한진칼 인적분할",
           "035420": "2013-08 NHN엔터 인적분할", "003550": "2021-05 LX홀딩스 인적분할",
           "009540": "2017-04 현대일렉트릭 등 인적분할", "023530": "2017-10 롯데지주 인적분할"}
BENCH = "069500"


def 만원(x):
    x = int(round(x))
    if x >= 10000:
        return "%d억 %s만원" % (x // 10000, format(x % 10000, ",")) if x % 10000 else "%d억원" % (x // 10000)
    return format(x, ",") + "만원"


def load(code):
    p = os.path.join(D, code + "_KS.csv")
    if not os.path.exists(p):
        return None
    df = pd.read_csv(p, parse_dates=["date"]).set_index("date")
    s = df["close"].astype(float)
    s = s[~s.index.duplicated()]
    return s[s > 0].loc[:ASOF]


raw = {c: load(c) for c in U}
bench = load(BENCH)
idx = bench.loc[START:ASOF].index
print("[판] %s ~ %s · 코스피200 ETF 거래일 %d개 · 가장 긴 빈 구간 %d일" % (
    idx[0].date(), idx[-1].date(), len(idx), int(np.diff(idx.values).astype("timedelta64[D]").astype(int).max())))

print("\n[점프 점검] 하루 ±30% 넘게 움직인 날 (인적분할·상하한가 흔적)")
for c, s in raw.items():
    if s is None:
        continue
    r = s.loc[START:ASOF].pct_change().dropna()
    big = r[r.abs() > 0.30]
    if len(big):
        print("  %s(%s): %s%s" % (U[c], c, ", ".join("%s %+.1f%%" % (d.date(), v * 100) for d, v in big.items()),
                                  " ← 뺌" if c in SPINOFF else ""))

px, dropped = {}, []
for c, s in raw.items():
    if s is None:
        dropped.append("%s(자료 없음)" % U[c]); continue
    if c in SPINOFF:
        dropped.append("%s(%s)" % (U[c], SPINOFF[c])); continue
    px[c] = s.reindex(idx).ffill()
P = pd.DataFrame(px)
B = bench.reindex(idx).ffill()
assert P.iloc[0].notna().all()
print("\n[뺀 종목 %d개] %s" % (len(dropped), " · ".join(dropped)))

g = (P.iloc[-1] / P.iloc[0]) * (1 - FEE)          # 한 종목에 전부: 원금 배수
gb = float(B.iloc[-1] / B.iloc[0]) * (1 - FEE)     # 코스피200 ETF 만
etf = WON * gb
n = len(g)
win = g[g > gb].sort_values(ascending=False)
print("\n[1,000만원 · %s ~ %s · 그대로 둠]" % (idx[0].date(), idx[-1].date()))
print("  코스피200 ETF 만: %s (%.2f배)" % (만원(etf), gb))
print("  대형주 %d종목 중 ETF 보다 더 남은 종목 %d개 · 덜 남은 종목 %d개 · 원금보다 줄어든 종목 %d개" % (
    n, len(win), n - len(win), int((g < 1).sum())))
print("  더 남은 종목: " + " · ".join("%s %s" % (U[c], 만원(WON * g[c])) for c in win.index))
print("  한 종목 끝 금액의 가운데(15번째): %s" % 만원(WON * float(np.median(g))))

print("\n[종목마다 — 전부 넣었을 때 / 10%%만 넣고 나머지는 ETF]")
for c in g.sort_values().index:
    print("  %-10s 전부 %12s · 10%%만 %12s" % (U[c], 만원(WON * g[c]), 만원(WON * (0.1 * g[c] + 0.9 * gb))))

print("\n[한 종목에 넣는 몫을 바꾸면 — 가장 나빴던 종목을 골랐을 때 · 가장 좋았던 종목을 골랐을 때]")
lo, hi = g.idxmin(), g.idxmax()
for w in [0.05, 0.1, 0.2, 0.3, 0.5, 1.0]:
    a_lo = WON * (w * g[lo] + (1 - w) * gb)
    a_hi = WON * (w * g[hi] + (1 - w) * gb)
    rel = (w * g + (1 - w) * gb) / gb
    print("  %3d%%  가장 나쁜 %s %12s (ETF 만의 %.1f%%) · 가장 좋은 %s %12s · ETF 보다 덜 남은 종목 %d개" % (
        round(w * 100), U[lo], 만원(a_lo), a_lo / etf * 100, U[hi], 만원(a_hi), int((rel < 1).sum())))
s = "005930"
print("  삼성전자를 고른 사람: 전부 %s · 10%%만 %s" % (만원(WON * g[s]), 만원(WON * (0.1 * g[s] + 0.9 * gb))))
med10 = float(np.median((0.1 * g + 0.9 * gb) / gb))
print("  10%%만 넣은 계좌의 가운데: ETF 만의 %.1f%%" % (med10 * 100))

print("\n[시작 달을 바꿔 — 10년씩, 시작 %s ~ 2016-10]" % START[:7])
rows = []
for st in pd.date_range(START, periods=400, freq="MS"):
    en = st + pd.DateOffset(years=10)
    if en > pd.Timestamp(ASOF):
        break
    i0 = idx.searchsorted(st); i1 = idx.searchsorted(en) - 1
    gg = (P.iloc[i1] / P.iloc[i0]).values * (1 - FEE)
    bb = float(B.iloc[i1] / B.iloc[i0]) * (1 - FEE)
    rr = gg / bb
    rows.append((np.median(rr), int((rr > 1).sum()), (0.1 * rr + 0.9).min(), (0.2 * rr + 0.8).min()))
R = np.array(rows)
print("  10년짜리 %d번 · 한 종목 끝 금액의 가운데가 ETF 보다 덜 남은 때 %d번" % (len(R), int((R[:, 0] < 1).sum())))
print("  ETF 보다 더 남은 종목 수: 가장 적을 때 %d개 · 가장 많을 때 %d개 (%d종목 중)" % (R[:, 1].min(), R[:, 1].max(), n))
print("  가장 나쁜 종목을 골라도 — 10%%만: ETF 만의 %.1f%% 이상 · 20%%: %.1f%% 이상" % (R[:, 2].min() * 100, R[:, 3].min() * 100))
print("\n끝.")
