Spaces:
Running
Running
| # -*- coding: utf-8 -*- | |
| """κΈ°μ€λΆν¬ β 'μ€λ ₯ 0μ΄μλ€λ©΄ μ΄λκΉμ§ κ°μκΉ'λ₯Ό κ³μ°νλ€. | |
| λ κ°μ§λ₯Ό ꡬλΆνλ€. | |
| κ°λ§ μ μΆμ μΉ (announce) | |
| μ°Έκ°μμκ² "μ΄ μ’ λͺ©μ μ΄μΌλ‘ μ΄λ§νΌκΉμ§ λμ¨λ€"κ³ λ―Έλ¦¬ μλ €μ£Όλ κ°. | |
| κ³Όκ±° 5λ μ λΈλ‘ λΆνΈμ€νΈλ©ν΄μ λΈλ€. νλ©΄μ 'μ΄μ νκ³μ 'μ μ΄λ€. | |
| μ±μ μ© κΈ°μ€ (live) | |
| μ€μ μμλ₯Ό μ νλ κ°. μμ¦μ΄ μ§νλ λ§νΌμ **μ€μ κ²½λ‘** μμμ | |
| μ€λ ₯ 0 μ°Έκ°μλ₯Ό λλ € λ§λ λ€. λ§€μΌ λ€μ κ³μ°νλ€. | |
| μ λλλκ° | |
| κΈ°μ€μ κ³Όκ±° λΆνΈμ€νΈλ©μΌλ‘ κ³ μ νλ©΄, μμ¦ μ€ κ·Έ μ’ λͺ©μ΄ ν¬κ² μ€λ₯Ό λ μ°Έκ°μ | |
| μ μμ μ±μ μ΄ ν¨κ» λΆνλ €μ Έ μμκ° μμ₯ λ°©ν₯μ λ°λΌκ°λ€. μ¬λ°λ₯Έ λ°μ¬μ€μ | |
| 'μμ₯μ΄ μ€μ λ‘ κ·Έλ κ² μμ§μμ λ μ΄μΌλ‘ λΌ μ μμλ μ±μ 'μ΄λ€. | |
| π κ°λ§ μ μ κ³ μ ·곡κ°νλ κ²μ μ«μκ° μλλΌ **λ°©λ²**μ΄λ€. | |
| μ«μλ₯Ό κ³ μ νλ©΄ μμ₯μ΄ μμκ³Ό λ€λ₯΄κ² μμ§μμ λ μ±μ μ΄ ν΅μ§Έλ‘ μ΄κΈλλ€. | |
| π κΈ°μ€ μ°Έκ°μλ μ°Έκ°μμ λκ°μ΄ μμλ£λ₯Ό λΈλ€. μ 물리면 μ°Έκ°μλ§ λΉμ©μ | |
| μ§κ³ μμνλ€. μμλ£κ° λμ μ’ λͺ©μΌμλ‘ κ·Έ μ°¨μ΄κ° 컀μ§λ€. | |
| """ | |
| import os, json, warnings | |
| import numpy as np, pandas as pd | |
| warnings.filterwarnings("ignore") | |
| ROOT = os.path.dirname(os.path.abspath(__file__)) | |
| BLOCK = 5 | |
| N_LIVE = 4000 | |
| N_ANNOUNCE = 20000 | |
| def _sim(returns, n, expo, fee, seed, block=None): | |
| """μ€λ ₯ 0 μ°Έκ°μ nλͺ μ μ΅μ’ μμ΅λ₯ . expo λ§νΌμ μκ°μ ν¬μ§μ μ λ ΈμΆνλ€.""" | |
| g = np.random.default_rng(seed) | |
| r = np.asarray(returns, dtype=float) | |
| L = len(r) | |
| out = np.empty(n) | |
| for i in range(n): | |
| if block: # λΆνΈμ€νΈλ© (κ°λ§ μ μΆμ ) | |
| nb = int(np.ceil(L / block)) | |
| st = g.integers(0, max(len(r) - block, 1), size=nb) | |
| path = np.concatenate([r[j:j+block] for j in st])[:L] | |
| else: # μ€μ κ²½λ‘ (μ±μ ) | |
| path = r | |
| act = g.random(L) < expo | |
| pos = np.where(act, g.choice([-1.0, 1.0], size=L), 0.0) | |
| cost = np.abs(np.diff(np.r_[0.0, pos])) * fee | |
| out[i] = np.prod(1 + np.r_[0.0, pos[:-1]] * path - cost) - 1 | |
| return np.sort(out) | |
| def live(prices, fee, expo=2/3, n=N_LIVE, seed=None): | |
| """μ±μ μ© β μμ¦ κ²½κ³ΌλΆμ μ€μ κ²½λ‘ μμμ λ§λ λ€. λ§€μΌ λ€μ λΆλ₯Έλ€. | |
| seed λ₯Ό λ μ§λ‘ μ£Όλ©΄ κ°μ λ κ°μ κ°μ΄ λμ μ¬νμ΄ λλ€. | |
| """ | |
| r = pd.Series(prices, dtype=float).sort_index().pct_change().fillna(0.0).values | |
| s = seed if seed is not None else int(pd.Timestamp.utcnow().strftime("%Y%m%d")) | |
| return _sim(r, n, expo, fee, s) | |
| def announce(returns, fee, days, expo=2/3, n=N_ANNOUNCE, seed=20260824): | |
| """κ°λ§ μ μΆμ β κ³Όκ±°λ₯Ό λΈλ‘ λΆνΈμ€νΈλ©ν΄μ 'μ΄λ§νΌκΉμ§ λμ¨λ€'λ₯Ό λΈλ€.""" | |
| r = np.asarray(returns, dtype=float) | |
| # μμ¦ κΈΈμ΄λ§νΌλ§ μ°λλ‘ μλΌ λΆμΈλ€ | |
| reps = int(np.ceil(days / len(r))) + 1 | |
| pool = np.tile(r, reps)[:max(len(r), days * 3)] | |
| return _sim(pool[:days] if len(pool) >= days else pool, n, expo, fee, seed, block=BLOCK) | |
| def pct_of(ret, refv): | |
| import bisect | |
| return bisect.bisect_left(refv, ret) / len(refv) | |
| def score_of(ret, refv): | |
| import math | |
| p = min(pct_of(ret, refv), 1 - 1e-6) | |
| return -math.log10(max(1.0 - p, 1e-6)) | |
| # βββββββββββββββββββββββ κ°λ§ 곡μ§κ° μμ± βββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| import seasons as S | |
| import scoring as SC | |
| import yfinance as yf | |
| s1 = S.SEASONS[1] | |
| days = (pd.Timestamp(s1["closes"]) - pd.Timestamp(s1["opens"])).days | |
| print("μμ¦ 1 κ°λ§ 곡μ§κ° β %dμΌ Β· μμλ£ λ°μ\n" % days) | |
| print(" %-6s %-8s %8s %11s %11s %11s" | |
| % ("μ’ λͺ©", "λ°μ΄ν°", "μμλ£", "μ΄ μ€μ", "μ΄ μμ5%", "μ΄ μμ1%")) | |
| out = {} | |
| for code, a in s1["assets"].items(): | |
| src = a["src"] | |
| if code == "BTC": | |
| p = os.path.join(os.path.dirname(ROOT), "btc-oracle", "data", "hourly.parquet") | |
| H = pd.read_parquet(p).sort_index() | |
| if "close" not in H.columns: | |
| H = H.rename(columns={"c": "close"}) | |
| px = H.close.groupby(H.index.normalize()).last() | |
| else: | |
| d = yf.download(src, period="5y", interval="1d", | |
| progress=False, auto_adjust=True)["Close"] | |
| px = (d.iloc[:, 0] if isinstance(d, pd.DataFrame) else d).dropna() | |
| r = px.pct_change().dropna().values | |
| fee = SC.fee_of(code) | |
| c = announce(r, fee, days) | |
| out[code] = {"p50": float(np.median(c)), "p95": float(np.percentile(c, 95)), | |
| "p99": float(np.percentile(c, 99)), "fee": fee, "days": days} | |
| print(" %-6s %-8s %7.2f%% %10.1f%% %10.1f%% %10.1f%%" | |
| % (code, src, fee * 100, np.median(c) * 100, | |
| np.percentile(c, 95) * 100, np.percentile(c, 99) * 100)) | |
| json.dump({"season": 1, "opens": s1["opens"], "closes": s1["closes"], "days": days, | |
| "method": "λΈλ‘λΆνΈμ€νΈλ© 5μΌ Β· 무μμ ν¬μ§μ Β· λ λ²λ¦¬μ§1 Β· μμλ£ λ°μ", | |
| "note": "κ°λ§ 곡μ§μ© μΆμ μΉ. μ€μ μ±μ μ μμ¦ μ€μ κ²½λ‘λ‘ λ§€μΌ μ¬κ³μ°νλ€.", | |
| "ceiling": out}, | |
| open(os.path.join(ROOT, "announce_ceiling.json"), "w"), | |
| ensure_ascii=False, indent=1) | |
| print("\n νλ©΄μ 'μ΄μ νκ³μ 'μλ μμ5%λ₯Ό μ΄λ€.") | |
| print(" μ μ₯: finchal/announce_ceiling.json") | |