Spaces:
Running
Running
File size: 7,931 Bytes
588034e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | # -*- coding: utf-8 -*-
"""์ฐธ๊ณ ์ ๋ต โ ๊ต๊ณผ์์ ๋์ค๋ ๊ท์น๋ค์ ๊ฐ์ ์ฑ์ ๊ธฐ๋ก ๋๋ฆฐ ๋๋์๋ณด๊ธฐ.
์ ๋ฃ๋๊ฐ
์์ฆ ์ด์๋ ์ฐธ๊ฐ์ ๊ณก์ ์ด ์์ด ๋น๊ต ์ฐจํธ๊ฐ ๋น์ด ์๋ค. ๊ทธ๋ฌ๋ฉด "์ผ๋ง๋ฅผ ๋ด์ผ
์ํ ๊ฒ์ธ๊ฐ"๋ฅผ ๊ฐ๋ ํ ๊ธฐ์ค์ด ํ๋ฉด์ ์๋ค. ๋๋ฆฌ ์๋ ค์ง ๊ท์น ๋ช ๊ฐ๋ฅผ ์ต๊ทผ
๊ตฌ๊ฐ์ ์ ์ฉํด ๊น์ ๋๋ฉด ๊ทธ ์๋ฆฌ๊ฐ ์ฒ๋๊ฐ ๋๋ค.
๐ด ์ด๊ฒ์ **๊ณผ๊ฑฐ ๊ตฌ๊ฐ ์ฌํ**์ด๋ค. ๋ฏธ๋ ์ฑ๊ณผ๊ฐ ์๋๊ณ , ์ฐธ๊ฐ์ ์ฑ์ ๋ ์๋๋ค.
ํ๋ฉด์ ๊ทธ ์ฌ์ค์ ๋ฐ๋์ ํจ๊ป ์ ๋๋ค. ๋ฐฑํ
์คํธ๋ฅผ ์ฑ์ ์ฒ๋ผ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด
์ด ๋ํ๊ฐ ์์ ๋ ค๋ ๋ฌธ์ ๊ทธ ์์ฒด๋ค.
๐ ์ฐธ๊ฐ์์ **์์ ํ ๊ฐ์ ์ฑ์ ๊ธฐยท๊ฐ์ ์์๋ฃ**๋ก ๋๋ฆฐ๋ค. ๊ท์น๋ง ๋ค๋ฅด๊ณ
๋๋จธ์ง ์กฐ๊ฑด์ด ๊ฐ์์ผ ๋น๊ต๊ฐ ์ฑ๋ฆฝํ๋ค. ์์๋ฃ๋ฅผ ๋นผ๊ณ ๊ทธ๋ฆฌ๋ฉด ํ์ ์ด ๋์
๊ท์น์ด ๋ถ๋นํ๊ฒ ์ข์ ๋ณด์ธ๋ค.
๐ ์ด๋ค ๊ท์น๋ ๋ฏธ๋๋ฅผ ๋ณด์ง ์๋๋ค. ๋ชจ๋ ์ ํธ๋ shift(1) ์ดํ์ ์ด๋ค.
์ด๋ํ๊ท ์ ๊ทธ๋ ์ข
๊ฐ๋ก ๊ณ์ฐํด ๊ทธ๋ ํฌ์ง์
์ ์ฐ๋ฉด ์ฑ์ ์ด ํต์งธ๋ก ๊ฑฐ์ง์ด ๋๋ค.
"""
import numpy as np
import pandas as pd
# ์ฌํด ์ฒซ ๊ฑฐ๋์ผ๋ถํฐ ์ฐ๋ค. "1์ 1์ผ์ ์ด ๊ท์น์ผ๋ก ์ถ๋ฐํ๋ค๋ฉด ์ง๊ธ ์ด๋์ธ๊ฐ" ๊ฐ
# ๊ตด๋ฌ๊ฐ๋ 90์ผ ์ฐฝ๋ณด๋ค ํจ์ฌ ์ฝ๊ธฐ ์ฝ๊ณ , ๊ธฐ์ค์ ์ด ๊ณ ์ ๋์ด ๋งค์ผ ๊ฐ์ด ํ๋ค๋ฆฌ์ง ์๋๋ค.
SINCE = "2026-01-01"
def _rsi(p, n=14):
d = p.diff()
up = d.clip(lower=0).ewm(alpha=1 / n, adjust=False).mean()
dn = (-d.clip(upper=0)).ewm(alpha=1 / n, adjust=False).mean()
return 100 - 100 / (1 + up / dn.replace(0, np.nan))
def _weights(px):
"""๊ท์น๋ณ ํฌ์ง์
์๊ณ์ด. ์ ๋ถ ์ ๋ ๊น์ง์ ์ ๋ณด๋ง ์ด๋ค."""
p = pd.Series(px, dtype=float).sort_index()
out = {}
out["BUYHOLD"] = pd.Series(1.0, index=p.index)
# 200์ผ์ โ ๋จ์ผ ๊ท์น์ผ๋ก๋ ๊ฐ์ฅ ๋๋ฆฌ ์๋ ค์ง ๊ฒ. ์๋ฉด ๋ค๊ณ ์๋๋ฉด ๋น์ด๋ค.
s200 = p.rolling(200).mean()
out["SMA200"] = (p > s200).astype(float).shift(1)
# ๊ณจ๋ /๋ฐ๋ ํฌ๋ก์ค โ 50์ผ์ ์ด 200์ผ์ ์ ๋์ผ๋ฉด ๋กฑ, ๋ด๋ ค๊ฐ๋ฉด ์
s50x = p.rolling(50).mean()
out["GOLDEN50_200"] = np.sign(s50x - s200).shift(1)
# ์ด๋ํ๊ท ๊ต์ฐจ โ ๊ฐ์ฅ ๋๋ฆฌ ์๋ ค์ง ์ถ์ธ ๊ท์น
s20, s50 = p.rolling(20).mean(), p.rolling(50).mean()
out["SMA20_50"] = np.sign(s20 - s50).shift(1)
# 60์ผ ๋ชจ๋ฉํ
โ ํ์ ๋ฌธํ์์ ๊ฐ์ฅ ๋ง์ด ๊ฒ์ฆ๋ ์ถ
out["MOM60"] = np.sign(p / p.shift(60) - 1).shift(1)
# ๋์น์ 20์ผ ์ฑ๋ ๋ํ
hi, lo = p.rolling(20).max(), p.rolling(20).min()
br = pd.Series(0.0, index=p.index)
br[p >= hi] = 1.0
br[p <= lo] = -1.0
out["DONCHIAN20"] = br.replace(0.0, np.nan).ffill().fillna(0.0).shift(1)
# RSI ์ญ์ถ์ธ โ ๊ณผ๋งค๋์ ์ฌ๊ณ ๊ณผ๋งค์์ ํ๋ค
r = _rsi(p)
out["RSI14"] = ((50 - r) / 20).clip(-1, 1).shift(1)
# ๋ณผ๋ฆฐ์ ๋ณต๊ท โ ํ๊ท ์์ ๋ฉ์ด์ง ๋งํผ ๋ฐ๋๋ก
m, sd = p.rolling(20).mean(), p.rolling(20).std()
out["BBAND20"] = (-(p - m) / (2 * sd.replace(0, np.nan))).clip(-1, 1).shift(1)
# MACD (12,26,9) โ ์ค์ค๋ ์ดํฐ ์ค ๊ฐ์ฅ ๋๋ฆฌ ์ฐ์ด๋ ๊ฒ.
# ํ์คํ ๊ทธ๋จ ๋ถํธ๋ก ๋ฐฉํฅ์ ์ ํ๋ค.
e12 = p.ewm(span=12, adjust=False).mean()
e26 = p.ewm(span=26, adjust=False).mean()
macd = e12 - e26
out["MACD"] = np.sign(macd - macd.ewm(span=9, adjust=False).mean()).shift(1)
# ์ผ๋ชฉ๊ท ํํ ๊ตฌ๋ฆ โ ์ข
๊ฐ๋ง ์์ผ๋ฏ๋ก ๊ณ ์ ๋์ ์ข
๊ฐ์ ์ด๋ ์ต๊ณ ยท์ต์ ๋ฅผ ์ด๋ค.
# ์๋ณธ ์ ์์ ์์ ํ ๊ฐ์ง๋ ์์ง๋ง ๊ตฌ๋ฆ์ ์์น ํ์ ์ ๊ฐ์ ๋ป์ ์ ์งํ๋ค.
conv = (p.rolling(9).max() + p.rolling(9).min()) / 2
base = (p.rolling(26).max() + p.rolling(26).min()) / 2
spa = ((conv + base) / 2).shift(26)
spb = ((p.rolling(52).max() + p.rolling(52).min()) / 2).shift(26)
top, bot = pd.concat([spa, spb], axis=1).max(axis=1), pd.concat([spa, spb], axis=1).min(axis=1)
ich = pd.Series(0.0, index=p.index)
ich[p > top] = 1.0
ich[p < bot] = -1.0
out["ICHIMOKU"] = ich.shift(1)
# 12๊ฐ์ ์ ๋ ๋ชจ๋ฉํ
โ ์ง๋ 1๋
์์ต์ด ์์๋ฉด ๋ณด์ , ์๋๋ฉด ๋น์ด๋ค.
out["ABSMOM12M"] = (p / p.shift(252) - 1 > 0).astype(float).shift(1)
# ์คํ ์บ์คํฑ (14,3) โ ๊ณผ๋งค์ยท๊ณผ๋งค๋ ํ์ ์ ๊ณ ์
lo14, hi14 = p.rolling(14).min(), p.rolling(14).max()
k = 100 * (p - lo14) / (hi14 - lo14).replace(0, np.nan)
out["STOCH14"] = ((50 - k.rolling(3).mean()) / 25).clip(-1, 1).shift(1)
# ๋ณ๋์ฑ ํ๊ฒํ
โ ๋ฐฉํฅ์ ๋กฑ ๊ณ ์ , ํฌ๊ธฐ๋ง ์กฐ์
sig = np.log(p).diff().ewm(span=60).std()
tgt = sig.iloc[:max(len(sig) // 2, 60)].median()
out["VOLTARGET"] = (tgt / sig).clip(0, 1).shift(1)
return {k: v.fillna(0.0) for k, v in out.items()}
LABEL = {
"BUYHOLD": {"ko": "๋จ์ ๋ณด์ ", "en": "Buy and hold"},
"SMA200": {"ko": "200์ผ์ ์์์๋ง ๋ณด์ ",
"en": "Hold only above the 200-day MA"},
"GOLDEN50_200": {"ko": "๊ณจ๋ ํฌ๋ก์ค 50/200", "en": "Golden cross 50/200"},
"SMA20_50": {"ko": "์ด๋ํ๊ท 20/50 ๊ต์ฐจ", "en": "SMA 20/50 crossover"},
"MOM60": {"ko": "60์ผ ๋ชจ๋ฉํ
", "en": "60-day momentum"},
"DONCHIAN20": {"ko": "๋์น์ 20์ผ ๋ํ", "en": "Donchian 20 breakout"},
"RSI14": {"ko": "RSI 14 ์ญ์ถ์ธ", "en": "RSI 14 mean reversion"},
"BBAND20": {"ko": "๋ณผ๋ฆฐ์ 20 ๋ณต๊ท", "en": "Bollinger 20 reversion"},
"MACD": {"ko": "MACD 12/26/9", "en": "MACD 12/26/9"},
"ICHIMOKU": {"ko": "์ผ๋ชฉ๊ท ํํ ๊ตฌ๋ฆ", "en": "Ichimoku cloud"},
"ABSMOM12M": {"ko": "12๊ฐ์ ์ ๋ ๋ชจ๋ฉํ
", "en": "12-month absolute momentum"},
"STOCH14": {"ko": "์คํ ์บ์คํฑ 14/3", "en": "Stochastic 14/3"},
"VOLTARGET": {"ko": "๋ณ๋์ฑ ํ๊ฒํ
", "en": "Volatility targeting"},
}
ORDER = ["BUYHOLD", "SMA200", "GOLDEN50_200", "SMA20_50", "MACD", "ICHIMOKU",
"MOM60", "ABSMOM12M", "DONCHIAN20", "RSI14", "STOCH14", "BBAND20",
"VOLTARGET"]
def curves(px, asset, scoring, lang="ko", since=SINCE):
"""์ฌํด ์ฒซ ๊ฑฐ๋์ผ๋ถํฐ ์ง๊ธ๊น์ง. ์ฐธ๊ฐ์์ ๊ฐ์ ์์งยท๊ฐ์ ์์๋ฃ๋ก ๊ณ์ฐํ๋ค.
๐ ์งํ ๊ณ์ฐ์๋ ๊ทธ ์ด์ ๋ฐ์ดํฐ๋ ์ด๋ค โ 1์ 2์ผ์ 200์ผ์ ์ ์๋
๊ฐ์ด
์์ด์ผ ๋์จ๋ค. ์๋ผ๋ธ ๋ค์ ์งํ๋ฅผ ๋ง๋ค๋ฉด ์ฐ์ด ๊ตฌ๊ฐ์ด ํต์งธ๋ก ๋น๊ณ ,
๊ทธ๋ฌ๋ฉด ๊ท์น์ด ์์ํ์ง๋ ์์ ์ํ๋ก ๊ทธ๋ ค์ง๋ค.
"""
p = pd.Series(px, dtype=float).sort_index()
if len(p) < 300: # 252์ผ ๋ชจ๋ฉํ
+ ์ฌ์
return {"t": [], "series": []}
w = _weights(p) # ์ ๊ตฌ๊ฐ์ผ๋ก ์งํ๋ฅผ ๋ง๋ ๋ค์
idx = p.index[p.index >= pd.Timestamp(since)] # ํ์ ๊ตฌ๊ฐ๋ง ์๋ฅธ๋ค
if len(idx) < 5:
return {"t": [], "series": []}
seg = p.loc[idx]
out = []
for k in ORDER:
pos = {t: float(w[k].loc[t]) for t in idx}
eq, _ = scoring.equity(pos, seg, asset)
out.append({
"key": k,
"label": LABEL[k]["en" if lang == "en" else "ko"],
"v": [round(float(x - 1) * 100, 4) for x in eq],
"ret": round(float(eq.iloc[-1] - 1) * 100, 2),
"turnover": round(scoring.turnover(pos, seg), 1),
"fee": round(scoring.fee_paid(pos, seg, asset) * 100, 2),
})
out.sort(key=lambda x: -x["ret"])
return {"t": [t.isoformat() for t in idx], "series": out,
"since": str(idx[0].date()), "window": len(idx),
"market": [round(float(x), 4) for x in (seg / seg.iloc[0] - 1) * 100]}
|