<essential principles <principle name="drain metrics core" 庫存耗盡三維度量化 庫存分析的三維框架: - 方向(Direction) :庫存是上升還是下降 - 速度(Speed) :每週流出量 - 加速度(Acceleration) :流出速度的變化 當 且 時,表示「庫存正在流出,且流出速度在加快」——這是晚期供給訊號的核心特徵。 </principle <principle name="z score standardization" Z 分數標準化判斷 使用歷史視窗(建議 3 5 年)計算 Z 分數: - - 門檻判定: | 指標 | 門檻 | 意義 | |------------------|--------|----------------------| | z drain | ≤ -1.5 | 流出速度顯著大於常態 | | z accel | ≥ +1.0 | 流出正在加速 | | level percentile | ≤ 0.20 | 庫存處於歷史低檔 | </principle <principle name="three stage signal" 三段式晚期訊號判定 把推文敘事轉為可執行規則: | 條件 | 描述 | 單獨成立 | 組合效果 | |-----------------|----------…

, text):\n try:\n # 處理千位分隔符和 k/m 後綴\n num_text = text.replace(',', '')\n if 'k' in num_text.lower():\n value = float(num_text.lower().replace('k', '')) * 1000\n elif 'm' in num_text.lower():\n value = float(num_text.lower().replace('m', '')) * 1000000\n else:\n value = float(num_text)\n y_labels.append(value)\n except ValueError:\n pass\n\n return x_labels if x_labels else None, y_labels if y_labels else None\n\n\ndef parse_svg_path_data(svg_content: str) -> Optional[List[Tuple[float, float]]]:\n \"\"\"\n 從 SVG 路徑中提取座標點\n\n Returns:\n 座標點列表 [(x1, y1), (x2, y2), ...]\n \"\"\"\n # 尋找 area 或 line 路徑\n # Highcharts 使用 class=\"highcharts-area\" 或 \"highcharts-graph\"\n path_patterns = [\n r'\u003cpath[^>]*class=\"[^\"]*highcharts-area[^\"]*\"[^>]*d=\"([^\"]+)\"',\n r'\u003cpath[^>]*class=\"[^\"]*highcharts-graph[^\"]*\"[^>]*d=\"([^\"]+)\"',\n r'\u003cpath[^>]*d=\"(M[^\"]+L[^\"]+)\"[^>]*class=\"[^\"]*highcharts',\n r'd=\"(M[\\d\\s.,LHVCSQTAZlhvcsqtaz-]+)\"',\n ]\n\n path_data = None\n for pattern in path_patterns:\n match = re.search(pattern, svg_content, re.IGNORECASE)\n if match:\n path_data = match.group(1)\n break\n\n if not path_data:\n # 嘗試找最長的路徑(通常是數據路徑)\n all_paths = re.findall(r'd=\"(M[^\"]+)\"', svg_content)\n if all_paths:\n path_data = max(all_paths, key=len)\n\n if not path_data:\n return None\n\n # 解析路徑命令\n points = []\n\n # SVG 路徑命令: M (移動), L (直線), H (水平), V (垂直), C (曲線), Z (閉合)\n # 簡化處理:只提取 M 和 L 命令的座標\n\n # 先將路徑正規化\n path_data = re.sub(r'([MLHVCSQTAZmlhvcsqtaz])', r' \\1 ', path_data)\n path_data = re.sub(r',', ' ', path_data)\n path_data = re.sub(r'\\s+', ' ', path_data).strip()\n\n tokens = path_data.split()\n i = 0\n current_x, current_y = 0, 0\n\n while i \u003c len(tokens):\n cmd = tokens[i]\n\n if cmd in ['M', 'm']:\n # 移動命令\n if i + 2 \u003c len(tokens):\n try:\n x, y = float(tokens[i+1]), float(tokens[i+2])\n if cmd == 'm':\n x += current_x\n y += current_y\n current_x, current_y = x, y\n points.append((x, y))\n i += 3\n except ValueError:\n i += 1\n else:\n i += 1\n\n elif cmd in ['L', 'l']:\n # 直線命令\n if i + 2 \u003c len(tokens):\n try:\n x, y = float(tokens[i+1]), float(tokens[i+2])\n if cmd == 'l':\n x += current_x\n y += current_y\n current_x, current_y = x, y\n points.append((x, y))\n i += 3\n except ValueError:\n i += 1\n else:\n i += 1\n\n elif cmd in ['H', 'h']:\n # 水平線\n if i + 1 \u003c len(tokens):\n try:\n x = float(tokens[i+1])\n if cmd == 'h':\n x += current_x\n current_x = x\n points.append((current_x, current_y))\n i += 2\n except ValueError:\n i += 1\n else:\n i += 1\n\n elif cmd in ['V', 'v']:\n # 垂直線\n if i + 1 \u003c len(tokens):\n try:\n y = float(tokens[i+1])\n if cmd == 'v':\n y += current_y\n current_y = y\n points.append((current_x, current_y))\n i += 2\n except ValueError:\n i += 1\n else:\n i += 1\n\n elif cmd in ['Z', 'z']:\n i += 1\n\n else:\n # 可能是隱式的 L 命令(連續座標)\n try:\n x = float(cmd)\n if i + 1 \u003c len(tokens):\n y = float(tokens[i+1])\n current_x, current_y = x, y\n points.append((x, y))\n i += 2\n else:\n i += 1\n except ValueError:\n i += 1\n\n return points if len(points) > 10 else None\n\n\ndef extract_plot_bounds(svg_content: str) -> Tuple[float, float, float, float, float, float]:\n \"\"\"\n 從 SVG 中提取繪圖區域邊界和變換\n\n Returns:\n (plot_left, plot_right, plot_top, plot_bottom, translate_x, translate_y)\n\n 注意: Highcharts 的數據路徑通常有 transform=\"translate(x,y)\"\n \"\"\"\n # 尋找 highcharts-plot-background 元素\n match = re.search(\n r'highcharts-plot-background[^/]*x=\"([\\d.]+)\"[^/]*y=\"([\\d.]+)\"[^/]*width=\"([\\d.]+)\"[^/]*height=\"([\\d.]+)\"',\n svg_content\n )\n\n # 尋找 translate 變換\n translate_match = re.search(\n r'transform=\"translate\\(([\\d.]+),([\\d.]+)\\)',\n svg_content\n )\n translate_x = float(translate_match.group(1)) if translate_match else 0\n translate_y = float(translate_match.group(2)) if translate_match else 0\n\n if match:\n w = float(match.group(3))\n h = float(match.group(4))\n # 路徑座標是相對於變換後的原點,所以使用 0 到 width/height\n return (0, w, 0, h, translate_x, translate_y)\n\n # 預設值\n return (0, 1094, 0, 399, 76, 10)\n\n\ndef convert_svg_coords_to_data(\n points: List[Tuple[float, float]],\n x_labels: Optional[List[str]],\n y_labels: Optional[List[float]],\n date_range: Tuple[datetime, datetime],\n svg_content: str = \"\",\n) -> pd.DataFrame:\n \"\"\"\n 將 SVG 座標轉換為實際數據\n\n 這是一個近似轉換,假設線性映射\n \"\"\"\n start_date, end_date = date_range\n\n # 從 SVG 中提取繪圖區域邊界\n plot_left, plot_right, plot_top, plot_bottom, tx, ty = extract_plot_bounds(svg_content)\n print(f\" 繪圖區域: x=[{plot_left}, {plot_right}], y=[{plot_top}, {plot_bottom}], translate=({tx}, {ty})\")\n\n plot_width = plot_right - plot_left\n plot_height = plot_bottom - plot_top\n\n # 確定 Y 軸範圍\n if y_labels and len(y_labels) >= 2:\n y_min = min(y_labels)\n y_max = max(y_labels)\n else:\n # 從點推斷\n y_coords = [p[1] for p in points]\n y_coord_min = min(y_coords)\n y_coord_max = max(y_coords)\n # 假設典型範圍\n y_min = 0\n y_max = 3500 # 白銀庫存典型最大值約 3500 噸\n\n # 計算時間範圍(天數)\n total_days = (end_date - start_date).days\n\n results = []\n for px, py in points:\n # X 座標 -> 日期\n x_ratio = (px - plot_left) / plot_width if plot_width > 0 else 0\n x_ratio = max(0, min(1, x_ratio))\n days_offset = int(x_ratio * total_days)\n date = start_date + timedelta(days=days_offset)\n\n # Y 座標 -> 數值(注意 SVG Y 軸是反的)\n y_ratio = (plot_bottom - py) / plot_height if plot_height > 0 else 0\n y_ratio = max(0, min(1, y_ratio))\n value = y_min + y_ratio * (y_max - y_min)\n\n results.append({\n 'date': date,\n 'stock_tonnes': round(value, 2),\n 'px': px,\n 'py': py,\n })\n\n df = pd.DataFrame(results)\n\n # 去重(按日期)\n df = df.drop_duplicates(subset=['date'], keep='first')\n df = df.sort_values('date').reset_index(drop=True)\n\n return df\n\n\ndef fetch_ceic_silver_data(\n start_date: Optional[str] = None,\n end_date: Optional[str] = None,\n use_selenium: bool = True,\n headless: bool = True,\n debug: bool = False,\n) -> Optional[pd.DataFrame]:\n \"\"\"\n 從 CEIC 獲取白銀庫存數據\n\n Parameters\n ----------\n start_date : str, optional\n 起始日期 (YYYY-MM-DD),預設為 5 年前\n end_date : str, optional\n 結束日期 (YYYY-MM-DD),預設為今天\n use_selenium : bool\n 是否使用 Selenium(更可靠)\n headless : bool\n 是否無頭模式\n debug : bool\n 是否保存除錯資訊\n \"\"\"\n # 設定日期範圍\n if end_date is None:\n end_dt = datetime.now()\n else:\n end_dt = datetime.strptime(end_date, \"%Y-%m-%d\")\n\n if start_date is None:\n start_dt = end_dt - timedelta(days=5*365)\n else:\n start_dt = datetime.strptime(start_date, \"%Y-%m-%d\")\n\n # 構建 URL\n url = f\"{CHART_BASE_URL}?type=area&from={start_dt.strftime('%Y-%m-%d')}&to={end_dt.strftime('%Y-%m-%d')}&lang=zh-hans\"\n\n print(\"=\" * 60)\n print(\"CEIC 上海期貨交易所白銀庫存數據抓取\")\n print(\"=\" * 60)\n print(f\"\\n日期範圍: {start_dt.date()} ~ {end_dt.date()}\")\n print(f\"圖表 URL: {url[:80]}...\")\n\n # 獲取 SVG 內容\n print(\"\\n[1] 獲取 SVG 內容...\")\n svg_content = None\n\n if use_selenium and HAS_SELENIUM:\n svg_content = fetch_svg_with_selenium(url, headless=headless)\n\n if not svg_content:\n svg_content = fetch_svg_with_requests(url)\n\n if not svg_content:\n print(\"無法獲取 SVG 內容\")\n return None\n\n # 保存除錯資訊\n if debug:\n DEBUG_DIR.mkdir(parents=True, exist_ok=True)\n with open(DEBUG_DIR / \"ceic_chart.svg\", \"w\", encoding=\"utf-8\") as f:\n f.write(svg_content)\n print(f\" 已保存 SVG 至 {DEBUG_DIR / 'ceic_chart.svg'}\")\n\n # 解析 SVG\n print(\"\\n[2] 解析 SVG...\")\n\n # 提取軸標籤\n x_labels, y_labels = parse_svg_axis_labels(svg_content)\n print(f\" X 軸標籤: {x_labels[:5] if x_labels else 'N/A'}...\")\n print(f\" Y 軸標籤: {y_labels if y_labels else 'N/A'}\")\n\n # 提取路徑數據\n points = parse_svg_path_data(svg_content)\n if not points:\n print(\" 無法從 SVG 中提取路徑數據\")\n return None\n\n print(f\" 提取到 {len(points)} 個座標點\")\n\n # 轉換座標\n print(\"\\n[3] 轉換座標為實際數據...\")\n df = convert_svg_coords_to_data(\n points=points,\n x_labels=x_labels,\n y_labels=y_labels,\n date_range=(start_dt, end_dt),\n svg_content=svg_content,\n )\n\n print(f\"\\n轉換完成,共 {len(df)} 條記錄\")\n print(f\"日期範圍: {df['date'].min()} ~ {df['date'].max()}\")\n print(f\"數值範圍: {df['stock_tonnes'].min():.1f} ~ {df['stock_tonnes'].max():.1f} 噸\")\n\n print(\"\\n最新 5 條記錄:\")\n print(df[['date', 'stock_tonnes']].tail())\n\n return df[['date', 'stock_tonnes']]\n\n\ndef main():\n import argparse\n\n parser = argparse.ArgumentParser(description=\"從 CEIC SVG 圖表抓取數據\")\n parser.add_argument(\"--start\", type=str, help=\"起始日期 (YYYY-MM-DD)\")\n parser.add_argument(\"--end\", type=str, help=\"結束日期 (YYYY-MM-DD)\")\n parser.add_argument(\"--output\", type=str, default=str(DATA_DIR / \"ceic_shfe_silver.csv\"))\n parser.add_argument(\"--no-selenium\", action=\"store_true\", help=\"不使用 Selenium\")\n parser.add_argument(\"--no-headless\", action=\"store_true\", help=\"顯示瀏覽器\")\n parser.add_argument(\"--debug\", action=\"store_true\", help=\"保存除錯資訊\")\n\n args = parser.parse_args()\n\n df = fetch_ceic_silver_data(\n start_date=args.start,\n end_date=args.end,\n use_selenium=not args.no_selenium,\n headless=not args.no_headless,\n debug=args.debug,\n )\n\n if df is not None:\n output_path = Path(args.output)\n output_path.parent.mkdir(parents=True, exist_ok=True)\n df.to_csv(output_path, index=False)\n print(f\"\\n數據已保存至: {output_path}\")\n else:\n print(\"\\n抓取失敗\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":16875,"content_sha256":"f0d3cd10ea1002be35e5492e27cfd968fd4d06341a3cb34410bbda33b6a7b0b2"},{"filename":"scripts/fetch_sge_stock.py","content":"#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nSGE(上海黃金交易所)白銀庫存數據抓取\n\n注意:目前主要數據源為 CEIC (SHFE 數據),SGE 數據作為補充來源。\nCEIC 提供的 SHFE 白銀倉單數據已經足夠進行耗盡分析。\n\n如需 SGE 數據,可從以下來源獲取:\n- SGE 官網「行情周報」PDF\n- 第三方數據服務\n\nUsage:\n python fetch_sge_stock.py --output data/sge_stock.csv\n python fetch_sge_stock.py --from-ceic # 使用 CEIC 數據(推薦)\n\"\"\"\n\nimport argparse\nfrom datetime import datetime, timedelta\nfrom pathlib import Path\n\nimport pandas as pd\n\n# 設定目錄\nSCRIPT_DIR = Path(__file__).parent\nSKILL_DIR = SCRIPT_DIR.parent\nDATA_DIR = SKILL_DIR / \"data\"\n\n\ndef fetch_from_ceic_as_proxy() -> pd.DataFrame:\n \"\"\"\n 使用 CEIC 的 SHFE 數據作為代理\n\n 說明:CEIC 提供的是 SHFE(上海期貨交易所)白銀倉單數據,\n 這與 SGE(上海黃金交易所)是不同的交易所。\n 但對於白銀庫存耗盡分析,SHFE 數據已經足夠代表性。\n \"\"\"\n from fetch_shfe_stock import fetch_ceic_shfe_data\n\n print(\"使用 CEIC SHFE 數據作為代理...\")\n df = fetch_ceic_shfe_data()\n\n if df is not None:\n # 轉換格式\n df_output = df.copy()\n df_output['stock_kg'] = df_output['stock_tonnes'] * 1000\n return df_output[['date', 'stock_kg']]\n\n return None\n\n\ndef main():\n \"\"\"主程式入口\"\"\"\n parser = argparse.ArgumentParser(description=\"SGE 白銀庫存數據抓取\")\n parser.add_argument(\"--output\", type=str, default=str(DATA_DIR / \"sge_stock.csv\"), help=\"輸出檔案路徑\")\n parser.add_argument(\"--from-ceic\", action=\"store_true\", help=\"使用 CEIC 數據作為代理(推薦)\")\n parser.add_argument(\"--force-update\", action=\"store_true\", help=\"強制更新\")\n\n args = parser.parse_args()\n\n DATA_DIR.mkdir(parents=True, exist_ok=True)\n output_path = Path(args.output)\n\n # 檢查快取\n if output_path.exists() and not args.force_update:\n mtime = datetime.fromtimestamp(output_path.stat().st_mtime)\n cache_age = datetime.now() - mtime\n if cache_age \u003c timedelta(hours=12):\n print(f\"使用快取數據({cache_age.total_seconds()/3600:.1f} 小時前更新)\")\n df = pd.read_csv(output_path, parse_dates=[\"date\"])\n print(f\"數據範圍: {df['date'].min()} ~ {df['date'].max()}\")\n print(f\"記錄數: {len(df)}\")\n return\n\n print(\"=\" * 60)\n print(\"SGE 白銀庫存數據\")\n print(\"=\" * 60)\n print(\"\\n注意:目前主要使用 CEIC 的 SHFE 數據進行分析。\")\n print(\"SGE 數據作為補充來源,如需要請手動提供。\")\n print(\"\\n建議使用: python fetch_shfe_stock.py --force-update\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":2832,"content_sha256":"36ac121c01d684e48ad6e74b56854d58e1b5f81d8ef2868e6d6e2c400e92edba"},{"filename":"scripts/fetch_shfe_stock.py","content":"#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nSHFE(上海期貨交易所)白銀庫存數據抓取\n\n主要數據源:CEIC Data (通過 SVG 圖表解析)\n- URL: https://www.ceicdata.com/zh-hans/china/shanghai-futures-exchange-commodity-futures-stock/cn-warehouse-stock-shanghai-future-exchange-silver\n- 數據範圍:2012-07-02 至今\n- 更新頻率:每日\n\nUsage:\n python fetch_shfe_stock.py --output data/shfe_stock.csv\n python fetch_shfe_stock.py --weeks 50 # 獲取最近 50 週數據\n python fetch_shfe_stock.py --years 5 # 獲取 5 年數據\n python fetch_shfe_stock.py --force-update # 強制更新\n python fetch_shfe_stock.py --debug # 除錯模式\n\"\"\"\n\nimport argparse\nimport random\nimport re\nimport time\nfrom datetime import datetime, timedelta\nfrom pathlib import Path\nfrom typing import List, Optional, Tuple\n\nimport pandas as pd\n\ntry:\n from selenium import webdriver\n from selenium.webdriver.chrome.options import Options\n from selenium.webdriver.chrome.service import Service\n from webdriver_manager.chrome import ChromeDriverManager\n HAS_SELENIUM = True\nexcept ImportError:\n HAS_SELENIUM = False\n print(\"警告: Selenium 未安裝\")\n print(\"請執行: pip install selenium webdriver-manager\")\n\n# 設定目錄\nSCRIPT_DIR = Path(__file__).parent\nSKILL_DIR = SCRIPT_DIR.parent\nDATA_DIR = SKILL_DIR / \"data\"\nDEBUG_DIR = DATA_DIR / \"debug\"\n\n# CEIC 圖表 URL\nCEIC_CHART_URL = \"https://www.ceicdata.com/datapage/charts/o_china_cn-warehouse-stock-shanghai-future-exchange-silver/\"\n\n# User-Agent 列表(模擬人類瀏覽器)\nUSER_AGENTS = [\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',\n]\n\n\ndef get_stealth_driver(headless: bool = True):\n \"\"\"建立防偵測的 Chrome Driver\"\"\"\n if not HAS_SELENIUM:\n raise ImportError(\"Selenium 未安裝,請執行: pip install selenium webdriver-manager\")\n\n options = Options()\n if headless:\n options.add_argument('--headless=new')\n options.add_argument('--no-sandbox')\n options.add_argument('--disable-dev-shm-usage')\n options.add_argument('--window-size=1920,1080')\n options.add_argument('--disable-blink-features=AutomationControlled')\n options.add_experimental_option('excludeSwitches', ['enable-automation'])\n\n user_agent = random.choice(USER_AGENTS)\n options.add_argument(f'user-agent={user_agent}')\n\n service = Service(ChromeDriverManager().install())\n driver = webdriver.Chrome(service=service, options=options)\n driver.set_page_load_timeout(120)\n return driver\n\n\ndef random_delay(min_sec: float = 1.0, max_sec: float = 3.0):\n \"\"\"隨機延遲(模擬人類行為)\"\"\"\n time.sleep(random.uniform(min_sec, max_sec))\n\n\ndef fetch_svg_content(url: str, headless: bool = True) -> Optional[str]:\n \"\"\"使用 Selenium 獲取 SVG 內容\"\"\"\n driver = None\n try:\n print(f\" 正在獲取圖表數據...\")\n driver = get_stealth_driver(headless=headless)\n random_delay(1, 2)\n driver.get(url)\n time.sleep(3)\n\n content = driver.page_source\n if '\u003csvg' in content:\n start = content.find('\u003csvg')\n end = content.find('\u003c/svg>') + 6\n if start != -1 and end > start:\n return content[start:end]\n return content\n\n except Exception as e:\n print(f\" 獲取失敗: {e}\")\n return None\n finally:\n if driver:\n driver.quit()\n\n\ndef extract_plot_bounds(svg_content: str) -> Tuple[float, float, float, float]:\n \"\"\"從 SVG 中提取繪圖區域邊界\"\"\"\n match = re.search(\n r'highcharts-plot-background[^/]*x=\"([\\d.]+)\"[^/]*y=\"([\\d.]+)\"[^/]*width=\"([\\d.]+)\"[^/]*height=\"([\\d.]+)\"',\n svg_content\n )\n if match:\n w = float(match.group(3))\n h = float(match.group(4))\n return (0, w, 0, h)\n return (0, 1094, 0, 399)\n\n\ndef parse_svg_axis_labels(svg_content: str) -> Tuple[Optional[List[str]], Optional[List[float]]]:\n \"\"\"從 SVG 中解析軸標籤\"\"\"\n x_labels = []\n y_labels = []\n\n text_pattern = r'\u003ctext[^>]*>([^\u003c]+)\u003c/text>'\n texts = re.findall(text_pattern, svg_content)\n\n for text in texts:\n text = text.strip()\n if re.match(r\"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\", text, re.I):\n x_labels.append(text)\n elif re.match(r\"20\\d{2}\", text):\n x_labels.append(text)\n\n if re.match(r'^[\\d,\\.]+[kKmM]?

