stock-analyzer-skill 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.claude-plugin/plugin.json +21 -0
  3. package/CHANGELOG.md +93 -0
  4. package/CONTRIBUTING.md +331 -0
  5. package/README.md +259 -0
  6. package/experts/README.md +119 -0
  7. package/experts/buffett.md +91 -0
  8. package/experts/chaogu_yangjia.md +125 -0
  9. package/experts/decide.md +212 -0
  10. package/experts/duan_yongping.md +106 -0
  11. package/experts/lynch.md +127 -0
  12. package/experts/soros.md +89 -0
  13. package/experts/xu_xiang.md +107 -0
  14. package/experts/zhao_laoge.md +143 -0
  15. package/experts/zuoshou_xinyi.md +144 -0
  16. package/install-plugin.js +69 -0
  17. package/methodology.md +455 -0
  18. package/package.json +43 -0
  19. package/scripts/__pycache__/announcements.cpython-314.pyc +0 -0
  20. package/scripts/__pycache__/backtest.cpython-314.pyc +0 -0
  21. package/scripts/__pycache__/chan.cpython-314.pyc +0 -0
  22. package/scripts/__pycache__/classifier.cpython-314.pyc +0 -0
  23. package/scripts/__pycache__/common.cpython-314.pyc +0 -0
  24. package/scripts/__pycache__/finance.cpython-314.pyc +0 -0
  25. package/scripts/__pycache__/init_pool.cpython-314.pyc +0 -0
  26. package/scripts/__pycache__/kline.cpython-314.pyc +0 -0
  27. package/scripts/__pycache__/patterns_local.cpython-314.pyc +0 -0
  28. package/scripts/__pycache__/quote.cpython-314.pyc +0 -0
  29. package/scripts/__pycache__/refresh_pool.cpython-314.pyc +0 -0
  30. package/scripts/__pycache__/screener.cpython-314.pyc +0 -0
  31. package/scripts/__pycache__/technical.cpython-314.pyc +0 -0
  32. package/scripts/announcements.py +118 -0
  33. package/scripts/backtest.py +528 -0
  34. package/scripts/chan.py +591 -0
  35. package/scripts/classifier.py +302 -0
  36. package/scripts/common.py +507 -0
  37. package/scripts/data/__init__.py +208 -0
  38. package/scripts/data/__pycache__/__init__.cpython-314.pyc +0 -0
  39. package/scripts/data/__pycache__/cache.cpython-314.pyc +0 -0
  40. package/scripts/data/__pycache__/config.cpython-314.pyc +0 -0
  41. package/scripts/data/__pycache__/types.cpython-314.pyc +0 -0
  42. package/scripts/data/cache.py +99 -0
  43. package/scripts/data/config.py +49 -0
  44. package/scripts/data/industry_thresholds.json +199 -0
  45. package/scripts/data/portfolio_example.json +14 -0
  46. package/scripts/data/sector_etf.csv +14 -0
  47. package/scripts/data/sector_mapping.json +64 -0
  48. package/scripts/data/sector_stocks.json +135 -0
  49. package/scripts/data/types.py +66 -0
  50. package/scripts/fetchers/__init__.py +130 -0
  51. package/scripts/fetchers/__pycache__/__init__.cpython-314.pyc +0 -0
  52. package/scripts/fetchers/__pycache__/akshare_finance.cpython-314.pyc +0 -0
  53. package/scripts/fetchers/__pycache__/akshare_kline.cpython-314.pyc +0 -0
  54. package/scripts/fetchers/__pycache__/akshare_quote.cpython-314.pyc +0 -0
  55. package/scripts/fetchers/__pycache__/baostock_kline.cpython-314.pyc +0 -0
  56. package/scripts/fetchers/__pycache__/eastmoney_finance.cpython-314.pyc +0 -0
  57. package/scripts/fetchers/__pycache__/eastmoney_kline.cpython-314.pyc +0 -0
  58. package/scripts/fetchers/__pycache__/eastmoney_quote.cpython-314.pyc +0 -0
  59. package/scripts/fetchers/__pycache__/efinance_finance.cpython-314.pyc +0 -0
  60. package/scripts/fetchers/__pycache__/efinance_kline.cpython-314.pyc +0 -0
  61. package/scripts/fetchers/__pycache__/efinance_quote.cpython-314.pyc +0 -0
  62. package/scripts/fetchers/__pycache__/pytdx_quote.cpython-314.pyc +0 -0
  63. package/scripts/fetchers/__pycache__/sina_kline.cpython-314.pyc +0 -0
  64. package/scripts/fetchers/__pycache__/sina_quote.cpython-314.pyc +0 -0
  65. package/scripts/fetchers/__pycache__/tencent_kline.cpython-314.pyc +0 -0
  66. package/scripts/fetchers/__pycache__/tencent_quote.cpython-314.pyc +0 -0
  67. package/scripts/fetchers/__pycache__/tushare_kline.cpython-314.pyc +0 -0
  68. package/scripts/fetchers/__pycache__/tushare_quote.cpython-314.pyc +0 -0
  69. package/scripts/fetchers/__pycache__/yfinance_kline.cpython-314.pyc +0 -0
  70. package/scripts/fetchers/akshare_finance.py +35 -0
  71. package/scripts/fetchers/akshare_kline.py +59 -0
  72. package/scripts/fetchers/akshare_quote.py +52 -0
  73. package/scripts/fetchers/baostock_kline.py +64 -0
  74. package/scripts/fetchers/eastmoney_finance.py +29 -0
  75. package/scripts/fetchers/eastmoney_kline.py +48 -0
  76. package/scripts/fetchers/eastmoney_quote.py +68 -0
  77. package/scripts/fetchers/efinance_finance.py +32 -0
  78. package/scripts/fetchers/efinance_kline.py +46 -0
  79. package/scripts/fetchers/efinance_quote.py +53 -0
  80. package/scripts/fetchers/pytdx_kline.py +70 -0
  81. package/scripts/fetchers/pytdx_quote.py +78 -0
  82. package/scripts/fetchers/sina_kline.py +30 -0
  83. package/scripts/fetchers/sina_quote.py +35 -0
  84. package/scripts/fetchers/tencent_kline.py +52 -0
  85. package/scripts/fetchers/tencent_quote.py +29 -0
  86. package/scripts/fetchers/tushare_kline.py +62 -0
  87. package/scripts/fetchers/tushare_quote.py +62 -0
  88. package/scripts/fetchers/yfinance_kline.py +66 -0
  89. package/scripts/finance.py +92 -0
  90. package/scripts/init_pool.py +105 -0
  91. package/scripts/kline.py +62 -0
  92. package/scripts/monitor.py +107 -0
  93. package/scripts/patterns_local.py +599 -0
  94. package/scripts/quote.py +69 -0
  95. package/scripts/refresh_pool.py +328 -0
  96. package/scripts/screener.py +434 -0
  97. package/scripts/strategies/__init__.py +11 -0
  98. package/scripts/strategies/__pycache__/__init__.cpython-314.pyc +0 -0
  99. package/scripts/strategies/__pycache__/registry.cpython-314.pyc +0 -0
  100. package/scripts/strategies/__pycache__/thresholds.cpython-314.pyc +0 -0
  101. package/scripts/strategies/factors/__init__.py +8 -0
  102. package/scripts/strategies/factors/__pycache__/__init__.cpython-314.pyc +0 -0
  103. package/scripts/strategies/factors/__pycache__/liquidity.cpython-314.pyc +0 -0
  104. package/scripts/strategies/factors/__pycache__/momentum.cpython-314.pyc +0 -0
  105. package/scripts/strategies/factors/__pycache__/quality.cpython-314.pyc +0 -0
  106. package/scripts/strategies/factors/__pycache__/valuation.cpython-314.pyc +0 -0
  107. package/scripts/strategies/factors/__pycache__/volatility.cpython-314.pyc +0 -0
  108. package/scripts/strategies/factors/liquidity.py +49 -0
  109. package/scripts/strategies/factors/momentum.py +45 -0
  110. package/scripts/strategies/factors/quality.py +54 -0
  111. package/scripts/strategies/factors/valuation.py +76 -0
  112. package/scripts/strategies/factors/volatility.py +89 -0
  113. package/scripts/strategies/registry.py +87 -0
  114. package/scripts/strategies/thresholds.py +28 -0
  115. package/scripts/technical/__init__.py +116 -0
  116. package/scripts/technical/__pycache__/__init__.cpython-314.pyc +0 -0
  117. package/scripts/technical/__pycache__/astock.cpython-314.pyc +0 -0
  118. package/scripts/technical/__pycache__/boll.cpython-314.pyc +0 -0
  119. package/scripts/technical/__pycache__/candlestick.cpython-314.pyc +0 -0
  120. package/scripts/technical/__pycache__/core.cpython-314.pyc +0 -0
  121. package/scripts/technical/__pycache__/kdj.cpython-314.pyc +0 -0
  122. package/scripts/technical/__pycache__/macd.cpython-314.pyc +0 -0
  123. package/scripts/technical/__pycache__/moving_average.cpython-314.pyc +0 -0
  124. package/scripts/technical/__pycache__/report.cpython-314.pyc +0 -0
  125. package/scripts/technical/__pycache__/rsi.cpython-314.pyc +0 -0
  126. package/scripts/technical/__pycache__/scoring.cpython-314.pyc +0 -0
  127. package/scripts/technical/__pycache__/signals.cpython-314.pyc +0 -0
  128. package/scripts/technical/__pycache__/trend.cpython-314.pyc +0 -0
  129. package/scripts/technical/__pycache__/volume.cpython-314.pyc +0 -0
  130. package/scripts/technical/astock.py +98 -0
  131. package/scripts/technical/boll.py +49 -0
  132. package/scripts/technical/candlestick.py +151 -0
  133. package/scripts/technical/core.py +92 -0
  134. package/scripts/technical/kdj.py +68 -0
  135. package/scripts/technical/macd.py +97 -0
  136. package/scripts/technical/moving_average.py +59 -0
  137. package/scripts/technical/report.py +221 -0
  138. package/scripts/technical/rsi.py +37 -0
  139. package/scripts/technical/scoring.py +392 -0
  140. package/scripts/technical/signals.py +70 -0
  141. package/scripts/technical/trend.py +143 -0
  142. package/scripts/technical/volume.py +113 -0
  143. package/scripts/technical.py +215 -0
  144. package/skills/financial-analyst/SKILL.md +141 -0
  145. package/skills/help/SKILL.md +188 -0
  146. package/skills/init/SKILL.md +66 -0
  147. package/skills/investment-researcher/SKILL.md +152 -0
  148. package/skills/market/SKILL.md +99 -0
  149. package/skills/portfolio/SKILL.md +96 -0
  150. package/skills/screener/SKILL.md +128 -0
  151. package/skills/sector/SKILL.md +102 -0
  152. package/skills/stock/SKILL.md +148 -0
  153. package/skills/technical/SKILL.md +168 -0
  154. package/workflow.md +91 -0
