{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a1fa4200",
   "metadata": {
    "papermill": {
     "duration": 0.00138,
     "end_time": "2026-10-29T21:45:28.844224+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:28.842844+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# NASDAQ100 52주 신고가 종목 (업종별)\n",
    "한경 페이지에서 NASDAQ100 구성종목을 동적 수집 → 52주 신고가 종목을 찾아 **업종별로 분류**한다.\n",
    "\n",
    "- 원본 Colab 스크립트를 로컬 이식하며 **일괄(batch) 다운로드**로 최적화 (수백 번 개별 호출 → 1~2회)\n",
    "- 번역(googletrans)은 선택: 미설치 시 자동으로 영문 사업요약 사용\n",
    "- 소스: https://www.hankyung.com/globalmarket/usa-stock-nasdaq100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "7f30f5b5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:28.846279Z",
     "iopub.status.busy": "2026-10-29T21:45:28.846183Z",
     "iopub.status.idle": "2026-10-29T21:45:28.850567Z",
     "shell.execute_reply": "2026-10-29T21:45:28.850214Z"
    },
    "papermill": {
     "duration": 0.00605,
     "end_time": "2026-10-29T21:45:28.850989+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:28.844939+00:00",
     "status": "completed"
    },
    "tags": [
     "parameters"
    ]
   },
   "outputs": [],
   "source": [
    "# ── 파라미터 ──\n",
    "do_translate = False   # True로 하면 googletrans로 사업요약 한글 번역 시도 (미설치 시 자동 skip)\n",
    "max_tickers = 0        # 0 = 전체, 그 외 숫자면 후보 상위 N개만 (속도 제한용)\n",
    "high_tolerance = 0.005 # 현재가가 52주 최고가의 (1-이값) 이상이면 '신고가권'으로 인정\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9226ca4f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:28.852787Z",
     "iopub.status.busy": "2026-10-29T21:45:28.852732Z",
     "iopub.status.idle": "2026-10-29T21:45:29.273983Z",
     "shell.execute_reply": "2026-10-29T21:45:29.273561Z"
    },
    "papermill": {
     "duration": 0.42275,
     "end_time": "2026-10-29T21:45:29.274495+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:28.851745+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import yfinance as yf\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import re, time, logging, requests\n",
    "from bs4 import BeautifulSoup\n",
    "from IPython.display import display\n",
    "logging.getLogger('yfinance').setLevel(logging.CRITICAL)\n",
    "logging.getLogger('urllib3').setLevel(logging.CRITICAL)\n",
    "\n",
    "# googletrans 는 선택 의존성 — 없으면 번역 없이 진행\n",
    "translator = None\n",
    "if do_translate:\n",
    "    try:\n",
    "        from googletrans import Translator\n",
    "        translator = Translator()\n",
    "    except Exception as e:\n",
    "        print(f'googletrans 사용 불가 ({e}) → 영문 요약 사용')\n",
    "        translator = None\n",
    "\n",
    "HK_URL = 'https://www.hankyung.com/globalmarket/usa-stock-nasdaq100'\n",
    "HEADERS = {'User-Agent': 'Mozilla/5.0 (compatible; LocalScraper/1.0)'}\n",
    "BLACKLIST = {'USA','US','USD','S','P','SP','SNP','CPI','PPI','EPS','PE','YOY','QOQ',\n",
    "             'ETF','AI','EV','GDP','FOMC','FED','ECB','BOJ','SEC','IRS','NY','EU',\n",
    "             'BOOK','POLL','MAP','TESAT','AT','DR','WR','SBA','CH','JM','AO','JB','BF','RSS'}\n",
    "\n",
    "def clean_to_yahoo_symbol(sym):\n",
    "    s = sym.strip().upper().replace('.', '-')\n",
    "    return {'BRK.B':'BRK-B','BF.B':'BF-B'}.get(sym.upper(), s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "14aea3e2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:29.276548Z",
     "iopub.status.busy": "2026-10-29T21:45:29.276417Z",
     "iopub.status.idle": "2026-10-29T21:45:29.546662Z",
     "shell.execute_reply": "2026-10-29T21:45:29.546286Z"
    },
    "papermill": {
     "duration": 0.271818,
     "end_time": "2026-10-29T21:45:29.547096+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:29.275278+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "한경 NASDAQ100 페이지에서 티커 수집 중...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "후보 티커 수: 105 (예시: ['ADR', 'TV', 'NVDA', 'AAPL', 'MSFT', 'A', 'GOOGL', 'C', 'GOOG', 'AMZN', 'SPCX', 'AVGO'])\n"
     ]
    }
   ],
   "source": [
    "def scrape_candidates(url):\n",
    "    html = requests.get(url, headers=HEADERS, timeout=30).text\n",
    "    soup = BeautifulSoup(html, 'lxml')\n",
    "    cands = []\n",
    "    has_hangul = re.compile(r'[가-힣]')\n",
    "    pat_paren = re.compile(r'\\(([A-Z]{1,6}(?:-[A-Z]{1,2})?)\\)')\n",
    "    for node in soup.find_all(string=True):\n",
    "        txt = str(node)\n",
    "        if has_hangul.search(txt):\n",
    "            for m in pat_paren.finditer(txt):\n",
    "                cands.append(m.group(1))\n",
    "    ticker_pat = re.compile(r'\\b[A-Z]{1,5}(?:-[A-Z]{1,2})?\\b')\n",
    "    for a in soup.find_all('a'):\n",
    "        text = a.get_text(' ', strip=True)\n",
    "        if text and len(text) <= 300 and has_hangul.search(text):\n",
    "            for c in ticker_pat.findall(text):\n",
    "                if c not in BLACKLIST:\n",
    "                    cands.append(c)\n",
    "    cleaned = []\n",
    "    for t in (clean_to_yahoo_symbol(x) for x in cands):\n",
    "        if 1 <= len(t) <= 6 and re.fullmatch(r'[A-Z\\-]+', t) and t not in cleaned:\n",
    "            cleaned.append(t)\n",
    "    return cleaned\n",
    "\n",
    "try:\n",
    "    print('한경 NASDAQ100 페이지에서 티커 수집 중...')\n",
    "    candidates = scrape_candidates(HK_URL)\n",
    "except Exception as e:\n",
    "    print(f'스크래핑 실패: {e}')\n",
    "    candidates = []\n",
    "if max_tickers and max_tickers > 0:\n",
    "    candidates = candidates[:max_tickers]\n",
    "print(f'후보 티커 수: {len(candidates)} (예시: {candidates[:12]})')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c92b1040",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:29.549066Z",
     "iopub.status.busy": "2026-10-29T21:45:29.549009Z",
     "iopub.status.idle": "2026-10-29T21:45:35.113018Z",
     "shell.execute_reply": "2026-10-29T21:45:35.065219Z"
    },
    "papermill": {
     "duration": 5.569148,
     "end_time": "2026-10-29T21:45:35.117121+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:29.547973+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "52주 신고가권 종목: 6개 → ['A', 'AAPL', 'AMGN', 'FAST', 'GILD', 'KDP']\n"
     ]
    }
   ],
   "source": [
    "# ── 일괄 다운로드 후 52주 신고가 벡터 판정 (개별 호출 최소화) ──\n",
    "high_tickers = []\n",
    "if candidates:\n",
    "    raw = yf.download(candidates, period='1y', interval='1d',\n",
    "                      auto_adjust=False, progress=False)\n",
    "    close = raw['Close'] if isinstance(raw.columns, pd.MultiIndex) else raw[['Close']]\n",
    "    close = close.dropna(how='all')\n",
    "    for t in close.columns:\n",
    "        ser = close[t].dropna()\n",
    "        if ser.size < 20:\n",
    "            continue\n",
    "        if ser.iloc[-1] >= ser.max() * (1 - high_tolerance):\n",
    "            high_tickers.append(t)\n",
    "print(f'52주 신고가권 종목: {len(high_tickers)}개 → {high_tickers}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e53ea2d7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:35.124189Z",
     "iopub.status.busy": "2026-10-29T21:45:35.123997Z",
     "iopub.status.idle": "2026-10-29T21:45:41.052439Z",
     "shell.execute_reply": "2026-10-29T21:45:41.050418Z"
    },
    "papermill": {
     "duration": 5.933684,
     "end_time": "2026-10-29T21:45:41.054428+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:35.120744+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def get_sector_and_profile(ticker):\n",
    "    sector, summary = 'Unknown', ''\n",
    "    try:\n",
    "        info = yf.Ticker(ticker).info\n",
    "        sector = info.get('sector', 'Unknown')\n",
    "        summary = info.get('longBusinessSummary', '') or ''\n",
    "    except Exception:\n",
    "        pass\n",
    "    if summary and translator is not None:\n",
    "        try:\n",
    "            summary = translator.translate(summary, src='en', dest='ko').text\n",
    "        except Exception:\n",
    "            pass\n",
    "    return sector, summary\n",
    "\n",
    "rows = []\n",
    "for t in high_tickers:\n",
    "    sector, summary = get_sector_and_profile(t)\n",
    "    rows.append({'Ticker': t, 'Sector': sector, 'Business Profile': summary})\n",
    "    time.sleep(0.3)\n",
    "high_df = pd.DataFrame(rows)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1c552e9b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:41.059391Z",
     "iopub.status.busy": "2026-10-29T21:45:41.059241Z",
     "iopub.status.idle": "2026-10-29T21:45:41.082802Z",
     "shell.execute_reply": "2026-10-29T21:45:41.081846Z"
    },
    "papermill": {
     "duration": 0.026626,
     "end_time": "2026-10-29T21:45:41.083562+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:41.056936+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div class=\"high-report\"><div class=\"high-report__summary\"><div class=\"high-report__metric\"><span>NASDAQ100 52주 신고가권</span><strong>6</strong><small>종목</small></div><div class=\"high-report__metric\"><span>분포 업종</span><strong>4</strong><small>개</small></div><div class=\"high-report__metric\"><span>최다 업종</span><strong>Healthcare</strong><small>3종목</small></div></div><section><h2 class=\"high-report__section-title\">업종별 종목수</h2><div class=\"high-report__counts\"><div class=\"high-report__count-card\"><span>Healthcare</span><b>3</b></div><div class=\"high-report__count-card\"><span>Consumer Defensive</span><b>1</b></div><div class=\"high-report__count-card\"><span>Industrials</span><b>1</b></div><div class=\"high-report__count-card\"><span>Technology</span><b>1</b></div></div></section><section><h2 class=\"high-report__section-title\">업종별 상세</h2><section class=\"high-report__sector\"><div class=\"high-report__sector-head\"><h3>Healthcare</h3><span class=\"high-report__badge\">3종목</span></div><div class=\"high-report__items\"><div class=\"high-report__item\"><div class=\"high-report__ticker\">A</div><div class=\"high-report__profile\">Agilent Technologies, Inc. provides application focused solutions to the life sciences, diagnostics, and applied chemical markets worldwide. The company operates through three segments: Life Sciences and Diagnostics Markets, Agilent CrossLab, and Applied Markets. The Life Sciences and Diagnostics Markets segment offers liquid chromatography systems and components; and liquid chromatography mass spectrometry systems....</div></div><div class=\"high-report__item\"><div class=\"high-report__ticker\">AMGN</div><div class=\"high-report__profile\">Amgen Inc. discovers, develops, manufactures, and delivers human therapeutics worldwide. The company&#x27;s principal products include Enbrel for the treatment of rheumatoid arthritis, plaque psoriasis, and psoriatic arthritis; Otezla for the treatment of adult patients with plaque psoriasis, psoriatic arthritis, and oral ulcers associated with Behçet&#x27;s disease; Prolia to treat postmenopausal women with osteoporosis; XGEV...</div></div><div class=\"high-report__item\"><div class=\"high-report__ticker\">GILD</div><div class=\"high-report__profile\">Gilead Sciences, Inc., a biopharmaceutical company, discovers, develops, and commercializes medicines in the areas of unmet medical need in the United States, Europe, and internationally. The company provides Biktarvy, Descovy, Genvoya, Odefsey, Sunlenca, Symtuza, and YeztugoFor the treatment of HIV-1 infection in patients. It also provides Epclusa, Livdelzi, and Vemlidy to treat chronic hepatitis C virus, primary bi...</div></div></div></section><section class=\"high-report__sector\"><div class=\"high-report__sector-head\"><h3>Consumer Defensive</h3><span class=\"high-report__badge\">1종목</span></div><div class=\"high-report__items\"><div class=\"high-report__item\"><div class=\"high-report__ticker\">KDP</div><div class=\"high-report__profile\">Keurig Dr Pepper Inc. owns, manufactures, and distributors beverages and single serve brewing systems in the United States and internationally. The company operates through three segments: U.S. Refreshment Beverages, U.S. Coffee, and International. It manufactures and distributes branded concentrates, syrup, and finished beverages, as well as sales of owned brands and third-party brands; tea, cocoa, and other product...</div></div></div></section><section class=\"high-report__sector\"><div class=\"high-report__sector-head\"><h3>Industrials</h3><span class=\"high-report__badge\">1종목</span></div><div class=\"high-report__items\"><div class=\"high-report__item\"><div class=\"high-report__ticker\">FAST</div><div class=\"high-report__profile\">Fastenal Company, together with its subsidiaries, engages in the wholesale distribution of industrial and construction supplies in the United States, Canada, Mexico, and internationally. It offers fasteners, and related industrial and construction supplies under the Fastenal name. The company&#x27;s fastener products include threaded fasteners, bolts, nuts, screws, studs, and related washers that are used in manufactured...</div></div></div></section><section class=\"high-report__sector\"><div class=\"high-report__sector-head\"><h3>Technology</h3><span class=\"high-report__badge\">1종목</span></div><div class=\"high-report__items\"><div class=\"high-report__item\"><div class=\"high-report__ticker\">AAPL</div><div class=\"high-report__profile\">Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. The company offers iPhone, a line of smartphones; Mac, a line of personal computers; iPad, a line of multi-purpose tablets; and wearables, home, and accessories comprising AirPods, Apple Vision Pro, Apple TV, Apple Watch, Beats products, and HomePod, as well as Apple branded and third-party ac...</div></div></div></section></section></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import HTML, display\n",
    "from html import escape\n",
    "\n",
    "\n",
    "def _short_profile(text, limit=420):\n",
    "    text = \" \".join(str(text or \"\").split())\n",
    "    if len(text) <= limit:\n",
    "        return text\n",
    "    return text[:limit].rstrip() + \"...\"\n",
    "\n",
    "\n",
    "def _render_high_report(high_df, market_name):\n",
    "    if high_df.empty:\n",
    "        return f\"\"\"\n",
    "        <div class=\"high-report high-report--empty\">\n",
    "            <div class=\"high-report__metric\">\n",
    "                <span>{escape(market_name)} 52주 신고가권</span>\n",
    "                <strong>0</strong><small>종목</small>\n",
    "            </div>\n",
    "        </div>\n",
    "        \"\"\"\n",
    "\n",
    "    work = high_df.copy()\n",
    "    work[\"Sector\"] = work[\"Sector\"].fillna(\"Unknown\").astype(str)\n",
    "    work[\"Ticker\"] = work[\"Ticker\"].fillna(\"\").astype(str)\n",
    "    work[\"Business Profile\"] = work[\"Business Profile\"].fillna(\"\").astype(str)\n",
    "    counts = work.groupby(\"Sector\").size().sort_values(ascending=False)\n",
    "    ordered_sectors = counts.index.tolist()\n",
    "\n",
    "    count_cards = []\n",
    "    for sector, count in counts.items():\n",
    "        count_cards.append(\n",
    "            f'<div class=\"high-report__count-card\"><span>{escape(sector)}</span><b>{int(count)}</b></div>'\n",
    "        )\n",
    "\n",
    "    sector_sections = []\n",
    "    for sector in ordered_sectors:\n",
    "        group = work[work[\"Sector\"] == sector].sort_values(\"Ticker\")\n",
    "        items = []\n",
    "        for _, row in group.iterrows():\n",
    "            profile = _short_profile(row.get(\"Business Profile\", \"\"))\n",
    "            if not profile:\n",
    "                profile = \"사업 내용 설명이 없습니다.\"\n",
    "            items.append(\n",
    "                '<div class=\"high-report__item\">'\n",
    "                f'<div class=\"high-report__ticker\">{escape(row[\"Ticker\"])}</div>'\n",
    "                f'<div class=\"high-report__profile\">{escape(profile)}</div>'\n",
    "                '</div>'\n",
    "            )\n",
    "        sector_sections.append(\n",
    "            '<section class=\"high-report__sector\">'\n",
    "            '<div class=\"high-report__sector-head\">'\n",
    "            f'<h3>{escape(sector)}</h3>'\n",
    "            f'<span class=\"high-report__badge\">{len(group)}종목</span>'\n",
    "            '</div>'\n",
    "            f'<div class=\"high-report__items\">{\"\".join(items)}</div>'\n",
    "            '</section>'\n",
    "        )\n",
    "\n",
    "    return (\n",
    "        '<div class=\"high-report\">'\n",
    "        '<div class=\"high-report__summary\">'\n",
    "        f'<div class=\"high-report__metric\"><span>{escape(market_name)} 52주 신고가권</span><strong>{len(work)}</strong><small>종목</small></div>'\n",
    "        f'<div class=\"high-report__metric\"><span>분포 업종</span><strong>{len(counts)}</strong><small>개</small></div>'\n",
    "        f'<div class=\"high-report__metric\"><span>최다 업종</span><strong>{escape(str(counts.index[0]))}</strong><small>{int(counts.iloc[0])}종목</small></div>'\n",
    "        '</div>'\n",
    "        '<section><h2 class=\"high-report__section-title\">업종별 종목수</h2>'\n",
    "        f'<div class=\"high-report__counts\">{\"\".join(count_cards)}</div></section>'\n",
    "        '<section><h2 class=\"high-report__section-title\">업종별 상세</h2>'\n",
    "        f'{\"\".join(sector_sections)}</section>'\n",
    "        '</div>'\n",
    "    )\n",
    "\n",
    "\n",
    "display(HTML(_render_high_report(high_df, \"NASDAQ100\")))\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.13"
  },
  "papermill": {
   "default_parameters": {},
   "duration": 13.268428,
   "end_time": "2026-10-29T21:45:41.402652+00:00",
   "environment_variables": {},
   "exception": null,
   "input_path": "/Users/seog-yejun/룬샤시_공시정리/태버지/11. NASDAQ100 52주 신고가 업종별.ipynb",
   "output_path": "/Users/seog-yejun/룬샤시_공시정리/태버지/11. NASDAQ100 52주 신고가 업종별.ipynb",
   "parameters": {},
   "start_time": "2026-10-29T21:45:28.134224+00:00",
   "version": "2.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}