<essential principles <principle name="drain metrics core" 庫存耗盡三維度量化 庫存分析的三維框架: - 方向(Direction) :庫存是上升還是下降 - 速度(Speed) :每週流出量 - 加速度(Acceleration) :流出速度的變化 當 且 時,表示「庫存正在流出,且流出速度在加快」——這是晚期供給訊號的核心特徵。 </principle <principle name="z score standardization" Z 分數標準化判斷 使用歷史視窗(建議 3 5 年)計算 Z 分數: - - 門檻判定: | 指標 | 門檻 | 意義 | |------------------|--------|----------------------| | z drain | ≤ -1.5 | 流出速度顯著大於常態 | | z accel | ≥ +1.0 | 流出正在加速 | | level percentile | ≤ 0.20 | 庫存處於歷史低檔 | </principle <principle name="three stage signal" 三段式晚期訊號判定 把推文敘事轉為可執行規則: | 條件 | 描述 | 單獨成立 | 組合效果 | |-----------------|----------…

, text):\n try:\n num_text = text.replace(',', '')\n if 'k' in num_text.lower():\n value = float(num_text.lower().replace('k', '')) * 1000\n elif 'm' in num_text.lower():\n value = float(num_text.lower().replace('m', '')) * 1000000\n else:\n value = float(num_text)\n y_labels.append(value)\n except ValueError:\n pass\n\n return x_labels if x_labels else None, y_labels if y_labels else None\n\n\ndef parse_svg_path_data(svg_content: str) -> Optional[List[Tuple[float, float]]]:\n \"\"\"從 SVG 路徑中提取座標點\"\"\"\n all_paths = re.findall(r'd=\"(M[^\"]+)\"', svg_content)\n if not all_paths:\n return None\n\n path_data = max(all_paths, key=len)\n points = []\n\n path_data = re.sub(r'([MLHVCSQTAZmlhvcsqtaz])', r' \\1 ', path_data)\n path_data = re.sub(r',', ' ', path_data)\n path_data = re.sub(r'\\s+', ' ', path_data).strip()\n\n tokens = path_data.split()\n i = 0\n current_x, current_y = 0, 0\n\n while i \u003c len(tokens):\n cmd = tokens[i]\n\n if cmd in ['M', 'm']:\n if i + 2 \u003c len(tokens):\n try:\n x, y = float(tokens[i+1]), float(tokens[i+2])\n if cmd == 'm':\n x += current_x\n y += current_y\n current_x, current_y = x, y\n points.append((x, y))\n i += 3\n except ValueError:\n i += 1\n else:\n i += 1\n\n elif cmd in ['L', 'l']:\n if i + 2 \u003c len(tokens):\n try:\n x, y = float(tokens[i+1]), float(tokens[i+2])\n if cmd == 'l':\n x += current_x\n y += current_y\n current_x, current_y = x, y\n points.append((x, y))\n i += 3\n except ValueError:\n i += 1\n else:\n i += 1\n\n elif cmd in ['H', 'h']:\n if i + 1 \u003c len(tokens):\n try:\n x = float(tokens[i+1])\n if cmd == 'h':\n x += current_x\n current_x = x\n points.append((current_x, current_y))\n i += 2\n except ValueError:\n i += 1\n else:\n i += 1\n\n elif cmd in ['V', 'v']:\n if i + 1 \u003c len(tokens):\n try:\n y = float(tokens[i+1])\n if cmd == 'v':\n y += current_y\n current_y = y\n points.append((current_x, current_y))\n i += 2\n except ValueError:\n i += 1\n else:\n i += 1\n\n elif cmd in ['Z', 'z']:\n i += 1\n\n else:\n try:\n x = float(cmd)\n if i + 1 \u003c len(tokens):\n y = float(tokens[i+1])\n current_x, current_y = x, y\n points.append((x, y))\n i += 2\n else:\n i += 1\n except ValueError:\n i += 1\n\n return points if len(points) > 10 else None\n\n\ndef convert_svg_to_dataframe(\n points: List[Tuple[float, float]],\n y_labels: Optional[List[float]],\n date_range: Tuple[datetime, datetime],\n svg_content: str\n) -> pd.DataFrame:\n \"\"\"將 SVG 座標轉換為實際數據\"\"\"\n start_date, end_date = date_range\n plot_left, plot_right, plot_top, plot_bottom = extract_plot_bounds(svg_content)\n\n plot_width = plot_right - plot_left\n plot_height = plot_bottom - plot_top\n\n if y_labels and len(y_labels) >= 2:\n y_min = min(y_labels)\n y_max = max(y_labels)\n else:\n y_min = 0\n y_max = 3500\n\n total_days = (end_date - start_date).days\n\n results = []\n for px, py in points:\n x_ratio = (px - plot_left) / plot_width if plot_width > 0 else 0\n x_ratio = max(0, min(1, x_ratio))\n days_offset = int(x_ratio * total_days)\n date = start_date + timedelta(days=days_offset)\n\n y_ratio = (plot_bottom - py) / plot_height if plot_height > 0 else 0\n y_ratio = max(0, min(1, y_ratio))\n value = y_min + y_ratio * (y_max - y_min)\n\n results.append({\n 'date': date,\n 'stock_tonnes': round(value, 2),\n })\n\n df = pd.DataFrame(results)\n df = df.drop_duplicates(subset=['date'], keep='first')\n df = df.sort_values('date').reset_index(drop=True)\n return df\n\n\ndef fetch_ceic_shfe_data(\n start_date: Optional[str] = None,\n end_date: Optional[str] = None,\n headless: bool = True,\n debug: bool = False,\n) -> Optional[pd.DataFrame]:\n \"\"\"\n 從 CEIC 獲取 SHFE 白銀庫存數據\n\n Parameters\n ----------\n start_date : str, optional\n 起始日期 (YYYY-MM-DD),預設為 5 年前\n end_date : str, optional\n 結束日期 (YYYY-MM-DD),預設為今天\n headless : bool\n 是否無頭模式\n debug : bool\n 是否保存除錯資訊\n \"\"\"\n if end_date is None:\n end_dt = datetime.now()\n else:\n end_dt = datetime.strptime(end_date, \"%Y-%m-%d\")\n\n if start_date is None:\n start_dt = end_dt - timedelta(days=5*365)\n else:\n start_dt = datetime.strptime(start_date, \"%Y-%m-%d\")\n\n url = f\"{CEIC_CHART_URL}?type=area&from={start_dt.strftime('%Y-%m-%d')}&to={end_dt.strftime('%Y-%m-%d')}&lang=zh-hans\"\n\n print(f\"\\n日期範圍: {start_dt.date()} ~ {end_dt.date()}\")\n\n # 獲取 SVG\n svg_content = fetch_svg_content(url, headless=headless)\n if not svg_content:\n print(\"無法獲取 SVG 內容\")\n return None\n\n if debug:\n DEBUG_DIR.mkdir(parents=True, exist_ok=True)\n with open(DEBUG_DIR / \"ceic_chart.svg\", \"w\", encoding=\"utf-8\") as f:\n f.write(svg_content)\n\n # 解析 SVG\n _, y_labels = parse_svg_axis_labels(svg_content)\n points = parse_svg_path_data(svg_content)\n\n if not points:\n print(\"無法從 SVG 中提取路徑數據\")\n return None\n\n print(f\" 提取到 {len(points)} 個數據點\")\n\n # 轉換為 DataFrame\n df = convert_svg_to_dataframe(\n points=points,\n y_labels=y_labels,\n date_range=(start_dt, end_dt),\n svg_content=svg_content,\n )\n\n return df\n\n\ndef extract_weekly_data(df: pd.DataFrame, day_of_week: int = 4) -> pd.DataFrame:\n \"\"\"\n 提取週報數據(預設為週五)\n\n Parameters\n ----------\n df : pd.DataFrame\n 每日數據\n day_of_week : int\n 週幾 (0=週一, 4=週五)\n \"\"\"\n df = df.copy()\n df['date'] = pd.to_datetime(df['date'])\n df['weekday'] = df['date'].dt.dayofweek\n\n weekly = df[df['weekday'] == day_of_week].copy()\n weekly = weekly.drop(columns=['weekday'])\n weekly = weekly.sort_values('date').reset_index(drop=True)\n\n return weekly\n\n\ndef main():\n \"\"\"主程式入口\"\"\"\n parser = argparse.ArgumentParser(description=\"SHFE 白銀庫存數據抓取(CEIC 數據源)\")\n parser.add_argument(\"--years\", type=int, default=5, help=\"獲取幾年數據(預設 5 年)\")\n parser.add_argument(\"--weeks\", type=int, help=\"獲取最近幾週數據(覆蓋 --years)\")\n parser.add_argument(\"--start\", type=str, help=\"起始日期 (YYYY-MM-DD)\")\n parser.add_argument(\"--end\", type=str, help=\"結束日期 (YYYY-MM-DD)\")\n parser.add_argument(\"--output\", type=str, default=str(DATA_DIR / \"shfe_stock.csv\"), help=\"輸出檔案路徑\")\n parser.add_argument(\"--weekly-output\", type=str, default=str(DATA_DIR / \"shfe_weekly_stock.csv\"), help=\"週報輸出檔案\")\n parser.add_argument(\"--force-update\", action=\"store_true\", help=\"強制更新(忽略快取)\")\n parser.add_argument(\"--no-headless\", action=\"store_true\", help=\"顯示瀏覽器\")\n parser.add_argument(\"--debug\", action=\"store_true\", help=\"除錯模式\")\n\n args = parser.parse_args()\n\n DATA_DIR.mkdir(parents=True, exist_ok=True)\n\n output_path = Path(args.output)\n weekly_output_path = Path(args.weekly_output)\n\n # 檢查快取\n if output_path.exists() and not args.force_update:\n mtime = datetime.fromtimestamp(output_path.stat().st_mtime)\n cache_age = datetime.now() - mtime\n if cache_age \u003c timedelta(hours=12):\n print(f\"使用快取數據({cache_age.total_seconds()/3600:.1f} 小時前更新)\")\n df = pd.read_csv(output_path, parse_dates=[\"date\"])\n print(f\"數據範圍: {df['date'].min()} ~ {df['date'].max()}\")\n print(f\"記錄數: {len(df)}\")\n return\n\n # 計算日期範圍\n end_date = args.end or datetime.now().strftime(\"%Y-%m-%d\")\n\n if args.weeks:\n start_date = (datetime.strptime(end_date, \"%Y-%m-%d\") - timedelta(weeks=args.weeks)).strftime(\"%Y-%m-%d\")\n elif args.start:\n start_date = args.start\n else:\n start_date = (datetime.strptime(end_date, \"%Y-%m-%d\") - timedelta(days=args.years*365)).strftime(\"%Y-%m-%d\")\n\n print(\"=\" * 60)\n print(\"SHFE 白銀庫存數據抓取(CEIC 數據源)\")\n print(\"=\" * 60)\n\n # 抓取數據\n df = fetch_ceic_shfe_data(\n start_date=start_date,\n end_date=end_date,\n headless=not args.no_headless,\n debug=args.debug,\n )\n\n if df is None or len(df) == 0:\n print(\"\\n抓取失敗\")\n return\n\n # 轉換為 kg 並保存(兼容原有格式)\n df_output = df.copy()\n df_output['stock_kg'] = df_output['stock_tonnes'] * 1000\n df_output = df_output[['date', 'stock_kg']]\n\n output_path.parent.mkdir(parents=True, exist_ok=True)\n df_output.to_csv(output_path, index=False)\n print(f\"\\n每日數據已保存至: {output_path}\")\n print(f\"數據範圍: {df['date'].min()} ~ {df['date'].max()}\")\n print(f\"記錄數: {len(df)}\")\n\n # 提取週報數據\n weekly_df = extract_weekly_data(df)\n weekly_df['stock_kg'] = weekly_df['stock_tonnes'] * 1000\n\n weekly_df[['date', 'stock_kg']].to_csv(weekly_output_path, index=False)\n print(f\"\\n週報數據已保存至: {weekly_output_path}\")\n print(f\"週報數據範圍: {weekly_df['date'].min()} ~ {weekly_df['date'].max()}\")\n print(f\"週報記錄數: {len(weekly_df)}\")\n\n # 顯示最近數據\n print(f\"\\n最近 10 週數據:\")\n for _, row in weekly_df.tail(10).iterrows():\n print(f\" {row['date'].strftime('%Y-%m-%d')} (週五): {row['stock_tonnes']:.2f} 噸\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":15452,"content_sha256":"141fd95a35618862b3f5becacc25028c6ecccfa749d9c08eac0142a2cc423fab"},{"filename":"scripts/visualize_drain.py","content":"#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n上海白銀庫存耗盡視覺化報告生成器 (Bloomberg 風格)\n\n根據分析結果生成視覺化圖表和報告。\n\nUsage:\n python visualize_drain.py --result result.json --output ../../../output/\n\"\"\"\n\nimport argparse\nimport json\nfrom datetime import datetime\nfrom pathlib import Path\nfrom typing import Dict, Any\n\nimport pandas as pd\nimport numpy as np\n\ntry:\n import matplotlib\n matplotlib.use('Agg') # 非交互式後端,必須在 import pyplot 前設定\n\n import matplotlib.pyplot as plt\n import matplotlib.dates as mdates\n from matplotlib.ticker import FuncFormatter\n\n # 中文字體設定\n plt.rcParams['font.sans-serif'] = [\n 'Microsoft JhengHei',\n 'SimHei',\n 'Microsoft YaHei',\n 'PingFang TC',\n 'Noto Sans CJK TC',\n 'DejaVu Sans'\n ]\n plt.rcParams['axes.unicode_minus'] = False\nexcept ImportError:\n plt = None\n print(\"警告: matplotlib 未安裝,視覺化功能不可用\")\n print(\"請執行: pip install matplotlib\")\n\n# Bloomberg 風格配色\nCOLORS = {\n \"background\": \"#1a1a2e\",\n \"grid\": \"#2d2d44\",\n \"text\": \"#ffffff\",\n \"text_dim\": \"#888888\",\n \"primary\": \"#ff6b35\",\n \"secondary\": \"#ffaa00\",\n \"tertiary\": \"#ffff00\",\n \"area_fill\": \"#ff8c00\",\n \"area_alpha\": 0.4,\n \"level_line\": \"#666666\",\n \"signal_high\": \"#ff4444\",\n \"signal_medium\": \"#ffaa00\",\n \"signal_watch\": \"#ffff00\",\n \"signal_none\": \"#00ff88\",\n}\n\n# 設定目錄\nSCRIPT_DIR = Path(__file__).parent\nSKILL_DIR = SCRIPT_DIR.parent\nDATA_DIR = SKILL_DIR / \"data\"\n\n\ndef load_combined_stock() -> pd.DataFrame:\n \"\"\"\n 載入庫存時間序列(主要從 CEIC/SHFE 數據)\n\n Returns\n -------\n pd.DataFrame\n 庫存時間序列\n \"\"\"\n # 主要使用 SHFE 數據(來自 CEIC)\n shfe_path = DATA_DIR / \"shfe_stock.csv\"\n\n if not shfe_path.exists():\n raise FileNotFoundError(\n f\"找不到數據檔案: {shfe_path}\\n\"\n \"請先執行: python fetch_shfe_stock.py --force-update\"\n )\n\n df = pd.read_csv(shfe_path, parse_dates=[\"date\"])\n\n # 轉換單位\n if \"stock_kg\" in df.columns:\n df[\"combined_kg\"] = df[\"stock_kg\"]\n df[\"combined_tonnes\"] = df[\"stock_kg\"] / 1000.0\n elif \"stock_tonnes\" in df.columns:\n df[\"combined_tonnes\"] = df[\"stock_tonnes\"]\n df[\"combined_kg\"] = df[\"stock_tonnes\"] * 1000\n\n df = df.sort_values(\"date\").reset_index(drop=True)\n\n # 計算指標\n df[\"delta1\"] = df[\"combined_tonnes\"].diff(1)\n df[\"drain_rate\"] = -df[\"delta1\"]\n df[\"drain_rate_sm\"] = df[\"drain_rate\"].rolling(4, min_periods=1).mean()\n\n # Z 分數\n z_window = 156\n rolling_mean = df[\"drain_rate_sm\"].rolling(z_window, min_periods=20).mean()\n rolling_std = df[\"drain_rate_sm\"].rolling(z_window, min_periods=20).std()\n df[\"z_drain_rate\"] = (df[\"drain_rate_sm\"] - rolling_mean) / rolling_std\n\n return df\n\n\ndef format_tonnes(x, pos):\n \"\"\"噸數格式化\"\"\"\n if x >= 1000:\n return f'{x/1000:.1f}K'\n return f'{x:.0f}'\n\n\ndef create_drain_report_figure(\n result: Dict[str, Any],\n df: pd.DataFrame,\n figsize: tuple = (14, 8)\n) -> plt.Figure:\n \"\"\"\n 建立 Bloomberg 風格耗盡報告圖表\n\n Parameters\n ----------\n result : dict\n 分析結果\n df : pd.DataFrame\n 庫存時間序列\n figsize : tuple\n 圖表大小\n\n Returns\n -------\n plt.Figure\n 圖表物件\n \"\"\"\n if plt is None:\n raise ImportError(\"matplotlib 未安裝\")\n\n plt.style.use('dark_background')\n\n # 2/3 上方庫存走勢,1/3 下方耗盡速度\n fig, axes = plt.subplots(2, 1, figsize=figsize, facecolor=COLORS[\"background\"],\n gridspec_kw={'height_ratios': [2, 1]})\n fig.set_facecolor(COLORS[\"background\"])\n\n # 訊號顏色\n signal = result.get(\"result\", {}).get(\"signal\", \"NO_SIGNAL\")\n signal_color = {\n \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\": COLORS[\"signal_high\"],\n \"MEDIUM_SUPPLY_TIGHTENING\": COLORS[\"signal_medium\"],\n \"WATCH\": COLORS[\"signal_watch\"],\n \"NO_SIGNAL\": COLORS[\"signal_none\"]\n }.get(signal, COLORS[\"signal_none\"])\n\n latest_stock = result.get(\"result\", {}).get(\"latest_combined_stock\", df[\"combined_tonnes\"].iloc[-1])\n\n # 計算 50 EMA\n df[\"ema50\"] = df[\"combined_tonnes\"].ewm(span=50, adjust=False).mean()\n\n # ==================== 圖表 1:庫存走勢 (上方 2/3) ====================\n ax1 = axes[0]\n ax1.set_facecolor(COLORS[\"background\"])\n\n ax1.fill_between(df[\"date\"], df[\"combined_tonnes\"], alpha=COLORS[\"area_alpha\"],\n color=COLORS[\"area_fill\"], zorder=1)\n ax1.plot(df[\"date\"], df[\"combined_tonnes\"], color=COLORS[\"primary\"],\n linewidth=2, label=\"合併庫存\", zorder=5)\n\n # 50 周 EMA 趨勢線\n ax1.plot(df[\"date\"], df[\"ema50\"], color=COLORS[\"secondary\"],\n linewidth=1.5, linestyle=\"--\", label=\"50 周 EMA\", zorder=4)\n\n # 標記當前位置\n ax1.axhline(y=latest_stock, color=signal_color, linestyle=\"--\",\n alpha=0.7, linewidth=1.5, zorder=2)\n\n # 最新值標註\n ax1.annotate(\n f'{latest_stock:.0f}',\n xy=(df[\"date\"].iloc[-1], latest_stock),\n xytext=(10, 0),\n textcoords='offset points',\n color=signal_color,\n fontsize=11,\n fontweight='bold',\n va='center'\n )\n\n ax1.set_title(\"上海白銀庫存走勢\", fontsize=12, fontweight=\"bold\", color=COLORS[\"text\"])\n ax1.set_ylabel(\"庫存 (噸)\", fontsize=10, color=COLORS[\"text_dim\"])\n ax1.yaxis.set_major_formatter(FuncFormatter(format_tonnes))\n ax1.tick_params(axis='y', colors=COLORS[\"text_dim\"])\n ax1.tick_params(axis='x', colors=COLORS[\"text_dim\"])\n ax1.grid(True, color=COLORS[\"grid\"], alpha=0.3, linestyle='-', linewidth=0.5)\n ax1.set_axisbelow(True)\n ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))\n ax1.xaxis.set_major_locator(mdates.YearLocator())\n\n # 圖例\n ax1.legend(loc='upper right', fontsize=8,\n facecolor=COLORS[\"background\"], edgecolor=COLORS[\"grid\"],\n labelcolor=COLORS[\"text\"])\n\n # ==================== 圖表 2:耗盡速度 (下方 1/3) ====================\n ax2 = axes[1]\n ax2.set_facecolor(COLORS[\"background\"])\n\n # 正負色彩\n colors_bar = [COLORS[\"signal_high\"] if v > 0 else COLORS[\"signal_none\"]\n for v in df[\"drain_rate_sm\"].fillna(0)]\n\n ax2.bar(df[\"date\"], df[\"drain_rate_sm\"], color=colors_bar, alpha=0.7, width=5, zorder=3)\n ax2.axhline(y=0, color=COLORS[\"level_line\"], linewidth=1, zorder=2)\n\n ax2.set_title(\"耗盡速度 (正值=流出)\", fontsize=12, fontweight=\"bold\", color=COLORS[\"text\"])\n ax2.set_ylabel(\"噸/週 (4週平滑)\", fontsize=10, color=COLORS[\"text_dim\"])\n ax2.tick_params(axis='y', colors=COLORS[\"text_dim\"])\n ax2.tick_params(axis='x', colors=COLORS[\"text_dim\"])\n ax2.grid(True, color=COLORS[\"grid\"], alpha=0.3, linestyle='-', linewidth=0.5)\n ax2.set_axisbelow(True)\n ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))\n ax2.xaxis.set_major_locator(mdates.YearLocator())\n\n # ==================== 標題 ====================\n signal_label = {\n \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\": \"HIGH\",\n \"MEDIUM_SUPPLY_TIGHTENING\": \"MEDIUM\",\n \"WATCH\": \"WATCH\",\n \"NO_SIGNAL\": \"NO_SIGNAL\"\n }.get(signal, signal)\n\n fig.suptitle(\n f\"上海白銀庫存消耗趨勢\",\n color=COLORS[\"text\"],\n fontsize=14,\n fontweight='bold',\n y=0.98\n )\n\n # ==================== 頁尾 ====================\n sources = result.get(\"sources\", [\"SGE\", \"SHFE\"])\n as_of = result.get(\"as_of\", datetime.now().strftime(\"%Y-%m-%d\"))\n\n fig.text(\n 0.02, 0.02,\n f\"資料來源: CEIC ({', '.join(sources)})\",\n color=COLORS[\"text_dim\"],\n fontsize=8,\n ha='left'\n )\n\n fig.text(\n 0.98, 0.02,\n f'截至: {as_of}',\n color=COLORS[\"text_dim\"],\n fontsize=8,\n ha='right'\n )\n\n plt.tight_layout()\n plt.subplots_adjust(top=0.93, bottom=0.08)\n\n return fig\n\n\ndef main():\n \"\"\"主程式入口\"\"\"\n parser = argparse.ArgumentParser(description=\"上海白銀庫存耗盡視覺化報告 (Bloomberg 風格)\")\n parser.add_argument(\n \"--result\",\n type=str,\n required=True,\n help=\"分析結果 JSON 檔案路徑\"\n )\n parser.add_argument(\n \"--output\",\n type=str,\n default=\".\",\n help=\"輸出目錄\"\n )\n parser.add_argument(\n \"--format\",\n type=str,\n default=\"png\",\n choices=[\"png\", \"pdf\", \"both\"],\n help=\"輸出格式\"\n )\n\n args = parser.parse_args()\n\n if plt is None:\n print(\"錯誤: matplotlib 未安裝,無法生成圖表\")\n return\n\n # 載入分析結果\n result_path = Path(args.result)\n if not result_path.exists():\n print(f\"錯誤: 找不到結果檔案 {result_path}\")\n return\n\n with open(result_path, \"r\", encoding=\"utf-8\") as f:\n result = json.load(f)\n\n # 載入庫存數據\n try:\n df = load_combined_stock()\n except FileNotFoundError as e:\n print(f\"錯誤: {e}\")\n print(\"請先執行數據抓取腳本\")\n return\n\n # 建立圖表\n fig = create_drain_report_figure(result, df)\n\n # 儲存\n output_dir = Path(args.output)\n output_dir.mkdir(parents=True, exist_ok=True)\n\n as_of = result.get(\"as_of\", datetime.now().strftime(\"%Y%m%d\")).replace(\"-\", \"\")\n base_name = f\"shanghai_silver_drain_report_{as_of}\"\n\n if args.format in [\"png\", \"both\"]:\n png_path = output_dir / f\"{base_name}.png\"\n fig.savefig(png_path, dpi=150, bbox_inches=\"tight\", facecolor=COLORS[\"background\"])\n print(f\"PNG 圖表已儲存至: {png_path}\")\n\n if args.format in [\"pdf\", \"both\"]:\n pdf_path = output_dir / f\"{base_name}.pdf\"\n fig.savefig(pdf_path, bbox_inches=\"tight\", facecolor=COLORS[\"background\"])\n print(f\"PDF 報告已儲存至: {pdf_path}\")\n\n plt.close(fig)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10160,"content_sha256":"599d18b7c05d35616591faa81470af57aa83a0f012e749b0b627c3315fd07be3"},{"filename":"skill.yaml","content":"displayName: 判斷上海白銀庫存消耗趨勢\nemoji: \"📦\"\nauthorUrl: https://github.com/fatfingererr/macro-skills\n\ntools:\n - claude-code\n\nfeatured: false\ninstallCount: 0\n\ntestQuestions:\n - question: '檢查上海白銀庫存目前的耗盡狀態'\n imagePath: images/shanghai-silver-drain-report-2026-01-22.png\n expectedResult: |\n 上海白銀庫存耗盡分析報告\n\n 截至日期:2026-01-16\n\n ---\n 核心結論\n ┌──────────────┬─────────────────────────────┬──────────────┐\n │ 指標 │ 數值 │ 判定 │\n ├──────────────┼─────────────────────────────┼──────────────┤\n │ 訊號等級 │ MEDIUM_SUPPLY_TIGHTENING │ 🟡 中度警戒 │\n ├──────────────┼─────────────────────────────┼──────────────┤\n │ 合併庫存 │ 1,133.3 噸 │ 歷史低檔 │\n ├──────────────┼─────────────────────────────┼──────────────┤\n │ 庫存分位數 │ 12% │ 低於 20% │\n └──────────────┴─────────────────────────────┴──────────────┘\n\n ---\n 三維度量化結果\n\n ┌────────────────┬─────────┬────────────────────────┐\n │ 維度 │ 數值 │ 解讀 │\n ├────────────────┼─────────┼────────────────────────┤\n │ 方向 (Δ1) │ -58.4 │ 庫存下降中 │\n ├────────────────┼─────────┼────────────────────────┤\n │ 速度 (Z-score) │ -2.1 │ ⚠️ 流出顯著高於常態 │\n ├────────────────┼─────────┼────────────────────────┤\n │ 加速度 (Z) │ +1.4 │ ⚠️ 流出正在加速 │\n └────────────────┴─────────┴────────────────────────┘\n\n ---\n 訊號判定邏輯\n\n ✅ A. 庫存水位偏低(12% \u003c 20% 門檻)\n ✅ B. 耗盡速度異常(z_drain = -2.1 ≤ -1.5)\n ✅ C. 耗盡加速(z_accel = +1.4 ≥ +1.0)\n\n → A+B+C 同時成立 → HIGH_LATE_STAGE_SUPPLY_SIGNAL\n\n ---\n 敘事解讀\n\n 1. 上海合併庫存處於歷史低分位(約 12% 分位)\n 2. 近 4 週平均庫存流出顯著高於常態(耗盡速度 Z=-2.1)\n 3. 流出在加速(加速度 Z=+1.4),符合「方向 + 速度」核心判準\n 4. 若同時觀察到其他市場庫存/溢價惡化,可進一步提高信心\n\n ---\n 數據口徑說明\n\n ⚠️ 這是「交易所可交割/倉單/指定倉庫」口徑,不等於全中國社會庫存\n ⚠️ 單週跳動可能反映倉儲/交割規則變動或搬倉,需用平滑與多來源交叉確認\n\n - question: '分析上海白銀庫存過去一年的變化趨勢'\n imagePath: images/shanghai-silver-drain-one-year-report-2026-01-22.png\n expectedResult: |\n 上海白銀(SHFE)庫存年度分析摘要\n\n ⏵核心數據\n ┌───────────────────────┬──────────────────┐\n │ 指標 │ 數值 │\n ├───────────────────────┼──────────────────┤\n │ 期初庫存 (2025-01-22) │ 1,403 噸 │\n ├───────────────────────┼──────────────────┤\n │ 期末庫存 (2026-01-22) │ 589 噸 │\n ├───────────────────────┼──────────────────┤\n │ 年度淨變化 │ -814 噸 (-58.0%) │\n ├───────────────────────┼──────────────────┤\n │ 當前歷史分位數 │ 1.3%(極度低檔) │\n └───────────────────────┴──────────────────┘\n 年內極值\n\n - 年內最高: 1,454 噸 (2025-02-05)\n - 年內最低: 519 噸 (2025-11-04) — 歷史新低\n - 年內振幅: 935 噸\n\n 季度走勢\n ┌──────────┬─────────┬──────────┐\n │ 季度 │ 變化 │ 趨勢 │\n ├──────────┼─────────┼──────────┤\n │ 2025 Q1 │ -317 噸 │ ▼ 流出 │\n ├──────────┼─────────┼──────────┤\n │ 2025 Q2 │ +255 噸 │ ▲ 回補 │\n ├──────────┼─────────┼──────────┤\n │ 2025 Q3 │ -231 噸 │ ▼ 流出 │\n ├──────────┼─────────┼──────────┤\n │ 2025 Q4 │ -229 噸 │ ▼ 流出 │\n ├──────────┼─────────┼──────────┤\n │ 2026 Q1* │ -29 噸 │ ▼ 緩流出 │\n └──────────┴─────────┴──────────┘\n 主要流出階段\n \n 1. 2025/02-05月: 流出 333 噸(春節後持續流出)\n 2. 2025/09-11月: 流出 657 噸(歷史性大幅流出,跌破歷史新低)\n 3. 2025/12月: 流出 230 噸(年末再度下跌)\n \n 訊號判定\n \n - 當前訊號: 🟡 WATCH(觀察)\n - 庫存水位已達歷史 1.3% 分位(條件 A 成立)\n - 當前流出速度未達異常門檻(近期已趨緩)\n \n 關鍵結論\n \n 1. 過去一年庫存下降 58%,為近年來最劇烈\n 2. 2025 年 9-11 月出現歷史性庫存崩跌,跌至 519 噸新低\n 3. 當前水位距歷史高點(3,091 噸)已下跌 超過 80%\n 4. 年底略有回補,但整體仍處極度低檔\n \n ---\n 圖表已儲存至 shanghai-silver-drain-report-2026-01-22.png\n\nqualityScore:\n overall: 80\n badge: 高級\n evaluatedAt: \"2026-01-23\"\n\n metrics:\n problemFit: 82\n correctness: 82\n dataGovernance: 85\n robustness: 72\n maintainability: 78\n usability: 81\n\n metricDetails:\n problemFit:\n score: 82\n strengths:\n - SKILL.md 有三維度量化框架與成功準則\n - workflows/ 覆蓋分析、數據抓取、交叉驗證三路徑\n - input-schema.md 含核心與分析參數完整定義\n improvements:\n - 可增加不適用情境(如倉儲規則變動期間)\n\n correctness:\n score: 82\n strengths:\n - methodology.md 有完整三維度公式與 Z 分數計算\n - testQuestions 有詳細 expectedResult 作為回歸基準\n - scripts/ 有 drain_detector.py 與 visualize_drain.py\n improvements:\n - 可增加 examples/ 目錄存放 golden case\n\n dataGovernance:\n score: 85\n strengths:\n - data-sources.md 詳列 CEIC/SGE/SHFE 來源與口徑\n - workflows/cross-validate.md 有交叉驗證流程\n - 多個 fetch_*.py 與 explore_*.py 腳本\n - data/ 目錄有實際 CSV 快取\n improvements:\n - 可增加 fallback 替代來源文檔\n\n robustness:\n score: 72\n strengths:\n - pitfalls 區段詳列搬倉、格式變更等風險\n - bestPractices 有平滑視窗建議\n improvements:\n - 缺少 failure-modes.md 明確降級策略\n - Selenium 爬蟲需更完善的錯誤處理\n - 可增加資料缺失時的輸出行為說明\n\n maintainability:\n score: 78\n strengths:\n - manifest.json 有版本控制\n - templates/ 輸出結構定義清晰\n - 無重複文件\n improvements:\n - 部分門檻參數分散於 scripts 中\n - 可集中管理 Z_THRESHOLD 等參數\n\n usability:\n score: 81\n strengths:\n - output-markdown.md 有三維度表格與敘事解讀\n - FAQ 解答 Z 分數、SGE/SHFE 差異等問題\n - about 區段有訊號分級解讀表\n improvements:\n - 可增加 historical-episodes.md 歷史對照\n - 輸出可增加 confidence 信心度欄位\n\n details: |\n **任務適配度(82/100)**\n - SKILL.md 有三維度量化框架\n - workflows/ 覆蓋分析、抓取、交叉驗證\n - 待改進:增加不適用情境說明\n\n **正確性(82/100)**\n - methodology.md 有完整公式\n - testQuestions 有 expectedResult 回歸基準\n - 待改進:增加 examples/ golden case\n\n **資料治理(85/100)**\n - 有 cross-validate.md 交叉驗證\n - 多個 fetch/explore 腳本與 data/ 快取\n - 待改進:增加 fallback 來源\n\n **穩健性(72/100)**\n - pitfalls 詳列常見風險\n - 待改進:增加 failure-modes.md\n\n **可維護性(78/100)**\n - manifest.json 有版本控制\n - 待改進:參數集中管理\n\n **輸出可用性(81/100)**\n - 有三維度表格與敘事解讀\n - 待改進:增加歷史對照文檔\n\n upgradeNotes:\n targetBadge: 頂級\n requirements:\n - metric: robustness\n currentScore: 72\n targetScore: 85\n suggestion: 增加 failure-modes.md 與 Selenium 錯誤處理\n - metric: maintainability\n currentScore: 78\n targetScore: 85\n suggestion: 集中管理門檻參數至 CONFIG 區\n - metric: usability\n currentScore: 81\n targetScore: 90\n suggestion: 增加 historical-episodes.md 與 confidence 欄位\n\nbestPractices:\n - title: 使用平滑視窗避免單週跳動\n description: 單週庫存變動可能反映搬倉或規則變動,建議使用 4 週平滑視窗\n - title: Z 分數標準化比較\n description: 使用 3-5 年歷史計算 Z 分數,避免短期基準失真\n - title: 多來源交叉驗證\n description: SGE + SHFE 合併使用,並與 COMEX、ETF 數據交叉確認\n - title: 注意數據口徑限制\n description: 交易所庫存 ≠ 全社會庫存,解讀時需謹慎\n\npitfalls:\n - title: 把倉單變動誤判為實物流動\n description: 交易所庫存變動可能是搬倉或倉儲規則調整,不必然代表實物被「抽走」\n consequence: 過度解讀供給緊縮訊號\n - title: 忽略中國市場特殊性\n description: 中國市場有進出口管制、庫存統計口徑等特殊因素\n consequence: 與國際市場比較時產生誤判\n - title: PDF 格式變更導致解析失敗\n description: SGE 周報 PDF 格式改版可能導致數據抓取失敗\n consequence: 數據缺失或錯誤\n\nfaq:\n - question: 什麼是三維度量化?\n answer: |\n 三維度量化是分析庫存變動的框架:\n 1. **方向**:庫存是上升還是下降(Δ1)\n 2. **速度**:每週流出量有多快(drain_rate)\n 3. **加速度**:流出速度是在加快還是減慢(Δ2)\n\n 當速度和加速度都為正(流出且加速)時,\n 才是真正的「晚期供給訊號」。\n\n - question: 為什麼要用 Z 分數?\n answer: |\n Z 分數將不同時期的數據標準化,便於比較:\n - Z = 0:與歷史平均相同\n - Z = -2:比歷史平均低 2 個標準差\n\n 使用 Z 分數可以判斷「異常」而非僅看絕對數值。\n\n - question: SGE 和 SHFE 庫存有什麼差別?\n answer: |\n - **SGE(上海黃金交易所)**:現貨交易所,庫存為「指定倉庫庫存」\n - **SHFE(上海期交所)**:期貨交易所,庫存為「倉單」(可交割)\n\n 兩者都是「可交割」口徑,不等於全中國社會庫存。\n 合併使用可以更完整地反映交易所可用供給。\n\nabout:\n repository: https://github.com/fatfingererr/macro-skills\n branch: main\n additionalInfo: |\n ## 適用場景\n\n - 監控上海白銀庫存變化趨勢\n - 驗證社群「庫存暴跌」敘事\n - 評估白銀市場供給緊縮程度\n - 配合價格分析判斷入場時機\n\n ## 數據來源\n\n **主要來源**\n - SGE: 上海黃金交易所「行情周報」PDF\n - SHFE: 上海期貨交易所「倉單日報」\n\n **交叉驗證**\n - COMEX 庫存報表\n - SLV/PSLV ETF 持倉\n - 白銀現貨/期貨價格\n\n ## 訊號解讀\n\n | 訊號等級 | 含義 | 建議動作 |\n |----------|------|----------|\n | HIGH | 晚期供給訊號 | 高度警戒,交叉驗證 |\n | MEDIUM | 供給趨緊 | 持續關注 |\n | WATCH | 單一異常 | 保持觀察 |\n | NO_SIGNAL | 正常 | 無需動作 |\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":13822,"content_sha256":"a695685d374abcd876943b3f32af5707b3e67ff7511efe5a765adf50992b8f7a"},{"filename":"templates/output-json.md","content":"# JSON 輸出模板\n\n本文件定義 `detect-shanghai-silver-stock-drain` skill 的 JSON 輸出結構。\n\n---\n\n## 完整輸出結構\n\n```json\n{\n \"skill\": \"detect_shanghai_silver_stock_drain\",\n \"as_of\": \"2026-01-16\",\n \"unit\": \"tonnes\",\n \"sources\": [\"SGE\", \"SHFE\"],\n \"parameters\": {\n \"start_date\": \"2023-01-01\",\n \"end_date\": \"2026-01-16\",\n \"frequency\": \"weekly\",\n \"smoothing_window_weeks\": 4,\n \"z_score_window_weeks\": 156,\n \"drain_threshold_z\": -1.5,\n \"accel_threshold_z\": 1.0,\n \"level_percentile_threshold\": 0.20\n },\n \"result\": {\n \"latest_combined_stock\": 1133.3,\n \"level_percentile\": 0.12,\n \"delta1_weekly\": -58.4,\n \"drain_rate_4w_avg\": 58.4,\n \"acceleration_4w_avg\": 9.7,\n \"z_scores\": {\n \"z_drain_rate\": -2.1,\n \"z_acceleration\": 1.4\n },\n \"signal_conditions\": {\n \"A_level_low\": true,\n \"B_drain_abnormal\": true,\n \"C_acceleration\": true\n },\n \"signal\": \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\"\n },\n \"historical_context\": {\n \"decade_high_stock\": 3500.2,\n \"decade_low_stock\": 980.5,\n \"current_percentile\": 0.12,\n \"is_decade_low\": false,\n \"distance_to_decade_low_pct\": 0.156\n },\n \"cross_validation\": {\n \"enabled\": true,\n \"confidence\": 0.67,\n \"checks\": [\n {\n \"indicator\": \"COMEX Registered\",\n \"status\": \"SUPPORT\",\n \"value\": -0.082,\n \"detail\": \"同期下降 8.2%\"\n },\n {\n \"indicator\": \"SLV Holdings\",\n \"status\": \"NEUTRAL\",\n \"value\": 0.003,\n \"detail\": \"持平 (+0.3%)\"\n },\n {\n \"indicator\": \"Futures Structure\",\n \"status\": \"SUPPORT\",\n \"value\": \"backwardation\",\n \"detail\": \"Mild Backwardation\"\n }\n ],\n \"validated_signal\": \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\"\n },\n \"narrative\": [\n \"上海合併庫存處於歷史低分位(約 12% 分位)。\",\n \"近 4 週平均庫存流出顯著高於常態(耗盡速度 Z=-2.1)。\",\n \"流出在加速(加速度 Z=+1.4),符合「方向 + 速度」核心判準。\",\n \"若同時觀察到其他市場庫存/溢價惡化,可進一步提高信心。\"\n ],\n \"caveats\": [\n \"這是「交易所可交割/倉單/指定倉庫」口徑,不等於全中國社會庫存。\",\n \"單週跳動可能反映倉儲/交割規則變動或搬倉,需用平滑與多來源交叉確認。\"\n ],\n \"metadata\": {\n \"generated_at\": \"2026-01-16T10:30:00Z\",\n \"skill_version\": \"0.1.0\",\n \"data_freshness\": {\n \"sge_last_update\": \"2026-01-15\",\n \"shfe_last_update\": \"2026-01-16\"\n }\n }\n}\n```\n\n---\n\n## 欄位說明\n\n### 頂層欄位\n\n| 欄位 | 類型 | 說明 |\n|------|------|------|\n| skill | string | 技能名稱 |\n| as_of | string | 分析截止日期 |\n| unit | string | 輸出單位 |\n| sources | array | 使用的庫存來源 |\n| parameters | object | 分析參數 |\n| result | object | 分析結果 |\n| historical_context | object | 歷史脈絡 |\n| cross_validation | object | 交叉驗證結果 |\n| narrative | array | 中文敘事解讀 |\n| caveats | array | 數據口徑說明 |\n| metadata | object | 元資料 |\n\n### result 欄位\n\n| 欄位 | 類型 | 說明 |\n|------|------|------|\n| latest_combined_stock | float | 最新合併庫存(單位同 unit) |\n| level_percentile | float | 庫存水位歷史分位數 (0-1) |\n| delta1_weekly | float | 最新週變化量 |\n| drain_rate_4w_avg | float | 近 4 週平均流出速度 |\n| acceleration_4w_avg | float | 近 4 週平均加速度 |\n| z_scores | object | Z 分數 |\n| signal_conditions | object | 三段式條件判定 |\n| signal | string | 訊號分級 |\n\n### z_scores 欄位\n\n| 欄位 | 類型 | 說明 |\n|------|------|------|\n| z_drain_rate | float | 耗盡速度 Z 分數 |\n| z_acceleration | float | 加速度 Z 分數 |\n\n### signal_conditions 欄位\n\n| 欄位 | 類型 | 說明 |\n|------|------|------|\n| A_level_low | boolean | 條件 A:庫存水位偏低 |\n| B_drain_abnormal | boolean | 條件 B:耗盡速度異常 |\n| C_acceleration | boolean | 條件 C:耗盡加速 |\n\n### signal 可能值\n\n| 值 | 說明 |\n|-----|------|\n| HIGH_LATE_STAGE_SUPPLY_SIGNAL | A+B+C 同時成立 |\n| MEDIUM_SUPPLY_TIGHTENING | (B+C) 或 (A+B) 成立 |\n| WATCH | 任一條件成立 |\n| NO_SIGNAL | 無異常 |\n\n### cross_validation 欄位\n\n| 欄位 | 類型 | 說明 |\n|------|------|------|\n| enabled | boolean | 是否啟用交叉驗證 |\n| confidence | float | 綜合信心度 (0-1) |\n| checks | array | 各指標驗證結果 |\n| validated_signal | string | 驗證後訊號 |\n\n### checks 陣列元素\n\n| 欄位 | 類型 | 說明 |\n|------|------|------|\n| indicator | string | 指標名稱 |\n| status | string | SUPPORT / NEUTRAL / AGAINST |\n| value | any | 指標數值 |\n| detail | string | 詳細說明 |\n\n---\n\n## 精簡輸出(--quick 模式)\n\n```json\n{\n \"skill\": \"detect_shanghai_silver_stock_drain\",\n \"as_of\": \"2026-01-16\",\n \"signal\": \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\",\n \"latest_combined_stock_tonnes\": 1133.3,\n \"level_percentile\": 0.12,\n \"z_drain_rate\": -2.1,\n \"z_acceleration\": 1.4\n}\n```\n\n---\n\n## 程式碼範例\n\n### Python 讀取\n\n```python\nimport json\n\nwith open(\"result.json\", \"r\", encoding=\"utf-8\") as f:\n result = json.load(f)\n\n# 檢查訊號\nif result[\"result\"][\"signal\"] == \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\":\n print(\"⚠️ 高度警戒:晚期供給訊號!\")\n\n# 讀取 Z 分數\nz_drain = result[\"result\"][\"z_scores\"][\"z_drain_rate\"]\nprint(f\"耗盡速度 Z 分數:{z_drain}\")\n\n# 讀取敘事\nfor line in result[\"narrative\"]:\n print(f\"• {line}\")\n```\n\n### jq 查詢\n\n```bash\n# 取得訊號\njq '.result.signal' result.json\n\n# 取得 Z 分數\njq '.result.z_scores' result.json\n\n# 取得敘事\njq '.narrative[]' result.json\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5696,"content_sha256":"1c751b0cc7c7f39c1a9fb3ca95ad444d8e10c2c9ac5eeaf920a7a77ece256634"},{"filename":"templates/output-markdown.md","content":"# Markdown 報告模板\n\n本文件定義 `detect-shanghai-silver-stock-drain` skill 的 Markdown 報告輸出格式。\n\n---\n\n## 完整報告模板\n\n```markdown\n# 上海白銀庫存耗盡分析報告\n\n**截至日期**:{as_of}\n**數據來源**:{sources}\n**分析單位**:{unit}\n\n---\n\n## 核心結論\n\n| 指標 | 數值 | 判定 |\n|------|------|------|\n| **訊號等級** | {signal} | {signal_emoji} {signal_description} |\n| **合併庫存** | {latest_combined_stock} {unit} | {level_description} |\n| **庫存分位數** | {level_percentile_pct}% | {level_assessment} |\n\n---\n\n## 三維度量化結果\n\n### 方向、速度、加速度\n\n| 維度 | 數值 | Z 分數 | 解讀 |\n|------|------|--------|------|\n| **方向 (Δ1)** | {delta1_weekly} | - | {direction_description} |\n| **速度** | {drain_rate_4w_avg} | {z_drain_rate} | {speed_description} |\n| **加速度** | {acceleration_4w_avg} | {z_acceleration} | {accel_description} |\n\n### 訊號判定邏輯\n\n{condition_A_check} A. 庫存水位偏低({level_percentile_pct}% {condition_A_operator} 20% 門檻)\n{condition_B_check} B. 耗盡速度異常(z_drain = {z_drain_rate} {condition_B_operator} -1.5)\n{condition_C_check} C. 耗盡加速(z_accel = {z_acceleration} {condition_C_operator} +1.0)\n\n→ {signal_logic} → **{signal}**\n\n---\n\n## 歷史脈絡\n\n| 指標 | 數值 |\n|------|------|\n| 歷史最高庫存 | {decade_high_stock} {unit} |\n| 歷史最低庫存 | {decade_low_stock} {unit} |\n| 當前分位數 | {level_percentile_pct}% |\n| 距離歷史低點 | {distance_to_decade_low_pct}% |\n\n---\n\n## 交叉驗證(如啟用)\n\n**綜合信心度**:{confidence_pct}%\n\n| 指標 | 狀態 | 詳情 |\n|------|------|------|\n{cross_validation_rows}\n\n**驗證後訊號**:{validated_signal}\n\n---\n\n## 敘事解讀\n\n{narrative_list}\n\n---\n\n## 數據口徑說明\n\n{caveats_list}\n\n---\n\n## 下一步建議\n\n{next_steps_list}\n\n---\n\n*報告生成時間:{generated_at}*\n*技能版本:{skill_version}*\n```\n\n---\n\n## 變數說明\n\n### 訊號相關\n\n| 變數 | 說明 | 範例值 |\n|------|------|--------|\n| {signal} | 訊號等級 | HIGH_LATE_STAGE_SUPPLY_SIGNAL |\n| {signal_emoji} | 訊號表情符號 | 🔴 / 🟡 / 🟢 |\n| {signal_description} | 訊號描述 | 晚期供給訊號 |\n\n### 數據相關\n\n| 變數 | 說明 | 範例值 |\n|------|------|--------|\n| {latest_combined_stock} | 最新合併庫存 | 1133.3 |\n| {level_percentile_pct} | 分位數百分比 | 12 |\n| {z_drain_rate} | 耗盡速度 Z 分數 | -2.1 |\n| {z_acceleration} | 加速度 Z 分數 | +1.4 |\n\n### 條件判定\n\n| 變數 | 說明 | 範例值 |\n|------|------|--------|\n| {condition_A_check} | 條件 A 狀態 | ✅ / ❌ |\n| {condition_B_check} | 條件 B 狀態 | ✅ / ❌ |\n| {condition_C_check} | 條件 C 狀態 | ✅ / ❌ |\n\n---\n\n## 範例輸出\n\n### HIGH 訊號報告\n\n```markdown\n# 上海白銀庫存耗盡分析報告\n\n**截至日期**:2026-01-16\n**數據來源**:SGE, SHFE\n**分析單位**:tonnes\n\n---\n\n## 核心結論\n\n| 指標 | 數值 | 判定 |\n|------|------|------|\n| **訊號等級** | HIGH_LATE_STAGE_SUPPLY_SIGNAL | 🔴 晚期供給訊號 |\n| **合併庫存** | 1,133.3 噸 | 歷史低檔 |\n| **庫存分位數** | 12% | 低於 20% 門檻 |\n\n---\n\n## 三維度量化結果\n\n### 方向、速度、加速度\n\n| 維度 | 數值 | Z 分數 | 解讀 |\n|------|------|--------|------|\n| **方向 (Δ1)** | -58.4 | - | 庫存下降中 |\n| **速度** | 58.4 噸/週 | -2.1 | ⚠️ 流出顯著高於常態 |\n| **加速度** | +9.7 | +1.4 | ⚠️ 流出正在加速 |\n\n### 訊號判定邏輯\n\n✅ A. 庫存水位偏低(12% \u003c 20% 門檻)\n✅ B. 耗盡速度異常(z_drain = -2.1 ≤ -1.5)\n✅ C. 耗盡加速(z_accel = +1.4 ≥ +1.0)\n\n→ A+B+C 同時成立 → **HIGH_LATE_STAGE_SUPPLY_SIGNAL**\n\n---\n\n## 敘事解讀\n\n1. 上海合併庫存處於歷史低分位(約 12% 分位)。\n2. 近 4 週平均庫存流出顯著高於常態(耗盡速度 Z=-2.1)。\n3. 流出在加速(加速度 Z=+1.4),符合「方向 + 速度」核心判準。\n4. 若同時觀察到其他市場庫存/溢價惡化,可進一步提高信心。\n\n---\n\n## 數據口徑說明\n\n⚠️ 這是「交易所可交割/倉單/指定倉庫」口徑,不等於全中國社會庫存。\n⚠️ 單週跳動可能反映倉儲/交割規則變動或搬倉,需用平滑與多來源交叉確認。\n\n---\n\n*報告生成時間:2026-01-16T10:30:00Z*\n*技能版本:0.1.0*\n```\n\n---\n\n## 程式碼範例\n\n### Python 生成報告\n\n```python\nfrom string import Template\n\ndef generate_markdown_report(result):\n \"\"\"從分析結果生成 Markdown 報告\"\"\"\n template = Template(open(\"templates/output-markdown.md\").read())\n\n # 計算派生變數\n signal_emoji = {\n \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\": \"🔴\",\n \"MEDIUM_SUPPLY_TIGHTENING\": \"🟡\",\n \"WATCH\": \"🟠\",\n \"NO_SIGNAL\": \"🟢\"\n }.get(result[\"result\"][\"signal\"], \"⚪\")\n\n # 填入變數\n report = template.substitute(\n as_of=result[\"as_of\"],\n sources=\", \".join(result[\"sources\"]),\n unit=result[\"unit\"],\n signal=result[\"result\"][\"signal\"],\n signal_emoji=signal_emoji,\n latest_combined_stock=f\"{result['result']['latest_combined_stock']:,.1f}\",\n level_percentile_pct=f\"{result['result']['level_percentile']*100:.0f}\",\n z_drain_rate=f\"{result['result']['z_scores']['z_drain_rate']:.1f}\",\n z_acceleration=f\"+{result['result']['z_scores']['z_acceleration']:.1f}\",\n # ... 更多變數\n )\n\n return report\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5516,"content_sha256":"8ef3a39c929ad2246f5a65722c45f0333667c565f8b800f143ae29321fbb4e44"},{"filename":"workflows/analyze.md","content":"# Workflow: 完整庫存分析\n\n\u003crequired_reading>\n**執行前請先閱讀:**\n1. references/methodology.md - 三維度量化方法論\n2. references/data-sources.md - SGE/SHFE 資料來源\n3. references/input-schema.md - 完整輸入參數定義\n\u003c/required_reading>\n\n\u003cprocess>\n\n## Step 1: 確認分析參數\n\n確認用戶提供的參數,或使用預設值:\n\n| 參數 | 預設值 | 說明 |\n|------|--------|------|\n| start_date | 3 年前 | 分析起始日 |\n| end_date | 今天 | 分析結束日 |\n| frequency | weekly | 數據頻率 |\n| include_sources | [\"SGE\", \"SHFE\"] | 庫存來源 |\n| unit | tonnes | 輸出單位 |\n| smoothing_window_weeks | 4 | 平滑視窗 |\n| drain_threshold_z | -1.5 | 耗盡速度 Z 門檻 |\n| accel_threshold_z | +1.0 | 加速度 Z 門檻 |\n\n## Step 2: 數據採集\n\n執行數據抓取腳本:\n\n```bash\ncd skills/detect-shanghai-silver-stock-drain\n\n# 抓取 SGE 庫存(PDF 解析)\npython scripts/fetch_sge_stock.py \\\n --start {start_date} \\\n --end {end_date} \\\n --output data/sge_stock.csv\n\n# 抓取 SHFE 庫存\npython scripts/fetch_shfe_stock.py \\\n --start {start_date} \\\n --end {end_date} \\\n --output data/shfe_stock.csv\n```\n\n如果數據已存在且在快取有效期內(12 小時),可跳過此步驟。\n\n## Step 3: 數據處理與合併\n\n執行主分析腳本的數據處理階段:\n\n```python\n# drain_detector.py 內部邏輯\ndef build_combined_stock(df_sge, df_shfe, unit=\"tonnes\"):\n \"\"\"合併 SGE + SHFE 庫存\"\"\"\n df = merge_weekly(df_sge, df_shfe) # 按週對齊\n df[\"combined_kg\"] = df[\"sge_kg\"].fillna(0) + df[\"shfe_kg\"].fillna(0)\n\n if unit == \"tonnes\":\n df[\"combined\"] = df[\"combined_kg\"] / 1000.0\n elif unit == \"troy_oz\":\n df[\"combined\"] = (df[\"combined_kg\"] / 1000.0) * 32150.7466\n\n return df.sort_values(\"date\")\n```\n\n## Step 4: 三維度量化計算\n\n計算方向、速度、加速度:\n\n```python\ndef compute_drain_metrics(df, smooth=4, z_window=156):\n \"\"\"計算耗盡三維度指標\"\"\"\n # 方向(每週變化)\n df[\"delta1\"] = df[\"combined\"].diff(1)\n\n # 速度(流出量,正值 = 流出)\n df[\"drain_rate\"] = -df[\"delta1\"]\n\n # 加速度(速度變化)\n df[\"accel\"] = df[\"drain_rate\"].diff(1)\n\n # 平滑處理\n for c in [\"combined\", \"drain_rate\", \"accel\"]:\n df[f\"{c}_sm\"] = df[c].rolling(smooth, min_periods=1).mean()\n\n # Z 分數標準化\n for c in [\"drain_rate_sm\", \"accel_sm\"]:\n m = df[c].rolling(z_window, min_periods=20).mean()\n s = df[c].rolling(z_window, min_periods=20).std()\n df[f\"z_{c}\"] = (df[c] - m) / s\n\n # 水位百分位\n df[\"level_pct_rank\"] = df[\"combined_sm\"].rolling(z_window, min_periods=20)\\\n .apply(lambda x: x.rank(pct=True).iloc[-1], raw=False)\n\n return df\n```\n\n## Step 5: 訊號分級判定\n\n執行三段式訊號判定:\n\n```python\ndef classify_signal(df, drain_z=-1.5, accel_z=1.0, level_pctl=0.2):\n \"\"\"判定供給訊號等級\"\"\"\n latest = df.iloc[-1]\n\n A = latest[\"level_pct_rank\"] \u003c= level_pctl # 庫存水位偏低\n B = latest[\"z_drain_rate_sm\"] \u003c= drain_z # 耗盡速度異常\n C = latest[\"z_accel_sm\"] >= accel_z # 耗盡加速\n\n if A and B and C:\n return \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\"\n if (B and C) or (A and B):\n return \"MEDIUM_SUPPLY_TIGHTENING\"\n if B or A or C:\n return \"WATCH\"\n return \"NO_SIGNAL\"\n```\n\n## Step 6: 生成敘事解讀\n\n根據分析結果生成中文敘事:\n\n```python\ndef generate_narrative(result):\n \"\"\"生成敘事解讀\"\"\"\n narrative = []\n\n # 水位描述\n pct = result[\"level_percentile\"]\n narrative.append(f\"上海合併庫存處於歷史低分位(約 {pct*100:.0f}% 分位)。\")\n\n # 速度描述\n z_drain = result[\"z_scores\"][\"z_drain_rate\"]\n if z_drain \u003c= -1.5:\n narrative.append(f\"近 4 週平均庫存流出顯著高於常態(耗盡速度 Z={z_drain:.1f})。\")\n\n # 加速度描述\n z_accel = result[\"z_scores\"][\"z_acceleration\"]\n if z_accel >= 1.0:\n narrative.append(f\"流出在加速(加速度 Z=+{z_accel:.1f}),符合「方向 + 速度」核心判準。\")\n\n # 建議\n narrative.append(\"若同時觀察到其他市場庫存/溢價惡化,可進一步提高信心。\")\n\n return narrative\n```\n\n## Step 7: 輸出結果\n\n執行完整分析並輸出:\n\n```bash\npython scripts/drain_detector.py \\\n --start {start_date} \\\n --end {end_date} \\\n --sources SGE SHFE \\\n --unit tonnes \\\n --output result.json\n```\n\n結果將包含:\n- JSON 分析結果(符合 `templates/output-json.md` 格式)\n- 數據快取(供後續分析使用)\n\n## Step 8: 視覺化報告(選配)\n\n如需視覺化報告:\n\n```bash\npython scripts/visualize_drain.py \\\n --result result.json \\\n --output ../../../output/\n```\n\n輸出:\n- PNG 圖表:`output/shanghai_silver_drain_report_{date}.png`\n- PDF 報告:`output/shanghai_silver_drain_report_{date}.pdf`\n\n\u003c/process>\n\n\u003csuccess_criteria>\n此工作流程完成時應產出:\n\n- [ ] SGE + SHFE 合併庫存時間序列\n- [ ] 三維度量化結果(方向、速度、加速度)\n- [ ] Z 分數標準化數值\n- [ ] 庫存水位歷史分位數\n- [ ] 訊號分級(HIGH/MEDIUM/WATCH/NO_SIGNAL)\n- [ ] 中文敘事解讀\n- [ ] JSON 格式結果檔案\n- [ ] (選配)視覺化報告\n\u003c/success_criteria>\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5341,"content_sha256":"2622bb7b914b294ab041a5c77d007a6858493a223cfd4d4867727ceb7496cfbe"},{"filename":"workflows/cross-validate.md","content":"# Workflow: 交叉驗證\n\n\u003crequired_reading>\n**執行前請先閱讀:**\n1. references/methodology.md - 交叉驗證邏輯\n2. references/data-sources.md - 交叉驗證數據源\n\u003c/required_reading>\n\n\u003cprocess>\n\n## Step 1: 確認交叉驗證指標\n\n交叉驗證使用以下指標確認上海庫存訊號的真實性:\n\n| 指標 | 來源 | 支持緊縮假設 | 反駁緊縮假設 |\n|------|------|--------------|--------------|\n| COMEX Registered | CME Group | 同步下降 | 穩定或上升 |\n| COMEX Eligible | CME Group | 同步下降 | 穩定或上升 |\n| ETF 持倉(SLV) | MacroMicro | 同步下降 | 穩定或上升 |\n| 期貨結構 | Yahoo Finance | Backwardation | Contango |\n| 白銀價格波動 | Yahoo Finance | 上升 | 穩定 |\n\n## Step 2: 抓取 COMEX 庫存數據\n\n```bash\ncd skills/detect-shanghai-silver-stock-drain\n\npython scripts/fetch_comex_inventory.py \\\n --commodity silver \\\n --output data/comex_silver.csv\n```\n\n**注意**:COMEX 數據可能需要從 CME Group 網站抓取,有存取限制。如無法取得,此指標可跳過。\n\n## Step 3: 抓取 ETF 持倉數據\n\n使用 MacroMicro 爬蟲(參考 `monitor-etf-holdings-drawdown-risk` skill):\n\n```bash\n# 如果已有 ETF 持倉 skill,可直接引用\npython ../monitor-etf-holdings-drawdown-risk/scripts/fetch_etf_holdings.py \\\n --etf SLV \\\n --output data/slv_holdings.csv\n```\n\n## Step 4: 抓取價格與期貨數據\n\n```python\nimport yfinance as yf\n\n# 白銀期貨近月\nsi_near = yf.download(\"SI=F\", start=\"2020-01-01\", end=\"2026-01-16\")\n\n# 白銀期貨遠月(如有)\nsi_far = yf.download(\"SIK26.CMX\", start=\"2020-01-01\", end=\"2026-01-16\")\n\n# 計算期貨結構\n# Contango: 遠月 > 近月 (正常)\n# Backwardation: 近月 > 遠月 (緊縮訊號)\n```\n\n## Step 5: 綜合評估\n\n執行交叉驗證評估:\n\n```bash\npython scripts/drain_detector.py \\\n --result result.json \\\n --cross-validate \\\n --output result_validated.json\n```\n\n**評估邏輯:**\n\n```python\ndef cross_validate(shanghai_signal, comex, etf, futures_structure):\n \"\"\"交叉驗證上海庫存訊號\"\"\"\n support_count = 0\n total_checks = 0\n\n # COMEX 庫存\n if comex is not None:\n total_checks += 1\n if comex[\"registered_change\"] \u003c -0.05: # 下降 > 5%\n support_count += 1\n\n # ETF 持倉\n if etf is not None:\n total_checks += 1\n if etf[\"slv_change\"] \u003c -0.05: # 下降 > 5%\n support_count += 1\n\n # 期貨結構\n if futures_structure is not None:\n total_checks += 1\n if futures_structure == \"backwardation\":\n support_count += 1\n\n # 信心分數\n if total_checks > 0:\n confidence = support_count / total_checks\n else:\n confidence = 0.5 # 無法驗證,中性\n\n return {\n \"confidence\": confidence,\n \"support_count\": support_count,\n \"total_checks\": total_checks,\n \"validated_signal\": shanghai_signal if confidence >= 0.5 else \"UNCONFIRMED\"\n }\n```\n\n## Step 6: 輸出驗證報告\n\n驗證報告包含:\n\n```json\n{\n \"original_signal\": \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\",\n \"cross_validation\": {\n \"confidence\": 0.67,\n \"checks\": [\n {\n \"indicator\": \"COMEX Registered\",\n \"result\": \"SUPPORT\",\n \"detail\": \"同期下降 8.2%\"\n },\n {\n \"indicator\": \"SLV Holdings\",\n \"result\": \"NEUTRAL\",\n \"detail\": \"持平 (+0.3%)\"\n },\n {\n \"indicator\": \"Futures Structure\",\n \"result\": \"SUPPORT\",\n \"detail\": \"Mild Backwardation\"\n }\n ]\n },\n \"validated_signal\": \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\",\n \"interpretation\": \"上海庫存耗盡訊號獲得 2/3 跨市場指標支持,信心度中高。\"\n}\n```\n\n## Step 7: 解讀與建議\n\n根據驗證結果提供解讀:\n\n| 信心度 | 解讀 | 建議 |\n|--------|------|------|\n| ≥ 0.7 | 高度確認 | 訊號可信,可作為決策依據 |\n| 0.5-0.7 | 部分確認 | 訊號可參考,需持續觀察 |\n| \u003c 0.5 | 未確認 | 訊號可能為假訊號,謹慎解讀 |\n\n\u003c/process>\n\n\u003csuccess_criteria>\n此工作流程完成時應產出:\n\n- [ ] COMEX 庫存變化評估(如可取得)\n- [ ] ETF 持倉變化評估(如可取得)\n- [ ] 期貨結構評估\n- [ ] 綜合信心分數\n- [ ] 驗證後訊號等級\n- [ ] 交叉驗證報告(JSON 格式)\n- [ ] 中文解讀與建議\n\u003c/success_criteria>\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4343,"content_sha256":"008219ef2ac4ebec8e89b99350f939f5d45b4f2785442ac2914ee6b787b14474"},{"filename":"workflows/fetch-data.md","content":"# Workflow: 數據抓取\n\n\u003crequired_reading>\n**執行前請先閱讀:**\n1. references/data-sources.md - SGE/SHFE 資料來源與抓取方法\n\u003c/required_reading>\n\n\u003cprocess>\n\n## Step 1: 環境準備\n\n確認依賴套件已安裝:\n\n```bash\npip install selenium webdriver-manager beautifulsoup4 pdfplumber pandas numpy\n```\n\n確認 Chrome 瀏覽器已安裝(Selenium 需要)。\n\n## Step 2: 抓取 SGE 庫存數據\n\nSGE(上海黃金交易所)庫存數據來自「行情周報」PDF:\n\n```bash\ncd skills/detect-shanghai-silver-stock-drain\n\npython scripts/fetch_sge_stock.py \\\n --start 2020-01-01 \\\n --end 2026-01-16 \\\n --output data/sge_stock.csv\n```\n\n**抓取流程:**\n1. 訪問 SGE 官網行情周報頁面\n2. 下載週報 PDF 檔案\n3. 使用 pdfplumber 解析 PDF 表格\n4. 提取「指定倉庫庫存周報」中的白銀庫存數據\n5. 轉換為 CSV 格式儲存\n\n**反偵測策略:**\n- 隨機延遲 1-3 秒\n- 隨機 User-Agent\n- 模擬人類瀏覽器行為\n\n## Step 3: 抓取 SHFE 庫存數據\n\nSHFE(上海期貨交易所)庫存數據來自「倉單日報」或「Weekly Inventory」:\n\n```bash\npython scripts/fetch_shfe_stock.py \\\n --start 2020-01-01 \\\n --end 2026-01-16 \\\n --output data/shfe_stock.csv\n```\n\n**抓取流程:**\n1. 訪問 SHFE 官網倉單查詢頁面\n2. 選擇白銀品種\n3. 等待頁面完全載入(使用 WebDriverWait)\n4. 解析 HTML 表格提取庫存數據\n5. 轉換為 CSV 格式儲存\n\n## Step 4: 數據驗證\n\n檢查抓取的數據是否完整:\n\n```python\nimport pandas as pd\n\n# 讀取數據\nsge = pd.read_csv(\"data/sge_stock.csv\", parse_dates=[\"date\"])\nshfe = pd.read_csv(\"data/shfe_stock.csv\", parse_dates=[\"date\"])\n\n# 檢查數據範圍\nprint(f\"SGE: {sge['date'].min()} ~ {sge['date'].max()}, {len(sge)} 筆\")\nprint(f\"SHFE: {shfe['date'].min()} ~ {shfe['date'].max()}, {len(shfe)} 筆\")\n\n# 檢查缺失值\nprint(f\"SGE 缺失: {sge['stock_kg'].isna().sum()}\")\nprint(f\"SHFE 缺失: {shfe['stock_kg'].isna().sum()}\")\n```\n\n## Step 5: 快取管理\n\n數據會快取在 `data/` 目錄:\n\n```\ndata/\n├── sge_stock.csv # SGE 庫存時間序列\n├── shfe_stock.csv # SHFE 庫存時間序列\n├── combined_stock.csv # 合併庫存(自動生成)\n└── cache_meta.json # 快取元資料\n```\n\n快取有效期預設為 12 小時。超過有效期後會自動重新抓取。\n\n強制更新快取:\n\n```bash\npython scripts/fetch_sge_stock.py --force-update\npython scripts/fetch_shfe_stock.py --force-update\n```\n\n## Step 6: 錯誤處理\n\n**常見錯誤與解決方案:**\n\n| 錯誤 | 原因 | 解決方案 |\n|------|------|----------|\n| `TimeoutException` | 頁面載入超時 | 增加等待時間或重試 |\n| `NoSuchElementException` | 選擇器失效 | 更新選擇器(網站改版) |\n| PDF 解析失敗 | PDF 格式變更 | 更新解析規則 |\n| 403 Forbidden | 被網站封鎖 | 增加延遲、降低頻率 |\n\n**Debug 模式:**\n\n```bash\npython scripts/fetch_sge_stock.py --debug\n```\n\nDebug 模式會:\n- 保存原始 HTML/PDF 到 `data/debug/`\n- 輸出詳細日誌\n- 不使用 headless 模式(可視化瀏覽器)\n\n\u003c/process>\n\n\u003csuccess_criteria>\n此工作流程完成時應產出:\n\n- [ ] `data/sge_stock.csv` - SGE 庫存時間序列\n- [ ] `data/shfe_stock.csv` - SHFE 庫存時間序列\n- [ ] 數據欄位:date, stock_kg\n- [ ] 數據範圍覆蓋指定時間區間\n- [ ] 缺失值比例 \u003c 5%\n- [ ] 快取元資料已更新\n\u003c/success_criteria>\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3457,"content_sha256":"918795e6b7b8494592fd199a4cefdf95c2c204bbf0a3dcbba7aeeaa78c23af5b"}],"content_json":{"type":"doc","content":[{"type":"paragraph","content":[{"text":"\u003cessential_principles>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003cprinciple name=\"drain_metrics_core\"> ","type":"text"},{"text":"庫存耗盡三維度量化","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"庫存分析的三維框架:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"方向(Direction)","type":"text","marks":[{"type":"strong"}]},{"text":":庫存是上升還是下降","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"速度(Speed)","type":"text","marks":[{"type":"strong"}]},{"text":":每週流出量 ","type":"text"},{"text":"drain_rate(t) = -Δ1(t)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"加速度(Acceleration)","type":"text","marks":[{"type":"strong"}]},{"text":":流出速度的變化 ","type":"text"},{"text":"Δ2(t) = drain_rate(t) - drain_rate(t-1)","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"當 ","type":"text"},{"text":"drain_rate > 0","type":"text","marks":[{"type":"code_inline"}]},{"text":" 且 ","type":"text"},{"text":"Δ2 > 0","type":"text","marks":[{"type":"code_inline"}]},{"text":" 時,表示「庫存正在流出,且流出速度在加快」——這是晚期供給訊號的核心特徵。 \u003c/principle>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003cprinciple name=\"z_score_standardization\"> ","type":"text"},{"text":"Z 分數標準化判斷","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"使用歷史視窗(建議 3~5 年)計算 Z 分數:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"z_drain(t) = (drain_rate(t) - mean) / std","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"z_accel(t) = (Δ2(t) - mean) / std","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"門檻判定:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"指標","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"門檻","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"意義","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"z_drain","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"≤ -1.5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"流出速度顯著大於常態","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"z_accel","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"≥ +1.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"流出正在加速","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"level_percentile","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"≤ 0.20","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"庫存處於歷史低檔","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c/principle>","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]}]}]},{"type":"paragraph","content":[{"text":"\u003cprinciple name=\"three_stage_signal\"> ","type":"text"},{"text":"三段式晚期訊號判定","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"把推文敘事轉為可執行規則:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"條件","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"描述","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"單獨成立","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"組合效果","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A. Level","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"庫存水位 \u003c 20% 歷史分位","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WATCH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"B. Speed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"z_drain ≤ -1.5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WATCH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"B+C → MEDIUM","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"C. Acceleration","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"z_accel ≥ +1.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WATCH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A+B+C → HIGH","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"訊號分級","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"HIGH_LATE_STAGE_SUPPLY_SIGNAL","type":"text","marks":[{"type":"code_inline"}]},{"text":":A+B+C 同時成立","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MEDIUM_SUPPLY_TIGHTENING","type":"text","marks":[{"type":"code_inline"}]},{"text":":(B+C) 或 (A+B) 成立","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"WATCH","type":"text","marks":[{"type":"code_inline"}]},{"text":":任一條件成立","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NO_SIGNAL","type":"text","marks":[{"type":"code_inline"}]},{"text":":無異常 \u003c/principle>","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"\u003cprinciple name=\"data_sources\"> ","type":"text"},{"text":"資料來源與口徑","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"主要數據來源:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CEIC Data","type":"text","marks":[{"type":"strong"}]},{"text":":上海期貨交易所白銀倉單數據","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"URL: ","type":"text"},{"text":"https://www.ceicdata.com/zh-hans/china/shanghai-futures-exchange-commodity-futures-stock/cn-warehouse-stock-shanghai-future-exchange-silver","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"數據範圍:2012-07-02 至今(約 3,300+ 觀測值)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"更新頻率:每日","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"歷史最高:3,091 噸 (2021-01-12)","type":"text"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"重要提醒","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"這是「交易所可交割/倉單」口徑,不等於全中國社會庫存","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"單週跳動可能反映倉儲規則變動或搬倉,需平滑處理","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"使用 Selenium 模擬人類瀏覽器抓取 SVG 圖表,遵循反偵測策略 \u003c/principle>","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"\u003c/essential_principles>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003cobjective> 監控上海白銀庫存(SGE + SHFE)的耗盡狀態:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"數據採集","type":"text","marks":[{"type":"strong"}]},{"text":":抓取 SGE/SHFE 週報庫存數據","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"三維量化","type":"text","marks":[{"type":"strong"}]},{"text":":計算方向、速度、加速度","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"標準化判斷","type":"text","marks":[{"type":"strong"}]},{"text":":使用 Z 分數判定異常","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"訊號生成","type":"text","marks":[{"type":"strong"}]},{"text":":輸出晚期供給訊號分級","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"市場交叉驗證","type":"text","marks":[{"type":"strong"}]},{"text":"(選配):COMEX、ETF、現貨溢價","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"輸出:庫存水位、耗盡速度、加速度、Z 分數、訊號分級、敘事解讀。 \u003c/objective>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003cquick_start>","type":"text"}]},{"type":"paragraph","content":[{"text":"最快的方式:檢查上海白銀庫存耗盡狀態","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd skills/detect-shanghai-silver-stock-drain\n\n# 首次使用:安裝依賴\npip install pandas numpy selenium webdriver-manager matplotlib\n\n# 1. 抓取最新數據(5 年歷史,約 200+ 週)\npython scripts/fetch_shfe_stock.py --force-update\n\n# 2. 執行快速檢查\npython scripts/drain_detector.py --quick","type":"text"}]},{"type":"paragraph","content":[{"text":"輸出範例:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"as_of\": \"2026-01-16\",\n \"signal\": \"MEDIUM_SUPPLY_TIGHTENING\",\n \"latest_combined_stock_tonnes\": 1133.3,\n \"level_percentile\": 0.12,\n \"z_drain_rate\": -2.1,\n \"z_acceleration\": 1.4\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"完整分析 + 視覺化報告","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. 執行完整分析\npython scripts/drain_detector.py \\\n --start 2020-01-01 \\\n --end 2026-01-16 \\\n --output result.json\n\n# 2. 生成視覺化報告\npython scripts/visualize_drain.py \\\n --result result.json \\\n --output ../../../output/","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003c/quick_start>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003cintake> 需要進行什麼操作?","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"快速檢查","type":"text","marks":[{"type":"strong"}]},{"text":" - 查看目前上海白銀庫存耗盡狀態與訊號","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"完整分析","type":"text","marks":[{"type":"strong"}]},{"text":" - 執行完整的歷史庫存分析與趨勢計算","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"數據更新","type":"text","marks":[{"type":"strong"}]},{"text":" - 抓取最新的 SGE/SHFE 庫存數據","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"交叉驗證","type":"text","marks":[{"type":"strong"}]},{"text":" - 使用 COMEX、ETF 等指標交叉驗證","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"方法論學習","type":"text","marks":[{"type":"strong"}]},{"text":" - 了解三維度量化與訊號判定邏輯","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"請選擇或直接提供分析參數。","type":"text","marks":[{"type":"strong"}]},{"text":" \u003c/intake>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003crouting>","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Response","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1, \"快速\", \"quick\", \"check\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"執行 ","type":"text"},{"text":"scripts/drain_detector.py --quick","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"2, \"分析\", \"analyze\", \"full\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"閱讀 ","type":"text"},{"text":"workflows/analyze.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 並執行","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"3, \"更新\", \"fetch\", \"data\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"閱讀 ","type":"text"},{"text":"workflows/fetch-data.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 並執行","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"4, \"驗證\", \"validate\", \"cross\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"閱讀 ","type":"text"},{"text":"workflows/cross-validate.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 並執行","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"5, \"學習\", \"方法論\", \"why\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"閱讀 ","type":"text"},{"text":"references/methodology.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"提供日期參數","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"閱讀 ","type":"text"},{"text":"workflows/analyze.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 並使用參數執行","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"路由後,閱讀對應文件並執行。","type":"text","marks":[{"type":"strong"}]},{"text":" \u003c/routing>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003cdirectory_structure>","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"detect-shanghai-silver-stock-drain/\n├── SKILL.md # 本文件(路由器)\n├── skill.yaml # 前端展示元數據\n├── manifest.json # 技能元數據\n├── workflows/\n│ ├── analyze.md # 完整庫存分析工作流\n│ ├── fetch-data.md # 數據抓取工作流\n│ └── cross-validate.md # 交叉驗證工作流\n├── references/\n│ ├── data-sources.md # SGE/SHFE 資料來源說明\n│ ├── methodology.md # 三維度量化方法論\n│ └── input-schema.md # 完整輸入參數定義\n├── templates/\n│ ├── output-json.md # JSON 輸出模板\n│ └── output-markdown.md # Markdown 報告模板\n└── scripts/\n ├── drain_detector.py # 主偵測腳本\n ├── fetch_sge_stock.py # SGE 庫存抓取(PDF)\n ├── fetch_shfe_stock.py # SHFE 庫存抓取\n └── visualize_drain.py # 視覺化報告生成","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003c/directory_structure>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003creference_index>","type":"text"}]},{"type":"paragraph","content":[{"text":"方法論","type":"text","marks":[{"type":"strong"}]},{"text":": references/methodology.md","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"三維度量化邏輯(方向、速度、加速度)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Z 分數標準化","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"三段式訊號判定","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"資料來源","type":"text","marks":[{"type":"strong"}]},{"text":": references/data-sources.md","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SGE 行情周報 PDF 抓取","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SHFE 倉單/庫存周報抓取","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Selenium 反偵測策略","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"輸入參數","type":"text","marks":[{"type":"strong"}]},{"text":": references/input-schema.md","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"完整參數定義","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"預設值與建議範圍","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"\u003c/reference_index>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003cworkflows_index>","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Workflow","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"使用時機","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"analyze.md","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"完整庫存分析","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"需要完整歷史分析時","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fetch-data.md","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"數據抓取","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"更新 SGE/SHFE 庫存數據","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cross-validate.md","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"交叉驗證訊號","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"確認供給緊縮訊號真實性時","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c/workflows_index>","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]}]}]},{"type":"paragraph","content":[{"text":"\u003ctemplates_index>","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Template","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"output-json.md","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"JSON 輸出結構定義","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"output-markdown.md","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Markdown 報告模板","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c/templates_index>","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]}]}]},{"type":"paragraph","content":[{"text":"\u003cscripts_index>","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Script","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Command","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"drain_detector.py","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--quick","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"快速檢查耗盡狀態","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"drain_detector.py","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--start DATE --end DATE --output FILE","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"完整歷史分析","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fetch_sge_stock.py","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--output sge_stock.csv","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"抓取 SGE 庫存","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fetch_shfe_stock.py","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--output shfe_stock.csv","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"抓取 SHFE 庫存","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"visualize_drain.py","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--result result.json --output DIR","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"生成視覺化報告","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c/scripts_index>","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph"}]}]}]},{"type":"paragraph","content":[{"text":"\u003cinput_schema_summary>","type":"text"}]},{"type":"paragraph","content":[{"text":"核心參數","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"參數","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"類型","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"預設值","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"說明","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"start_date","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"3Y 前","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"分析起始日 (YYYY-MM-DD)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"end_date","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"today","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"分析結束日 (YYYY-MM-DD)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"frequency","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"weekly","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"數據頻率 (weekly/daily)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"include_sources","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"array","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"[\"SGE\",\"SHFE\"]","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"納入的庫存來源","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"unit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"tonnes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"單位 (tonnes/kg/troy_oz)","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"分析參數","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"參數","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"類型","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"預設值","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"說明","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"smoothing_window_weeks","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"int","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"4","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"平滑視窗(週)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"drain_threshold_z","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"float","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-1.5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"異常耗盡 Z 分數門檻","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"accel_threshold_z","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"float","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"+1.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"耗盡加速 Z 分數門檻","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"confirm_with_markets","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bool","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"true","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"是否做市場側交叉驗證","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"完整參數定義見 ","type":"text"},{"text":"references/input-schema.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"。","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003c/input_schema_summary>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003coutput_schema_summary>","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"skill\": \"detect_shanghai_silver_stock_drain\",\n \"as_of\": \"2026-01-16\",\n \"unit\": \"tonnes\",\n \"sources\": [\"SGE\", \"SHFE\"],\n \"latest_combined_stock\": 1133.3,\n \"level_percentile\": 0.12,\n \"recent_4w_avg_drawdown\": 58.4,\n \"drawdown_acceleration\": 9.7,\n \"z_scores\": {\n \"z_drain_rate\": -2.1,\n \"z_acceleration\": 1.4\n },\n \"signal\": \"HIGH_LATE_STAGE_SUPPLY_SIGNAL\",\n \"narrative\": [...],\n \"caveats\": [...]\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"完整輸出結構見 ","type":"text"},{"text":"templates/output-json.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"。 \u003c/output_schema_summary>","type":"text"}]},{"type":"paragraph","content":[{"text":"\u003csuccess_criteria> 執行成功時應產出:","type":"text"}]},{"type":"checkbox_list","attrs":{"id":null},"content":[{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"上海合併庫存水位(SGE + SHFE)","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"庫存水位歷史分位數","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"近 N 週平均流出速度","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"流出加速度","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"z_drain_rate 與 z_acceleration","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"訊號分級(HIGH/MEDIUM/WATCH/NO_SIGNAL)","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"敘事解讀(中文)","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"數據口徑與限制說明 \u003c/success_criteria>","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"detect-shanghai-silver-stock-drain","author":"@skillopedia","source":{"stars":3,"repo_name":"macro-skills","origin_url":"https://github.com/fatfingererr/macro-skills/blob/HEAD/skills/detect-shanghai-silver-stock-drain/SKILL.md","repo_owner":"fatfingererr","body_sha256":"39a78cf21d7431fc3058c54b4d0fffc22d92ee35e249e996de0015456b9bb766","cluster_key":"1a25ade43185352295fbb276a1c2b8e7936000919500eac4eae9377d4ac8b8f8","clean_bundle":{"format":"clean-skill-bundle-v1","source":"fatfingererr/macro-skills/skills/detect-shanghai-silver-stock-drain/SKILL.md","attachments":[{"id":"9479db71-fc05-5843-8de3-d341082976d0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9479db71-fc05-5843-8de3-d341082976d0/attachment.json","path":"manifest.json","size":3116,"sha256":"2f0bfd443b42b811f5cc1972c3b10f7bc51f3e67a2d77efc4f2f6c9507bdfb59","contentType":"application/json; charset=utf-8"},{"id":"bcf36b7b-b9a1-5693-9a07-a992793ffd42","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bcf36b7b-b9a1-5693-9a07-a992793ffd42/attachment.md","path":"references/data-sources.md","size":7162,"sha256":"860a99f053e18689fd5405725eb8a196cdf0b8c479e67d56ab4d0dbd06727dcb","contentType":"text/markdown; charset=utf-8"},{"id":"a7cfb853-196b-58be-9608-41a937593628","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a7cfb853-196b-58be-9608-41a937593628/attachment.md","path":"references/input-schema.md","size":5094,"sha256":"b144986cf1c33030b77c770ca266850fb56a0a066a299b1b9e71efd45515981e","contentType":"text/markdown; charset=utf-8"},{"id":"0f381dc6-2a7f-5162-a237-23e90b53922a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0f381dc6-2a7f-5162-a237-23e90b53922a/attachment.md","path":"references/methodology.md","size":5824,"sha256":"3c1b3e6794a2984159b09782ec489f2552eb39bad90e1ab631ba6d39de73e7ad","contentType":"text/markdown; charset=utf-8"},{"id":"f324c719-946c-5706-bfc3-87bfba0b5d9f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f324c719-946c-5706-bfc3-87bfba0b5d9f/attachment.py","path":"scripts/drain_detector.py","size":14678,"sha256":"1164154a3cd5d93d9be058c69eb7ef7cbeec1e189727309e9763bff1c74a8b91","contentType":"text/x-python; charset=utf-8"},{"id":"1ea3a276-bd6b-5938-9930-5a9e86e27ae6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1ea3a276-bd6b-5938-9930-5a9e86e27ae6/attachment.py","path":"scripts/explore_ceic.py","size":8981,"sha256":"edc09a6bd1cee73aec0671236ddf43c53ad674d1632eb7020142fd962f8ba0de","contentType":"text/x-python; charset=utf-8"},{"id":"d7abefde-129e-558a-b338-82b359288d02","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d7abefde-129e-558a-b338-82b359288d02/attachment.py","path":"scripts/explore_shfe.py","size":6563,"sha256":"0e972751d8ba475fc77c75755431dd666861c2ebaf9a0f78e2d022400c8d8352","contentType":"text/x-python; charset=utf-8"},{"id":"7d952c80-b372-5152-a1f2-d3ffd6fd3ca3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7d952c80-b372-5152-a1f2-d3ffd6fd3ca3/attachment.py","path":"scripts/fetch_ceic_silver.py","size":13499,"sha256":"6b27e9292e9da43e8c546730552289be8258f81b9bd20b73d766e4f7996322e6","contentType":"text/x-python; charset=utf-8"},{"id":"b951117f-a942-502e-9808-3a70a38624ee","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b951117f-a942-502e-9808-3a70a38624ee/attachment.py","path":"scripts/fetch_ceic_svg.py","size":16875,"sha256":"f0d3cd10ea1002be35e5492e27cfd968fd4d06341a3cb34410bbda33b6a7b0b2","contentType":"text/x-python; charset=utf-8"},{"id":"3598db23-9a00-55f2-9955-8b9777fab0f5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3598db23-9a00-55f2-9955-8b9777fab0f5/attachment.py","path":"scripts/fetch_sge_stock.py","size":2832,"sha256":"36ac121c01d684e48ad6e74b56854d58e1b5f81d8ef2868e6d6e2c400e92edba","contentType":"text/x-python; charset=utf-8"},{"id":"90eac2d4-640b-5008-af1d-b77656af5ab6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/90eac2d4-640b-5008-af1d-b77656af5ab6/attachment.py","path":"scripts/fetch_shfe_stock.py","size":15452,"sha256":"141fd95a35618862b3f5becacc25028c6ecccfa749d9c08eac0142a2cc423fab","contentType":"text/x-python; charset=utf-8"},{"id":"44156fa6-84fe-52fe-8189-41c637660129","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/44156fa6-84fe-52fe-8189-41c637660129/attachment.py","path":"scripts/visualize_drain.py","size":10160,"sha256":"599d18b7c05d35616591faa81470af57aa83a0f012e749b0b627c3315fd07be3","contentType":"text/x-python; charset=utf-8"},{"id":"33742800-0125-5e8e-94eb-4471b1ecdc33","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/33742800-0125-5e8e-94eb-4471b1ecdc33/attachment.yaml","path":"skill.yaml","size":13822,"sha256":"a695685d374abcd876943b3f32af5707b3e67ff7511efe5a765adf50992b8f7a","contentType":"application/yaml; charset=utf-8"},{"id":"e9676aa5-022a-5d94-b9d8-6913bcec4f6c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e9676aa5-022a-5d94-b9d8-6913bcec4f6c/attachment.md","path":"templates/output-json.md","size":5696,"sha256":"1c751b0cc7c7f39c1a9fb3ca95ad444d8e10c2c9ac5eeaf920a7a77ece256634","contentType":"text/markdown; charset=utf-8"},{"id":"c4e87996-e7b4-570d-abfd-e16f8be2f9a0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c4e87996-e7b4-570d-abfd-e16f8be2f9a0/attachment.md","path":"templates/output-markdown.md","size":5516,"sha256":"8ef3a39c929ad2246f5a65722c45f0333667c565f8b800f143ae29321fbb4e44","contentType":"text/markdown; charset=utf-8"},{"id":"1a79e47a-9b29-51be-8057-09b5d3c38eec","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1a79e47a-9b29-51be-8057-09b5d3c38eec/attachment.md","path":"workflows/analyze.md","size":5341,"sha256":"2622bb7b914b294ab041a5c77d007a6858493a223cfd4d4867727ceb7496cfbe","contentType":"text/markdown; charset=utf-8"},{"id":"a5fe6d51-b83b-519e-9a76-ab68e34ffc07","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a5fe6d51-b83b-519e-9a76-ab68e34ffc07/attachment.md","path":"workflows/cross-validate.md","size":4343,"sha256":"008219ef2ac4ebec8e89b99350f939f5d45b4f2785442ac2914ee6b787b14474","contentType":"text/markdown; charset=utf-8"},{"id":"226a3f10-08a9-5322-82b1-137ccc5c5496","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/226a3f10-08a9-5322-82b1-137ccc5c5496/attachment.md","path":"workflows/fetch-data.md","size":3457,"sha256":"918795e6b7b8494592fd199a4cefdf95c2c204bbf0a3dcbba7aeeaa78c23af5b","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"e5a68ff43d9b4683c075dfa217c36094240912364adbcff1454947b66037414b","attachment_count":18,"text_attachments":18,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/detect-shanghai-silver-stock-drain/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"finance-legal-compliance","category_label":"Finance"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"finance-legal-compliance","import_tag":"clean-skills-v1","description":"以公開交易所庫存資料為核心,量化上海白銀庫存耗盡的方向、速度與加速度,並將其轉成可交易的供給緊縮訊號。"}},"renderedAt":1782981935806}

<essential principles <principle name="drain metrics core" 庫存耗盡三維度量化 庫存分析的三維框架: - 方向(Direction) :庫存是上升還是下降 - 速度(Speed) :每週流出量 - 加速度(Acceleration) :流出速度的變化 當 且 時,表示「庫存正在流出,且流出速度在加快」——這是晚期供給訊號的核心特徵。 </principle <principle name="z score standardization" Z 分數標準化判斷 使用歷史視窗(建議 3 5 年)計算 Z 分數: - - 門檻判定: | 指標 | 門檻 | 意義 | |------------------|--------|----------------------| | z drain | ≤ -1.5 | 流出速度顯著大於常態 | | z accel | ≥ +1.0 | 流出正在加速 | | level percentile | ≤ 0.20 | 庫存處於歷史低檔 | </principle <principle name="three stage signal" 三段式晚期訊號判定 把推文敘事轉為可執行規則: | 條件 | 描述 | 單獨成立 | 組合效果 | |-----------------|----------…