Spaces:
Running
Running
| # -*- coding: utf-8 -*- | |
| """์์ธ ์์ง โ ์์ค ํ๋์ ๊ธฐ๋์ง ์๋๋ค. | |
| ๐ด ์์ธ ์ ๊ณต์๋ค์ ๋ฐ์ดํฐ์ผํฐ IP๋ฅผ ๋ง๋๋ค. ์์ธ๋ฅผ ๋์ง๋ ๊ฒ์ด ์๋๋ผ ๋น ์๋ต์ด๋ | |
| ๋ด ์ฐจ๋จ ํ์ด์ง๋ฅผ 200์ผ๋ก ๋๋ ค์ฃผ๋ฏ๋ก, ์์ธ ์ฒ๋ฆฌ๋ง์ผ๋ก๋ ์กํ์ง ์๋๋ค. | |
| ๊ทธ๋์ ์์ค๋ฅผ ํ๋ ๋์ง ์๊ณ ์ฌ๋ค๋ฆฌ๋ก ๋ด๋ ค๊ฐ๋ค. | |
| ์์ | |
| 1) Yahoo chart API ์ง์ ํธ์ถ โ yfinance ์ ์ฟ ํค/ํฌ๋ผ ์ ์ฐจ๋ฅผ ๊ฑด๋๋ด๋ค | |
| 2) Stooq CSV โ ๋ฏธ๊ตญ ์ฃผ์ยทETF. ํค๊ฐ ํ์ ์๋ค | |
| 3) CoinGecko โ ์ฝ์ธ ์ ์ฉ | |
| ๐ "์ฑ๊ณต"์ ํ์ ์ ์์ธ ์์์ด ์๋๋ผ **๋ง๋ ๊ฐ์**๋ค. | |
| 0๊ฑด์ ์ฑ๊ณต์ผ๋ก ์ธ๋ฉด ์ฌ๋ค๋ฆฌ๊ฐ ์ฒซ ์นธ์์ ๋ฉ์ถ๋ค. | |
| """ | |
| import io, time | |
| import pandas as pd | |
| UA = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " | |
| "(KHTML, like Gecko) Chrome/122.0 Safari/537.36"} | |
| MIN_BARS = 30 # ์ด๋ณด๋ค ์ ์ผ๋ฉด ์คํจ๋ก ๋ณธ๋ค | |
| # ์ข ๋ชฉ์ฝ๋ โ ์์ค๋ณ ์ฌ๋ณผ | |
| MAP = { | |
| "NVDA": {"feed": "NVDA", "yahoo": "NVDA", "yfin": "NVDA", "stooq": "nvda.us"}, | |
| "GOLD": {"feed": "GOLD", "yahoo": "GLD", "yfin": "GLD", "stooq": "gld.us"}, | |
| "OIL": {"feed": "OIL", "yahoo": "USO", "yfin": "USO", "stooq": "uso.us"}, | |
| "BTC": {"feed": "BTC", "yahoo": "BTC-USD", "yfin": "BTC-USD", | |
| "stooq": "btcusd", "gecko": "bitcoin"}, | |
| } | |
| def _yahoo(sym, days): | |
| import requests | |
| u = ("https://query1.finance.yahoo.com/v8/finance/chart/%s" | |
| "?range=%s&interval=1d" % (sym, "10y" if days > 1200 else "5y")) | |
| r = requests.get(u, headers=UA, timeout=25) | |
| if r.status_code != 200: | |
| raise ValueError("HTTP %d %s" % (r.status_code, r.text[:60])) | |
| j = r.json()["chart"]["result"][0] | |
| ts = j["timestamp"] | |
| cl = j["indicators"]["quote"][0]["close"] | |
| # ์์ ์ฃผ๊ฐ๊ฐ ์์ผ๋ฉด ๊ทธ์ชฝ์ ์ด๋ค. ๋ถํ ยท๋ฐฐ๋น์ด ์์ต๋ฅ ์ ์์ด๋ฉด ์ ๋๋ค. | |
| adj = j.get("indicators", {}).get("adjclose") | |
| if adj and adj[0].get("adjclose"): | |
| cl = adj[0]["adjclose"] | |
| s = pd.Series(cl, index=pd.to_datetime(ts, unit="s")).dropna() | |
| s.index = s.index.normalize().tz_localize(None) | |
| return s[~s.index.duplicated(keep="last")] | |
| def _yfin(sym, days): | |
| """yfinance ์์ฒด ๊ฒฝ๋ก. ์ฟ ํคยทํฌ๋ผ์ ์ป์ด ์ค๋ ์ ์ฐจ๊ฐ ๋ฐ๋ก ์์ด ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅด๋ค.""" | |
| import yfinance as yf | |
| d = yf.download(sym, period="5y", interval="1d", progress=False, | |
| auto_adjust=True, threads=False) | |
| if isinstance(d.columns, pd.MultiIndex): | |
| d.columns = d.columns.droplevel(1) | |
| s = d["Close"].dropna() | |
| s.index = pd.to_datetime(s.index).tz_localize(None) | |
| return s | |
| def _stooq(sym, days): | |
| import requests | |
| r = requests.get("https://stooq.com/q/d/l/?s=%s&i=d" % sym, | |
| headers=UA, timeout=25) | |
| if not r.text.lstrip().lower().startswith("date"): | |
| # ๋ด ์ฐจ๋จ ํ์ด์ง๊ฐ 200์ผ๋ก ์จ๋ค. 200์ ์ฑ๊ณต์ผ๋ก ์ธ๋ฉด ์ ๋๋ค. | |
| raise ValueError("stooq ์ฐจ๋จ(HTML ์๋ต)") | |
| d = pd.read_csv(io.StringIO(r.text)) | |
| if "Close" not in d.columns: | |
| raise ValueError("stooq ์๋ต์ Close ์์") | |
| s = pd.Series(d["Close"].values, | |
| index=pd.to_datetime(d["Date"])).dropna() | |
| return s | |
| def _gecko(cid, days): | |
| import requests | |
| r = requests.get("https://api.coingecko.com/api/v3/coins/%s/market_chart" | |
| "?vs_currency=usd&days=%d&interval=daily" % (cid, min(days, 3650)), | |
| headers=UA, timeout=25) | |
| j = r.json() | |
| if "prices" not in j: | |
| raise ValueError("coingecko: %s" % str(j)[:120]) | |
| p = j["prices"] | |
| s = pd.Series([v for _, v in p], | |
| index=pd.to_datetime([t for t, _ in p], unit="ms")) | |
| s.index = s.index.normalize().tz_localize(None) | |
| return s[~s.index.duplicated(keep="last")] | |
| def _feed_h(sym, bars): | |
| """์๊ฐ๋ด ํผ๋. ์ฑ์ ๊ฒฉ์๊ฐ ์ด๊ฑธ ์ด๋ค. | |
| ๐ ์ผ๋ด์ผ๋ก ์ฑ์ ํ๋ฉด ๊ฐ๋ง์ผ์ ํ๋ฃจ๋ฅผ ํต์งธ๋ก ๊ธฐ๋ค๋ ค์ผ ์ฒซ ์์ต๋ฅ ์ด ๋์จ๋ค. | |
| ๊ท์น์๋ '๋งค์๊ฐ ๊ฐฑ์ ๊ฐ๋ฅ'์ด๋ผ๊ณ ์จ ๋๊ณ ์ฑ์ ๋ง ์ผ ๋จ์์ด๋ฉด ์ด๊ธ๋๋ค. | |
| """ | |
| import os | |
| from huggingface_hub import hf_hub_download | |
| repo = os.environ.get("FINCHAL_FEED", "FINAL-Bench/finchal-prices") | |
| tok = os.environ.get("HF_TOKEN") or os.environ.get("FINCHAL_HF_TOKEN") | |
| f = hf_hub_download(repo, "prices_1h/%s.csv" % sym, repo_type="dataset", token=tok) | |
| d = pd.read_csv(f) | |
| s = pd.Series(d["close"].values, index=pd.to_datetime(d["ts"])).dropna() | |
| s = s[~s.index.duplicated(keep="last")].sort_index() | |
| return s.iloc[-bars:] | |
| def hourly(asset, bars=2000): | |
| """์๊ฐ๋ด. ํผ๋์๋ง ์๋ค. ์์ผ๋ฉด None ์ ๋๋ ค์ฃผ๊ณ ํธ์ถ๋ถ๊ฐ ์ผ๋ด์ผ๋ก ๋ด๋ ค๊ฐ๋ค.""" | |
| sym = MAP.get(asset, {}).get("feed") | |
| if not sym: | |
| return None | |
| try: | |
| s = _feed_h(sym, bars) | |
| return s if len(s) >= 24 else None | |
| except Exception: | |
| return None | |
| def _feed(sym, days): | |
| """์ฐ๋ฆฌ๊ฐ ๋ฐํํ ์์ธ ํผ๋. ๊ณต๊ฐ ์์ค๊ฐ ์ ๋ถ ๋งํ ์๋ฒ์์๋ ์ด๊ฒ ์ ์ผํ ๊ธธ์ด๋ค. | |
| ๐ด ์ค์ธก: HF Space์์ Yahoo 429 ยท Stooq ๋ด์ฐจ๋จ ยท CoinGecko ์ฐจ๋จ. | |
| ๋ฐ๊นฅ ์ธํฐ๋ท์ ๋๋๋ฐ ์์ธ ์ ๊ณต์๋ค์ด ๋ฐ์ดํฐ์ผํฐ IP๋ฅผ ๋ง๋๋ค. | |
| ๊ทธ๋์ ์์ธ๋ฅผ '๊ฐ์ ธ์ค๋' ๋์ '๋ฐ๋' ๊ตฌ์กฐ๋ก ๋ฐ๊พผ๋ค โ | |
| ๋คํธ์ํฌ๊ฐ ๋๋ ๊ณณ์์ publish_prices.py ๊ฐ ๋ฐ์ดํฐ์ ์ ์ฌ๋ฆฌ๊ณ , | |
| ์๋ฒ๋ ๊ทธ ๋ฐ์ดํฐ์ ์ ์ฝ๋๋ค. | |
| """ | |
| import os | |
| from huggingface_hub import hf_hub_download | |
| repo = os.environ.get("FINCHAL_FEED", "FINAL-Bench/finchal-prices") | |
| tok = os.environ.get("HF_TOKEN") or os.environ.get("FINCHAL_HF_TOKEN") | |
| f = hf_hub_download(repo, "prices/%s.csv" % sym, repo_type="dataset", token=tok) | |
| d = pd.read_csv(f) | |
| s = pd.Series(d["close"].values, index=pd.to_datetime(d["date"])).dropna() | |
| return s[~s.index.duplicated(keep="last")] | |
| # ํผ๋๋ฅผ ๋งจ ์์ ๋๋ค. ์๋ฒ์์ ์ ์ผํ๊ฒ ํตํ๋ ๊ฒฝ๋ก์ด๊ณ , | |
| # ๋ก์ปฌ์์๋ ํผ๋๊ฐ ์์ผ๋ฉด ๋ฐ๋ก ๋ค์ ์นธ์ผ๋ก ๋ด๋ ค๊ฐ๋ค. | |
| LADDER = [("feed", _feed), ("yahoo", _yahoo), ("yfin", _yfin), | |
| ("stooq", _stooq), ("gecko", _gecko)] | |
| def daily(asset, days=1800): | |
| """์ผ๋ด ์ข ๊ฐ. ์ด๋ ์์ค์์ ์๋์ง๋ ํจ๊ป ๋๋ ค์ค๋ค. | |
| ๋ฐํ: (์๋ฆฌ์ฆ, ์์ค๋ช , ์๋๊ธฐ๋ก) | |
| """ | |
| m = MAP.get(asset, {}) | |
| trail = [] | |
| for name, fn in LADDER: | |
| sym = m.get(name) | |
| if not sym: | |
| continue | |
| t0 = time.time() | |
| try: | |
| s = fn(sym, days) | |
| n = len(s) | |
| trail.append("%s:%d๋ด/%.1fs" % (name, n, time.time() - t0)) | |
| if n >= MIN_BARS: # 0๊ฑด์ ์ฑ๊ณต์ด ์๋๋ค | |
| return s.sort_index().iloc[-days:], name, trail | |
| except Exception as e: | |
| # ๐ด ์์ธ '์ข ๋ฅ'๋ง ๋จ๊ธฐ๋ฉด ์ ๋งํ๋์ง ๋ชจ๋ฅธ๋ค. ์ง๋จ์ด ์ ๋๋ ๋ก๊ทธ๋ | |
| # ์๋ ๋ก๊ทธ์ ๊ฐ๋ค. ๋ฉ์์ง๊น์ง ๋จ๊ธด๋ค. | |
| trail.append("%s:%s %s" % (name, type(e).__name__, str(e)[:90])) | |
| return None, None, trail | |
| if __name__ == "__main__": | |
| for a in MAP: | |
| s, src, tr = daily(a, 400) | |
| print("%-5s %-7s %s %s" % ( | |
| a, src or "์คํจ", ("%d๋ด ๋ง์ง๋ง %.2f" % (len(s), s.iloc[-1])) if s is not None else "-", | |
| " ยท ".join(tr))) | |