@@ -0,0 +1,66 @@
1
+ """Yahoo Finance K 线数据源(需要 yfinance 包)。"""
2
+ import sys
3
+ from pathlib import Path
4
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
5
+
6
+ from common import BaseFetcher
7
+
8
+ try:
9
+ import yfinance as yf
10
+ HAS_YFINANCE = True
11
+ except ImportError:
12
+ HAS_YFINANCE = False
13
+
14
+
15
+ def _to_yf_symbol(code: str) -> str:
16
+ """转换为 yfinance 符号格式。"""
17
+ plain = code.lstrip("shszSHSZbjBJ").zfill(6)
18
+ if plain.startswith(("60", "68", "51", "56", "58")):
19
+ return f"{plain}.SS"
20
+ elif plain.startswith(("00", "30", "15", "16", "18")):
21
+ return f"{plain}.SZ"
22
+ elif plain.startswith(("43", "83", "87", "88", "92")):
23
+ return f"{plain}.BJ"
24
+ return code
25
+
26
+
27
+ class YfinanceKlineFetcher(BaseFetcher):
28
+ """Yahoo Finance K 线数据源 (优先级 4) - 需要安装 yfinance 包。"""
29
+
30
+ def __init__(self):
31
+ super().__init__("yfinance_kline", priority=4)
32
+
33
+ def fetch(self, code: str, **kwargs) -> list | None:
34
+ if not HAS_YFINANCE:
35
+ return None
36
+ try:
37
+ scale = kwargs.get("scale", 240)
38
+ datalen = kwargs.get("datalen", 30)
39
+ symbol = _to_yf_symbol(code)
40
+
41
+ ticker = yf.Ticker(symbol)
42
+ if scale == 240:
43
+ df = ticker.history(period=f"{datalen}d")
44
+ elif scale == 60:
45
+ df = ticker.history(period="60d", interval="1h")
46
+ elif scale == 5:
47
+ df = ticker.history(period="5d", interval="5m")
48
+ else:
49
+ df = ticker.history(period=f"{datalen}d")
50
+
51
+ if df is None or df.empty:
52
+ return None
53
+
54
+ result = []
55
+ for idx, row in df.iterrows():
56
+ result.append({
57
+ "day": str(idx)[:10],
58
+ "open": str(round(row.get("Open", 0), 2)),
59
+ "close": str(round(row.get("Close", 0), 2)),
60
+ "high": str(round(row.get("High", 0), 2)),
61
+ "low": str(round(row.get("Low", 0), 2)),
62
+ "volume": str(int(row.get("Volume", 0))),
63
+ })
64
+ return result if result else None
65
+ except Exception:
66
+ return None
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 财务数据查询(多数据源自动切换)。
4
+ 数据源: 东方财富 → efinance → akshare
5
+ 用法:
6
+ finance.py SH600989 # 单只,最近 4 季
7
+ finance.py -c SH600989,SZ000807 # 批量
8
+ finance.py -j SH600989 # JSON 输出
9
+ finance.py --sources # 显示可用数据源
10
+ """
11
+ import sys
12
+ import json
13
+ from common import normalize_finance_code, parallel_map, err, DataError
14
+ from data import get_finance
15
+
16
+
17
+ def fetch(code: str, use_cache: bool = True) -> list:
18
+ """返回最近 4 季的财务数据(dict 列表,兼容旧接口)。"""
19
+ records = get_finance(code, use_cache=use_cache)
20
+ return [r.to_dict() for r in records]
21
+
22
+
23
+ def render_table(records: list) -> str:
24
+ if not records:
25
+ return "(无数据)"
26
+ fields = [
27
+ ("eps", "每股收益"),
28
+ ("roe", "ROE%"),
29
+ ("revenue_yoy", "营收同比%"),
30
+ ("net_profit_yoy", "净利同比%"),
31
+ ("gross_margin", "毛利率%"),
32
+ ("net_margin", "净利率%"),
33
+ ("debt_ratio", "负债率%"),
34
+ ("bps", "每股净资产"),
35
+ ("ocf_per_share", "每股现金流"),
36
+ ]
37
+ lines = []
38
+ header = " | ".join(["报告期"] + [label for _, label in fields])
39
+ lines.append(header)
40
+ lines.append("-" * len(header))
41
+ for r in records:
42
+ period = r.get("report_date", "?")
43
+ values = [r.get(key, "-") for key, _ in fields]
44
+ lines.append(" | ".join([period] + [str(v)[:8] for v in values]))
45
+ return "\n".join(lines)
46
+
47
+
48
+ def main():
49
+ if len(sys.argv) < 2:
50
+ err("用法: finance.py <代码> [-c codes] [-j] [--sources]")
51
+ args = sys.argv[1:]
52
+
53
+ if "--sources" in args:
54
+ from fetchers import get_finance_fetchers
55
+ fetchers = get_finance_fetchers()
56
+ print("可用财务数据源:")
57
+ for f in fetchers:
58
+ print(f" - {f.name} (优先级 {f.priority})")
59
+ return
60
+
61
+ json_mode = "-j" in args
62
+ args = [a for a in args if a not in ("-j", "--sources")]
63
+ if not args:
64
+ err("缺少代码")
65
+
66
+ if args[0] == "-c":
67
+ codes = args[1].split(",")
68
+ else:
69
+ codes = [args[0]]
70
+
71
+ normalized_codes = [normalize_finance_code(c) for c in codes]
72
+
73
+ if len(normalized_codes) > 1:
74
+ results = parallel_map(fetch, normalized_codes, max_workers=4, timeout=30)
75
+ all_results = {k: v for k, v in results.items() if v}
76
+ else:
77
+ all_results = {normalized_codes[0]: fetch(normalized_codes[0])}
78
+
79
+ if json_mode:
80
+ print(json.dumps(all_results, ensure_ascii=False, indent=2))
81
+ return
82
+
83
+ for code, records in all_results.items():
84
+ print(f"\n=== {code} ===")
85
+ print(render_table(records))
86
+
87
+
88
+ if __name__ == "__main__":
89
+ try:
90
+ main()
91
+ except DataError as e:
92
+ sys.exit(1)
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 首次安装初始化脚本 — 为每个板块拉取前 20 只股票。
4
+
5
+ 用法:
6
+ python3 scripts/init_pool.py # 检测并初始化(已有数据则跳过)
7
+ python3 scripts/init_pool.py --force # 强制重新初始化
8
+ python3 scripts/init_pool.py --top 30 # 每板块取 Top 30
9
+
10
+ 退出码始终为 0,不阻塞安装流程。
11
+ """
12
+
13
+ import argparse
14
+ import json
15
+ import os
16
+ import sys
17
+
18
+ # 复用 refresh_pool 的核心逻辑
19
+ sys.path.insert(0, os.path.dirname(__file__))
20
+ from refresh_pool import (
21
+ POOL_FILE,
22
+ load_mapping,
23
+ load_current_pool,
24
+ refresh_pool,
25
+ )
26
+
27
+ # 初始化阈值:低于此值视为未初始化
28
+ MIN_SECTORS = 10
29
+ MIN_STOCKS = 100
30
+
31
+
32
+ def is_pool_populated() -> tuple[bool, str]:
33
+ """检查股票池是否已有足够数据。返回 (是否已初始化, 描述)。"""
34
+ if not os.path.exists(POOL_FILE):
35
+ return False, "股票池文件不存在"
36
+
37
+ try:
38
+ with open(POOL_FILE, "r", encoding="utf-8") as f:
39
+ data = json.load(f)
40
+ except (json.JSONDecodeError, OSError):
41
+ return False, "股票池文件损坏"
42
+
43
+ sectors = {k: v for k, v in data.items() if not k.startswith("_")}
44
+ total_stocks = sum(len(v) for v in sectors.values())
45
+
46
+ if len(sectors) < MIN_SECTORS:
47
+ return False, f"仅 {len(sectors)} 个板块(需要 ≥{MIN_SECTORS})"
48
+ if total_stocks < MIN_STOCKS:
49
+ return False, f"仅 {total_stocks} 只股票(需要 ≥{MIN_STOCKS})"
50
+
51
+ return True, f"{len(sectors)} 个板块,{total_stocks} 只股票"
52
+
53
+
54
+ def init_pool(top_n: int = 20, force: bool = False) -> bool:
55
+ """初始化股票池。返回是否实际执行了初始化。"""
56
+ # 检查是否已初始化
57
+ if not force:
58
+ populated, desc = is_pool_populated()
59
+ if populated:
60
+ print(f"✅ 股票池已存在({desc}),跳过初始化")
61
+ print(" 如需刷新,运行: python3 scripts/refresh_pool.py")
62
+ return False
63
+
64
+ # 检查 API Token
65
+ token = os.environ.get("EASTMONEY_API_TOKEN", "")
66
+ if not token:
67
+ print("⚠️ 未设置 EASTMONEY_API_TOKEN,无法从东财 API 拉取数据")
68
+ print()
69
+ print("请按以下步骤操作:")
70
+ print(" 1. 设置环境变量: export EASTMONEY_API_TOKEN=你的token")
71
+ print(" (东财 push2 API 的 ut 参数,可从网页版 F12 抓取)")
72
+ print(" 2. 运行初始化: python3 scripts/init_pool.py --force")
73
+ print()
74
+ print("或直接运行: python3 scripts/refresh_pool.py")
75
+ return False
76
+
77
+ # 执行初始化
78
+ print(f"🚀 初始化股票池(每板块 Top {top_n})...")
79
+ print()
80
+
81
+ try:
82
+ new_pool = refresh_pool(top_n=top_n, dry_run=False, show_diff=False)
83
+ total = sum(len(v) for v in new_pool.values())
84
+ print()
85
+ print(f"✅ 初始化完成: {len(new_pool)} 个板块,共 {total} 只股票")
86
+ return True
87
+ except Exception as e:
88
+ print(f"\n❌ 初始化失败: {e}", file=sys.stderr)
89
+ print(" 可稍后重试: python3 scripts/init_pool.py --force")
90
+ return False
91
+
92
+
93
+ def main():
94
+ parser = argparse.ArgumentParser(description="首次安装初始化股票池")
95
+ parser.add_argument("--force", "-f", action="store_true",
96
+ help="强制重新初始化(忽略已有数据)")
97
+ parser.add_argument("--top", "-n", type=int, default=20,
98
+ help="每板块取 Top N(默认 20)")
99
+ args = parser.parse_args()
100
+
101
+ init_pool(top_n=args.top, force=args.force)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ K 线数据查询(多数据源自动切换)。
4
+ 数据源: 新浪 → 东方财富 → 腾讯 → efinance → akshare → tushare → 通达信 → baostock → yfinance
5
+ 用法:
6
+ kline.py sh600989 # 日 K,30 根
7
+ kline.py sh600989 5 48 # 5 分钟 K,48 根
8
+ kline.py sh600989 240 30 -j # JSON
9
+ kline.py --sources # 显示可用数据源
10
+ """
11
+ import sys
12
+ import json
13
+ from common import normalize_quote_code, err, DataError
14
+ from data import get_kline
15
+
16
+
17
+ def fetch(symbol: str, scale: int, datalen: int, use_cache: bool = True) -> list:
18
+ """获取 K 线数据,返回 dict 列表(兼容旧接口)。"""
19
+ bars = get_kline(symbol, scale, datalen, use_cache=use_cache)
20
+ return [b.to_dict() for b in bars]
21
+
22
+
23
+ def render_table(records: list) -> str:
24
+ if not records:
25
+ return "(无数据)"
26
+ lines = []
27
+ for d in records:
28
+ lines.append(f"{d['day']} | O:{d['open']:>7} H:{d['high']:>7} L:{d['low']:>7} C:{d['close']:>7} V:{d['volume']:>12}")
29
+ return "\n".join(lines)
30
+
31
+
32
+ def main():
33
+ args = sys.argv[1:]
34
+
35
+ if "--sources" in args:
36
+ from fetchers import get_kline_fetchers
37
+ fetchers = get_kline_fetchers()
38
+ print("可用 K 线数据源:")
39
+ for f in fetchers:
40
+ print(f" - {f.name} (优先级 {f.priority})")
41
+ return
42
+
43
+ json_mode = "-j" in args
44
+ args = [a for a in args if a not in ("-j", "--sources")]
45
+ if not args:
46
+ err("用法: kline.py <symbol> [scale=240] [datalen=30] [-j]")
47
+ symbol = normalize_quote_code(args[0])
48
+ scale = int(args[1]) if len(args) > 1 else 240
49
+ datalen = int(args[2]) if len(args) > 2 else 30
50
+
51
+ records = fetch(symbol, scale, datalen)
52
+ if json_mode:
53
+ print(json.dumps(records, ensure_ascii=False, indent=2))
54
+ else:
55
+ print(render_table(records))
56
+
57
+
58
+ if __name__ == "__main__":
59
+ try:
60
+ main()
61
+ except DataError as e:
62
+ sys.exit(1)
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 数据源健康检查和缓存监控。
4
+ 用法:
5
+ python3 scripts/monitor.py # 完整健康检查
6
+ python3 scripts/monitor.py --cache # 缓存状态
7
+ python3 scripts/monitor.py --sources # 数据源状态
8
+ python3 scripts/monitor.py --cleanup # 清理过期缓存
9
+ """
10
+ import argparse
11
+ import json
12
+ import os
13
+ import sys
14
+ import time
15
+ from pathlib import Path
16
+
17
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
18
+ from common import CACHE_DIR, cache_cleanup
19
+
20
+
21
+ def check_cache_status():
22
+ """检查缓存目录状态。"""
23
+ if not CACHE_DIR.exists():
24
+ print("缓存目录不存在")
25
+ return
26
+
27
+ files = list(CACHE_DIR.glob("*.cache"))
28
+ total_size = sum(f.stat().st_size for f in files)
29
+ expired = sum(1 for f in files if time.time() - f.stat().st_mtime > 21600)
30
+
31
+ print(f"缓存文件数: {len(files)}")
32
+ print(f"缓存总大小: {total_size / 1024:.1f} KB")
33
+ print(f"过期文件数: {expired}")
34
+
35
+ # 按前缀统计
36
+ prefixes = {}
37
+ for f in files:
38
+ prefix = f.name.split("_")[0] if "_" in f.name else "other"
39
+ prefixes[prefix] = prefixes.get(prefix, 0) + 1
40
+ if prefixes:
41
+ print("\n按类型分布:")
42
+ for prefix, count in sorted(prefixes.items()):
43
+ print(f" {prefix}: {count} 个")
44
+
45
+
46
+ def check_sources():
47
+ """检查数据源可用性。"""
48
+ from fetchers import get_quote_fetchers, get_kline_fetchers, get_finance_fetchers
49
+
50
+ print("=== 行情数据源 ===")
51
+ for f in get_quote_fetchers():
52
+ status = "✅ 可用" if f.is_available() else "❌ 熔断"
53
+ print(f" {f.name} (优先级 {f.priority}) - {status}")
54
+
55
+ print("\n=== K线数据源 ===")
56
+ for f in get_kline_fetchers():
57
+ status = "✅ 可用" if f.is_available() else "❌ 熔断"
58
+ print(f" {f.name} (优先级 {f.priority}) - {status}")
59
+
60
+ print("\n=== 财务数据源 ===")
61
+ for f in get_finance_fetchers():
62
+ status = "✅ 可用" if f.is_available() else "❌ 熔断"
63
+ print(f" {f.name} (优先级 {f.priority}) - {status}")
64
+
65
+
66
+ def run_health_check():
67
+ """完整健康检查。"""
68
+ print("🔍 stock-analyzer-skill 健康检查\n")
69
+
70
+ print("--- 缓存状态 ---")
71
+ check_cache_status()
72
+
73
+ print("\n--- 数据源状态 ---")
74
+ check_sources()
75
+
76
+ print("\n--- 行业阈值配置 ---")
77
+ thresholds_path = Path(__file__).resolve().parent / "data" / "industry_thresholds.json"
78
+ if thresholds_path.exists():
79
+ thresholds = json.loads(thresholds_path.read_text(encoding="utf-8"))
80
+ industries = [k for k in thresholds.keys() if not k.startswith("_")]
81
+ print(f" 已配置 {len(industries)} 个行业: {', '.join(industries)}")
82
+ else:
83
+ print(" ⚠ 行业阈值配置文件不存在")
84
+
85
+ print("\n✅ 健康检查完成")
86
+
87
+
88
+ def main():
89
+ parser = argparse.ArgumentParser(description="数据源健康检查和缓存监控")
90
+ parser.add_argument("--cache", action="store_true", help="显示缓存状态")
91
+ parser.add_argument("--sources", action="store_true", help="显示数据源状态")
92
+ parser.add_argument("--cleanup", action="store_true", help="清理过期缓存")
93
+ args = parser.parse_args()
94
+
95
+ if args.cache:
96
+ check_cache_status()
97
+ elif args.sources:
98
+ check_sources()
99
+ elif args.cleanup:
100
+ cleaned = cache_cleanup()
101
+ print(f"已清理 {cleaned} 个过期缓存文件")
102
+ else:
103
+ run_health_check()
104
+
105
+
106
+ if __name__ == "__main__":
107
+ main()