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.
- package/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +21 -0
- package/CHANGELOG.md +93 -0
- package/CONTRIBUTING.md +331 -0
- package/README.md +259 -0
- package/experts/README.md +119 -0
- package/experts/buffett.md +91 -0
- package/experts/chaogu_yangjia.md +125 -0
- package/experts/decide.md +212 -0
- package/experts/duan_yongping.md +106 -0
- package/experts/lynch.md +127 -0
- package/experts/soros.md +89 -0
- package/experts/xu_xiang.md +107 -0
- package/experts/zhao_laoge.md +143 -0
- package/experts/zuoshou_xinyi.md +144 -0
- package/install-plugin.js +69 -0
- package/methodology.md +455 -0
- package/package.json +43 -0
- package/scripts/__pycache__/announcements.cpython-314.pyc +0 -0
- package/scripts/__pycache__/backtest.cpython-314.pyc +0 -0
- package/scripts/__pycache__/chan.cpython-314.pyc +0 -0
- package/scripts/__pycache__/classifier.cpython-314.pyc +0 -0
- package/scripts/__pycache__/common.cpython-314.pyc +0 -0
- package/scripts/__pycache__/finance.cpython-314.pyc +0 -0
- package/scripts/__pycache__/init_pool.cpython-314.pyc +0 -0
- package/scripts/__pycache__/kline.cpython-314.pyc +0 -0
- package/scripts/__pycache__/patterns_local.cpython-314.pyc +0 -0
- package/scripts/__pycache__/quote.cpython-314.pyc +0 -0
- package/scripts/__pycache__/refresh_pool.cpython-314.pyc +0 -0
- package/scripts/__pycache__/screener.cpython-314.pyc +0 -0
- package/scripts/__pycache__/technical.cpython-314.pyc +0 -0
- package/scripts/announcements.py +118 -0
- package/scripts/backtest.py +528 -0
- package/scripts/chan.py +591 -0
- package/scripts/classifier.py +302 -0
- package/scripts/common.py +507 -0
- package/scripts/data/__init__.py +208 -0
- package/scripts/data/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/data/__pycache__/cache.cpython-314.pyc +0 -0
- package/scripts/data/__pycache__/config.cpython-314.pyc +0 -0
- package/scripts/data/__pycache__/types.cpython-314.pyc +0 -0
- package/scripts/data/cache.py +99 -0
- package/scripts/data/config.py +49 -0
- package/scripts/data/industry_thresholds.json +199 -0
- package/scripts/data/portfolio_example.json +14 -0
- package/scripts/data/sector_etf.csv +14 -0
- package/scripts/data/sector_mapping.json +64 -0
- package/scripts/data/sector_stocks.json +135 -0
- package/scripts/data/types.py +66 -0
- package/scripts/fetchers/__init__.py +130 -0
- package/scripts/fetchers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/akshare_finance.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/akshare_kline.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/akshare_quote.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/baostock_kline.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/eastmoney_finance.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/eastmoney_kline.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/eastmoney_quote.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/efinance_finance.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/efinance_kline.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/efinance_quote.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/pytdx_quote.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/sina_kline.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/sina_quote.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/tencent_kline.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/tencent_quote.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/tushare_kline.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/tushare_quote.cpython-314.pyc +0 -0
- package/scripts/fetchers/__pycache__/yfinance_kline.cpython-314.pyc +0 -0
- package/scripts/fetchers/akshare_finance.py +35 -0
- package/scripts/fetchers/akshare_kline.py +59 -0
- package/scripts/fetchers/akshare_quote.py +52 -0
- package/scripts/fetchers/baostock_kline.py +64 -0
- package/scripts/fetchers/eastmoney_finance.py +29 -0
- package/scripts/fetchers/eastmoney_kline.py +48 -0
- package/scripts/fetchers/eastmoney_quote.py +68 -0
- package/scripts/fetchers/efinance_finance.py +32 -0
- package/scripts/fetchers/efinance_kline.py +46 -0
- package/scripts/fetchers/efinance_quote.py +53 -0
- package/scripts/fetchers/pytdx_kline.py +70 -0
- package/scripts/fetchers/pytdx_quote.py +78 -0
- package/scripts/fetchers/sina_kline.py +30 -0
- package/scripts/fetchers/sina_quote.py +35 -0
- package/scripts/fetchers/tencent_kline.py +52 -0
- package/scripts/fetchers/tencent_quote.py +29 -0
- package/scripts/fetchers/tushare_kline.py +62 -0
- package/scripts/fetchers/tushare_quote.py +62 -0
- package/scripts/fetchers/yfinance_kline.py +66 -0
- package/scripts/finance.py +92 -0
- package/scripts/init_pool.py +105 -0
- package/scripts/kline.py +62 -0
- package/scripts/monitor.py +107 -0
- package/scripts/patterns_local.py +599 -0
- package/scripts/quote.py +69 -0
- package/scripts/refresh_pool.py +328 -0
- package/scripts/screener.py +434 -0
- package/scripts/strategies/__init__.py +11 -0
- package/scripts/strategies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/strategies/__pycache__/registry.cpython-314.pyc +0 -0
- package/scripts/strategies/__pycache__/thresholds.cpython-314.pyc +0 -0
- package/scripts/strategies/factors/__init__.py +8 -0
- package/scripts/strategies/factors/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/strategies/factors/__pycache__/liquidity.cpython-314.pyc +0 -0
- package/scripts/strategies/factors/__pycache__/momentum.cpython-314.pyc +0 -0
- package/scripts/strategies/factors/__pycache__/quality.cpython-314.pyc +0 -0
- package/scripts/strategies/factors/__pycache__/valuation.cpython-314.pyc +0 -0
- package/scripts/strategies/factors/__pycache__/volatility.cpython-314.pyc +0 -0
- package/scripts/strategies/factors/liquidity.py +49 -0
- package/scripts/strategies/factors/momentum.py +45 -0
- package/scripts/strategies/factors/quality.py +54 -0
- package/scripts/strategies/factors/valuation.py +76 -0
- package/scripts/strategies/factors/volatility.py +89 -0
- package/scripts/strategies/registry.py +87 -0
- package/scripts/strategies/thresholds.py +28 -0
- package/scripts/technical/__init__.py +116 -0
- package/scripts/technical/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/astock.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/boll.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/candlestick.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/core.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/kdj.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/macd.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/moving_average.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/report.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/rsi.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/scoring.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/signals.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/trend.cpython-314.pyc +0 -0
- package/scripts/technical/__pycache__/volume.cpython-314.pyc +0 -0
- package/scripts/technical/astock.py +98 -0
- package/scripts/technical/boll.py +49 -0
- package/scripts/technical/candlestick.py +151 -0
- package/scripts/technical/core.py +92 -0
- package/scripts/technical/kdj.py +68 -0
- package/scripts/technical/macd.py +97 -0
- package/scripts/technical/moving_average.py +59 -0
- package/scripts/technical/report.py +221 -0
- package/scripts/technical/rsi.py +37 -0
- package/scripts/technical/scoring.py +392 -0
- package/scripts/technical/signals.py +70 -0
- package/scripts/technical/trend.py +143 -0
- package/scripts/technical/volume.py +113 -0
- package/scripts/technical.py +215 -0
- package/skills/financial-analyst/SKILL.md +141 -0
- package/skills/help/SKILL.md +188 -0
- package/skills/init/SKILL.md +66 -0
- package/skills/investment-researcher/SKILL.md +152 -0
- package/skills/market/SKILL.md +99 -0
- package/skills/portfolio/SKILL.md +96 -0
- package/skills/screener/SKILL.md +128 -0
- package/skills/sector/SKILL.md +102 -0
- package/skills/stock/SKILL.md +148 -0
- package/skills/technical/SKILL.md +168 -0
- package/workflow.md +91 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
A 股个股类型分类器。
|
|
4
|
+
根据财务数据、行情数据和 K 线走势特征,自动判定 7 种类型。
|
|
5
|
+
可被 technical.py 或其他模块 import 使用。
|
|
6
|
+
"""
|
|
7
|
+
from common import to_float, clamp
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ── 类型 → 推荐指标映射 ──
|
|
11
|
+
|
|
12
|
+
TYPE_INDICATOR_MAP = {
|
|
13
|
+
"题材股": {
|
|
14
|
+
"priority": ["K线形态", "涨停分析", "量比", "RSI"],
|
|
15
|
+
"deprioritized": ["MACD", "KDJ"],
|
|
16
|
+
},
|
|
17
|
+
"蓝筹股": {
|
|
18
|
+
"priority": ["均线系统", "BOLL", "MACD背离", "箱体"],
|
|
19
|
+
"deprioritized": ["KDJ"],
|
|
20
|
+
},
|
|
21
|
+
"强成长股": {
|
|
22
|
+
"priority": ["MACD趋势", "BOLL上轨", "量比", "RSI"],
|
|
23
|
+
"deprioritized": ["KDJ"],
|
|
24
|
+
},
|
|
25
|
+
"周期股": {
|
|
26
|
+
"priority": ["MACD背离", "KDJ钝化", "缠论背驰", "BOLL"],
|
|
27
|
+
"deprioritized": ["简单均线"],
|
|
28
|
+
},
|
|
29
|
+
"稳成长股": {
|
|
30
|
+
"priority": ["均线排列", "MACD", "BOLL", "RSI", "老鸭头"],
|
|
31
|
+
"deprioritized": ["KDJ"],
|
|
32
|
+
},
|
|
33
|
+
"防御股": {
|
|
34
|
+
"priority": ["BOLL", "RSI", "均线粘合", "箱体"],
|
|
35
|
+
"deprioritized": ["KDJ"],
|
|
36
|
+
},
|
|
37
|
+
"普通股": {
|
|
38
|
+
"priority": ["均线", "MACD", "KDJ", "BOLL", "RSI", "量价"],
|
|
39
|
+
"deprioritized": [],
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def classify_stock(fin_record=None, quote_record=None, kline_records=None):
|
|
45
|
+
"""
|
|
46
|
+
判定 A 股个股类型。
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
fin_record: 单期财务数据 dict(finance.py 返回的 records[0])
|
|
50
|
+
字段: ROEJQ, PARENTNETPROFITTZ, XSMLL, ZCFZL 等
|
|
51
|
+
quote_record: 实时行情 dict(quote.py 返回)
|
|
52
|
+
字段: circulating_cap, total_cap, turnover, pe 等
|
|
53
|
+
kline_records: K 线数据 list
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
{
|
|
57
|
+
"type": "强成长股"|...|"普通股",
|
|
58
|
+
"confidence": "高"|"中"|"低",
|
|
59
|
+
"reasons": [...],
|
|
60
|
+
"priority_indicators": [...],
|
|
61
|
+
"deprioritized": [...],
|
|
62
|
+
}
|
|
63
|
+
"""
|
|
64
|
+
reasons = []
|
|
65
|
+
confidence = "高"
|
|
66
|
+
|
|
67
|
+
# 提取行情特征
|
|
68
|
+
circulating_cap = to_float(quote_record.get("circulating_cap")) if quote_record else 0
|
|
69
|
+
total_cap = to_float(quote_record.get("total_cap")) if quote_record else 0
|
|
70
|
+
turnover = to_float(quote_record.get("turnover")) if quote_record else 0
|
|
71
|
+
|
|
72
|
+
# 提取财务特征
|
|
73
|
+
has_finance = fin_record and isinstance(fin_record, dict)
|
|
74
|
+
roe = to_float(fin_record.get("ROEJQ")) if has_finance else 0
|
|
75
|
+
profit_growth = to_float(fin_record.get("PARENTNETPROFITTZ")) if has_finance else 0
|
|
76
|
+
gross_margin = to_float(fin_record.get("XSMLL")) if has_finance else 0
|
|
77
|
+
debt = to_float(fin_record.get("ZCFZL")) if has_finance else 0
|
|
78
|
+
|
|
79
|
+
# 连板检测
|
|
80
|
+
has_limit_streak = False
|
|
81
|
+
if kline_records and len(kline_records) >= 10:
|
|
82
|
+
from common import board_type as _board_type
|
|
83
|
+
code = quote_record.get("code", "") if quote_record else ""
|
|
84
|
+
bd = _board_type(code)
|
|
85
|
+
limit_ratio = {"主板": 9.5, "创业板": 19.5, "科创板": 19.5, "北交所": 29.5}.get(bd, 9.5)
|
|
86
|
+
for i in range(len(kline_records) - 1, max(len(kline_records) - 5, 0), -1):
|
|
87
|
+
r = kline_records[i]
|
|
88
|
+
prev = kline_records[i - 1] if i > 0 else r
|
|
89
|
+
chg = (to_float(r.get("close")) - to_float(prev.get("close"))) / max(to_float(prev.get("close")), 0.01) * 100
|
|
90
|
+
if chg >= limit_ratio * 0.95:
|
|
91
|
+
has_limit_streak = True
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
# ── 规则分类(优先级从高到低) ──
|
|
95
|
+
stock_type = "普通股"
|
|
96
|
+
confidence = "低"
|
|
97
|
+
|
|
98
|
+
# 1. 题材股
|
|
99
|
+
if circulating_cap > 0 and circulating_cap < 100 and (has_limit_streak or turnover > 10):
|
|
100
|
+
stock_type = "题材股"
|
|
101
|
+
reasons.append(f"流通市值{circulating_cap:.0f}亿(小盘)")
|
|
102
|
+
if has_limit_streak:
|
|
103
|
+
reasons.append("近期有连板记录")
|
|
104
|
+
if turnover > 10:
|
|
105
|
+
reasons.append(f"换手率{turnover:.1f}%(高活跃度)")
|
|
106
|
+
confidence = "高" if has_limit_streak else "中"
|
|
107
|
+
|
|
108
|
+
# 2. 蓝筹股
|
|
109
|
+
elif circulating_cap > 1000 and (not has_finance or roe > 10):
|
|
110
|
+
stock_type = "蓝筹股"
|
|
111
|
+
reasons.append(f"流通市值{circulating_cap:.0f}亿(大盘)")
|
|
112
|
+
if roe > 10:
|
|
113
|
+
reasons.append(f"ROE {roe:.1f}%")
|
|
114
|
+
confidence = "高"
|
|
115
|
+
else:
|
|
116
|
+
confidence = "中"
|
|
117
|
+
|
|
118
|
+
# 有财务数据时的精确分类
|
|
119
|
+
elif has_finance:
|
|
120
|
+
# 3. 强成长股
|
|
121
|
+
if profit_growth > 30 and roe > 15 and circulating_cap < 500:
|
|
122
|
+
stock_type = "强成长股"
|
|
123
|
+
reasons.append(f"净利增速{profit_growth:.0f}%")
|
|
124
|
+
reasons.append(f"ROE {roe:.1f}%")
|
|
125
|
+
reasons.append(f"流通市值{circulating_cap:.0f}亿")
|
|
126
|
+
confidence = "高"
|
|
127
|
+
|
|
128
|
+
# 4. 周期股(增速波动大+毛利偏低)
|
|
129
|
+
elif abs(profit_growth) > 50 and gross_margin > 0 and gross_margin < 25:
|
|
130
|
+
stock_type = "周期股"
|
|
131
|
+
reasons.append(f"净利增速波动大({profit_growth:.0f}%)")
|
|
132
|
+
reasons.append(f"毛利率{gross_margin:.1f}%(偏低)")
|
|
133
|
+
confidence = "中"
|
|
134
|
+
|
|
135
|
+
# 5. 稳成长股
|
|
136
|
+
elif 15 <= profit_growth <= 30 and roe > 12:
|
|
137
|
+
stock_type = "稳成长股"
|
|
138
|
+
reasons.append(f"净利增速{profit_growth:.0f}%")
|
|
139
|
+
reasons.append(f"ROE {roe:.1f}%")
|
|
140
|
+
confidence = "高"
|
|
141
|
+
|
|
142
|
+
# 6. 防御股
|
|
143
|
+
elif abs(profit_growth) < 20 and debt > 0 and debt < 50:
|
|
144
|
+
stock_type = "防御股"
|
|
145
|
+
reasons.append(f"增速稳定({profit_growth:.0f}%)")
|
|
146
|
+
reasons.append(f"负债率{debt:.1f}%(低)")
|
|
147
|
+
confidence = "中"
|
|
148
|
+
|
|
149
|
+
else:
|
|
150
|
+
stock_type = "普通股"
|
|
151
|
+
reasons.append("不满足以上特定类型条件")
|
|
152
|
+
confidence = "低"
|
|
153
|
+
else:
|
|
154
|
+
# 无财务数据,退化为市值分类
|
|
155
|
+
if circulating_cap > 500:
|
|
156
|
+
stock_type = "蓝筹股"
|
|
157
|
+
reasons.append(f"流通市值{circulating_cap:.0f}亿(推测大盘)")
|
|
158
|
+
confidence = "低"
|
|
159
|
+
elif circulating_cap < 100 and turnover > 5:
|
|
160
|
+
stock_type = "题材股"
|
|
161
|
+
reasons.append(f"流通市值{circulating_cap:.0f}亿(小盘)+高换手")
|
|
162
|
+
confidence = "低"
|
|
163
|
+
else:
|
|
164
|
+
stock_type = "普通股"
|
|
165
|
+
reasons.append("无财务数据,无法精确分类")
|
|
166
|
+
confidence = "低"
|
|
167
|
+
|
|
168
|
+
# ── 返回指标建议 ──
|
|
169
|
+
indicator_map = TYPE_INDICATOR_MAP.get(stock_type, TYPE_INDICATOR_MAP["普通股"])
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
"type": stock_type,
|
|
173
|
+
"confidence": confidence,
|
|
174
|
+
"reasons": reasons,
|
|
175
|
+
"priority_indicators": indicator_map["priority"],
|
|
176
|
+
"deprioritized": indicator_map["deprioritized"],
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ── 行业推断 ──
|
|
181
|
+
|
|
182
|
+
def infer_industry(name: str, code: str = "") -> str:
|
|
183
|
+
"""根据股票名称和代码推断行业分类(关键词匹配 + 代码段推断)。"""
|
|
184
|
+
name = name.upper()
|
|
185
|
+
# 科创板(688开头)默认归类为科技
|
|
186
|
+
if code.startswith("688") or code.startswith("sh688"):
|
|
187
|
+
return "科技"
|
|
188
|
+
# 金融子类:银行、券商
|
|
189
|
+
if any(kw in name for kw in ["银行"]):
|
|
190
|
+
return "银行"
|
|
191
|
+
if any(kw in name for kw in ["证券", "券商"]):
|
|
192
|
+
return "券商"
|
|
193
|
+
# 金融(保险、信托等)
|
|
194
|
+
if any(kw in name for kw in ["保险", "信托", "金融", "资管", "期货"]):
|
|
195
|
+
return "金融"
|
|
196
|
+
# 地产
|
|
197
|
+
if any(kw in name for kw in ["地产", "置业", "置地", "房产", "万科", "保利",
|
|
198
|
+
"碧桂园", "融创", "恒大", "绿地", "华润置地"]):
|
|
199
|
+
return "地产"
|
|
200
|
+
# 医药
|
|
201
|
+
if any(kw in name for kw in ["医药", "药业", "制药", "生物", "疫苗", "医疗",
|
|
202
|
+
"器械", "基因", "诊断", "CRO", "创新药"]):
|
|
203
|
+
return "医药"
|
|
204
|
+
# 科技子类:半导体、软件
|
|
205
|
+
if any(kw in name for kw in ["半导体", "芯片", "晶圆", "封测", "存储", "EDA"]):
|
|
206
|
+
return "半导体"
|
|
207
|
+
if any(kw in name for kw in ["软件", "信息", "数据", "云计算", "人工智能",
|
|
208
|
+
"物联网", "网络", "SaaS", "数字"]):
|
|
209
|
+
return "软件"
|
|
210
|
+
# 科技(其他)
|
|
211
|
+
if any(kw in name for kw in ["科技", "智能", "电子", "通信", "计算",
|
|
212
|
+
"技术", "威视", "光电", "机器人",
|
|
213
|
+
"安防", "集成", "激光", "光学", "显示", "面板"]):
|
|
214
|
+
return "科技"
|
|
215
|
+
# 消费
|
|
216
|
+
if any(kw in name for kw in ["白酒", "食品", "饮料", "乳业", "调味", "啤酒",
|
|
217
|
+
"茅台", "五粮液", "海天", "伊利", "牧原",
|
|
218
|
+
"家电", "美的", "格力", "海尔"]):
|
|
219
|
+
return "消费"
|
|
220
|
+
# 能源
|
|
221
|
+
if any(kw in name for kw in ["石油", "煤炭", "天然气", "能源", "石化", "燃气",
|
|
222
|
+
"中石油", "中石化", "中海油"]):
|
|
223
|
+
return "能源"
|
|
224
|
+
# 周期
|
|
225
|
+
if any(kw in name for kw in ["钢铁", "有色", "铜", "铝", "锌", "黄金", "矿业",
|
|
226
|
+
"化工", "化纤", "水泥", "稀土", "锂"]):
|
|
227
|
+
return "周期"
|
|
228
|
+
# 制造
|
|
229
|
+
if any(kw in name for kw in ["汽车", "机械", "制造", "装备", "新能源", "电池",
|
|
230
|
+
"光伏", "风电", "宁德", "比亚迪"]):
|
|
231
|
+
return "制造"
|
|
232
|
+
# 军工
|
|
233
|
+
if any(kw in name for kw in ["军工", "航天", "航空", "兵器", "船舶", "电科"]):
|
|
234
|
+
return "军工"
|
|
235
|
+
# 交运
|
|
236
|
+
if any(kw in name for kw in ["航空", "航运", "港口", "铁路", "高速", "物流"]):
|
|
237
|
+
return "交运"
|
|
238
|
+
return "默认"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ── 统一画像入口 ──
|
|
242
|
+
|
|
243
|
+
def profile_stock(quote: dict, fin: dict = None, kline_records: list = None) -> dict:
|
|
244
|
+
"""统一个股画像:行业 + 类型 + 指标建议。
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
quote: 行情 dict(含 code, name 等)
|
|
248
|
+
fin: 财务数据 dict(可选)
|
|
249
|
+
kline_records: K 线数据列表(可选)
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
{
|
|
253
|
+
"industry": "金融"|...|"默认",
|
|
254
|
+
"type": "蓝筹股"|...|"普通股",
|
|
255
|
+
"confidence": "高"|"中"|"低",
|
|
256
|
+
"reasons": [...],
|
|
257
|
+
"priority_indicators": [...],
|
|
258
|
+
"deprioritized": [...],
|
|
259
|
+
}
|
|
260
|
+
"""
|
|
261
|
+
name = quote.get("name", "")
|
|
262
|
+
code = quote.get("code", "")
|
|
263
|
+
|
|
264
|
+
industry = infer_industry(name, code)
|
|
265
|
+
classification = classify_stock(fin, quote, kline_records)
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
"industry": industry,
|
|
269
|
+
**classification,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# ── 命令行快速测试 ──
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
import sys
|
|
276
|
+
import json
|
|
277
|
+
|
|
278
|
+
if len(sys.argv) < 2:
|
|
279
|
+
print("用法: python3 classifier.py <code>")
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
|
|
282
|
+
from common import normalize_quote_code, normalize_finance_code
|
|
283
|
+
from quote import fetch_batch
|
|
284
|
+
from kline import fetch as fetch_kline
|
|
285
|
+
|
|
286
|
+
code = normalize_quote_code(sys.argv[1])
|
|
287
|
+
quotes = fetch_batch([code])
|
|
288
|
+
quote = quotes[0] if quotes else {}
|
|
289
|
+
records = fetch_kline(code, 240, 100)
|
|
290
|
+
|
|
291
|
+
# 尝试拉取财务数据
|
|
292
|
+
fin_record = None
|
|
293
|
+
try:
|
|
294
|
+
from finance import fetch as fetch_finance
|
|
295
|
+
fn_code = normalize_finance_code(code)
|
|
296
|
+
fin_data = fetch_finance(fn_code)
|
|
297
|
+
fin_record = fin_data[0] if fin_data else None
|
|
298
|
+
except Exception:
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
result = classify_stock(fin_record, quote, records)
|
|
302
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|