# -*- coding: utf-8 -*-
"""수익 난 대형주 — 그날 바로 팔까, 고점에서 꺾일 때 팔까 (AlgoLab · 2026-10-28)

돌리는 법: 이 파일 옆에 data_profitsell/ 폴더를 두고  python run_profitsell.py
표준 라이브러리만 씁니다. 가격 자료는 2026-10-02 까지 얼려 둔 일봉 종가(날짜·종가)입니다.
  u2007/  2007-12-28 실제 코스피 시가총액 상위 40종(상장 유지 = 야후 수정종가 · 상장폐지·합병 6종 = 네이버 종가 + 후신 이음)
  us30/   2005년 무렵 미국 시가총액 상위 30종(야후 수정종가) · index/ KODEX200(2009-04 이전 네이버 종가 이음) · SPY

30% 오른 날 : 2008-01 부터 매달 첫 거래일에 산 종목이, 산 뒤 500거래일(약 2년) 안에 처음으로
              종가가 산 값의 1.3배 이상이 된 날 t. 그날 계좌 = 1,000만원. (같은 날이 여러 달에서 나오면 한 번만 센다)
바로        : t 종가에 팔아 지수 ETF(KODEX200 / SPY)로 옮긴다
꺾이면      : t 뒤 가장 높았던 종가보다 20% 이상 내려간 첫 날 종가에 팔아 지수 ETF로 옮긴다(3년 안에 안 꺾이면 그대로)
그대로      : 3년 들고 간다
평가        : 셋 다 t + 750거래일(약 3년) 종가로 같은 날 평가 · 옮길 때 비용 국내 0.4% · 미국 0.2% · 세금 없음
국내 종목은 하루 ±31% 넘는 등락을 액면분할 오류로 보고 0 으로 지운다.
"""
import csv
import math
import os

HERE = os.path.dirname(os.path.abspath(__file__))
D = os.path.join(HERE, "data_profitsell")
H, WIN, BUY0 = 750, 500, "2008-01-01"
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()


def load(path, clean):
    d, c = [], []
    for r in csv.reader(open(path, encoding="utf-8")):
        if r and r[0][:1].isdigit():
            v = float(r[1])
            if clean and v <= 0:
                continue
            d.append(r[0]); c.append(v)
    if clean:
        x, p = [c[0]], 1.0              # 누적곱을 먼저 하고 첫 값을 곱한다(연구 코드 numpy 와 반올림 순서까지 같게)
        for i in range(1, len(c)):
            g = c[i] / c[i - 1] - 1
            p *= 1 + (0.0 if abs(g) > 0.31 else g)
            x.append(c[0] * p)
        c = x
    return d, c


def idx_at(xd, xc, day):
    lo, hi = 0, len(xd)
    while lo < hi:                      # 그 날짜 이하의 마지막 지수 종가
        m = (lo + hi) // 2
        if xd[m] <= day:
            lo = m + 1
        else:
            hi = m
    return xc[lo - 1]


def events(names, folder, index_file, cost, X, clean):
    xd, xc = load(index_file, False)
    out = []
    for nm in names:
        d, c = load(os.path.join(folder, nm + ".csv"), 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)
            t = next((j for j in range(b + 1, lim) if c[j] >= (1 + X) * c[b]), None)
            if t is None or t + H >= len(c) or (nm, t) in seen or d[t] < xd[0] or d[t + H] > xd[-1]:
                continue
            seen.add((nm, t))
            seg = c[t:t + H + 1]
            ix = [idx_at(xd, xc, dd) for dd in d[t:t + H + 1]]
            out.append({"code": nm, "t": d[t], "dates": d[t:t + H + 1], "seg": seg, "ix": ix, "cost": cost})
    return out


def trail_delay(seg, Y):
    pk = seg[0]
    for k in range(1, len(seg)):
        pk = max(pk, seg[k])
        if seg[k] <= (1 - Y) * pk:
            return k
    return None


def value(e, delay, to="idx"):
    seg, ix, cost = e["seg"], e["ix"], e["cost"]
    if delay is None:
        return seg[-1] / seg[0]
    v = seg[delay] / seg[0]
    if to == "idx":
        return v * ix[-1] / ix[delay] * (1 - cost)
    return v * (1 - cost / 2)


def q(a, p):
    s = sorted(a); k = (len(s) - 1) * p; f = math.floor(k)
    return s[f] if f == k else s[f] + (s[f + 1] - s[f]) * (k - f)


def won(x):
    return f"{round(x * 1000):,}만원"


def cell(evs, Y):
    hold = [value(e, None) for e in evs]
    now = [value(e, 0) for e in evs]
    dl = [trail_delay(e["seg"], Y) for e in evs]
    tr = [value(e, x) for e, x in zip(evs, dl)]
    return hold, now, dl, tr


