ostock-backend / scripts /build_cache.py
johnaness's picture
Add yfinance 401 fallback cache + /cache_info
7dfd114
"""yfinance fallback ์บ์‹œ ๋นŒ๋”.
๋กœ์ปฌ(yfinance ๊ฐ€ ์ •์ƒ ๋™์ž‘ํ•˜๋Š” ํ™˜๊ฒฝ)์—์„œ ์‹คํ–‰ํ•ด ๊ฐ ๋ผ์šฐํ„ฐ ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ณ ,
๊ทธ ๊ฒฐ๊ณผ JSON ์„ backend/cache/ ์— ์ €์žฅํ•œ๋‹ค. Hugging Face Spaces ์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ์„ผํ„ฐ
IP ์—์„œ yfinance ๊ฐ€ 401 ๋กœ ์ฐจ๋‹จ๋˜๋Š” ํ™˜๊ฒฝ์—์„œ ์ด ์บ์‹œ๊ฐ€ fallback ์œผ๋กœ ์ œ๊ณต๋œ๋‹ค.
์‹คํ–‰:
cd D:/OStock/backend
.venv/Scripts/python.exe scripts/build_cache.py
์ฃผ์˜: news/prediction ๋ผ์šฐํ„ฐ๋Š” transformers->torch ๋ฅผ ๋Œ์–ด์™€ import ์ˆœ์„œ ์ด์Šˆ๊ฐ€
์žˆ์œผ๋ฏ€๋กœ, ์—ฌ๊ธฐ์„œ๋Š” yfinance ๊ธฐ๋ฐ˜ ๋ผ์šฐํ„ฐ(market/quotes/financial/technical)๋งŒ import ํ•œ๋‹ค.
"""
import os
import sys
import json
import datetime
# scripts/ ์—์„œ ์‹คํ–‰ํ•ด๋„ backend/ ๋ชจ๋“ˆ์„ import ํ•  ์ˆ˜ ์žˆ๋„๋ก backend ๊ฒฝ๋กœ ์ถ”๊ฐ€
BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, BACKEND_DIR)
from config import SECTOR_TICKERS # noqa: E402
from routers import market, quotes, financial, technical # noqa: E402
CACHE_DIR = os.path.join(BACKEND_DIR, "cache")
def _save(relpath, data):
path = os.path.join(CACHE_DIR, relpath)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def _is_error(result):
"""์—”๋“œํฌ์ธํŠธ ๊ฒฐ๊ณผ๊ฐ€ ์—๋Ÿฌ/๋นˆ๊ฐ’์ด๋ฉด True (์ €์žฅ ์Šคํ‚ต + ์‹คํŒจ ์นด์šดํŠธ์šฉ)."""
if result is None:
return True
if isinstance(result, dict):
if "error" in result:
return True
vals = list(result.values())
# sectors/bond ์ฒ˜๋Ÿผ ๋ชจ๋“  ํ•˜์œ„ ํ•ญ๋ชฉ์ด ์—๋Ÿฌ dict ์ธ ๊ฒฝ์šฐ
if vals and all(isinstance(v, dict) and "error" in v for v in vals):
return True
return False
if isinstance(result, list):
return len(result) == 0
return False
def main():
os.makedirs(CACHE_DIR, exist_ok=True)
stats = {"ok": 0, "fail": 0, "failed": []}
def run(label, relpath, fn):
try:
result = fn()
except Exception as e: # noqa: BLE001
result = {"error": str(e)}
if _is_error(result):
stats["fail"] += 1
stats["failed"].append(label)
print(f" [FAIL] {label}")
else:
_save(relpath, result)
stats["ok"] += 1
print(f" [ OK ] {label}")
print("== ๋ฌดํŒŒ๋ผ๋ฏธํ„ฐ ์—”๋“œํฌ์ธํŠธ ==")
run("indices", "indices.json", market.get_market_indices)
run("sectors", "sectors.json", market.get_sector_data)
run("get_bond_data", "get_bond_data.json", market.get_bond_data)
print(f"== ์ข…๋ชฉ๋ณ„ ์—”๋“œํฌ์ธํŠธ ({len(SECTOR_TICKERS)} ์ข…๋ชฉ x 4) ==")
for _name, tk in SECTOR_TICKERS.items():
run(f"stocks/{tk}", f"stocks/{tk}.json",
lambda tk=tk: quotes.get_stock_data(ticker=tk))
# period/interval ์„ ๋ช…์‹œ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค. ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋ฉด FastAPI ๊ฐ€
# ์ฃผ์ž…ํ•˜์ง€ ์•Š์•„ ๊ธฐ๋ณธ๊ฐ’์ด Query(...) ๊ฐ์ฒด๋กœ ๋‚จ์•„ .lower() ์—์„œ ๊นจ์ง„๋‹ค.
run(f"stock_chart/{tk}", f"stock_chart/{tk}.json",
lambda tk=tk: quotes.get_stock_chart(ticker=tk, period="1mo", interval="1d"))
run(f"financial_info/{tk}", f"financial_info/{tk}.json",
lambda tk=tk: financial.get_financial_info(ticker=tk))
run(f"technical_info/{tk}", f"technical_info/{tk}.json",
lambda tk=tk: technical.get_technical_info(ticker=tk))
meta = {
"generated_at": datetime.datetime.now().astimezone().isoformat(timespec="seconds"),
"endpoints": [
"indices", "sectors", "get_bond_data",
"stocks", "stock_chart", "financial_info", "technical_info",
],
"ticker_count": len(SECTOR_TICKERS),
"success": stats["ok"],
"failed": stats["fail"],
}
with open(os.path.join(CACHE_DIR, ".meta.json"), "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
print(f"\n์™„๋ฃŒ: ์„ฑ๊ณต {stats['ok']} / ์‹คํŒจ {stats['fail']}")
if stats["failed"]:
print("์‹คํŒจ ํ•ญ๋ชฉ:", ", ".join(stats["failed"]))
print("์ƒ์„ฑ ์‹œ๊ฐ:", meta["generated_at"])
if __name__ == "__main__":
main()