{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "82376284",
   "metadata": {
    "papermill": {
     "duration": 0.003319,
     "end_time": "2026-10-29T21:45:43.729540+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:43.726221+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# US: S&P 500 RS 상대강도 + 모멘텀"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "490be8b7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:43.735473Z",
     "iopub.status.busy": "2026-10-29T21:45:43.735282Z",
     "iopub.status.idle": "2026-10-29T21:45:46.047975Z",
     "shell.execute_reply": "2026-10-29T21:45:46.047238Z"
    },
    "papermill": {
     "duration": 2.316741,
     "end_time": "2026-10-29T21:45:46.049346+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:43.732605+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "taebuji_common 로드 완료\n"
     ]
    }
   ],
   "source": [
    "# [US] 라이브러리 임포트\n",
    "import yfinance as yf\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import requests\n",
    "from bs4 import BeautifulSoup\n",
    "from IPython.display import display\n",
    "\n",
    "# ★ taebuji_common: yfinance rate limit 우회 (IP rotation + Yahoo Chart API)\n",
    "import sys, os\n",
    "_TB = os.path.dirname(os.path.abspath(\"__file__\")) if \"__file__\" in dir() else \"/Users/loonshots/룬샤시_공시정리/태버지\"\n",
    "if _TB not in sys.path:\n",
    "    sys.path.insert(0, _TB)\n",
    "from taebuji_common import yc_close_wide, yc_ohlcv_wide, tz_naive, SessionManager\n",
    "\n",
    "print(\"taebuji_common 로드 완료\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "d2dfd2e4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:46.059852Z",
     "iopub.status.busy": "2026-10-29T21:45:46.058372Z",
     "iopub.status.idle": "2026-10-29T21:45:46.915351Z",
     "shell.execute_reply": "2026-10-29T21:45:46.913424Z"
    },
    "papermill": {
     "duration": 0.863035,
     "end_time": "2026-10-29T21:45:46.916242+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:46.053207+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "S&P 500 종목 수: 503\n"
     ]
    }
   ],
   "source": [
    "# [US] S&P 500 유니버스\n",
    "from io import StringIO\n",
    "\n",
    "def get_sp500_tickers():\n",
    "    url = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'\n",
    "    resp = requests.get(url, headers={\"User-Agent\": \"Mozilla/5.0\"})\n",
    "    resp.raise_for_status()\n",
    "    tables = pd.read_html(StringIO(resp.text))\n",
    "    for table in tables:\n",
    "        if \"Symbol\" in table.columns:\n",
    "            return [ticker.replace('.', '-') for ticker in table[\"Symbol\"].tolist()]\n",
    "    raise ValueError(\"S&P 500 테이블에서 'Symbol' 열을 찾을 수 없습니다.\")\n",
    "\n",
    "universe = get_sp500_tickers()\n",
    "print(f\"S&P 500 종목 수: {len(universe)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "efc1c93d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:46.921525Z",
     "iopub.status.busy": "2026-10-29T21:45:46.921416Z",
     "iopub.status.idle": "2026-10-29T21:45:48.011396Z",
     "shell.execute_reply": "2026-10-29T21:45:48.010642Z"
    },
    "papermill": {
     "duration": 1.093414,
     "end_time": "2026-10-29T21:45:48.012360+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:46.918946+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "[*********************100%***********************]  1 of 1 completed"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SPY 데이터: 2023-10-30 ~ 2026-10-29\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# [US] 벤치마크 다운로드 (SPY) - RS용 3년\n",
    "spy_rs = yf.download(\"SPY\", period=\"3y\", auto_adjust=False)[\"Close\"]\n",
    "spy_rs = spy_rs.dropna()\n",
    "spy_rs = tz_naive(spy_rs)  # ★ tz-naive normalize for alignment\n",
    "print(f\"SPY 데이터: {spy_rs.index[0].date()} ~ {spy_rs.index[-1].date()}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "056eadb5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:48.023293Z",
     "iopub.status.busy": "2026-10-29T21:45:48.023092Z",
     "iopub.status.idle": "2026-10-29T21:45:48.028695Z",
     "shell.execute_reply": "2026-10-29T21:45:48.027814Z"
    },
    "papermill": {
     "duration": 0.012815,
     "end_time": "2026-10-29T21:45:48.029773+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:48.016958+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# [US] RS 계산 함수 (다중 윈도우)\n",
    "def compute_mansfield_rs(price_series, benchmark_series, ma_period):\n",
    "    relative = price_series / benchmark_series\n",
    "    ma = relative.rolling(window=ma_period, min_periods=ma_period).mean()\n",
    "    return ((relative / ma) - 1) * 100\n",
    "\n",
    "def normalize_to_100_scale(x, scale=12):\n",
    "    return 100 * (1 / (1 + np.exp(-x / scale)))\n",
    "\n",
    "RS_WINDOWS = [60, 120, 250]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "18e4aef4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:45:48.036878Z",
     "iopub.status.busy": "2026-10-29T21:45:48.036712Z",
     "iopub.status.idle": "2026-10-29T21:46:03.937306Z",
     "shell.execute_reply": "2026-10-29T21:46:03.936887Z"
    },
    "papermill": {
     "duration": 15.905281,
     "end_time": "2026-10-29T21:46:03.937929+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:45:48.032648+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "S&P 500 503종목 Yahoo Chart API 병렬 다운로드 중...\n",
      "  → 기본 requests.Session (IP rotation 비활성)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "다운로드 완료: shape=(753, 495), 성공=495/503\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "RS 계산 완료: 495개 종목\n"
     ]
    }
   ],
   "source": [
    "# [US] RS 계산 실행 (60/120/250일 + 평균)  ★ IP rotation + Yahoo Chart API 직접 호출\n",
    "mansfield_rows = []\n",
    "all_rows_for_fallback = []\n",
    "\n",
    "print(f\"S&P 500 {len(universe)}종목 Yahoo Chart API 병렬 다운로드 중...\")\n",
    "all_us_close = yc_close_wide(universe, range_str=\"3y\", interval=\"1d\", min_coverage=1.0, ffill=False)\n",
    "print(f\"다운로드 완료: shape={all_us_close.shape}, 성공={len(all_us_close.columns)}/{len(universe)}\")\n",
    "\n",
    "for ticker in universe:\n",
    "    try:\n",
    "        if ticker not in all_us_close.columns:\n",
    "            continue\n",
    "        px = all_us_close[ticker].dropna()\n",
    "        aligned = pd.concat([px, spy_rs], axis=1, join=\"inner\")\n",
    "        aligned.columns = [\"etf\", \"spy\"]\n",
    "\n",
    "        if aligned.empty or len(aligned) < min(RS_WINDOWS):\n",
    "            continue\n",
    "\n",
    "        latest = {}\n",
    "        for win in RS_WINDOWS:\n",
    "            if len(aligned) >= win:\n",
    "                rs_series = compute_mansfield_rs(aligned[\"etf\"], aligned[\"spy\"], ma_period=win).dropna()\n",
    "                if not rs_series.empty:\n",
    "                    raw = float(rs_series.iloc[-1])\n",
    "                    latest[f\"RS_{win}d\"] = round(raw, 2)\n",
    "                    latest[f\"Norm_RS_{win}d\"] = round(normalize_to_100_scale(raw), 2)\n",
    "\n",
    "        raw_keys = [k for k in latest if k.startswith(\"RS_\") and k.endswith(\"d\")]\n",
    "        if not raw_keys:\n",
    "            continue\n",
    "\n",
    "        raw_vals = [latest[k] for k in raw_keys]\n",
    "        rs_avg = round(sum(raw_vals) / len(raw_vals), 2)\n",
    "        norm_rs_avg = round(normalize_to_100_scale(rs_avg), 2)\n",
    "\n",
    "        row = {\n",
    "            \"Ticker\": ticker,\n",
    "            \"RS_60d\": np.nan, \"RS_120d\": np.nan, \"RS_250d\": np.nan, \"RS_avg\": rs_avg,\n",
    "            \"Norm_RS_60d\": np.nan, \"Norm_RS_120d\": np.nan, \"Norm_RS_250d\": np.nan, \"Norm_RS_avg\": norm_rs_avg\n",
    "        }\n",
    "        for win in RS_WINDOWS:\n",
    "            if f\"RS_{win}d\" in latest:\n",
    "                row[f\"RS_{win}d\"] = latest[f\"RS_{win}d\"]\n",
    "                row[f\"Norm_RS_{win}d\"] = latest[f\"Norm_RS_{win}d\"]\n",
    "\n",
    "        mansfield_rows.append(row)\n",
    "        all_rows_for_fallback.append(row)\n",
    "\n",
    "    except Exception:\n",
    "        continue\n",
    "\n",
    "mansfield_df_us = pd.DataFrame(mansfield_rows)\n",
    "if not mansfield_df_us.empty:\n",
    "    mansfield_df_us = mansfield_df_us.sort_values(by=\"Norm_RS_avg\", ascending=False)\n",
    "print(f\"RS 계산 완료: {len(mansfield_df_us)}개 종목\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f48bce90",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:46:03.942009Z",
     "iopub.status.busy": "2026-10-29T21:46:03.941929Z",
     "iopub.status.idle": "2026-10-29T21:46:03.950678Z",
     "shell.execute_reply": "2026-10-29T21:46:03.950279Z"
    },
    "papermill": {
     "duration": 0.010946,
     "end_time": "2026-10-29T21:46:03.951139+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:46:03.940193+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "정규화 Mansfield RS (60/120/250 + 평균) - Norm_RS_avg >= 70:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Ticker</th>\n",
       "      <th>RS_60d</th>\n",
       "      <th>RS_120d</th>\n",
       "      <th>RS_250d</th>\n",
       "      <th>RS_avg</th>\n",
       "      <th>Norm_RS_60d</th>\n",
       "      <th>Norm_RS_120d</th>\n",
       "      <th>Norm_RS_250d</th>\n",
       "      <th>Norm_RS_avg</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>314</th>\n",
       "      <td>MRNA</td>\n",
       "      <td>36.74</td>\n",
       "      <td>100.65</td>\n",
       "      <td>187.86</td>\n",
       "      <td>108.42</td>\n",
       "      <td>95.53</td>\n",
       "      <td>99.98</td>\n",
       "      <td>100.00</td>\n",
       "      <td>99.99</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>96</th>\n",
       "      <td>CRL</td>\n",
       "      <td>26.47</td>\n",
       "      <td>48.99</td>\n",
       "      <td>65.04</td>\n",
       "      <td>46.83</td>\n",
       "      <td>90.08</td>\n",
       "      <td>98.34</td>\n",
       "      <td>99.56</td>\n",
       "      <td>98.02</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>244</th>\n",
       "      <td>ILMN</td>\n",
       "      <td>20.96</td>\n",
       "      <td>38.71</td>\n",
       "      <td>64.41</td>\n",
       "      <td>41.36</td>\n",
       "      <td>85.15</td>\n",
       "      <td>96.18</td>\n",
       "      <td>99.54</td>\n",
       "      <td>96.91</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>178</th>\n",
       "      <td>P</td>\n",
       "      <td>16.75</td>\n",
       "      <td>39.63</td>\n",
       "      <td>54.13</td>\n",
       "      <td>36.84</td>\n",
       "      <td>80.15</td>\n",
       "      <td>96.45</td>\n",
       "      <td>98.91</td>\n",
       "      <td>95.56</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>387</th>\n",
       "      <td>RVTY</td>\n",
       "      <td>18.92</td>\n",
       "      <td>33.65</td>\n",
       "      <td>42.76</td>\n",
       "      <td>31.78</td>\n",
       "      <td>82.88</td>\n",
       "      <td>94.29</td>\n",
       "      <td>97.24</td>\n",
       "      <td>93.39</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>267</th>\n",
       "      <td>KDP</td>\n",
       "      <td>10.10</td>\n",
       "      <td>10.66</td>\n",
       "      <td>11.80</td>\n",
       "      <td>10.85</td>\n",
       "      <td>69.89</td>\n",
       "      <td>70.86</td>\n",
       "      <td>72.77</td>\n",
       "      <td>71.18</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>160</th>\n",
       "      <td>EBAY</td>\n",
       "      <td>10.43</td>\n",
       "      <td>7.73</td>\n",
       "      <td>13.54</td>\n",
       "      <td>10.57</td>\n",
       "      <td>70.46</td>\n",
       "      <td>65.57</td>\n",
       "      <td>75.55</td>\n",
       "      <td>70.70</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>323</th>\n",
       "      <td>NDAQ</td>\n",
       "      <td>10.30</td>\n",
       "      <td>12.79</td>\n",
       "      <td>8.40</td>\n",
       "      <td>10.50</td>\n",
       "      <td>70.24</td>\n",
       "      <td>74.37</td>\n",
       "      <td>66.81</td>\n",
       "      <td>70.58</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>167</th>\n",
       "      <td>EMR</td>\n",
       "      <td>8.28</td>\n",
       "      <td>12.25</td>\n",
       "      <td>10.37</td>\n",
       "      <td>10.30</td>\n",
       "      <td>66.60</td>\n",
       "      <td>73.51</td>\n",
       "      <td>70.35</td>\n",
       "      <td>70.23</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>311</th>\n",
       "      <td>MU</td>\n",
       "      <td>-3.86</td>\n",
       "      <td>-3.52</td>\n",
       "      <td>37.89</td>\n",
       "      <td>10.17</td>\n",
       "      <td>42.02</td>\n",
       "      <td>42.71</td>\n",
       "      <td>95.92</td>\n",
       "      <td>70.00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>71 rows × 9 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "    Ticker  RS_60d  RS_120d  RS_250d  RS_avg  Norm_RS_60d  Norm_RS_120d  \\\n",
       "314   MRNA   36.74   100.65   187.86  108.42        95.53         99.98   \n",
       "96     CRL   26.47    48.99    65.04   46.83        90.08         98.34   \n",
       "244   ILMN   20.96    38.71    64.41   41.36        85.15         96.18   \n",
       "178      P   16.75    39.63    54.13   36.84        80.15         96.45   \n",
       "387   RVTY   18.92    33.65    42.76   31.78        82.88         94.29   \n",
       "..     ...     ...      ...      ...     ...          ...           ...   \n",
       "267    KDP   10.10    10.66    11.80   10.85        69.89         70.86   \n",
       "160   EBAY   10.43     7.73    13.54   10.57        70.46         65.57   \n",
       "323   NDAQ   10.30    12.79     8.40   10.50        70.24         74.37   \n",
       "167    EMR    8.28    12.25    10.37   10.30        66.60         73.51   \n",
       "311     MU   -3.86    -3.52    37.89   10.17        42.02         42.71   \n",
       "\n",
       "     Norm_RS_250d  Norm_RS_avg  \n",
       "314        100.00        99.99  \n",
       "96          99.56        98.02  \n",
       "244         99.54        96.91  \n",
       "178         98.91        95.56  \n",
       "387         97.24        93.39  \n",
       "..            ...          ...  \n",
       "267         72.77        71.18  \n",
       "160         75.55        70.70  \n",
       "323         66.81        70.58  \n",
       "167         70.35        70.23  \n",
       "311         95.92        70.00  \n",
       "\n",
       "[71 rows x 9 columns]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# [US] RS 결과 출력\n",
    "if not mansfield_df_us.empty:\n",
    "    df_70 = mansfield_df_us[mansfield_df_us[\"Norm_RS_avg\"] >= 70].copy()\n",
    "    if df_70.empty:\n",
    "        print(\"Norm_RS_avg >= 70 조건 만족 종목 없음 -> 상위 10개 출력:\")\n",
    "        display(mansfield_df_us.head(10))\n",
    "    else:\n",
    "        print(\"정규화 Mansfield RS (60/120/250 + 평균) - Norm_RS_avg >= 70:\")\n",
    "        display(df_70)\n",
    "else:\n",
    "    if all_rows_for_fallback:\n",
    "        fb = pd.DataFrame(all_rows_for_fallback).sort_values(by=\"Norm_RS_avg\", ascending=False).head(10)\n",
    "        print(\"유효 RS 결과가 거의 없습니다 -> 상위 10개 임시 출력:\")\n",
    "        display(fb)\n",
    "    else:\n",
    "        print(\"RS 결과가 비어 있습니다. 데이터/네트워크를 확인하세요.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "eee80ac4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:46:03.954352Z",
     "iopub.status.busy": "2026-10-29T21:46:03.954280Z",
     "iopub.status.idle": "2026-10-29T21:46:18.330912Z",
     "shell.execute_reply": "2026-10-29T21:46:18.330452Z"
    },
    "papermill": {
     "duration": 14.378895,
     "end_time": "2026-10-29T21:46:18.331538+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:46:03.952643+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "변동성 조정 모멘텀 상위 20 (S&P500 기준)\n",
      "503종목 Yahoo Chart API 다운로드 중 (1y)...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "다운로드 완료: shape=(251, 500)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Top 1</th>\n",
       "      <th>Top 2</th>\n",
       "      <th>Top 3</th>\n",
       "      <th>Top 4</th>\n",
       "      <th>Top 5</th>\n",
       "      <th>Top 6</th>\n",
       "      <th>Top 7</th>\n",
       "      <th>Top 8</th>\n",
       "      <th>Top 9</th>\n",
       "      <th>Top 10</th>\n",
       "      <th>Top 11</th>\n",
       "      <th>Top 12</th>\n",
       "      <th>Top 13</th>\n",
       "      <th>Top 14</th>\n",
       "      <th>Top 15</th>\n",
       "      <th>Top 16</th>\n",
       "      <th>Top 17</th>\n",
       "      <th>Top 18</th>\n",
       "      <th>Top 19</th>\n",
       "      <th>Top 20</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Date</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2026-10-16</th>\n",
       "      <td>MPC</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VLO</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>KO</td>\n",
       "      <td>APA</td>\n",
       "      <td>CRL</td>\n",
       "      <td>A</td>\n",
       "      <td>COP</td>\n",
       "      <td>TMO</td>\n",
       "      <td>BDX</td>\n",
       "      <td>CVX</td>\n",
       "      <td>XOM</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>P</td>\n",
       "      <td>IQV</td>\n",
       "      <td>ABBV</td>\n",
       "      <td>V</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-19</th>\n",
       "      <td>MPC</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VLO</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>CRL</td>\n",
       "      <td>KO</td>\n",
       "      <td>APA</td>\n",
       "      <td>BDX</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>V</td>\n",
       "      <td>P</td>\n",
       "      <td>A</td>\n",
       "      <td>TMO</td>\n",
       "      <td>CVX</td>\n",
       "      <td>IQV</td>\n",
       "      <td>COP</td>\n",
       "      <td>FTNT</td>\n",
       "      <td>XOM</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-20</th>\n",
       "      <td>MPC</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VLO</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>CRL</td>\n",
       "      <td>KO</td>\n",
       "      <td>BDX</td>\n",
       "      <td>APA</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>V</td>\n",
       "      <td>ABBV</td>\n",
       "      <td>ADP</td>\n",
       "      <td>BIIB</td>\n",
       "      <td>EXPD</td>\n",
       "      <td>PFE</td>\n",
       "      <td>FFIV</td>\n",
       "      <td>CNC</td>\n",
       "      <td>ZBRA</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-21</th>\n",
       "      <td>MPC</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VLO</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>CRL</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>WST</td>\n",
       "      <td>A</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>TMO</td>\n",
       "      <td>IQV</td>\n",
       "      <td>BDX</td>\n",
       "      <td>APA</td>\n",
       "      <td>V</td>\n",
       "      <td>WAT</td>\n",
       "      <td>FTNT</td>\n",
       "      <td>PFE</td>\n",
       "      <td>FFIV</td>\n",
       "      <td>KO</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-22</th>\n",
       "      <td>MPC</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VLO</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>CRL</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>BDX</td>\n",
       "      <td>BIIB</td>\n",
       "      <td>APA</td>\n",
       "      <td>WST</td>\n",
       "      <td>ADP</td>\n",
       "      <td>KO</td>\n",
       "      <td>PFE</td>\n",
       "      <td>CVX</td>\n",
       "      <td>V</td>\n",
       "      <td>IQV</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>A</td>\n",
       "      <td>WAT</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-23</th>\n",
       "      <td>MPC</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VLO</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>CRL</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>APA</td>\n",
       "      <td>CVX</td>\n",
       "      <td>COP</td>\n",
       "      <td>FTNT</td>\n",
       "      <td>XOM</td>\n",
       "      <td>BDX</td>\n",
       "      <td>BIIB</td>\n",
       "      <td>WST</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>CRWD</td>\n",
       "      <td>EXPD</td>\n",
       "      <td>WAT</td>\n",
       "      <td>TRGP</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-26</th>\n",
       "      <td>MPC</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VLO</td>\n",
       "      <td>CRL</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>APA</td>\n",
       "      <td>WST</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>CVX</td>\n",
       "      <td>FTNT</td>\n",
       "      <td>COP</td>\n",
       "      <td>CRWD</td>\n",
       "      <td>FFIV</td>\n",
       "      <td>P</td>\n",
       "      <td>EXPD</td>\n",
       "      <td>A</td>\n",
       "      <td>XOM</td>\n",
       "      <td>BIIB</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-27</th>\n",
       "      <td>CRL</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>MPC</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>EXPD</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>WAT</td>\n",
       "      <td>WST</td>\n",
       "      <td>A</td>\n",
       "      <td>VLO</td>\n",
       "      <td>FTNT</td>\n",
       "      <td>CRWD</td>\n",
       "      <td>P</td>\n",
       "      <td>APA</td>\n",
       "      <td>TMO</td>\n",
       "      <td>BDX</td>\n",
       "      <td>BIIB</td>\n",
       "      <td>CVX</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-28</th>\n",
       "      <td>CRL</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>MPC</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>PSX</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>WAT</td>\n",
       "      <td>WST</td>\n",
       "      <td>A</td>\n",
       "      <td>VLO</td>\n",
       "      <td>TMO</td>\n",
       "      <td>EXPD</td>\n",
       "      <td>NTAP</td>\n",
       "      <td>LH</td>\n",
       "      <td>CRWD</td>\n",
       "      <td>P</td>\n",
       "      <td>APA</td>\n",
       "      <td>IQV</td>\n",
       "      <td>BDX</td>\n",
       "      <td>ABBV</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-29</th>\n",
       "      <td>CRL</td>\n",
       "      <td>RVTY</td>\n",
       "      <td>MPC</td>\n",
       "      <td>ILMN</td>\n",
       "      <td>PSX</td>\n",
       "      <td>WST</td>\n",
       "      <td>WAT</td>\n",
       "      <td>ABBV</td>\n",
       "      <td>LH</td>\n",
       "      <td>A</td>\n",
       "      <td>TMO</td>\n",
       "      <td>VLO</td>\n",
       "      <td>VEEV</td>\n",
       "      <td>APA</td>\n",
       "      <td>EXPD</td>\n",
       "      <td>GILD</td>\n",
       "      <td>CVX</td>\n",
       "      <td>COP</td>\n",
       "      <td>AES</td>\n",
       "      <td>BIIB</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           Top 1 Top 2 Top 3 Top 4 Top 5 Top 6 Top 7 Top 8 Top 9 Top 10  \\\n",
       "Date                                                                      \n",
       "2026-10-16   MPC   PSX   VLO  ILMN  RVTY  VEEV    KO   APA   CRL      A   \n",
       "2026-10-19   MPC   PSX   VLO  VEEV  ILMN  RVTY   CRL    KO   APA    BDX   \n",
       "2026-10-20   MPC   PSX   VLO  VEEV  ILMN  RVTY   CRL    KO   BDX    APA   \n",
       "2026-10-21   MPC   PSX   VLO  ILMN   CRL  VEEV  RVTY   WST     A   NTAP   \n",
       "2026-10-22   MPC   PSX   VLO  RVTY   CRL  ILMN  VEEV   BDX  BIIB    APA   \n",
       "2026-10-23   MPC   PSX   VLO  RVTY   CRL  ILMN  VEEV   APA   CVX    COP   \n",
       "2026-10-26   MPC   PSX   VLO   CRL  RVTY  ILMN  VEEV   APA   WST   NTAP   \n",
       "2026-10-27   CRL  RVTY   MPC  ILMN   PSX  VEEV  EXPD  NTAP   WAT    WST   \n",
       "2026-10-28   CRL  RVTY   MPC  ILMN   PSX  VEEV   WAT   WST     A    VLO   \n",
       "2026-10-29   CRL  RVTY   MPC  ILMN   PSX   WST   WAT  ABBV    LH      A   \n",
       "\n",
       "           Top 11 Top 12 Top 13 Top 14 Top 15 Top 16 Top 17 Top 18 Top 19  \\\n",
       "Date                                                                        \n",
       "2026-10-16    COP    TMO    BDX    CVX    XOM   NTAP      P    IQV   ABBV   \n",
       "2026-10-19   NTAP      V      P      A    TMO    CVX    IQV    COP   FTNT   \n",
       "2026-10-20   NTAP      V   ABBV    ADP   BIIB   EXPD    PFE   FFIV    CNC   \n",
       "2026-10-21    TMO    IQV    BDX    APA      V    WAT   FTNT    PFE   FFIV   \n",
       "2026-10-22    WST    ADP     KO    PFE    CVX      V    IQV   NTAP      A   \n",
       "2026-10-23   FTNT    XOM    BDX   BIIB    WST   NTAP   CRWD   EXPD    WAT   \n",
       "2026-10-26    CVX   FTNT    COP   CRWD   FFIV      P   EXPD      A    XOM   \n",
       "2026-10-27      A    VLO   FTNT   CRWD      P    APA    TMO    BDX   BIIB   \n",
       "2026-10-28    TMO   EXPD   NTAP     LH   CRWD      P    APA    IQV    BDX   \n",
       "2026-10-29    TMO    VLO   VEEV    APA   EXPD   GILD    CVX    COP    AES   \n",
       "\n",
       "           Top 20  \n",
       "Date               \n",
       "2026-10-16      V  \n",
       "2026-10-19    XOM  \n",
       "2026-10-20   ZBRA  \n",
       "2026-10-21     KO  \n",
       "2026-10-22    WAT  \n",
       "2026-10-23   TRGP  \n",
       "2026-10-26   BIIB  \n",
       "2026-10-27    CVX  \n",
       "2026-10-28   ABBV  \n",
       "2026-10-29   BIIB  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# [US] 변동성 조정 모멘텀 상위 20\n",
    "print(\"변동성 조정 모멘텀 상위 20 (S&P500 기준)\")\n",
    "# ★ IP rotation + Yahoo Chart API 직접 호출\n",
    "print(f\"{len(universe)}종목 Yahoo Chart API 다운로드 중 (1y)...\")\n",
    "data_us = yc_close_wide(universe, range_str=\"1y\", interval=\"1d\", min_coverage=1.0, ffill=False)\n",
    "print(f\"다운로드 완료: shape={data_us.shape}\")\n",
    "data_us = data_us.dropna(axis=1, how='any').sort_index()\n",
    "\n",
    "def calculate_risk_adjusted_momentum(data, top_n=20, last_n_days=10):\n",
    "    daily_top = {}\n",
    "    trading_days = data.index[-last_n_days:]\n",
    "    for day in trading_days:\n",
    "        end_idx = data.index.get_loc(day)\n",
    "        start_3m = max(0, end_idx - 63 + 1)\n",
    "        start_6m = max(0, end_idx - 126 + 1)\n",
    "        start_12m = max(0, end_idx - 252 + 1)\n",
    "\n",
    "        returns_3m = data.iloc[start_3m:end_idx + 1].pct_change().dropna().mean()\n",
    "        returns_6m = data.iloc[start_6m:end_idx + 1].pct_change().dropna().mean()\n",
    "        returns_12m = data.iloc[start_12m:end_idx + 1].pct_change().dropna().mean()\n",
    "\n",
    "        vol_3m = data.iloc[start_3m:end_idx + 1].pct_change().dropna().std()\n",
    "        vol_6m = data.iloc[start_6m:end_idx + 1].pct_change().dropna().std()\n",
    "        vol_12m = data.iloc[start_12m:end_idx + 1].pct_change().dropna().std()\n",
    "\n",
    "        avg = (returns_3m / vol_3m + returns_6m / vol_6m + returns_12m / vol_12m) / 3\n",
    "        top = avg.nlargest(top_n).index.tolist()\n",
    "        daily_top[day] = top\n",
    "\n",
    "    df = pd.DataFrame.from_dict(daily_top, orient='index', columns=[f'Top {i+1}' for i in range(top_n)])\n",
    "    df.index.name = 'Date'\n",
    "    return df\n",
    "\n",
    "momentum_df_us = calculate_risk_adjusted_momentum(data_us)\n",
    "display(momentum_df_us)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa9d1cb8",
   "metadata": {
    "papermill": {
     "duration": 0.001404,
     "end_time": "2026-10-29T21:46:18.335388+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:46:18.333984+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# KR: 한국 종목 RS 상대강도 + 모멘텀"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "0020272c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:46:18.338642Z",
     "iopub.status.busy": "2026-10-29T21:46:18.338561Z",
     "iopub.status.idle": "2026-10-29T21:46:19.221528Z",
     "shell.execute_reply": "2026-10-29T21:46:19.220970Z"
    },
    "papermill": {
     "duration": 0.88523,
     "end_time": "2026-10-29T21:46:19.221954+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:46:18.336724+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "KR 종목 수: 2587 (KOSPI: 825, KOSDAQ: 1762)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>code</th>\n",
       "      <th>name</th>\n",
       "      <th>market</th>\n",
       "      <th>yahoo_ticker</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>420610</td>\n",
       "      <td>인텔리빅스</td>\n",
       "      <td>KQ</td>\n",
       "      <td>420610.KQ</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>439270</td>\n",
       "      <td>크리에이츠</td>\n",
       "      <td>KQ</td>\n",
       "      <td>439270.KQ</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>179880</td>\n",
       "      <td>멜콘</td>\n",
       "      <td>KQ</td>\n",
       "      <td>179880.KQ</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>250030</td>\n",
       "      <td>진코스텍</td>\n",
       "      <td>KQ</td>\n",
       "      <td>250030.KQ</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>468670</td>\n",
       "      <td>브릴스</td>\n",
       "      <td>KQ</td>\n",
       "      <td>468670.KQ</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     code   name market yahoo_ticker\n",
       "0  420610  인텔리빅스     KQ    420610.KQ\n",
       "1  439270  크리에이츠     KQ    439270.KQ\n",
       "2  179880     멜콘     KQ    179880.KQ\n",
       "3  250030   진코스텍     KQ    250030.KQ\n",
       "4  468670    브릴스     KQ    468670.KQ"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from io import StringIO\n",
    "import pandas as pd\n",
    "import requests\n",
    "\n",
    "def get_krx_stock_list(digits_only_for_yahoo=True):\n",
    "    \"\"\"KIND에서 KOSPI + KOSDAQ 상장종목 목록 수집\"\"\"\n",
    "    url = \"https://kind.krx.co.kr/corpgeneral/corpList.do?method=download&searchType=13\"\n",
    "\n",
    "    resp = requests.get(\n",
    "        url,\n",
    "        headers={\"User-Agent\": \"Mozilla/5.0\"},\n",
    "        timeout=30,\n",
    "    )\n",
    "    resp.raise_for_status()\n",
    "    resp.encoding = \"euc-kr\"\n",
    "\n",
    "    df = pd.read_html(StringIO(resp.text), flavor=\"lxml\")[0]\n",
    "    df = df[df[\"시장구분\"].isin([\"유가\", \"코스닥\"])].copy()\n",
    "\n",
    "    df[\"종목코드\"] = df[\"종목코드\"].astype(str).str.strip().str.zfill(6)\n",
    "\n",
    "    # Yahoo ticker 용이면 6자리 숫자 코드만 유지\n",
    "    if digits_only_for_yahoo:\n",
    "        df = df[df[\"종목코드\"].str.fullmatch(r\"\\d{6}\")].copy()\n",
    "\n",
    "    market_map = {\"유가\": \"KS\", \"코스닥\": \"KQ\"}\n",
    "\n",
    "    out = pd.DataFrame({\n",
    "        \"code\": df[\"종목코드\"],\n",
    "        \"name\": df[\"회사명\"],\n",
    "        \"market\": df[\"시장구분\"].map(market_map),\n",
    "    })\n",
    "    out[\"yahoo_ticker\"] = out[\"code\"] + \".\" + out[\"market\"]\n",
    "    return out.reset_index(drop=True)\n",
    "\n",
    "df_krx = get_krx_stock_list()\n",
    "company_map = pd.Series(df_krx[\"name\"].values, index=df_krx[\"yahoo_ticker\"]).to_dict()\n",
    "tickers_kr = df_krx[\"yahoo_ticker\"].tolist()\n",
    "\n",
    "print(\n",
    "    f\"KR 종목 수: {len(df_krx)} \"\n",
    "    f\"(KOSPI: {len(df_krx[df_krx['market']=='KS'])}, \"\n",
    "    f\"KOSDAQ: {len(df_krx[df_krx['market']=='KQ'])})\"\n",
    ")\n",
    "display(df_krx.head(5))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b52ed19a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:46:19.227595Z",
     "iopub.status.busy": "2026-10-29T21:46:19.227499Z",
     "iopub.status.idle": "2026-10-29T21:46:19.230137Z",
     "shell.execute_reply": "2026-10-29T21:46:19.229515Z"
    },
    "papermill": {
     "duration": 0.00651,
     "end_time": "2026-10-29T21:46:19.230566+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:46:19.224056+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[KR] 벤치마크 = ^KS11 (KOSPI), 3년 데이터 예정\n"
     ]
    }
   ],
   "source": [
    "# [KR] 벤치마크 다운로드 (KOSPI) — 3년 (250일 MA 계산용)\n",
    "# 데이터는 cell[11]에서 yc_close_wide로 일괄 다운로드하고 여기선 세션만 준비\n",
    "RS_WINDOWS_KR = [60, 120, 250]\n",
    "LAST_N_MOMENTUM_DAYS_KR = 10\n",
    "\n",
    "# Pullback Rebound 파라미터 (참조 파일과 동일)\n",
    "RS_CUT = 65.0\n",
    "MA_MID = 60\n",
    "MA_SHORT = 20\n",
    "RSI_SOFT = 55.0\n",
    "PULLBACK_LOOKBACK = 20\n",
    "PULLBACK_MAX = 0.30\n",
    "PULLBACK_CENTER = 10.0\n",
    "VOL_QUANTILE_CUT = 0.95\n",
    "TOP_N_PULLBACK = 30\n",
    "\n",
    "print(\"[KR] 벤치마크 = ^KS11 (KOSPI), 3년 데이터 예정\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a565785f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:46:19.234815Z",
     "iopub.status.busy": "2026-10-29T21:46:19.234737Z",
     "iopub.status.idle": "2026-10-29T21:47:33.107647Z",
     "shell.execute_reply": "2026-10-29T21:47:33.107152Z"
    },
    "papermill": {
     "duration": 73.87591,
     "end_time": "2026-10-29T21:47:33.108428+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:46:19.232518+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "한국 2587종목 + KOSPI Yahoo Chart API 병렬 다운로드 중 (3년)...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Failed to fetch 7/2588 tickers: ['420610.KQ', '377460.KQ', '217480.KQ', '121800.KQ', '068940.KQ']\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "다운로드 완료: shape=(730, 2543), 성공=2543/2588\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "RS 계산 완료: 2542개 종목 (Norm_RS ≥ 70: 386개)\n"
     ]
    }
   ],
   "source": [
    "# [KR] RS 계산 실행 — Log-based Mansfield RS + 3윈도우 평균 (참조 파일과 동일)\n",
    "# ★ IP rotation + Yahoo Chart API, 3년 데이터\n",
    "def compute_mansfield_rs_log_kr(stock, benchmark, ma_period):\n",
    "    stock = stock.where(stock > 0, np.nan)\n",
    "    benchmark = benchmark.where(benchmark > 0, np.nan)\n",
    "    rel = (stock / benchmark).replace([np.inf, -np.inf], np.nan)\n",
    "    log_rel = np.log(rel)\n",
    "    ma = log_rel.rolling(window=ma_period, min_periods=ma_period).mean()\n",
    "    return (log_rel - ma) * 100\n",
    "\n",
    "print(f\"한국 {len(tickers_kr)}종목 + KOSPI Yahoo Chart API 병렬 다운로드 중 (3년)...\")\n",
    "all_kr_close = yc_close_wide(\n",
    "    tickers_kr + [\"^KS11\"],\n",
    "    range_str=\"3y\",\n",
    "    interval=\"1d\",\n",
    "    adjusted=True,\n",
    "    min_coverage=0.3,  # 3년 중 30% 이상이면 허용 (신규 상장 포함)\n",
    "    ffill=False,\n",
    ")\n",
    "print(f\"다운로드 완료: shape={all_kr_close.shape}, 성공={len(all_kr_close.columns)}/{len(tickers_kr)+1}\")\n",
    "\n",
    "if \"^KS11\" not in all_kr_close.columns:\n",
    "    raise RuntimeError(\"KOSPI(^KS11) 데이터 로드 실패\")\n",
    "\n",
    "kospi = all_kr_close[\"^KS11\"].dropna()\n",
    "assets_kr_daily = all_kr_close[[t for t in tickers_kr if t in all_kr_close.columns]].copy()\n",
    "\n",
    "# Mansfield RS (3 윈도우 평균, log 기반)\n",
    "mansfield_rows_kr = []\n",
    "all_rs_results_kr = []\n",
    "\n",
    "for ticker in assets_kr_daily.columns:\n",
    "    try:\n",
    "        s = assets_kr_daily[ticker].dropna()\n",
    "        idx = s.index.intersection(kospi.index)\n",
    "        if len(idx) < min(RS_WINDOWS_KR):\n",
    "            continue\n",
    "        s = s.loc[idx]\n",
    "        k = kospi.loc[idx]\n",
    "\n",
    "        latest = {}\n",
    "        for win in RS_WINDOWS_KR:\n",
    "            if len(s) >= win:\n",
    "                rs_series = compute_mansfield_rs_log_kr(s, k, ma_period=win).dropna()\n",
    "                if not rs_series.empty:\n",
    "                    raw = float(rs_series.iloc[-1])\n",
    "                    latest[f\"RS_{win}d\"] = round(raw, 2)\n",
    "                    latest[f\"Norm_RS_{win}d\"] = round(normalize_to_100_scale(raw), 2)\n",
    "\n",
    "        raw_keys = [k for k in latest.keys() if k.startswith(\"RS_\") and k.endswith(\"d\")]\n",
    "        if not raw_keys:\n",
    "            continue\n",
    "\n",
    "        raw_vals = [latest[k] for k in raw_keys]\n",
    "        rs_avg = round(sum(raw_vals) / len(raw_vals), 2)\n",
    "        norm_rs_avg = round(normalize_to_100_scale(rs_avg), 2)\n",
    "\n",
    "        row = {\n",
    "            \"Ticker\": ticker,\n",
    "            \"Name\": company_map.get(ticker, ticker),\n",
    "            \"RS_60d\": np.nan, \"RS_120d\": np.nan, \"RS_250d\": np.nan, \"RS_avg\": rs_avg,\n",
    "            \"Norm_RS_60d\": np.nan, \"Norm_RS_120d\": np.nan, \"Norm_RS_250d\": np.nan, \"Norm_RS_avg\": norm_rs_avg,\n",
    "        }\n",
    "        for win in RS_WINDOWS_KR:\n",
    "            if f\"RS_{win}d\" in latest:\n",
    "                row[f\"RS_{win}d\"] = latest[f\"RS_{win}d\"]\n",
    "                row[f\"Norm_RS_{win}d\"] = latest[f\"Norm_RS_{win}d\"]\n",
    "\n",
    "        mansfield_rows_kr.append(row)\n",
    "        all_rs_results_kr.append((ticker, norm_rs_avg, rs_avg))\n",
    "    except Exception:\n",
    "        continue\n",
    "\n",
    "mansfield_df_kr_full = pd.DataFrame(mansfield_rows_kr).sort_values(by=\"Norm_RS_avg\", ascending=False).reset_index(drop=True)\n",
    "mansfield_df_kr_70 = mansfield_df_kr_full[mansfield_df_kr_full[\"Norm_RS_avg\"] >= 70].copy()\n",
    "\n",
    "print(f\"RS 계산 완료: {len(mansfield_df_kr_full)}개 종목 (Norm_RS ≥ 70: {len(mansfield_df_kr_70)}개)\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "8240a12f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:47:33.251152Z",
     "iopub.status.busy": "2026-10-29T21:47:33.250928Z",
     "iopub.status.idle": "2026-10-29T21:47:33.268631Z",
     "shell.execute_reply": "2026-10-29T21:47:33.268239Z"
    },
    "papermill": {
     "duration": 0.032755,
     "end_time": "2026-10-29T21:47:33.269133+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:47:33.236378+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📌 Mansfield RS (60/120/250일 + 평균) 상위 30:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Ticker</th>\n",
       "      <th>Name</th>\n",
       "      <th>RS_60d</th>\n",
       "      <th>RS_120d</th>\n",
       "      <th>RS_250d</th>\n",
       "      <th>RS_avg</th>\n",
       "      <th>Norm_RS_60d</th>\n",
       "      <th>Norm_RS_120d</th>\n",
       "      <th>Norm_RS_250d</th>\n",
       "      <th>Norm_RS_avg</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>001470.KS</td>\n",
       "      <td>삼부토건</td>\n",
       "      <td>112.35</td>\n",
       "      <td>233.72</td>\n",
       "      <td>272.81</td>\n",
       "      <td>206.29</td>\n",
       "      <td>99.99</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>011810.KS</td>\n",
       "      <td>STX</td>\n",
       "      <td>356.55</td>\n",
       "      <td>428.50</td>\n",
       "      <td>441.90</td>\n",
       "      <td>408.98</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>412540.KQ</td>\n",
       "      <td>제일엠앤에스</td>\n",
       "      <td>231.07</td>\n",
       "      <td>280.21</td>\n",
       "      <td>281.74</td>\n",
       "      <td>264.34</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>084440.KQ</td>\n",
       "      <td>유비온</td>\n",
       "      <td>70.99</td>\n",
       "      <td>125.71</td>\n",
       "      <td>133.82</td>\n",
       "      <td>110.17</td>\n",
       "      <td>99.73</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>99.99</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>351320.KQ</td>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>75.43</td>\n",
       "      <td>126.13</td>\n",
       "      <td>123.48</td>\n",
       "      <td>108.35</td>\n",
       "      <td>99.81</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>99.99</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>065420.KQ</td>\n",
       "      <td>에스아이리소스</td>\n",
       "      <td>69.18</td>\n",
       "      <td>127.41</td>\n",
       "      <td>122.24</td>\n",
       "      <td>106.28</td>\n",
       "      <td>99.69</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>99.99</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>031860.KQ</td>\n",
       "      <td>디에이치엑스컴퍼니</td>\n",
       "      <td>19.29</td>\n",
       "      <td>137.20</td>\n",
       "      <td>180.66</td>\n",
       "      <td>112.38</td>\n",
       "      <td>83.31</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>99.99</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>017040.KS</td>\n",
       "      <td>광명전기</td>\n",
       "      <td>58.74</td>\n",
       "      <td>122.27</td>\n",
       "      <td>115.23</td>\n",
       "      <td>98.75</td>\n",
       "      <td>99.26</td>\n",
       "      <td>100.00</td>\n",
       "      <td>99.99</td>\n",
       "      <td>99.97</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>203400.KQ</td>\n",
       "      <td>에이비온</td>\n",
       "      <td>114.12</td>\n",
       "      <td>120.21</td>\n",
       "      <td>54.21</td>\n",
       "      <td>96.18</td>\n",
       "      <td>99.99</td>\n",
       "      <td>100.00</td>\n",
       "      <td>98.92</td>\n",
       "      <td>99.97</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>092870.KQ</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>83.66</td>\n",
       "      <td>106.96</td>\n",
       "      <td>100.52</td>\n",
       "      <td>97.05</td>\n",
       "      <td>99.91</td>\n",
       "      <td>99.99</td>\n",
       "      <td>99.98</td>\n",
       "      <td>99.97</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>079650.KQ</td>\n",
       "      <td>서산</td>\n",
       "      <td>31.27</td>\n",
       "      <td>84.16</td>\n",
       "      <td>113.22</td>\n",
       "      <td>76.22</td>\n",
       "      <td>93.12</td>\n",
       "      <td>99.91</td>\n",
       "      <td>99.99</td>\n",
       "      <td>99.83</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>109670.KQ</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>56.30</td>\n",
       "      <td>94.50</td>\n",
       "      <td>68.06</td>\n",
       "      <td>72.95</td>\n",
       "      <td>99.09</td>\n",
       "      <td>99.96</td>\n",
       "      <td>99.66</td>\n",
       "      <td>99.77</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>042510.KQ</td>\n",
       "      <td>라온시큐어</td>\n",
       "      <td>64.51</td>\n",
       "      <td>82.90</td>\n",
       "      <td>64.83</td>\n",
       "      <td>70.75</td>\n",
       "      <td>99.54</td>\n",
       "      <td>99.90</td>\n",
       "      <td>99.55</td>\n",
       "      <td>99.73</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>159010.KQ</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>36.01</td>\n",
       "      <td>62.64</td>\n",
       "      <td>114.16</td>\n",
       "      <td>70.94</td>\n",
       "      <td>95.26</td>\n",
       "      <td>99.46</td>\n",
       "      <td>99.99</td>\n",
       "      <td>99.73</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>396300.KQ</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>58.16</td>\n",
       "      <td>77.82</td>\n",
       "      <td>77.29</td>\n",
       "      <td>71.09</td>\n",
       "      <td>99.22</td>\n",
       "      <td>99.85</td>\n",
       "      <td>99.84</td>\n",
       "      <td>99.73</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>15</th>\n",
       "      <td>064290.KQ</td>\n",
       "      <td>인텍플러스</td>\n",
       "      <td>47.97</td>\n",
       "      <td>63.50</td>\n",
       "      <td>90.91</td>\n",
       "      <td>67.46</td>\n",
       "      <td>98.20</td>\n",
       "      <td>99.50</td>\n",
       "      <td>99.95</td>\n",
       "      <td>99.64</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>145210.KS</td>\n",
       "      <td>다이나믹디자인</td>\n",
       "      <td>7.78</td>\n",
       "      <td>83.48</td>\n",
       "      <td>107.71</td>\n",
       "      <td>66.32</td>\n",
       "      <td>65.66</td>\n",
       "      <td>99.90</td>\n",
       "      <td>99.99</td>\n",
       "      <td>99.60</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17</th>\n",
       "      <td>032580.KQ</td>\n",
       "      <td>피델릭스</td>\n",
       "      <td>32.66</td>\n",
       "      <td>49.82</td>\n",
       "      <td>106.89</td>\n",
       "      <td>63.12</td>\n",
       "      <td>93.83</td>\n",
       "      <td>98.45</td>\n",
       "      <td>99.99</td>\n",
       "      <td>99.48</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18</th>\n",
       "      <td>425420.KQ</td>\n",
       "      <td>티에프이</td>\n",
       "      <td>60.06</td>\n",
       "      <td>71.82</td>\n",
       "      <td>56.64</td>\n",
       "      <td>62.84</td>\n",
       "      <td>99.33</td>\n",
       "      <td>99.75</td>\n",
       "      <td>99.12</td>\n",
       "      <td>99.47</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>19</th>\n",
       "      <td>053800.KQ</td>\n",
       "      <td>안랩</td>\n",
       "      <td>58.07</td>\n",
       "      <td>74.49</td>\n",
       "      <td>55.41</td>\n",
       "      <td>62.66</td>\n",
       "      <td>99.21</td>\n",
       "      <td>99.80</td>\n",
       "      <td>99.02</td>\n",
       "      <td>99.46</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>010580.KS</td>\n",
       "      <td>에스엠벡셀</td>\n",
       "      <td>58.12</td>\n",
       "      <td>72.98</td>\n",
       "      <td>48.17</td>\n",
       "      <td>59.76</td>\n",
       "      <td>99.22</td>\n",
       "      <td>99.77</td>\n",
       "      <td>98.23</td>\n",
       "      <td>99.32</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>21</th>\n",
       "      <td>278650.KQ</td>\n",
       "      <td>HLB바이오스텝</td>\n",
       "      <td>75.60</td>\n",
       "      <td>81.13</td>\n",
       "      <td>20.13</td>\n",
       "      <td>58.95</td>\n",
       "      <td>99.82</td>\n",
       "      <td>99.88</td>\n",
       "      <td>84.26</td>\n",
       "      <td>99.27</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22</th>\n",
       "      <td>219130.KQ</td>\n",
       "      <td>타이거일렉</td>\n",
       "      <td>37.11</td>\n",
       "      <td>54.15</td>\n",
       "      <td>83.74</td>\n",
       "      <td>58.33</td>\n",
       "      <td>95.66</td>\n",
       "      <td>98.91</td>\n",
       "      <td>99.91</td>\n",
       "      <td>99.23</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>23</th>\n",
       "      <td>263860.KQ</td>\n",
       "      <td>지니언스</td>\n",
       "      <td>52.08</td>\n",
       "      <td>72.94</td>\n",
       "      <td>48.27</td>\n",
       "      <td>57.76</td>\n",
       "      <td>98.71</td>\n",
       "      <td>99.77</td>\n",
       "      <td>98.24</td>\n",
       "      <td>99.19</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>24</th>\n",
       "      <td>144960.KQ</td>\n",
       "      <td>뉴파워프라즈마</td>\n",
       "      <td>41.41</td>\n",
       "      <td>61.34</td>\n",
       "      <td>70.28</td>\n",
       "      <td>57.68</td>\n",
       "      <td>96.92</td>\n",
       "      <td>99.40</td>\n",
       "      <td>99.71</td>\n",
       "      <td>99.19</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25</th>\n",
       "      <td>080580.KQ</td>\n",
       "      <td>오킨스전자</td>\n",
       "      <td>45.70</td>\n",
       "      <td>63.77</td>\n",
       "      <td>62.31</td>\n",
       "      <td>57.26</td>\n",
       "      <td>97.83</td>\n",
       "      <td>99.51</td>\n",
       "      <td>99.45</td>\n",
       "      <td>99.16</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26</th>\n",
       "      <td>078350.KQ</td>\n",
       "      <td>한양디지텍</td>\n",
       "      <td>57.48</td>\n",
       "      <td>64.67</td>\n",
       "      <td>47.08</td>\n",
       "      <td>56.41</td>\n",
       "      <td>99.18</td>\n",
       "      <td>99.55</td>\n",
       "      <td>98.06</td>\n",
       "      <td>99.10</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>27</th>\n",
       "      <td>088280.KQ</td>\n",
       "      <td>쏘닉스</td>\n",
       "      <td>56.24</td>\n",
       "      <td>53.84</td>\n",
       "      <td>51.00</td>\n",
       "      <td>53.69</td>\n",
       "      <td>99.09</td>\n",
       "      <td>98.89</td>\n",
       "      <td>98.59</td>\n",
       "      <td>98.87</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>28</th>\n",
       "      <td>072950.KQ</td>\n",
       "      <td>빛샘전자</td>\n",
       "      <td>43.27</td>\n",
       "      <td>56.26</td>\n",
       "      <td>61.52</td>\n",
       "      <td>53.68</td>\n",
       "      <td>97.36</td>\n",
       "      <td>99.09</td>\n",
       "      <td>99.41</td>\n",
       "      <td>98.87</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>29</th>\n",
       "      <td>052460.KQ</td>\n",
       "      <td>아이크래프트</td>\n",
       "      <td>26.38</td>\n",
       "      <td>67.22</td>\n",
       "      <td>66.82</td>\n",
       "      <td>53.47</td>\n",
       "      <td>90.01</td>\n",
       "      <td>99.63</td>\n",
       "      <td>99.62</td>\n",
       "      <td>98.85</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       Ticker       Name  RS_60d  RS_120d  RS_250d  RS_avg  Norm_RS_60d  \\\n",
       "0   001470.KS       삼부토건  112.35   233.72   272.81  206.29        99.99   \n",
       "1   011810.KS        STX  356.55   428.50   441.90  408.98       100.00   \n",
       "2   412540.KQ     제일엠앤에스  231.07   280.21   281.74  264.34       100.00   \n",
       "3   084440.KQ        유비온   70.99   125.71   133.82  110.17        99.73   \n",
       "4   351320.KQ    넥사다이내믹스   75.43   126.13   123.48  108.35        99.81   \n",
       "5   065420.KQ    에스아이리소스   69.18   127.41   122.24  106.28        99.69   \n",
       "6   031860.KQ  디에이치엑스컴퍼니   19.29   137.20   180.66  112.38        83.31   \n",
       "7   017040.KS       광명전기   58.74   122.27   115.23   98.75        99.26   \n",
       "8   203400.KQ       에이비온  114.12   120.21    54.21   96.18        99.99   \n",
       "9   092870.KQ        엑시콘   83.66   106.96   100.52   97.05        99.91   \n",
       "10  079650.KQ         서산   31.27    84.16   113.22   76.22        93.12   \n",
       "11  109670.KQ       씨싸이트   56.30    94.50    68.06   72.95        99.09   \n",
       "12  042510.KQ      라온시큐어   64.51    82.90    64.83   70.75        99.54   \n",
       "13  159010.KQ       아스플로   36.01    62.64   114.16   70.94        95.26   \n",
       "14  396300.KQ     HT로보틱스   58.16    77.82    77.29   71.09        99.22   \n",
       "15  064290.KQ      인텍플러스   47.97    63.50    90.91   67.46        98.20   \n",
       "16  145210.KS    다이나믹디자인    7.78    83.48   107.71   66.32        65.66   \n",
       "17  032580.KQ       피델릭스   32.66    49.82   106.89   63.12        93.83   \n",
       "18  425420.KQ       티에프이   60.06    71.82    56.64   62.84        99.33   \n",
       "19  053800.KQ         안랩   58.07    74.49    55.41   62.66        99.21   \n",
       "20  010580.KS      에스엠벡셀   58.12    72.98    48.17   59.76        99.22   \n",
       "21  278650.KQ   HLB바이오스텝   75.60    81.13    20.13   58.95        99.82   \n",
       "22  219130.KQ      타이거일렉   37.11    54.15    83.74   58.33        95.66   \n",
       "23  263860.KQ       지니언스   52.08    72.94    48.27   57.76        98.71   \n",
       "24  144960.KQ    뉴파워프라즈마   41.41    61.34    70.28   57.68        96.92   \n",
       "25  080580.KQ      오킨스전자   45.70    63.77    62.31   57.26        97.83   \n",
       "26  078350.KQ      한양디지텍   57.48    64.67    47.08   56.41        99.18   \n",
       "27  088280.KQ        쏘닉스   56.24    53.84    51.00   53.69        99.09   \n",
       "28  072950.KQ       빛샘전자   43.27    56.26    61.52   53.68        97.36   \n",
       "29  052460.KQ     아이크래프트   26.38    67.22    66.82   53.47        90.01   \n",
       "\n",
       "    Norm_RS_120d  Norm_RS_250d  Norm_RS_avg  \n",
       "0         100.00        100.00       100.00  \n",
       "1         100.00        100.00       100.00  \n",
       "2         100.00        100.00       100.00  \n",
       "3         100.00        100.00        99.99  \n",
       "4         100.00        100.00        99.99  \n",
       "5         100.00        100.00        99.99  \n",
       "6         100.00        100.00        99.99  \n",
       "7         100.00         99.99        99.97  \n",
       "8         100.00         98.92        99.97  \n",
       "9          99.99         99.98        99.97  \n",
       "10         99.91         99.99        99.83  \n",
       "11         99.96         99.66        99.77  \n",
       "12         99.90         99.55        99.73  \n",
       "13         99.46         99.99        99.73  \n",
       "14         99.85         99.84        99.73  \n",
       "15         99.50         99.95        99.64  \n",
       "16         99.90         99.99        99.60  \n",
       "17         98.45         99.99        99.48  \n",
       "18         99.75         99.12        99.47  \n",
       "19         99.80         99.02        99.46  \n",
       "20         99.77         98.23        99.32  \n",
       "21         99.88         84.26        99.27  \n",
       "22         98.91         99.91        99.23  \n",
       "23         99.77         98.24        99.19  \n",
       "24         99.40         99.71        99.19  \n",
       "25         99.51         99.45        99.16  \n",
       "26         99.55         98.06        99.10  \n",
       "27         98.89         98.59        98.87  \n",
       "28         99.09         99.41        98.87  \n",
       "29         99.63         99.62        98.85  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "📌 Normalized Mansfield RS ≥ 70 (필터링):\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Ticker</th>\n",
       "      <th>Name</th>\n",
       "      <th>RS_60d</th>\n",
       "      <th>RS_120d</th>\n",
       "      <th>RS_250d</th>\n",
       "      <th>RS_avg</th>\n",
       "      <th>Norm_RS_60d</th>\n",
       "      <th>Norm_RS_120d</th>\n",
       "      <th>Norm_RS_250d</th>\n",
       "      <th>Norm_RS_avg</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>001470.KS</td>\n",
       "      <td>삼부토건</td>\n",
       "      <td>112.35</td>\n",
       "      <td>233.72</td>\n",
       "      <td>272.81</td>\n",
       "      <td>206.29</td>\n",
       "      <td>99.99</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>011810.KS</td>\n",
       "      <td>STX</td>\n",
       "      <td>356.55</td>\n",
       "      <td>428.50</td>\n",
       "      <td>441.90</td>\n",
       "      <td>408.98</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>412540.KQ</td>\n",
       "      <td>제일엠앤에스</td>\n",
       "      <td>231.07</td>\n",
       "      <td>280.21</td>\n",
       "      <td>281.74</td>\n",
       "      <td>264.34</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>084440.KQ</td>\n",
       "      <td>유비온</td>\n",
       "      <td>70.99</td>\n",
       "      <td>125.71</td>\n",
       "      <td>133.82</td>\n",
       "      <td>110.17</td>\n",
       "      <td>99.73</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>99.99</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>351320.KQ</td>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>75.43</td>\n",
       "      <td>126.13</td>\n",
       "      <td>123.48</td>\n",
       "      <td>108.35</td>\n",
       "      <td>99.81</td>\n",
       "      <td>100.00</td>\n",
       "      <td>100.00</td>\n",
       "      <td>99.99</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>381</th>\n",
       "      <td>054940.KQ</td>\n",
       "      <td>엑사이엔씨</td>\n",
       "      <td>8.79</td>\n",
       "      <td>19.84</td>\n",
       "      <td>2.67</td>\n",
       "      <td>10.43</td>\n",
       "      <td>67.53</td>\n",
       "      <td>83.94</td>\n",
       "      <td>55.54</td>\n",
       "      <td>70.46</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>382</th>\n",
       "      <td>352090.KQ</td>\n",
       "      <td>스톰테크</td>\n",
       "      <td>9.16</td>\n",
       "      <td>19.48</td>\n",
       "      <td>2.53</td>\n",
       "      <td>10.39</td>\n",
       "      <td>68.21</td>\n",
       "      <td>83.53</td>\n",
       "      <td>55.26</td>\n",
       "      <td>70.39</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>383</th>\n",
       "      <td>377330.KQ</td>\n",
       "      <td>이지트로닉스</td>\n",
       "      <td>17.24</td>\n",
       "      <td>23.45</td>\n",
       "      <td>-9.84</td>\n",
       "      <td>10.28</td>\n",
       "      <td>80.80</td>\n",
       "      <td>87.59</td>\n",
       "      <td>30.58</td>\n",
       "      <td>70.20</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>384</th>\n",
       "      <td>189330.KQ</td>\n",
       "      <td>씨이랩</td>\n",
       "      <td>-10.44</td>\n",
       "      <td>10.53</td>\n",
       "      <td>30.49</td>\n",
       "      <td>10.19</td>\n",
       "      <td>29.52</td>\n",
       "      <td>70.64</td>\n",
       "      <td>92.70</td>\n",
       "      <td>70.04</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>385</th>\n",
       "      <td>138040.KS</td>\n",
       "      <td>메리츠금융지주</td>\n",
       "      <td>6.04</td>\n",
       "      <td>21.19</td>\n",
       "      <td>3.34</td>\n",
       "      <td>10.19</td>\n",
       "      <td>62.32</td>\n",
       "      <td>85.39</td>\n",
       "      <td>56.92</td>\n",
       "      <td>70.04</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>386 rows × 10 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "        Ticker     Name  RS_60d  RS_120d  RS_250d  RS_avg  Norm_RS_60d  \\\n",
       "0    001470.KS     삼부토건  112.35   233.72   272.81  206.29        99.99   \n",
       "1    011810.KS      STX  356.55   428.50   441.90  408.98       100.00   \n",
       "2    412540.KQ   제일엠앤에스  231.07   280.21   281.74  264.34       100.00   \n",
       "3    084440.KQ      유비온   70.99   125.71   133.82  110.17        99.73   \n",
       "4    351320.KQ  넥사다이내믹스   75.43   126.13   123.48  108.35        99.81   \n",
       "..         ...      ...     ...      ...      ...     ...          ...   \n",
       "381  054940.KQ    엑사이엔씨    8.79    19.84     2.67   10.43        67.53   \n",
       "382  352090.KQ     스톰테크    9.16    19.48     2.53   10.39        68.21   \n",
       "383  377330.KQ   이지트로닉스   17.24    23.45    -9.84   10.28        80.80   \n",
       "384  189330.KQ      씨이랩  -10.44    10.53    30.49   10.19        29.52   \n",
       "385  138040.KS  메리츠금융지주    6.04    21.19     3.34   10.19        62.32   \n",
       "\n",
       "     Norm_RS_120d  Norm_RS_250d  Norm_RS_avg  \n",
       "0          100.00        100.00       100.00  \n",
       "1          100.00        100.00       100.00  \n",
       "2          100.00        100.00       100.00  \n",
       "3          100.00        100.00        99.99  \n",
       "4          100.00        100.00        99.99  \n",
       "..            ...           ...          ...  \n",
       "381         83.94         55.54        70.46  \n",
       "382         83.53         55.26        70.39  \n",
       "383         87.59         30.58        70.20  \n",
       "384         70.64         92.70        70.04  \n",
       "385         85.39         56.92        70.04  \n",
       "\n",
       "[386 rows x 10 columns]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# [KR] RS 결과 출력\n",
    "print(\"📌 Mansfield RS (60/120/250일 + 평균) 상위 30:\")\n",
    "display(mansfield_df_kr_full.head(30))\n",
    "\n",
    "print(\"\\n📌 Normalized Mansfield RS ≥ 70 (필터링):\")\n",
    "if not mansfield_df_kr_70.empty:\n",
    "    display(mansfield_df_kr_70)\n",
    "else:\n",
    "    print(\"⚠️ RS ≥ 70 조건 만족 종목 없음\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "059f9066",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:47:33.275505Z",
     "iopub.status.busy": "2026-10-29T21:47:33.275406Z",
     "iopub.status.idle": "2026-10-29T21:47:33.406408Z",
     "shell.execute_reply": "2026-10-29T21:47:33.405974Z"
    },
    "papermill": {
     "duration": 0.134298,
     "end_time": "2026-10-29T21:47:33.406892+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:47:33.272594+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📌 변동성 조정 모멘텀 상위 20 (한국 종목, 최근 10일):\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Top 1</th>\n",
       "      <th>Top 2</th>\n",
       "      <th>Top 3</th>\n",
       "      <th>Top 4</th>\n",
       "      <th>Top 5</th>\n",
       "      <th>Top 6</th>\n",
       "      <th>Top 7</th>\n",
       "      <th>Top 8</th>\n",
       "      <th>Top 9</th>\n",
       "      <th>Top 10</th>\n",
       "      <th>Top 11</th>\n",
       "      <th>Top 12</th>\n",
       "      <th>Top 13</th>\n",
       "      <th>Top 14</th>\n",
       "      <th>Top 15</th>\n",
       "      <th>Top 16</th>\n",
       "      <th>Top 17</th>\n",
       "      <th>Top 18</th>\n",
       "      <th>Top 19</th>\n",
       "      <th>Top 20</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Date</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2026-10-16</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>파커스</td>\n",
       "      <td>라온시큐어</td>\n",
       "      <td>KPX케미칼</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>바이오비쥬</td>\n",
       "      <td>아이컴포넌트</td>\n",
       "      <td>블루콤</td>\n",
       "      <td>지어소프트</td>\n",
       "      <td>안랩</td>\n",
       "      <td>아이앤씨</td>\n",
       "      <td>나이스정보통신</td>\n",
       "      <td>대림제지</td>\n",
       "      <td>에이텀</td>\n",
       "      <td>코스맥스</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-19</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>지어소프트</td>\n",
       "      <td>파커스</td>\n",
       "      <td>라온시큐어</td>\n",
       "      <td>KPX케미칼</td>\n",
       "      <td>대림제지</td>\n",
       "      <td>대원미디어</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>바이오비쥬</td>\n",
       "      <td>삼아알미늄</td>\n",
       "      <td>블루콤</td>\n",
       "      <td>안랩</td>\n",
       "      <td>아이앤씨</td>\n",
       "      <td>나이스정보통신</td>\n",
       "      <td>서산</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-20</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>지어소프트</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>파커스</td>\n",
       "      <td>안랩</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>대원미디어</td>\n",
       "      <td>삼아알미늄</td>\n",
       "      <td>KPX케미칼</td>\n",
       "      <td>아이앤씨</td>\n",
       "      <td>지씨지놈</td>\n",
       "      <td>서산</td>\n",
       "      <td>라온시큐어</td>\n",
       "      <td>대림제지</td>\n",
       "      <td>블루콤</td>\n",
       "      <td>바이오비쥬</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-21</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>파커스</td>\n",
       "      <td>삼아알미늄</td>\n",
       "      <td>아이앤씨</td>\n",
       "      <td>피델릭스</td>\n",
       "      <td>서산</td>\n",
       "      <td>대원미디어</td>\n",
       "      <td>대림제지</td>\n",
       "      <td>안랩</td>\n",
       "      <td>지어소프트</td>\n",
       "      <td>아이크래프트</td>\n",
       "      <td>나래나노텍</td>\n",
       "      <td>나이스디앤비</td>\n",
       "      <td>라온시큐어</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-22</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>피델릭스</td>\n",
       "      <td>유니퀘스트</td>\n",
       "      <td>파커스</td>\n",
       "      <td>삼아알미늄</td>\n",
       "      <td>대원미디어</td>\n",
       "      <td>서산</td>\n",
       "      <td>지어소프트</td>\n",
       "      <td>나이스디앤비</td>\n",
       "      <td>안랩</td>\n",
       "      <td>혜인</td>\n",
       "      <td>대림제지</td>\n",
       "      <td>자이에스앤디</td>\n",
       "      <td>알멕</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-23</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>피델릭스</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>서산</td>\n",
       "      <td>인티큐브</td>\n",
       "      <td>파커스</td>\n",
       "      <td>대원미디어</td>\n",
       "      <td>유니퀘스트</td>\n",
       "      <td>펌텍코리아</td>\n",
       "      <td>나래나노텍</td>\n",
       "      <td>나이스디앤비</td>\n",
       "      <td>지어소프트</td>\n",
       "      <td>삼아알미늄</td>\n",
       "      <td>DB</td>\n",
       "      <td>알멕</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-26</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>인티큐브</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>피델릭스</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>파커스</td>\n",
       "      <td>서산</td>\n",
       "      <td>듀오백</td>\n",
       "      <td>블루콤</td>\n",
       "      <td>유니퀘스트</td>\n",
       "      <td>펌텍코리아</td>\n",
       "      <td>원풍</td>\n",
       "      <td>나래나노텍</td>\n",
       "      <td>로스웰</td>\n",
       "      <td>키움제11호스팩</td>\n",
       "      <td>한국화장품제조</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-27</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>인티큐브</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>피델릭스</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>서산</td>\n",
       "      <td>파커스</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>유니퀘스트</td>\n",
       "      <td>씨싸이트</td>\n",
       "      <td>안랩</td>\n",
       "      <td>지어소프트</td>\n",
       "      <td>인텍플러스</td>\n",
       "      <td>나래나노텍</td>\n",
       "      <td>에이팩트</td>\n",
       "      <td>타이거일렉</td>\n",
       "      <td>라온시큐어</td>\n",
       "      <td>오킨스전자</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-28</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>인티큐브</td>\n",
       "      <td>안랩</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>피델릭스</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>서산</td>\n",
       "      <td>인텍플러스</td>\n",
       "      <td>파커스</td>\n",
       "      <td>나래나노텍</td>\n",
       "      <td>오킨스전자</td>\n",
       "      <td>타이거일렉</td>\n",
       "      <td>원풍</td>\n",
       "      <td>라온시큐어</td>\n",
       "      <td>티에프이</td>\n",
       "      <td>에이팩트</td>\n",
       "      <td>씨싸이트</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2026-10-29</th>\n",
       "      <td>넥사다이내믹스</td>\n",
       "      <td>아스플로</td>\n",
       "      <td>엑시콘</td>\n",
       "      <td>인티큐브</td>\n",
       "      <td>피델릭스</td>\n",
       "      <td>롯데렌탈</td>\n",
       "      <td>원풍</td>\n",
       "      <td>서산</td>\n",
       "      <td>유니퀘스트</td>\n",
       "      <td>인텍플러스</td>\n",
       "      <td>코람코더원리츠</td>\n",
       "      <td>타이거일렉</td>\n",
       "      <td>HT로보틱스</td>\n",
       "      <td>티에프이</td>\n",
       "      <td>마이크로컨텍솔</td>\n",
       "      <td>오킨스전자</td>\n",
       "      <td>안랩</td>\n",
       "      <td>피에스케이홀딩스</td>\n",
       "      <td>디아이</td>\n",
       "      <td>나래나노텍</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "              Top 1   Top 2   Top 3 Top 4    Top 5    Top 6    Top 7   Top 8  \\\n",
       "Date                                                                           \n",
       "2026-10-16  넥사다이내믹스    아스플로  HT로보틱스  롯데렌탈      엑시콘  코람코더원리츠      파커스   라온시큐어   \n",
       "2026-10-19  넥사다이내믹스    롯데렌탈  HT로보틱스  아스플로      엑시콘  코람코더원리츠    지어소프트     파커스   \n",
       "2026-10-20  넥사다이내믹스  HT로보틱스    롯데렌탈  아스플로  코람코더원리츠    지어소프트      엑시콘     파커스   \n",
       "2026-10-21  넥사다이내믹스  HT로보틱스    아스플로   엑시콘     롯데렌탈     씨싸이트  코람코더원리츠     파커스   \n",
       "2026-10-22  넥사다이내믹스  HT로보틱스    아스플로  롯데렌탈  코람코더원리츠      엑시콘     씨싸이트    피델릭스   \n",
       "2026-10-23  넥사다이내믹스    아스플로  HT로보틱스   엑시콘     롯데렌탈  코람코더원리츠     피델릭스    씨싸이트   \n",
       "2026-10-26  넥사다이내믹스    아스플로  HT로보틱스  인티큐브      엑시콘  코람코더원리츠     피델릭스    롯데렌탈   \n",
       "2026-10-27  넥사다이내믹스     엑시콘    인티큐브  아스플로   HT로보틱스     피델릭스  코람코더원리츠      서산   \n",
       "2026-10-28  넥사다이내믹스     엑시콘    아스플로  인티큐브       안랩     롯데렌탈     피델릭스  HT로보틱스   \n",
       "2026-10-29  넥사다이내믹스    아스플로     엑시콘  인티큐브     피델릭스     롯데렌탈       원풍      서산   \n",
       "\n",
       "              Top 9  Top 10   Top 11  Top 12  Top 13 Top 14   Top 15  Top 16  \\\n",
       "Date                                                                           \n",
       "2026-10-16   KPX케미칼    씨싸이트    바이오비쥬  아이컴포넌트     블루콤  지어소프트       안랩    아이앤씨   \n",
       "2026-10-19    라온시큐어  KPX케미칼     대림제지   대원미디어    씨싸이트  바이오비쥬    삼아알미늄     블루콤   \n",
       "2026-10-20       안랩    씨싸이트    대원미디어   삼아알미늄  KPX케미칼   아이앤씨     지씨지놈      서산   \n",
       "2026-10-21    삼아알미늄    아이앤씨     피델릭스      서산   대원미디어   대림제지       안랩   지어소프트   \n",
       "2026-10-22    유니퀘스트     파커스    삼아알미늄   대원미디어      서산  지어소프트   나이스디앤비      안랩   \n",
       "2026-10-23       서산    인티큐브      파커스   대원미디어   유니퀘스트  펌텍코리아    나래나노텍  나이스디앤비   \n",
       "2026-10-26     씨싸이트     파커스       서산     듀오백     블루콤  유니퀘스트    펌텍코리아      원풍   \n",
       "2026-10-27      파커스    롯데렌탈    유니퀘스트    씨싸이트      안랩  지어소프트    인텍플러스   나래나노텍   \n",
       "2026-10-28  코람코더원리츠      서산    인텍플러스     파커스   나래나노텍  오킨스전자    타이거일렉      원풍   \n",
       "2026-10-29    유니퀘스트   인텍플러스  코람코더원리츠   타이거일렉  HT로보틱스   티에프이  마이크로컨텍솔   오킨스전자   \n",
       "\n",
       "             Top 17    Top 18    Top 19   Top 20  \n",
       "Date                                              \n",
       "2026-10-16  나이스정보통신      대림제지       에이텀     코스맥스  \n",
       "2026-10-19       안랩      아이앤씨   나이스정보통신       서산  \n",
       "2026-10-20    라온시큐어      대림제지       블루콤    바이오비쥬  \n",
       "2026-10-21   아이크래프트     나래나노텍    나이스디앤비    라온시큐어  \n",
       "2026-10-22       혜인      대림제지    자이에스앤디       알멕  \n",
       "2026-10-23    지어소프트     삼아알미늄        DB       알멕  \n",
       "2026-10-26    나래나노텍       로스웰  키움제11호스팩  한국화장품제조  \n",
       "2026-10-27     에이팩트     타이거일렉     라온시큐어    오킨스전자  \n",
       "2026-10-28    라온시큐어      티에프이      에이팩트     씨싸이트  \n",
       "2026-10-29       안랩  피에스케이홀딩스       디아이    나래나노텍  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# [KR] 변동성 조정 모멘텀 상위 20 — dropna 버그 제거\n",
    "# 참조 파일과 동일하게 함수 내부 valid 필터만 사용\n",
    "def calculate_risk_adjusted_momentum_kr(data, last_n_days=10):\n",
    "    daily_top = {}\n",
    "    clean = data.dropna(how=\"all\")\n",
    "    if clean.empty:\n",
    "        return pd.DataFrame()\n",
    "    dates = clean.index[-last_n_days:] if len(clean.index) >= last_n_days else clean.index\n",
    "\n",
    "    for day in dates:\n",
    "        end_idx = data.index.get_indexer_for([day])[0]\n",
    "        s3 = data.iloc[max(0, end_idx-62): end_idx+1]\n",
    "        s6 = data.iloc[max(0, end_idx-125): end_idx+1]\n",
    "        s12 = data.iloc[max(0, end_idx-251): end_idx+1]\n",
    "\n",
    "        r3 = s3.pct_change(fill_method=None).mean(skipna=True)\n",
    "        r6 = s6.pct_change(fill_method=None).mean(skipna=True)\n",
    "        r12 = s12.pct_change(fill_method=None).mean(skipna=True)\n",
    "\n",
    "        v3 = s3.pct_change(fill_method=None).std(skipna=True)\n",
    "        v6 = s6.pct_change(fill_method=None).std(skipna=True)\n",
    "        v12 = s12.pct_change(fill_method=None).std(skipna=True)\n",
    "\n",
    "        valid = (v3 > 0) & (v6 > 0) & (v12 > 0)\n",
    "        if valid.sum() == 0:\n",
    "            daily_top[day] = [np.nan] * 20\n",
    "            continue\n",
    "\n",
    "        risk_adj = (r3[valid]/v3[valid] + r6[valid]/v6[valid] + r12[valid]/v12[valid]) / 3\n",
    "        top_20 = risk_adj.nlargest(min(20, risk_adj.shape[0])).index.tolist()\n",
    "        top_20 += [np.nan] * (20 - len(top_20))\n",
    "        daily_top[day] = top_20\n",
    "\n",
    "    out = pd.DataFrame.from_dict(daily_top, orient=\"index\", columns=[f\"Top {i+1}\" for i in range(20)])\n",
    "    out.index.name = \"Date\"\n",
    "    return out\n",
    "\n",
    "print(\"📌 변동성 조정 모멘텀 상위 20 (한국 종목, 최근 10일):\")\n",
    "momentum_df_kr = calculate_risk_adjusted_momentum_kr(assets_kr_daily, last_n_days=LAST_N_MOMENTUM_DAYS_KR)\n",
    "\n",
    "# 종목명으로 변환\n",
    "momentum_df_kr_named = momentum_df_kr.copy()\n",
    "for col in momentum_df_kr_named.columns:\n",
    "    momentum_df_kr_named[col] = momentum_df_kr_named[col].map(lambda t: company_map.get(t, t) if isinstance(t, str) else t)\n",
    "\n",
    "display(momentum_df_kr_named)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4faa8bf5",
   "metadata": {
    "papermill": {
     "duration": 0.001866,
     "end_time": "2026-10-29T21:47:33.410780+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:47:33.408914+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## 🚀 주간 변동성 Top 30 (한국 종목)\n",
    "최근 1주치 `(High - Low) / Close × 100`으로 변동성 상위 30 종목 선별.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "fd7de00c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:47:33.414883Z",
     "iopub.status.busy": "2026-10-29T21:47:33.414809Z",
     "iopub.status.idle": "2026-10-29T21:48:34.862616Z",
     "shell.execute_reply": "2026-10-29T21:48:34.862114Z"
    },
    "papermill": {
     "duration": 61.450878,
     "end_time": "2026-10-29T21:48:34.863491+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:47:33.412613+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2587종목 OHLCV 다운로드 중 (3개월)...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Failed to fetch 7/2587 tickers: ['420610.KQ', '377460.KQ', '217480.KQ', '121800.KQ', '068940.KQ']\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "다운로드 완료: shape=(62, 12900)\n",
      "\n",
      "🚀 최근 1주 변동성 Top 30 (한국 종목):\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Name</th>\n",
       "      <th>Vol_1W_%</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Ticker</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>142760.KQ</th>\n",
       "      <td>모아라이프플러스</td>\n",
       "      <td>127.966</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>340360.KQ</th>\n",
       "      <td>다보링크</td>\n",
       "      <td>127.397</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>335870.KQ</th>\n",
       "      <td>윙스풋</td>\n",
       "      <td>98.625</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>043610.KQ</th>\n",
       "      <td>KT지니뮤직</td>\n",
       "      <td>80.000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>396690.KS</th>\n",
       "      <td>미래에셋글로벌리츠</td>\n",
       "      <td>73.383</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>368600.KQ</th>\n",
       "      <td>아이씨에이치</td>\n",
       "      <td>71.630</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>060900.KQ</th>\n",
       "      <td>에이전트AI</td>\n",
       "      <td>66.818</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>376930.KQ</th>\n",
       "      <td>노을</td>\n",
       "      <td>65.337</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>095910.KQ</th>\n",
       "      <td>에스에너지</td>\n",
       "      <td>62.487</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>354320.KQ</th>\n",
       "      <td>알멕</td>\n",
       "      <td>58.125</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>457600.KQ</th>\n",
       "      <td>벡트로보틱스</td>\n",
       "      <td>55.340</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>011080.KQ</th>\n",
       "      <td>형지I&amp;C</td>\n",
       "      <td>53.125</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>050110.KQ</th>\n",
       "      <td>캠시스</td>\n",
       "      <td>51.553</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>199730.KQ</th>\n",
       "      <td>바이오인프라</td>\n",
       "      <td>50.587</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>065650.KQ</th>\n",
       "      <td>하이퍼코퍼레이션</td>\n",
       "      <td>50.069</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>101670.KQ</th>\n",
       "      <td>하이드로리튬</td>\n",
       "      <td>50.000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>071950.KS</th>\n",
       "      <td>코아스</td>\n",
       "      <td>49.812</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>179880.KQ</th>\n",
       "      <td>멜콘</td>\n",
       "      <td>49.647</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>276730.KQ</th>\n",
       "      <td>한울앤제주</td>\n",
       "      <td>49.257</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>093240.KS</th>\n",
       "      <td>형지엘리트</td>\n",
       "      <td>48.729</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>011300.KS</th>\n",
       "      <td>우성머티리얼스</td>\n",
       "      <td>47.816</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>377330.KQ</th>\n",
       "      <td>이지트로닉스</td>\n",
       "      <td>47.656</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>900100.KQ</th>\n",
       "      <td>파이온엑스</td>\n",
       "      <td>46.835</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>099520.KQ</th>\n",
       "      <td>DGI</td>\n",
       "      <td>45.039</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>456160.KQ</th>\n",
       "      <td>지투지바이오</td>\n",
       "      <td>44.672</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>451760.KQ</th>\n",
       "      <td>컨텍</td>\n",
       "      <td>44.222</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>411080.KQ</th>\n",
       "      <td>샌즈랩</td>\n",
       "      <td>42.785</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>186230.KQ</th>\n",
       "      <td>그린플러스</td>\n",
       "      <td>42.715</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>419050.KQ</th>\n",
       "      <td>삼기에너지솔루션즈</td>\n",
       "      <td>42.243</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>053800.KQ</th>\n",
       "      <td>안랩</td>\n",
       "      <td>41.159</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                Name  Vol_1W_%\n",
       "Ticker                        \n",
       "142760.KQ   모아라이프플러스   127.966\n",
       "340360.KQ       다보링크   127.397\n",
       "335870.KQ        윙스풋    98.625\n",
       "043610.KQ     KT지니뮤직    80.000\n",
       "396690.KS  미래에셋글로벌리츠    73.383\n",
       "368600.KQ     아이씨에이치    71.630\n",
       "060900.KQ     에이전트AI    66.818\n",
       "376930.KQ         노을    65.337\n",
       "095910.KQ      에스에너지    62.487\n",
       "354320.KQ         알멕    58.125\n",
       "457600.KQ     벡트로보틱스    55.340\n",
       "011080.KQ      형지I&C    53.125\n",
       "050110.KQ        캠시스    51.553\n",
       "199730.KQ     바이오인프라    50.587\n",
       "065650.KQ   하이퍼코퍼레이션    50.069\n",
       "101670.KQ     하이드로리튬    50.000\n",
       "071950.KS        코아스    49.812\n",
       "179880.KQ         멜콘    49.647\n",
       "276730.KQ      한울앤제주    49.257\n",
       "093240.KS      형지엘리트    48.729\n",
       "011300.KS    우성머티리얼스    47.816\n",
       "377330.KQ     이지트로닉스    47.656\n",
       "900100.KQ      파이온엑스    46.835\n",
       "099520.KQ        DGI    45.039\n",
       "456160.KQ     지투지바이오    44.672\n",
       "451760.KQ         컨텍    44.222\n",
       "411080.KQ        샌즈랩    42.785\n",
       "186230.KQ      그린플러스    42.715\n",
       "419050.KQ  삼기에너지솔루션즈    42.243\n",
       "053800.KQ         안랩    41.159"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# [KR] 주간 변동성 Top 30 — 최근 1주 (H-L)/C × 100\n",
    "# OHLC 별도 다운로드 (close만 받던 기존 데이터에 OHLC 없음)\n",
    "print(f\"{len(tickers_kr)}종목 OHLCV 다운로드 중 (3개월)...\")\n",
    "ohlcv_kr = yc_ohlcv_wide(tickers_kr, range_str=\"3mo\", interval=\"1d\", adjusted=True)\n",
    "print(f\"다운로드 완료: shape={ohlcv_kr.shape}\")\n",
    "\n",
    "# yfinance 호환 MultiIndex (level0=OHLCV, level1=Ticker)\n",
    "highs_daily = ohlcv_kr[\"High\"]\n",
    "lows_daily  = ohlcv_kr[\"Low\"]\n",
    "closes_daily = ohlcv_kr[\"Close\"]\n",
    "\n",
    "weekly_high = highs_daily.resample(\"W-FRI\").max()\n",
    "weekly_low  = lows_daily.resample(\"W-FRI\").min()\n",
    "weekly_close = closes_daily.resample(\"W-FRI\").last()\n",
    "\n",
    "weekly_range_pct = (weekly_high - weekly_low) / weekly_close * 100.0\n",
    "latest_vol = weekly_range_pct.iloc[-1].dropna()\n",
    "\n",
    "top30_weekly_vol = latest_vol.sort_values(ascending=False).head(30).to_frame(\"Vol_1W_%\")\n",
    "top30_weekly_vol[\"Name\"] = top30_weekly_vol.index.map(lambda t: company_map.get(t, t))\n",
    "top30_weekly_vol = top30_weekly_vol[[\"Name\", \"Vol_1W_%\"]].round(3)\n",
    "oneweek_vol_kr = latest_vol  # Pullback에서 재사용\n",
    "\n",
    "print(\"\\n🚀 최근 1주 변동성 Top 30 (한국 종목):\")\n",
    "display(top30_weekly_vol)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8420fc2b",
   "metadata": {
    "papermill": {
     "duration": 0.00199,
     "end_time": "2026-10-29T21:48:34.868890+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:48:34.866900+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## 🟢 Pullback Rebound 후보 Top 30 (한국 종목)\n",
    "Norm_RS ≥ 65, MA20/MA60 근처, RSI14 ≤ 55, 20일 고점 대비 3-30% 눌림 조건을 점수화하여 상위 30 종목.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "18910ca5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-29T21:48:34.873913Z",
     "iopub.status.busy": "2026-10-29T21:48:34.873827Z",
     "iopub.status.idle": "2026-10-29T21:48:35.165424Z",
     "shell.execute_reply": "2026-10-29T21:48:35.165152Z"
    },
    "papermill": {
     "duration": 0.294791,
     "end_time": "2026-10-29T21:48:35.165928+00:00",
     "exception": false,
     "start_time": "2026-10-29T21:48:34.871137+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "🟢 Pullback Rebound 후보 Top 30 (한국 종목, 점수형):\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Ticker</th>\n",
       "      <th>Name</th>\n",
       "      <th>Last</th>\n",
       "      <th>MA20</th>\n",
       "      <th>MA60</th>\n",
       "      <th>Dist_to_MA60_%</th>\n",
       "      <th>Pullback_20D_%</th>\n",
       "      <th>RSI14</th>\n",
       "      <th>Vol_1W_%</th>\n",
       "      <th>Norm_RS_avg</th>\n",
       "      <th>Score</th>\n",
       "      <th>Pullback_OK</th>\n",
       "      <th>RSI_low(&lt;=55)</th>\n",
       "      <th>MA60_up(&gt;=0)</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>303360.KQ</td>\n",
       "      <td>프로티아</td>\n",
       "      <td>3615.0</td>\n",
       "      <td>3784.249988</td>\n",
       "      <td>3521.638883</td>\n",
       "      <td>2.65</td>\n",
       "      <td>10.07</td>\n",
       "      <td>46.60</td>\n",
       "      <td>4.01</td>\n",
       "      <td>78.17</td>\n",
       "      <td>75.49</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>100120.KQ</td>\n",
       "      <td>뷰웍스</td>\n",
       "      <td>27700.0</td>\n",
       "      <td>28770.000000</td>\n",
       "      <td>27163.333333</td>\n",
       "      <td>1.98</td>\n",
       "      <td>8.88</td>\n",
       "      <td>48.03</td>\n",
       "      <td>6.14</td>\n",
       "      <td>80.82</td>\n",
       "      <td>75.05</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>025000.KS</td>\n",
       "      <td>KPX케미칼</td>\n",
       "      <td>59000.0</td>\n",
       "      <td>60860.000000</td>\n",
       "      <td>55958.333333</td>\n",
       "      <td>5.44</td>\n",
       "      <td>10.33</td>\n",
       "      <td>46.33</td>\n",
       "      <td>5.42</td>\n",
       "      <td>85.90</td>\n",
       "      <td>73.08</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>403850.KQ</td>\n",
       "      <td>더핑크퐁컴퍼니</td>\n",
       "      <td>12300.0</td>\n",
       "      <td>12429.000000</td>\n",
       "      <td>12227.000000</td>\n",
       "      <td>0.60</td>\n",
       "      <td>10.09</td>\n",
       "      <td>49.55</td>\n",
       "      <td>11.87</td>\n",
       "      <td>79.79</td>\n",
       "      <td>72.77</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>022220.KQ</td>\n",
       "      <td>티케이지애강</td>\n",
       "      <td>773.0</td>\n",
       "      <td>803.500000</td>\n",
       "      <td>765.250000</td>\n",
       "      <td>1.01</td>\n",
       "      <td>7.98</td>\n",
       "      <td>39.02</td>\n",
       "      <td>0.00</td>\n",
       "      <td>75.65</td>\n",
       "      <td>72.50</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>088790.KS</td>\n",
       "      <td>진도</td>\n",
       "      <td>2390.0</td>\n",
       "      <td>2454.500000</td>\n",
       "      <td>2342.250000</td>\n",
       "      <td>2.04</td>\n",
       "      <td>11.48</td>\n",
       "      <td>48.50</td>\n",
       "      <td>11.30</td>\n",
       "      <td>77.82</td>\n",
       "      <td>72.03</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>033560.KQ</td>\n",
       "      <td>블루콤</td>\n",
       "      <td>5340.0</td>\n",
       "      <td>5695.000000</td>\n",
       "      <td>5043.250000</td>\n",
       "      <td>5.88</td>\n",
       "      <td>11.44</td>\n",
       "      <td>46.20</td>\n",
       "      <td>14.23</td>\n",
       "      <td>92.20</td>\n",
       "      <td>71.56</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>236200.KQ</td>\n",
       "      <td>슈프리마</td>\n",
       "      <td>54900.0</td>\n",
       "      <td>55760.000000</td>\n",
       "      <td>55220.000000</td>\n",
       "      <td>-0.58</td>\n",
       "      <td>9.11</td>\n",
       "      <td>49.79</td>\n",
       "      <td>9.11</td>\n",
       "      <td>84.29</td>\n",
       "      <td>71.34</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>096530.KQ</td>\n",
       "      <td>씨젠</td>\n",
       "      <td>33600.0</td>\n",
       "      <td>36062.500000</td>\n",
       "      <td>33100.000000</td>\n",
       "      <td>1.51</td>\n",
       "      <td>13.62</td>\n",
       "      <td>43.86</td>\n",
       "      <td>5.51</td>\n",
       "      <td>81.84</td>\n",
       "      <td>70.86</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>067920.KQ</td>\n",
       "      <td>이글루</td>\n",
       "      <td>5920.0</td>\n",
       "      <td>6003.500000</td>\n",
       "      <td>5681.000000</td>\n",
       "      <td>4.21</td>\n",
       "      <td>10.03</td>\n",
       "      <td>50.53</td>\n",
       "      <td>21.45</td>\n",
       "      <td>82.50</td>\n",
       "      <td>70.86</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>006730.KQ</td>\n",
       "      <td>서부T&amp;D</td>\n",
       "      <td>11370.0</td>\n",
       "      <td>11718.500000</td>\n",
       "      <td>11142.666667</td>\n",
       "      <td>2.04</td>\n",
       "      <td>10.61</td>\n",
       "      <td>47.00</td>\n",
       "      <td>4.57</td>\n",
       "      <td>66.60</td>\n",
       "      <td>70.16</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>006400.KS</td>\n",
       "      <td>삼성SDI</td>\n",
       "      <td>538000.0</td>\n",
       "      <td>540850.000000</td>\n",
       "      <td>521033.333333</td>\n",
       "      <td>3.26</td>\n",
       "      <td>9.12</td>\n",
       "      <td>50.84</td>\n",
       "      <td>17.01</td>\n",
       "      <td>79.41</td>\n",
       "      <td>70.14</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>121890.KQ</td>\n",
       "      <td>에스디시스템</td>\n",
       "      <td>2535.0</td>\n",
       "      <td>2678.250000</td>\n",
       "      <td>2533.183333</td>\n",
       "      <td>0.07</td>\n",
       "      <td>12.59</td>\n",
       "      <td>45.37</td>\n",
       "      <td>17.36</td>\n",
       "      <td>86.44</td>\n",
       "      <td>70.07</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>018670.KS</td>\n",
       "      <td>SK가스</td>\n",
       "      <td>252500.0</td>\n",
       "      <td>261700.000000</td>\n",
       "      <td>243666.666667</td>\n",
       "      <td>3.63</td>\n",
       "      <td>10.78</td>\n",
       "      <td>46.72</td>\n",
       "      <td>6.73</td>\n",
       "      <td>73.25</td>\n",
       "      <td>69.57</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>093520.KQ</td>\n",
       "      <td>매커스</td>\n",
       "      <td>27350.0</td>\n",
       "      <td>27295.000000</td>\n",
       "      <td>26500.000000</td>\n",
       "      <td>3.21</td>\n",
       "      <td>8.22</td>\n",
       "      <td>52.79</td>\n",
       "      <td>12.43</td>\n",
       "      <td>81.27</td>\n",
       "      <td>69.44</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>15</th>\n",
       "      <td>053300.KQ</td>\n",
       "      <td>한국정보인증</td>\n",
       "      <td>5090.0</td>\n",
       "      <td>5173.250000</td>\n",
       "      <td>4975.750000</td>\n",
       "      <td>2.30</td>\n",
       "      <td>9.59</td>\n",
       "      <td>48.81</td>\n",
       "      <td>7.86</td>\n",
       "      <td>67.33</td>\n",
       "      <td>69.40</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>005960.KS</td>\n",
       "      <td>동부건설</td>\n",
       "      <td>7740.0</td>\n",
       "      <td>8076.500000</td>\n",
       "      <td>7795.166667</td>\n",
       "      <td>-0.71</td>\n",
       "      <td>8.73</td>\n",
       "      <td>43.05</td>\n",
       "      <td>8.53</td>\n",
       "      <td>77.61</td>\n",
       "      <td>69.26</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17</th>\n",
       "      <td>021240.KS</td>\n",
       "      <td>코웨이</td>\n",
       "      <td>101600.0</td>\n",
       "      <td>103455.000000</td>\n",
       "      <td>99573.993880</td>\n",
       "      <td>2.03</td>\n",
       "      <td>6.27</td>\n",
       "      <td>46.30</td>\n",
       "      <td>5.71</td>\n",
       "      <td>78.29</td>\n",
       "      <td>69.21</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18</th>\n",
       "      <td>048530.KQ</td>\n",
       "      <td>인트론바이오</td>\n",
       "      <td>2875.0</td>\n",
       "      <td>2891.750000</td>\n",
       "      <td>2810.750000</td>\n",
       "      <td>2.29</td>\n",
       "      <td>8.87</td>\n",
       "      <td>48.82</td>\n",
       "      <td>14.96</td>\n",
       "      <td>71.23</td>\n",
       "      <td>68.64</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>19</th>\n",
       "      <td>013990.KQ</td>\n",
       "      <td>아가방컴퍼니</td>\n",
       "      <td>4835.0</td>\n",
       "      <td>5026.000000</td>\n",
       "      <td>4779.333333</td>\n",
       "      <td>1.16</td>\n",
       "      <td>9.63</td>\n",
       "      <td>48.43</td>\n",
       "      <td>14.17</td>\n",
       "      <td>69.58</td>\n",
       "      <td>68.62</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>002960.KS</td>\n",
       "      <td>한국쉘석유</td>\n",
       "      <td>509000.0</td>\n",
       "      <td>536650.000000</td>\n",
       "      <td>516250.000000</td>\n",
       "      <td>-1.40</td>\n",
       "      <td>10.70</td>\n",
       "      <td>40.02</td>\n",
       "      <td>5.30</td>\n",
       "      <td>73.58</td>\n",
       "      <td>68.61</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>21</th>\n",
       "      <td>021040.KQ</td>\n",
       "      <td>대호특수강</td>\n",
       "      <td>975.0</td>\n",
       "      <td>950.400000</td>\n",
       "      <td>949.233333</td>\n",
       "      <td>2.71</td>\n",
       "      <td>11.36</td>\n",
       "      <td>52.09</td>\n",
       "      <td>21.33</td>\n",
       "      <td>77.63</td>\n",
       "      <td>68.41</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22</th>\n",
       "      <td>001120.KS</td>\n",
       "      <td>LX인터내셔널</td>\n",
       "      <td>40200.0</td>\n",
       "      <td>43197.500000</td>\n",
       "      <td>40458.333333</td>\n",
       "      <td>-0.64</td>\n",
       "      <td>10.27</td>\n",
       "      <td>37.85</td>\n",
       "      <td>8.08</td>\n",
       "      <td>66.82</td>\n",
       "      <td>68.02</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>23</th>\n",
       "      <td>013580.KS</td>\n",
       "      <td>계룡건설산업</td>\n",
       "      <td>23600.0</td>\n",
       "      <td>22930.000000</td>\n",
       "      <td>22165.500000</td>\n",
       "      <td>6.47</td>\n",
       "      <td>11.28</td>\n",
       "      <td>51.53</td>\n",
       "      <td>15.89</td>\n",
       "      <td>88.95</td>\n",
       "      <td>67.60</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>24</th>\n",
       "      <td>068790.KQ</td>\n",
       "      <td>DMS</td>\n",
       "      <td>7440.0</td>\n",
       "      <td>7628.500000</td>\n",
       "      <td>7494.666667</td>\n",
       "      <td>-0.73</td>\n",
       "      <td>9.93</td>\n",
       "      <td>49.26</td>\n",
       "      <td>7.53</td>\n",
       "      <td>71.93</td>\n",
       "      <td>67.52</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25</th>\n",
       "      <td>009780.KQ</td>\n",
       "      <td>엠에스씨</td>\n",
       "      <td>7000.0</td>\n",
       "      <td>7261.500000</td>\n",
       "      <td>6954.000000</td>\n",
       "      <td>0.66</td>\n",
       "      <td>8.74</td>\n",
       "      <td>45.61</td>\n",
       "      <td>5.29</td>\n",
       "      <td>67.63</td>\n",
       "      <td>67.15</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26</th>\n",
       "      <td>251970.KQ</td>\n",
       "      <td>펌텍코리아</td>\n",
       "      <td>65100.0</td>\n",
       "      <td>67140.000000</td>\n",
       "      <td>59620.833333</td>\n",
       "      <td>9.19</td>\n",
       "      <td>9.33</td>\n",
       "      <td>49.58</td>\n",
       "      <td>9.22</td>\n",
       "      <td>92.92</td>\n",
       "      <td>67.06</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>27</th>\n",
       "      <td>041520.KQ</td>\n",
       "      <td>이엘씨</td>\n",
       "      <td>11480.0</td>\n",
       "      <td>12211.500000</td>\n",
       "      <td>11741.666667</td>\n",
       "      <td>-2.23</td>\n",
       "      <td>12.03</td>\n",
       "      <td>42.52</td>\n",
       "      <td>14.81</td>\n",
       "      <td>84.14</td>\n",
       "      <td>67.01</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>28</th>\n",
       "      <td>005430.KS</td>\n",
       "      <td>한국공항</td>\n",
       "      <td>86200.0</td>\n",
       "      <td>90020.000000</td>\n",
       "      <td>85806.666667</td>\n",
       "      <td>0.46</td>\n",
       "      <td>13.54</td>\n",
       "      <td>48.33</td>\n",
       "      <td>6.38</td>\n",
       "      <td>80.24</td>\n",
       "      <td>66.98</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>29</th>\n",
       "      <td>028100.KS</td>\n",
       "      <td>동아지질</td>\n",
       "      <td>18400.0</td>\n",
       "      <td>19203.500000</td>\n",
       "      <td>18472.833333</td>\n",
       "      <td>-0.39</td>\n",
       "      <td>6.98</td>\n",
       "      <td>42.29</td>\n",
       "      <td>3.80</td>\n",
       "      <td>76.66</td>\n",
       "      <td>66.86</td>\n",
       "      <td>True</td>\n",
       "      <td>True</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       Ticker     Name      Last           MA20           MA60  \\\n",
       "0   303360.KQ     프로티아    3615.0    3784.249988    3521.638883   \n",
       "1   100120.KQ      뷰웍스   27700.0   28770.000000   27163.333333   \n",
       "2   025000.KS   KPX케미칼   59000.0   60860.000000   55958.333333   \n",
       "3   403850.KQ  더핑크퐁컴퍼니   12300.0   12429.000000   12227.000000   \n",
       "4   022220.KQ   티케이지애강     773.0     803.500000     765.250000   \n",
       "5   088790.KS       진도    2390.0    2454.500000    2342.250000   \n",
       "6   033560.KQ      블루콤    5340.0    5695.000000    5043.250000   \n",
       "7   236200.KQ     슈프리마   54900.0   55760.000000   55220.000000   \n",
       "8   096530.KQ       씨젠   33600.0   36062.500000   33100.000000   \n",
       "9   067920.KQ      이글루    5920.0    6003.500000    5681.000000   \n",
       "10  006730.KQ    서부T&D   11370.0   11718.500000   11142.666667   \n",
       "11  006400.KS    삼성SDI  538000.0  540850.000000  521033.333333   \n",
       "12  121890.KQ   에스디시스템    2535.0    2678.250000    2533.183333   \n",
       "13  018670.KS     SK가스  252500.0  261700.000000  243666.666667   \n",
       "14  093520.KQ      매커스   27350.0   27295.000000   26500.000000   \n",
       "15  053300.KQ   한국정보인증    5090.0    5173.250000    4975.750000   \n",
       "16  005960.KS     동부건설    7740.0    8076.500000    7795.166667   \n",
       "17  021240.KS      코웨이  101600.0  103455.000000   99573.993880   \n",
       "18  048530.KQ   인트론바이오    2875.0    2891.750000    2810.750000   \n",
       "19  013990.KQ   아가방컴퍼니    4835.0    5026.000000    4779.333333   \n",
       "20  002960.KS    한국쉘석유  509000.0  536650.000000  516250.000000   \n",
       "21  021040.KQ    대호특수강     975.0     950.400000     949.233333   \n",
       "22  001120.KS  LX인터내셔널   40200.0   43197.500000   40458.333333   \n",
       "23  013580.KS   계룡건설산업   23600.0   22930.000000   22165.500000   \n",
       "24  068790.KQ      DMS    7440.0    7628.500000    7494.666667   \n",
       "25  009780.KQ     엠에스씨    7000.0    7261.500000    6954.000000   \n",
       "26  251970.KQ    펌텍코리아   65100.0   67140.000000   59620.833333   \n",
       "27  041520.KQ      이엘씨   11480.0   12211.500000   11741.666667   \n",
       "28  005430.KS     한국공항   86200.0   90020.000000   85806.666667   \n",
       "29  028100.KS     동아지질   18400.0   19203.500000   18472.833333   \n",
       "\n",
       "    Dist_to_MA60_%  Pullback_20D_%  RSI14  Vol_1W_%  Norm_RS_avg  Score  \\\n",
       "0             2.65           10.07  46.60      4.01        78.17  75.49   \n",
       "1             1.98            8.88  48.03      6.14        80.82  75.05   \n",
       "2             5.44           10.33  46.33      5.42        85.90  73.08   \n",
       "3             0.60           10.09  49.55     11.87        79.79  72.77   \n",
       "4             1.01            7.98  39.02      0.00        75.65  72.50   \n",
       "5             2.04           11.48  48.50     11.30        77.82  72.03   \n",
       "6             5.88           11.44  46.20     14.23        92.20  71.56   \n",
       "7            -0.58            9.11  49.79      9.11        84.29  71.34   \n",
       "8             1.51           13.62  43.86      5.51        81.84  70.86   \n",
       "9             4.21           10.03  50.53     21.45        82.50  70.86   \n",
       "10            2.04           10.61  47.00      4.57        66.60  70.16   \n",
       "11            3.26            9.12  50.84     17.01        79.41  70.14   \n",
       "12            0.07           12.59  45.37     17.36        86.44  70.07   \n",
       "13            3.63           10.78  46.72      6.73        73.25  69.57   \n",
       "14            3.21            8.22  52.79     12.43        81.27  69.44   \n",
       "15            2.30            9.59  48.81      7.86        67.33  69.40   \n",
       "16           -0.71            8.73  43.05      8.53        77.61  69.26   \n",
       "17            2.03            6.27  46.30      5.71        78.29  69.21   \n",
       "18            2.29            8.87  48.82     14.96        71.23  68.64   \n",
       "19            1.16            9.63  48.43     14.17        69.58  68.62   \n",
       "20           -1.40           10.70  40.02      5.30        73.58  68.61   \n",
       "21            2.71           11.36  52.09     21.33        77.63  68.41   \n",
       "22           -0.64           10.27  37.85      8.08        66.82  68.02   \n",
       "23            6.47           11.28  51.53     15.89        88.95  67.60   \n",
       "24           -0.73            9.93  49.26      7.53        71.93  67.52   \n",
       "25            0.66            8.74  45.61      5.29        67.63  67.15   \n",
       "26            9.19            9.33  49.58      9.22        92.92  67.06   \n",
       "27           -2.23           12.03  42.52     14.81        84.14  67.01   \n",
       "28            0.46           13.54  48.33      6.38        80.24  66.98   \n",
       "29           -0.39            6.98  42.29      3.80        76.66  66.86   \n",
       "\n",
       "    Pullback_OK  RSI_low(<=55)  MA60_up(>=0)  \n",
       "0          True           True          True  \n",
       "1          True           True          True  \n",
       "2          True           True          True  \n",
       "3          True           True          True  \n",
       "4          True           True          True  \n",
       "5          True           True          True  \n",
       "6          True           True          True  \n",
       "7          True           True         False  \n",
       "8          True           True          True  \n",
       "9          True           True          True  \n",
       "10         True           True          True  \n",
       "11         True           True          True  \n",
       "12         True           True          True  \n",
       "13         True           True          True  \n",
       "14         True           True          True  \n",
       "15         True           True          True  \n",
       "16         True           True         False  \n",
       "17         True           True          True  \n",
       "18         True           True          True  \n",
       "19         True           True          True  \n",
       "20         True           True         False  \n",
       "21         True           True          True  \n",
       "22         True           True         False  \n",
       "23         True           True          True  \n",
       "24         True           True         False  \n",
       "25         True           True          True  \n",
       "26         True           True          True  \n",
       "27         True           True         False  \n",
       "28         True           True          True  \n",
       "29         True           True         False  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# [KR] Pullback Rebound 후보 Top 30 (가격만, 점수형)\n",
    "def _rsi_kr(series, period=14):\n",
    "    delta = series.diff()\n",
    "    up = delta.clip(lower=0)\n",
    "    down = -delta.clip(upper=0)\n",
    "    roll_up = up.ewm(alpha=1/period, adjust=False).mean()\n",
    "    roll_down = down.ewm(alpha=1/period, adjust=False).mean()\n",
    "    rs = roll_up / roll_down\n",
    "    return 100 - (100 / (1 + rs))\n",
    "\n",
    "\n",
    "def build_pullback_rebound_kr(\n",
    "    price_df,\n",
    "    mansfield_df,\n",
    "    oneweek_vol=None,\n",
    "    rs_cut=RS_CUT,\n",
    "    ma_mid=MA_MID,\n",
    "    ma_short=MA_SHORT,\n",
    "    pullback_lookback=PULLBACK_LOOKBACK,\n",
    "    pullback_max=PULLBACK_MAX,\n",
    "    pullback_center=PULLBACK_CENTER,\n",
    "    rsi_soft=RSI_SOFT,\n",
    "    vol_quantile_cut=VOL_QUANTILE_CUT,\n",
    "    top_n=TOP_N_PULLBACK,\n",
    "):\n",
    "    if mansfield_df is None or mansfield_df.empty:\n",
    "        return pd.DataFrame()\n",
    "\n",
    "    strong = mansfield_df[mansfield_df[\"Norm_RS_avg\"] >= rs_cut].copy()\n",
    "    if strong.empty:\n",
    "        return pd.DataFrame()\n",
    "\n",
    "    universe = [t for t in strong[\"Ticker\"].tolist() if t in price_df.columns]\n",
    "    if not universe:\n",
    "        return pd.DataFrame()\n",
    "\n",
    "    rows = []\n",
    "    for t in universe:\n",
    "        s = price_df[t].dropna()\n",
    "        if len(s) < max(ma_mid, ma_short, pullback_lookback) + 5:\n",
    "            continue\n",
    "\n",
    "        last = float(s.iloc[-1])\n",
    "        ma_mid_v = float(s.rolling(ma_mid).mean().iloc[-1])\n",
    "        ma_short_v = float(s.rolling(ma_short).mean().iloc[-1])\n",
    "\n",
    "        hh = float(s.tail(pullback_lookback).max())\n",
    "        pullback_pct = (hh - last) / hh * 100.0\n",
    "        rsi14 = float(_rsi_kr(s, 14).iloc[-1])\n",
    "        dist60 = (last / ma_mid_v - 1.0) * 100.0\n",
    "\n",
    "        vol = np.nan\n",
    "        if oneweek_vol is not None and t in oneweek_vol.index:\n",
    "            vol = float(oneweek_vol.loc[t])\n",
    "\n",
    "        rows.append({\n",
    "            \"Ticker\": t,\n",
    "            \"Name\": company_map.get(t, t),\n",
    "            \"Last\": last,\n",
    "            f\"MA{ma_short}\": ma_short_v,\n",
    "            f\"MA{ma_mid}\": ma_mid_v,\n",
    "            \"Dist_to_MA60_%\": round(dist60, 2),\n",
    "            \"Pullback_20D_%\": round(pullback_pct, 2),\n",
    "            \"RSI14\": round(rsi14, 2),\n",
    "            \"Vol_1W_%\": round(vol, 2) if pd.notna(vol) else np.nan,\n",
    "            \"Norm_RS_avg\": float(strong.loc[strong[\"Ticker\"] == t, \"Norm_RS_avg\"].iloc[0]),\n",
    "        })\n",
    "\n",
    "    cand = pd.DataFrame(rows)\n",
    "    if cand.empty:\n",
    "        return cand\n",
    "\n",
    "    # 변동성 상위 5% 제외\n",
    "    if cand[\"Vol_1W_%\"].notna().sum() >= 10:\n",
    "        vol_cut = float(cand[\"Vol_1W_%\"].quantile(vol_quantile_cut))\n",
    "        cand = cand[cand[\"Vol_1W_%\"].isna() | (cand[\"Vol_1W_%\"] <= vol_cut)].copy()\n",
    "\n",
    "    if cand.empty:\n",
    "        return cand\n",
    "\n",
    "    # 점수화 (참조 파일과 동일 가중치)\n",
    "    rs_score = (cand[\"Norm_RS_avg\"] / 100.0).clip(0, 1)\n",
    "    pb = cand[\"Pullback_20D_%\"].clip(lower=0, upper=pullback_max * 100)\n",
    "    pb_score = (1 - (np.abs(pb - pullback_center) / pullback_center)).clip(lower=0)\n",
    "    rsi = cand[\"RSI14\"].clip(0, 100)\n",
    "    rsi_score = ((rsi_soft - rsi) / rsi_soft).clip(lower=0).clip(upper=1)\n",
    "    d = cand[\"Dist_to_MA60_%\"]\n",
    "    ma_score = (1 - (np.abs(d - 2.0) / 8.0)).clip(lower=0)\n",
    "\n",
    "    if cand[\"Vol_1W_%\"].notna().sum() > 0:\n",
    "        vmax = cand[\"Vol_1W_%\"].max()\n",
    "        vmin = cand[\"Vol_1W_%\"].min()\n",
    "        if vmax > vmin:\n",
    "            vol_score = (1 - (cand[\"Vol_1W_%\"] - vmin) / (vmax - vmin)).fillna(0.5)\n",
    "        else:\n",
    "            vol_score = pd.Series(0.5, index=cand.index)\n",
    "    else:\n",
    "        vol_score = pd.Series(0.5, index=cand.index)\n",
    "\n",
    "    cand[\"Score\"] = (\n",
    "        0.45 * rs_score +\n",
    "        0.20 * pb_score +\n",
    "        0.15 * rsi_score +\n",
    "        0.15 * ma_score +\n",
    "        0.05 * vol_score\n",
    "    ) * 100.0\n",
    "    cand[\"Score\"] = cand[\"Score\"].round(2)\n",
    "\n",
    "    cand[\"Pullback_OK\"] = cand[\"Pullback_20D_%\"].between(3, pullback_max * 100)\n",
    "    cand[\"RSI_low(<=55)\"] = cand[\"RSI14\"] <= rsi_soft\n",
    "    cand[\"MA60_up(>=0)\"] = cand[\"Dist_to_MA60_%\"] >= 0\n",
    "\n",
    "    cand = cand.sort_values(\"Score\", ascending=False).head(top_n).reset_index(drop=True)\n",
    "    return cand\n",
    "\n",
    "\n",
    "pullback_candidates_kr = build_pullback_rebound_kr(\n",
    "    price_df=assets_kr_daily,\n",
    "    mansfield_df=mansfield_df_kr_full,\n",
    "    oneweek_vol=oneweek_vol_kr if \"oneweek_vol_kr\" in dir() else None,\n",
    ")\n",
    "\n",
    "print(\"\\n🟢 Pullback Rebound 후보 Top 30 (한국 종목, 점수형):\")\n",
    "if pullback_candidates_kr.empty:\n",
    "    print(\"⚠️ 후보가 없습니다. RS_CUT=60 또는 VOL_QUANTILE_CUT=1.0로 완화 시도.\")\n",
    "else:\n",
    "    display(pullback_candidates_kr)\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": 172.872323,
   "end_time": "2026-10-29T21:48:35.586758+00:00",
   "environment_variables": {},
   "exception": null,
   "input_path": "/Users/seog-yejun/룬샤시_공시정리/태버지/4. 종목 상대강도.ipynb",
   "output_path": "/Users/seog-yejun/룬샤시_공시정리/태버지/4. 종목 상대강도.ipynb",
   "parameters": {},
   "start_time": "2026-10-29T21:45:42.714435+00:00",
   "version": "2.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}