def report(name, names, folder, index_file, cost, clean, kr):
    print(f"== {name}")
    evs = events(names, folder, index_file, cost, 0.30, clean)
    hold, now, dl, tr = cell(evs, 0.20)
    n = len(evs)
    print(f"30% 오른 날 {n}번 · 종목 {len({e['code'] for e in evs})}개 · 첫 {min(e['t'] for e in evs)} · 끝 {max(e['t'] for e in evs)}")
    print(f"3년 뒤 바로 판 쪽이 꺾이면 판 쪽보다 더 남은 것 {sum(a > b for a, b in zip(now, tr))}번")
    sold = [e["seg"][x] / e["seg"][0] for e, x in zip(evs, dl) if x is not None]
    print(f"꺾여서 판 것 {len(sold)}번 · 판 날 계좌가 이미 1,000만원보다 적었던 것 {sum(s < 1 for s in sold)}번")
    print(f"3년 뒤 1,000만원보다 줄어든 것: 그대로 {sum(h < 1 for h in hold)}번 · 꺾이면 {sum(t < 1 for t in tr)}번 · 바로 {sum(x < 1 for x in now)}번")
    print(f"단순 평균(1,000만원 기준): 그대로 {won(sum(hold) / n)} · 바로 {won(sum(now) / n)} · 꺾이면 {won(sum(tr) / n)}")
    print(f"덜 남은 쪽 열 번에 한 번꼴 경계(하위 10%): 그대로 {won(q(hold, .1))} · 바로 {won(q(now, .1))} · 꺾이면 {won(q(tr, .1))}")
    first = {}
    for i, e in enumerate(evs):
        first.setdefault(e["code"], i)
    print(f"종목마다 첫 사건만: {len(first)}개 중 {sum(now[i] > tr[i] for i in first.values())}개 바로 승")
    if not kr:
        return
    for Y in (0.10, 0.15, 0.25):
        _, n2, _, t2 = cell(evs, Y)
        print(f"  꺾임 기준 {int(Y * 100)}%: 바로 쪽이 더 남은 것 {sum(a > b for a, b in zip(n2, t2))}번")
    for X in (0.20, 0.50):
        ev2 = events(names, folder, index_file, cost, X, clean)
        _, n2, _, t2 = cell(ev2, 0.20)
        print(f"  {int(X * 100)}% 오른 날(꺾임 20%): {len(ev2)}번 중 바로 쪽이 더 남은 것 {sum(a > b for a, b in zip(n2, t2))}번")
    rec = [i for i, e in enumerate(evs) if e["t"] >= "2020-01-01"]
    print(f"2020년 이후 30% 오른 날 {len(rec)}번 중 바로 쪽이 더 남은 것 {sum(now[i] > tr[i] for i in rec)}번 · "
          f"줄어든 것 바로 {sum(now[i] < 1 for i in rec)}번 · 꺾이면 {sum(tr[i] < 1 for i in rec)}번")
    cash = [value(e, x, 'cash') for e, x in zip(evs, dl)]
    print(f"판 돈을 현금(이자 0)으로 두면: 꺾이면 쪽 3년 뒤 1,000만원 아래 {sum(c < 1 for c in cash)}번")
    ss = [i for i, e in enumerate(evs) if e["code"] == "005930"]
    print(f"삼성전자만: 30% 오른 날 {len(ss)}번 · 바로 쪽이 더 남은 것 {sum(now[i] > tr[i] for i in ss)}번 · "
          f"셋 중 그냥 둔 쪽이 가장 많이 남은 것 {sum(hold[i] > max(now[i], tr[i]) for i in ss)}번")
    print("삼성전자(005930) — 30% 오른 날 전부:")
    for i, e in enumerate(evs):
        if e["code"] == "005930":
            x = dl[i]
            sd = e["dates"][x] if x is not None else "안 꺾임"
            sv = won(e["seg"][x] / e["seg"][0]) if x is not None else "-"
            print(f"  {e['t']} → 꺾여 판 날 {sd}(그날 계좌 {sv}) · 3년 뒤 {e['dates'][-1]}: 바로 {won(now[i])} · 꺾이면 {won(tr[i])} · 그대로 {won(hold[i])}")


if __name__ == "__main__":
    kr = sorted(f[:-4] for f in os.listdir(os.path.join(D, "u2007")) if f.endswith(".csv"))
    report("국내 · 2007년 말 시가총액 상위 40 (판 돈 → KODEX200)", kr, os.path.join(D, "u2007"),
           os.path.join(D, "index", "KODEX200.csv"), 0.004, True, True)
    report("미국 · 대형주 30 (판 돈 → SPY)", US, os.path.join(D, "us30"), os.path.join(D, "index", "SPY.csv"), 0.002, False, False)
    print("끝.")
