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,208 @@
1
+ """
2
+ 统一数据获取 API。
3
+
4
+ 用法:
5
+ from data import get_quote, get_kline, get_finance
6
+
7
+ quote = get_quote("sh600989")
8
+ quotes = get_quotes(["sh600989", "sz000807"])
9
+ bars = get_kline("sh600989", scale=240, datalen=30)
10
+ records = get_finance("SH600989")
11
+ """
12
+ import threading
13
+ from typing import Optional
14
+
15
+ from .types import Quote, KlineBar, FinanceRecord
16
+ from .config import get_config
17
+ from . import cache
18
+ import sys
19
+ from pathlib import Path
20
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
21
+
22
+
23
+ def _get_common_helpers():
24
+ """延迟导入 common,避免 common.py ↔ data/__init__.py 循环导入。"""
25
+ from common import to_float, to_int
26
+ return to_float, to_int
27
+
28
+ # 延迟导入 fetchers(避免循环导入),线程安全
29
+ _fetchers_lock = threading.Lock()
30
+ _fetchers_loaded = False
31
+ _quote_manager = None
32
+ _kline_manager = None
33
+ _finance_manager = None
34
+
35
+
36
+ def _load_fetchers():
37
+ global _fetchers_loaded, _quote_manager, _kline_manager, _finance_manager
38
+ if _fetchers_loaded:
39
+ return
40
+ with _fetchers_lock:
41
+ if _fetchers_loaded:
42
+ return
43
+ from fetchers import get_quote_manager, get_kline_manager, get_finance_manager
44
+ _quote_manager = get_quote_manager()
45
+ _kline_manager = get_kline_manager()
46
+ _finance_manager = get_finance_manager()
47
+ _fetchers_loaded = True
48
+
49
+
50
+ def get_quote(code: str, use_cache: bool = True) -> Optional[Quote]:
51
+ """获取单只股票行情。"""
52
+ _load_fetchers()
53
+ cfg = get_config()
54
+ key = f"quote_{code}"
55
+
56
+ if use_cache:
57
+ cached = cache.get_json(key, cfg.quote_cache_ttl)
58
+ if cached:
59
+ return _dict_to_quote(cached)
60
+
61
+ result = _quote_manager.fetch(code)
62
+ if result is None:
63
+ return None
64
+
65
+ quote = _dict_to_quote(result)
66
+
67
+ if use_cache and quote.has_basic_data():
68
+ cache.set_json(key, quote.to_dict())
69
+
70
+ return quote
71
+
72
+
73
+ def get_quotes(codes: list, use_cache: bool = True) -> list:
74
+ """批量获取行情。"""
75
+ from common import parallel_map
76
+ cfg = get_config()
77
+ results = parallel_map(lambda c: get_quote(c, use_cache), codes,
78
+ max_workers=cfg.max_workers, timeout=30)
79
+ return [q for q in results.values() if q is not None]
80
+
81
+
82
+ def get_kline(code: str, scale: int = 240, datalen: int = 30,
83
+ use_cache: bool = True) -> list:
84
+ """获取 K 线数据。"""
85
+ _load_fetchers()
86
+ cfg = get_config()
87
+ key = f"kline_{code}_{scale}_{datalen}"
88
+
89
+ if use_cache:
90
+ cached = cache.get_json(key, cfg.kline_cache_ttl)
91
+ if cached:
92
+ return [_dict_to_kline_bar(bar) for bar in cached]
93
+
94
+ records = _kline_manager.fetch(code, scale=scale, datalen=datalen)
95
+ if not records:
96
+ return []
97
+
98
+ bars = [_dict_to_kline_bar(r) for r in records]
99
+
100
+ if use_cache and bars:
101
+ cache.set_json(key, [b.to_dict() for b in bars])
102
+
103
+ return bars
104
+
105
+
106
+ def get_finance(code: str, use_cache: bool = True) -> list:
107
+ """获取财务数据。"""
108
+ _load_fetchers()
109
+ cfg = get_config()
110
+ key = f"finance_{code}"
111
+
112
+ if use_cache:
113
+ cached = cache.get_json(key, cfg.finance_cache_ttl)
114
+ if cached:
115
+ records = [_dict_to_finance(r) for r in cached]
116
+ # 校验缓存有效性:至少有一个非零数据点
117
+ if any(r.eps != 0 or r.roe != 0 for r in records):
118
+ return records
119
+ # 零值缓存视为无效,忽略并重新拉取
120
+
121
+ result = _finance_manager.fetch(code)
122
+ if not result:
123
+ return []
124
+
125
+ records = [_dict_to_finance(r) for r in result]
126
+
127
+ if use_cache and records:
128
+ cache.set_json(key, [r.to_dict() for r in records])
129
+
130
+ return records
131
+
132
+
133
+ # ---------- 内部转换函数(使用 common.to_float / common.to_int) ----------
134
+
135
+
136
+ def _dict_to_quote(d: dict) -> Quote:
137
+ to_float, to_int = _get_common_helpers()
138
+ return Quote(
139
+ code=d.get("code", ""),
140
+ name=d.get("name", ""),
141
+ price=to_float(d.get("price")),
142
+ prev_close=to_float(d.get("prev_close")),
143
+ open=to_float(d.get("open")),
144
+ high=to_float(d.get("high")),
145
+ low=to_float(d.get("low")),
146
+ change_pct=to_float(d.get("change_pct")),
147
+ change_amt=to_float(d.get("change_amt")),
148
+ volume=to_int(d.get("volume")),
149
+ amount=to_float(d.get("amount")),
150
+ turnover=to_float(d.get("turnover")),
151
+ pe=to_float(d.get("pe")),
152
+ pb=to_float(d.get("pb")),
153
+ total_cap=to_float(d.get("total_cap")),
154
+ circulating_cap=to_float(d.get("circulating_cap")),
155
+ source=d.get("source", ""),
156
+ )
157
+
158
+
159
+ def _dict_to_kline_bar(d: dict) -> KlineBar:
160
+ to_float, to_int = _get_common_helpers()
161
+ return KlineBar(
162
+ day=d.get("day", ""),
163
+ open=to_float(d.get("open")),
164
+ high=to_float(d.get("high")),
165
+ low=to_float(d.get("low")),
166
+ close=to_float(d.get("close")),
167
+ volume=to_int(d.get("volume")),
168
+ amount=to_float(d.get("amount")),
169
+ pct_chg=to_float(d.get("pct_chg")),
170
+ source=d.get("source", ""),
171
+ )
172
+
173
+
174
+ def _dict_to_finance(d: dict) -> FinanceRecord:
175
+ """将 fetcher 返回的 dict 转为 FinanceRecord,支持东财原始字段名映射。"""
176
+ to_float, _ = _get_common_helpers()
177
+ FIELD_MAP = {
178
+ "report_date": ["REPORT_DATE", "REPORTDATETIME", "NOTICE_DATE"],
179
+ "eps": ["EPSJB"],
180
+ "roe": ["ROEJQ"],
181
+ "revenue_yoy": ["TOTALOPERATEREVETZ"],
182
+ "net_profit_yoy": ["PARENTNETPROFITTZ"],
183
+ "gross_margin": ["XSMLL"],
184
+ "net_margin": ["XSJLL"],
185
+ "debt_ratio": ["ZCFZL"],
186
+ "bps": ["BPS"],
187
+ "ocf_per_share": ["MGJYXJJE"],
188
+ }
189
+
190
+ def _find(candidates, default=""):
191
+ for k in candidates:
192
+ if k in d and d[k] not in (None, "", "-"):
193
+ return d[k]
194
+ return default
195
+
196
+ return FinanceRecord(
197
+ report_date=str(_find(FIELD_MAP["report_date"]))[:10],
198
+ eps=to_float(_find(FIELD_MAP["eps"])),
199
+ roe=to_float(_find(FIELD_MAP["roe"])),
200
+ revenue_yoy=to_float(_find(FIELD_MAP["revenue_yoy"])),
201
+ net_profit_yoy=to_float(_find(FIELD_MAP["net_profit_yoy"])),
202
+ gross_margin=to_float(_find(FIELD_MAP["gross_margin"])),
203
+ net_margin=to_float(_find(FIELD_MAP["net_margin"])),
204
+ debt_ratio=to_float(_find(FIELD_MAP["debt_ratio"])),
205
+ bps=to_float(_find(FIELD_MAP["bps"])),
206
+ ocf_per_share=to_float(_find(FIELD_MAP["ocf_per_share"])),
207
+ source=d.get("source", ""),
208
+ )
@@ -0,0 +1,99 @@
1
+ """统一缓存管理。"""
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import tempfile
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ _DEFAULT_CACHE_DIR = Path(__file__).resolve().parent.parent.parent / ".cache"
11
+ CACHE_DIR = Path(os.getenv("STOCK_CACHE_DIR", str(_DEFAULT_CACHE_DIR)))
12
+
13
+
14
+ def _ensure_dir():
15
+ CACHE_DIR.mkdir(exist_ok=True)
16
+
17
+
18
+ def get(key: str, ttl_seconds: int) -> Optional[bytes]:
19
+ """读取缓存,TTL 超时返回 None。"""
20
+ _ensure_dir()
21
+ f = CACHE_DIR / f"{key}.cache"
22
+ if not f.exists():
23
+ return None
24
+ if time.time() - f.stat().st_mtime > ttl_seconds:
25
+ f.unlink(missing_ok=True)
26
+ return None
27
+ return f.read_bytes()
28
+
29
+
30
+ def set(key: str, data: bytes):
31
+ """写入缓存(原子写入:先写临时文件,再 rename)。"""
32
+ _ensure_dir()
33
+ f = CACHE_DIR / f"{key}.cache"
34
+ fd, tmp_path = tempfile.mkstemp(dir=CACHE_DIR, suffix=".tmp")
35
+ try:
36
+ os.write(fd, data)
37
+ os.close(fd)
38
+ fd = -1
39
+ os.replace(tmp_path, f)
40
+ except Exception:
41
+ if fd >= 0:
42
+ try:
43
+ os.close(fd)
44
+ except OSError:
45
+ pass
46
+ Path(tmp_path).unlink(missing_ok=True)
47
+ raise
48
+
49
+
50
+ def get_json(key: str, ttl_seconds: int):
51
+ """读取 JSON 缓存。"""
52
+ raw = get(key, ttl_seconds)
53
+ if raw is None:
54
+ return None
55
+ try:
56
+ return json.loads(raw)
57
+ except json.JSONDecodeError:
58
+ return None
59
+
60
+
61
+ def set_json(key: str, data):
62
+ """写入 JSON 缓存。"""
63
+ set(key, json.dumps(data, ensure_ascii=False).encode())
64
+
65
+
66
+ def clear(prefix: str = ""):
67
+ """清除指定前缀或全部缓存。"""
68
+ if not CACHE_DIR.exists():
69
+ return
70
+ for f in CACHE_DIR.glob("*.cache"):
71
+ if not prefix or f.stem.startswith(prefix):
72
+ f.unlink()
73
+
74
+
75
+ def cleanup(prefix: str = None, max_age_seconds: int = 86400):
76
+ """清理过期缓存。prefix 为空时清理所有过期文件。返回清理数量。"""
77
+ _ensure_dir()
78
+ cleaned = 0
79
+ for f in CACHE_DIR.glob("*.cache"):
80
+ if prefix and not f.name.startswith(prefix):
81
+ continue
82
+ if time.time() - f.stat().st_mtime > max_age_seconds:
83
+ f.unlink(missing_ok=True)
84
+ cleaned += 1
85
+ return cleaned
86
+
87
+
88
+ def cache_key(url: str) -> str:
89
+ """用 URL 的 SHA256 生成缓存键。"""
90
+ return hashlib.sha256(url.encode()).hexdigest()[:32]
91
+
92
+
93
+ def cache_key_for_stock(prefix: str, code: str, **params) -> str:
94
+ """生成股票相关的缓存键,支持按代码清除。
95
+ 格式: {prefix}_{code}_{param_hash}
96
+ """
97
+ param_str = "_".join(f"{k}={v}" for k, v in sorted(params.items()))
98
+ param_hash = hashlib.md5(param_str.encode()).hexdigest()[:8] if param_str else ""
99
+ return f"{prefix}_{code}_{param_hash}".rstrip("_")
@@ -0,0 +1,49 @@
1
+ """统一配置管理。"""
2
+ import os
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class DataConfig:
8
+ """数据层配置。"""
9
+ # 缓存 TTL (秒)
10
+ quote_cache_ttl: int = 900 # 15 分钟
11
+ kline_cache_ttl: int = 21600 # 6 小时
12
+ finance_cache_ttl: int = 21600 # 6 小时
13
+ ann_cache_ttl: int = 1800 # 30 分钟
14
+
15
+ # 熔断器
16
+ circuit_failure_threshold: int = 5
17
+ circuit_recovery_timeout: int = 60
18
+
19
+ # 并发
20
+ max_workers: int = 8
21
+
22
+ @classmethod
23
+ def from_env(cls) -> "DataConfig":
24
+ """从环境变量加载配置。"""
25
+ cfg = cls()
26
+ cfg.quote_cache_ttl = int(os.getenv("DATA_QUOTE_TTL", cfg.quote_cache_ttl))
27
+ cfg.kline_cache_ttl = int(os.getenv("DATA_KLINE_TTL", cfg.kline_cache_ttl))
28
+ cfg.finance_cache_ttl = int(os.getenv("DATA_FINANCE_TTL", cfg.finance_cache_ttl))
29
+ cfg.circuit_failure_threshold = int(os.getenv("DATA_CIRCUIT_THRESHOLD", cfg.circuit_failure_threshold))
30
+ cfg.circuit_recovery_timeout = int(os.getenv("DATA_CIRCUIT_TIMEOUT", cfg.circuit_recovery_timeout))
31
+ cfg.max_workers = int(os.getenv("DATA_MAX_WORKERS", cfg.max_workers))
32
+ return cfg
33
+
34
+
35
+ _config = None
36
+
37
+
38
+ def get_config() -> DataConfig:
39
+ """获取全局配置单例。"""
40
+ global _config
41
+ if _config is None:
42
+ _config = DataConfig.from_env()
43
+ return _config
44
+
45
+
46
+ def reset_config():
47
+ """重置配置单例(用于测试或动态重载)。"""
48
+ global _config
49
+ _config = None
@@ -0,0 +1,199 @@
1
+ {
2
+ "_description": "行业差异化阈值表。按申万一级行业分组,定义各行业的基本面和估值阈值。",
3
+ "_usage": "screener.py 和 technical/scoring.py 按行业选择阈值,避免一刀切误判。",
4
+ "金融": {
5
+ "roe_min": 10,
6
+ "roe_excellent": 15,
7
+ "gross_margin_min": 0,
8
+ "debt_ratio_max": 90,
9
+ "pe_undervalued": 8,
10
+ "pe_reasonable": 12,
11
+ "pe_expensive": 18,
12
+ "peg_undervalued": 0.8,
13
+ "peg_reasonable": 1.5,
14
+ "vol_threshold": 0.015,
15
+ "notes": "银行/保险 ROE 天然 10-15%,负债率 90%+ 属正常,用资本充足率替代负债率",
16
+ "profit_growth_excellent": 15,
17
+ "revenue_growth_excellent": 10
18
+ },
19
+ "银行": {
20
+ "roe_min": 8,
21
+ "roe_excellent": 12,
22
+ "gross_margin_min": 0,
23
+ "debt_ratio_max": 95,
24
+ "pe_undervalued": 5,
25
+ "pe_reasonable": 8,
26
+ "pe_expensive": 12,
27
+ "peg_undervalued": 0.5,
28
+ "peg_reasonable": 1.0,
29
+ "vol_threshold": 0.012,
30
+ "notes": "银行 ROE 天然偏低(8-12%),PE 极低(5-8倍),高负债率属正常",
31
+ "profit_growth_excellent": 12,
32
+ "revenue_growth_excellent": 8
33
+ },
34
+ "券商": {
35
+ "roe_min": 8,
36
+ "roe_excellent": 18,
37
+ "gross_margin_min": 0,
38
+ "debt_ratio_max": 85,
39
+ "pe_undervalued": 10,
40
+ "pe_reasonable": 18,
41
+ "pe_expensive": 28,
42
+ "peg_undervalued": 0.8,
43
+ "peg_reasonable": 1.5,
44
+ "vol_threshold": 0.025,
45
+ "notes": "券商 ROE 波动大(牛市高熊市低),PE 中等,周期性强",
46
+ "profit_growth_excellent": 25,
47
+ "revenue_growth_excellent": 20
48
+ },
49
+ "消费": {
50
+ "roe_min": 15,
51
+ "roe_excellent": 25,
52
+ "gross_margin_min": 30,
53
+ "debt_ratio_max": 50,
54
+ "pe_undervalued": 15,
55
+ "pe_reasonable": 25,
56
+ "pe_expensive": 40,
57
+ "peg_undervalued": 0.8,
58
+ "peg_reasonable": 1.5,
59
+ "vol_threshold": 0.02,
60
+ "notes": "白酒/食品 ROE 天然高,毛利率 60%+ 常见,估值享受溢价",
61
+ "profit_growth_excellent": 25,
62
+ "revenue_growth_excellent": 20
63
+ },
64
+ "科技": {
65
+ "roe_min": 10,
66
+ "roe_excellent": 20,
67
+ "gross_margin_min": 20,
68
+ "debt_ratio_max": 60,
69
+ "pe_undervalued": 20,
70
+ "pe_reasonable": 40,
71
+ "pe_expensive": 60,
72
+ "peg_undervalued": 0.8,
73
+ "peg_reasonable": 2.0,
74
+ "vol_threshold": 0.03,
75
+ "notes": "科技股 PE 30-50 常态,高研发投入导致短期 ROE 偏低,不能用传统标准",
76
+ "profit_growth_excellent": 50,
77
+ "revenue_growth_excellent": 35
78
+ },
79
+ "半导体": {
80
+ "roe_min": 8,
81
+ "roe_excellent": 15,
82
+ "gross_margin_min": 25,
83
+ "debt_ratio_max": 55,
84
+ "pe_undervalued": 25,
85
+ "pe_reasonable": 50,
86
+ "pe_expensive": 80,
87
+ "peg_undervalued": 0.8,
88
+ "peg_reasonable": 2.0,
89
+ "vol_threshold": 0.035,
90
+ "notes": "半导体重资产,ROE 天然偏低(8-15%),PE 容忍度高(25-80),研发投入大",
91
+ "profit_growth_excellent": 60,
92
+ "revenue_growth_excellent": 40
93
+ },
94
+ "软件": {
95
+ "roe_min": 12,
96
+ "roe_excellent": 22,
97
+ "gross_margin_min": 50,
98
+ "debt_ratio_max": 45,
99
+ "pe_undervalued": 30,
100
+ "pe_reasonable": 60,
101
+ "pe_expensive": 100,
102
+ "peg_undervalued": 1.0,
103
+ "peg_reasonable": 2.5,
104
+ "vol_threshold": 0.032,
105
+ "notes": "软件轻资产高毛利(50%+),ROE 偏高,PE 估值更激进(30-100)",
106
+ "profit_growth_excellent": 45,
107
+ "revenue_growth_excellent": 30
108
+ },
109
+ "周期": {
110
+ "roe_min": 8,
111
+ "roe_excellent": 15,
112
+ "gross_margin_min": 10,
113
+ "debt_ratio_max": 65,
114
+ "pe_undervalued": 8,
115
+ "pe_reasonable": 15,
116
+ "pe_expensive": 25,
117
+ "peg_undervalued": 0.5,
118
+ "peg_reasonable": 1.0,
119
+ "vol_threshold": 0.025,
120
+ "notes": "周期股用 3 年均值 ROE,单年高增速往往是周期顶部信号",
121
+ "profit_growth_excellent": 30,
122
+ "revenue_growth_excellent": 25
123
+ },
124
+ "医药": {
125
+ "roe_min": 12,
126
+ "roe_excellent": 20,
127
+ "gross_margin_min": 40,
128
+ "debt_ratio_max": 45,
129
+ "pe_undervalued": 15,
130
+ "pe_reasonable": 30,
131
+ "pe_expensive": 50,
132
+ "peg_undervalued": 0.8,
133
+ "peg_reasonable": 1.8,
134
+ "vol_threshold": 0.028,
135
+ "notes": "创新药高 PE 合理,仿制药/中药估值较低,需区分细分赛道",
136
+ "profit_growth_excellent": 35,
137
+ "revenue_growth_excellent": 25
138
+ },
139
+ "制造": {
140
+ "roe_min": 12,
141
+ "roe_excellent": 18,
142
+ "gross_margin_min": 15,
143
+ "debt_ratio_max": 60,
144
+ "pe_undervalued": 12,
145
+ "pe_reasonable": 22,
146
+ "pe_expensive": 35,
147
+ "peg_undervalued": 0.8,
148
+ "peg_reasonable": 1.5,
149
+ "vol_threshold": 0.025,
150
+ "notes": "制造业分化大,高端制造(新能源/半导体)估值高于传统制造",
151
+ "profit_growth_excellent": 35,
152
+ "revenue_growth_excellent": 25
153
+ },
154
+ "地产": {
155
+ "roe_min": 8,
156
+ "roe_excellent": 15,
157
+ "gross_margin_min": 15,
158
+ "debt_ratio_max": 80,
159
+ "pe_undervalued": 5,
160
+ "pe_reasonable": 10,
161
+ "pe_expensive": 15,
162
+ "peg_undervalued": 0.5,
163
+ "peg_reasonable": 1.0,
164
+ "vol_threshold": 0.028,
165
+ "notes": "地产用净负债率<100% 替代资产负债率,PE 天然低",
166
+ "profit_growth_excellent": 15,
167
+ "revenue_growth_excellent": 10
168
+ },
169
+ "能源": {
170
+ "roe_min": 10,
171
+ "roe_excellent": 18,
172
+ "gross_margin_min": 15,
173
+ "debt_ratio_max": 55,
174
+ "pe_undervalued": 6,
175
+ "pe_reasonable": 12,
176
+ "pe_expensive": 20,
177
+ "peg_undervalued": 0.5,
178
+ "peg_reasonable": 1.0,
179
+ "vol_threshold": 0.022,
180
+ "notes": "煤炭/石油周期性强,高分红低估值是常态",
181
+ "profit_growth_excellent": 20,
182
+ "revenue_growth_excellent": 15
183
+ },
184
+ "默认": {
185
+ "roe_min": 12,
186
+ "roe_excellent": 18,
187
+ "gross_margin_min": 20,
188
+ "debt_ratio_max": 60,
189
+ "pe_undervalued": 15,
190
+ "pe_reasonable": 25,
191
+ "pe_expensive": 40,
192
+ "peg_undervalued": 0.8,
193
+ "peg_reasonable": 1.5,
194
+ "vol_threshold": 0.025,
195
+ "notes": "未匹配行业的默认阈值",
196
+ "profit_growth_excellent": 40,
197
+ "revenue_growth_excellent": 30
198
+ }
199
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 1,
3
+ "note": "示例持仓配置。复制此文件到 portfolio.json 并修改 codes 字段为你的实际持仓。",
4
+ "codes": [
5
+ "sh600989",
6
+ "sz000807",
7
+ "sh518880",
8
+ "sh603993",
9
+ "sz002415",
10
+ "sh603799",
11
+ "sh601211",
12
+ "sz300438"
13
+ ]
14
+ }
@@ -0,0 +1,14 @@
1
+ code,name,category,bk_code
2
+ sh510050,上证50ETF,宽基,
3
+ sh510300,沪深300ETF,宽基,
4
+ sh510500,中证500ETF,宽基,
5
+ sh512010,医药ETF,行业,BK0465
6
+ sh512480,半导体ETF,行业,BK0917
7
+ sh512690,白酒ETF,行业,BK0896
8
+ sh512800,银行ETF,行业,BK0475
9
+ sh513120,港股创新药ETF,跨境,
10
+ sh518880,黄金ETF,商品,
11
+ sh515030,新能源车ETF,行业,BK0900
12
+ sh512660,军工ETF,行业,BK0490
13
+ sh515790,光伏ETF,行业,BK0588
14
+ sh516160,新能源ETF,行业,BK0493
@@ -0,0 +1,64 @@
1
+ {
2
+ "_meta": {
3
+ "description": "板块名 → 东财BK代码映射,供 refresh_pool.py 使用",
4
+ "updated": "2026-06-05",
5
+ "api": "https://push2.eastmoney.com/api/qt/clist/get?fs=b:{BK_CODE}"
6
+ },
7
+ "金融": {
8
+ "bk_codes": ["BK0475", "BK0449"],
9
+ "note": "银行+证券"
10
+ },
11
+ "消费": {
12
+ "bk_codes": ["BK0896", "BK0438"],
13
+ "note": "白酒+食品饮料"
14
+ },
15
+ "医药": {
16
+ "bk_codes": ["BK0465", "BK0899"],
17
+ "note": "医药生物+创新药"
18
+ },
19
+ "资源": {
20
+ "bk_codes": ["BK0519", "BK1649"],
21
+ "note": "稀缺资源+油气"
22
+ },
23
+ "电力": {
24
+ "bk_codes": ["BK1024", "BK0432"],
25
+ "note": "绿色电力+电力"
26
+ },
27
+ "石化": {
28
+ "bk_codes": ["BK0431"],
29
+ "note": "石油石化"
30
+ },
31
+ "高股息": {
32
+ "bk_codes": [],
33
+ "note": "跨板块主题,从其他板块筛选PE<20且ROE>8%的标的",
34
+ "filter": "dividend"
35
+ },
36
+ "科技": {
37
+ "bk_codes": ["BK0448", "BK0482"],
38
+ "note": "消费电子+计算机"
39
+ },
40
+ "机器人": {
41
+ "bk_codes": ["BK1184", "BK1090"],
42
+ "note": "人形机器人+机器人概念"
43
+ },
44
+ "PCB/AI算力": {
45
+ "bk_codes": ["BK0877", "BK1134"],
46
+ "note": "PCB+算力"
47
+ },
48
+ "军工": {
49
+ "bk_codes": ["BK0490"],
50
+ "note": "军工"
51
+ },
52
+ "半导体": {
53
+ "bk_codes": ["BK0917"],
54
+ "note": "半导体概念"
55
+ },
56
+ "新能源": {
57
+ "bk_codes": ["BK0900"],
58
+ "note": "新能源车"
59
+ },
60
+ "光伏": {
61
+ "bk_codes": ["BK0588"],
62
+ "note": "光伏概念"
63
+ }
64
+ }