stock-analyzer-skill 1.1.0 → 1.2.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 (102) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +110 -39
  3. package/data/reports/202506_Stock_Analysis_Summary.md +271 -0
  4. package/experts/README.md +23 -2
  5. package/experts/buffett.md +44 -1
  6. package/experts/chaogu_yangjia.md +50 -0
  7. package/experts/decide.md +54 -2
  8. package/experts/duan_yongping.md +45 -0
  9. package/experts/lynch.md +48 -1
  10. package/experts/soros.md +43 -0
  11. package/experts/xu_xiang.md +44 -0
  12. package/experts/zhao_laoge.md +53 -0
  13. package/experts/zuoshou_xinyi.md +66 -0
  14. package/methodology.md +313 -13
  15. package/package.json +1 -1
  16. package/scripts/__pycache__/screener.cpython-314.pyc +0 -0
  17. package/scripts/api/__init__.py +22 -0
  18. package/scripts/api/__pycache__/__init__.cpython-314.pyc +0 -0
  19. package/scripts/api/__pycache__/quote_cli.cpython-314.pyc +0 -0
  20. package/scripts/api/__pycache__/screener_cli.cpython-314.pyc +0 -0
  21. package/scripts/api/quote_cli.py +106 -0
  22. package/scripts/api/screener_cli.py +149 -0
  23. package/scripts/business/__init__.py +15 -0
  24. package/scripts/business/__pycache__/__init__.cpython-314.pyc +0 -0
  25. package/scripts/business/__pycache__/screening_service.cpython-314.pyc +0 -0
  26. package/scripts/business/__pycache__/stock_analysis.cpython-314.pyc +0 -0
  27. package/scripts/business/screening_service.py +267 -0
  28. package/scripts/business/stock_analysis.py +183 -0
  29. package/scripts/common/__init__.py +334 -0
  30. package/scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  31. package/scripts/common/__pycache__/http.cpython-314.pyc +0 -0
  32. package/scripts/common/__pycache__/parsers.cpython-314.pyc +0 -0
  33. package/scripts/common/__pycache__/utils.cpython-314.pyc +0 -0
  34. package/scripts/common/__pycache__/validators.cpython-314.pyc +0 -0
  35. package/scripts/common/exceptions/__init__.py +172 -0
  36. package/scripts/common/exceptions/__pycache__/__init__.cpython-314.pyc +0 -0
  37. package/scripts/common/http.py +79 -0
  38. package/scripts/common/metrics.py +92 -0
  39. package/scripts/common/parsers.py +125 -0
  40. package/scripts/common/utils.py +195 -0
  41. package/scripts/common/validators.py +219 -0
  42. package/scripts/config/__init__.py +24 -0
  43. package/scripts/config/__pycache__/__init__.cpython-314.pyc +0 -0
  44. package/scripts/config/__pycache__/loader.cpython-314.pyc +0 -0
  45. package/scripts/config/data_source.yaml +126 -0
  46. package/scripts/config/industry_thresholds.yaml +158 -0
  47. package/scripts/config/limits.yaml +48 -0
  48. package/scripts/config/loader.py +141 -0
  49. package/scripts/config/notification.yaml +57 -0
  50. package/scripts/config/scoring.yaml +159 -0
  51. package/scripts/data/__pycache__/config.cpython-314.pyc +0 -0
  52. package/scripts/data/__pycache__/types.cpython-314.pyc +0 -0
  53. package/scripts/data/config.py +56 -4
  54. package/scripts/data/portfolio.json +100 -0
  55. package/scripts/data/portfolio_example.json +66 -11
  56. package/scripts/data/sector_stocks.json +244 -80
  57. package/scripts/data/types.py +3 -3
  58. package/scripts/fetchers/__init__.py +54 -0
  59. package/scripts/fetchers/__pycache__/__init__.cpython-314.pyc +0 -0
  60. package/scripts/fetchers/__pycache__/akshare_quote.cpython-314.pyc +0 -0
  61. package/scripts/fetchers/__pycache__/eastmoney_event.cpython-314.pyc +0 -0
  62. package/scripts/fetchers/__pycache__/eastmoney_flow.cpython-314.pyc +0 -0
  63. package/scripts/fetchers/__pycache__/eastmoney_lhb.cpython-314.pyc +0 -0
  64. package/scripts/fetchers/__pycache__/eastmoney_quote.cpython-314.pyc +0 -0
  65. package/scripts/fetchers/__pycache__/efinance_quote.cpython-314.pyc +0 -0
  66. package/scripts/fetchers/__pycache__/sina_quote.cpython-314.pyc +0 -0
  67. package/scripts/fetchers/__pycache__/tencent_quote.cpython-314.pyc +0 -0
  68. package/scripts/fetchers/akshare_quote.py +17 -3
  69. package/scripts/fetchers/eastmoney_event.py +148 -0
  70. package/scripts/fetchers/eastmoney_flow.py +118 -0
  71. package/scripts/fetchers/eastmoney_lhb.py +134 -0
  72. package/scripts/fetchers/eastmoney_quote.py +3 -3
  73. package/scripts/fetchers/efinance_quote.py +17 -3
  74. package/scripts/fetchers/sina_quote.py +5 -9
  75. package/scripts/fetchers/tencent_quote.py +3 -1
  76. package/scripts/monitor/__init__.py +13 -0
  77. package/scripts/monitor/__pycache__/__init__.cpython-314.pyc +0 -0
  78. package/scripts/monitor/__pycache__/manager.cpython-314.pyc +0 -0
  79. package/scripts/monitor/channels/__init__.py +6 -0
  80. package/scripts/monitor/channels/__pycache__/__init__.cpython-314.pyc +0 -0
  81. package/scripts/monitor/channels/__pycache__/bark.cpython-314.pyc +0 -0
  82. package/scripts/monitor/channels/__pycache__/base.cpython-314.pyc +0 -0
  83. package/scripts/monitor/channels/bark.py +75 -0
  84. package/scripts/monitor/channels/base.py +36 -0
  85. package/scripts/monitor/health.py +148 -0
  86. package/scripts/monitor/manager.py +229 -0
  87. package/scripts/portfolio/__init__.py +13 -0
  88. package/scripts/portfolio/__pycache__/__init__.cpython-314.pyc +0 -0
  89. package/scripts/portfolio/__pycache__/manager.cpython-314.pyc +0 -0
  90. package/scripts/portfolio/manager.py +329 -0
  91. package/scripts/portfolio/performance.py +209 -0
  92. package/scripts/screener.py +78 -23
  93. package/scripts/strategies/factors/__pycache__/liquidity.cpython-314.pyc +0 -0
  94. package/scripts/strategies/factors/liquidity.py +1 -1
  95. package/skills/backtest/SKILL.md +57 -0
  96. package/skills/help/SKILL.md +69 -5
  97. package/skills/monitor/SKILL.md +98 -0
  98. package/skills/portfolio/SKILL.md +135 -40
  99. package/skills/stock/SKILL.md +99 -1
  100. package/skills/{init → stock-init}/SKILL.md +5 -5
  101. package/workflow.md +36 -2
  102. package/scripts/common.py +0 -507
@@ -123,3 +123,53 @@
123
123
  ### 调用方式
124
124
 
125
125
  debate 模式中重点关注:情绪周期阶段——用全市场涨停家数/跌停家数/炸板率/昨涨停今日溢价四条数据定位(主升/震荡/退潮/冰点)。养家的核心判断不在个股,而在"此刻市场情绪处于什么阶段"。
126
+
127
+ **代码示例:**
128
+
129
+ ```python
130
+ from data import get_quote
131
+ from common import to_float
132
+
133
+ # 基础数据
134
+ quote = get_quote("sh600989")
135
+
136
+ # 情绪维度:情绪周期判断(需结合市场整体数据)
137
+ # 示例市场数据(实际需从市场接口获取)
138
+ market_up_count = 65 # 全市场涨停家数
139
+ market_down_count = 15 # 全市场跌停家数
140
+ break_rate = 0.25 # 炸板率
141
+
142
+ # 判断情绪周期阶段
143
+ if market_down_count > 50 and break_rate > 0.6:
144
+ # 冰点期:绝望中蕴含机会
145
+ phase = "冰点"
146
+ sentiment_score = 100
147
+ elif market_up_count > 80:
148
+ # 主升期:赚钱效应扩散
149
+ phase = "主升"
150
+ sentiment_score = 80
151
+ elif 40 <= market_up_count <= 60:
152
+ # 震荡期:分化
153
+ phase = "震荡"
154
+ sentiment_score = 50
155
+ else:
156
+ # 退潮期
157
+ phase = "退潮"
158
+ sentiment_score = 0
159
+
160
+ # 技术面维度:市场温度计
161
+ if market_up_count > 80:
162
+ tech_score = 100
163
+ elif market_up_count >= 40:
164
+ tech_score = 50
165
+ else:
166
+ tech_score = 0
167
+
168
+ # 风险维度
169
+ if market_down_count < 10:
170
+ risk_score = 100
171
+ elif market_down_count <= 30:
172
+ risk_score = 30
173
+ else:
174
+ risk_score = 0
175
+ ```
package/experts/decide.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # 专家圆桌决策引擎
2
2
 
3
+ > 版本:v1.1.0(2026-06-08)
4
+ > 更新:适配新的 `scripts/data/` 和 `scripts/common/` 模块
5
+
3
6
  > 整合 8 位专家的独立评分,生成加权投票结果。用于 stock skill debate 模式。
4
7
 
5
8
  ## 一、投票机制
@@ -122,13 +125,62 @@
122
125
 
123
126
  ## 五、使用流程(stock skill debate 模式)
124
127
 
125
- 1. **获取数据**:运行 `scripts/quote.py`、`scripts/finance.py`、`scripts/kline.py` 获取目标股票完整数据
126
- 2. **获取大盘数据**:运行 `scripts/quote.py sh000001,sh510300 -j` 判断市场环境
128
+ 1. **获取数据**:使用 `scripts/data/` 获取目标股票完整数据
129
+ 2. **获取大盘数据**:使用 `get_quote("sh000001")` `get_quote("sh510300")` 判断市场环境
127
130
  3. **专家打分**:每位专家按各自 `§九 评分矩阵` 对目标股票独立打分(使用步骤1的数据)
128
131
  4. **应用调整**:根据市场环境(步骤2)调整长线/短线权重
129
132
  5. **冲突解决**:按 §三 规则处理分歧,特别注意巴菲特否决权和养家情绪周期
130
133
  6. **输出结果**:按 §四 格式输出,含评分表、方向、风险、仓位
131
134
 
135
+ **代码示例:**
136
+
137
+ ```python
138
+ from data import get_quote, get_finance, get_kline
139
+ import statistics
140
+
141
+ # 1. 获取股票数据
142
+ quote = get_quote("sh600989")
143
+ fin = get_finance("sh600989")[0]
144
+ bars = get_kline("sh600989", scale=240, datalen=30)
145
+
146
+ # 2. 获取大盘数据判断市场环境
147
+ index_quote = get_quote("sh000001")
148
+ prev_close = index_quote.get("prev_close", 0)
149
+ current_price = index_quote.get("price", 0)
150
+
151
+ # 判断市场状态
152
+ if prev_close > 0:
153
+ change_pct = (current_price / prev_close - 1) * 100
154
+ if change_pct > 3:
155
+ market_state = "牛市"
156
+ long_weight = 0.40
157
+ short_weight = 0.60
158
+ elif change_pct < -3:
159
+ market_state = "熊市"
160
+ long_weight = 0.65
161
+ short_weight = 0.35
162
+ else:
163
+ market_state = "震荡"
164
+ long_weight = 0.55
165
+ short_weight = 0.45
166
+
167
+ # 3. 计算专家评分(以巴菲特为例)
168
+ roe = float(fin.get("ROEJQ", 0))
169
+ pe = float(quote.get("pe", 0))
170
+
171
+ # 巴菲特评分矩阵
172
+ buffett_scores = {
173
+ "基本面": 100 if roe >= 20 else (75 if roe >= 15 else (40 if roe >= 10 else 0)),
174
+ "估值": 100 if pe <= 15 else (60 if pe <= 25 else (25 if pe <= 40 else 0)),
175
+ }
176
+
177
+ # 4. 计算综合分
178
+ # ... (按 decide.md 规则计算)
179
+
180
+ # 5. 输出结果
181
+ print(f"市场状态: {market_state} | 长线权重: {long_weight} | 短线权重: {short_weight}")
182
+ ```
183
+
132
184
  ---
133
185
 
134
186
  ## 六、信心指数计算
@@ -104,3 +104,48 @@
104
104
  ### 调用方式
105
105
 
106
106
  debate 模式中,先判断"生意模式"—商业模式好→所有维度分不减;一般→总分×0.8。估值用 PE 绝对值+PE 历史分位综合判断。A 股需对管理层可信度单独折扣 10-20%。
107
+
108
+ **代码示例:**
109
+
110
+ ```python
111
+ from data import get_quote, get_finance
112
+ from common import to_float
113
+
114
+ # 获取数据
115
+ quote = get_quote("sh600989")
116
+ fin = get_finance("sh600989")[0]
117
+
118
+ # 估值维度:PE
119
+ pe = to_float(quote.get("pe", 0))
120
+ risk_free_rate = 0.03 # 无风险利率
121
+ pe_threshold = 1 / risk_free_rate # 约 33 倍
122
+
123
+ if pe < pe_threshold * 0.75: # < 25 倍
124
+ pe_score = 100
125
+ elif pe < pe_threshold: # < 33 倍
126
+ pe_score = 70
127
+ elif pe < pe_threshold * 1.2: # < 40 倍
128
+ pe_score = 40
129
+ else:
130
+ pe_score = 0
131
+
132
+ # 安全边际维度:自由现金流
133
+ eps = to_float(fin.get("EPSJB", 0))
134
+ ocf_per_share = to_float(fin.get("MGJYXJJE", 0))
135
+ if eps > 0 and ocf_per_share > eps * 0.8:
136
+ fcf_score = 100 # FCF 持续为正
137
+ elif eps > 0:
138
+ fcf_score = 50
139
+ else:
140
+ fcf_score = 0
141
+
142
+ # 商业模式判断(简化版:基于行业和ROE)
143
+ industry = infer_industry(quote.get("name", ""), quote.get("code", ""))
144
+ roe = to_float(fin.get("ROEJQ", 0))
145
+ if roe >= 20:
146
+ business_score = 100 # 优秀商业模式
147
+ elif roe >= 15:
148
+ business_score = 60
149
+ else:
150
+ business_score = 20
151
+ ```
package/experts/lynch.md CHANGED
@@ -124,4 +124,51 @@
124
124
 
125
125
  ### 调用方式
126
126
 
127
- debate 模式中,用 scripts/ 获取 PE 和净利增速计算 PEG,对照上表打分。增速取近 3 年 CAGR,若数据不足则用最新季度年化。
127
+ debate 模式中,用 `scripts/data/` 获取 PE 和净利增速计算 PEG,对照上表打分。增速取近 3 年 CAGR,若数据不足则用最新季度年化。
128
+
129
+ **代码示例:**
130
+
131
+ ```python
132
+ from data import get_quote, get_finance
133
+ from common import to_float
134
+
135
+ # 获取数据
136
+ quote = get_quote("sh600989")
137
+ fin = get_finance("sh600989")[0]
138
+
139
+ # 基本面维度:净利增速
140
+ profit_growth = to_float(fin.get("PARENTNETPROFITTZ", 0)) # 净利润同比增长%
141
+ if profit_growth >= 25:
142
+ base_score = 100
143
+ elif profit_growth >= 20:
144
+ base_score = 80
145
+ elif profit_growth >= 15:
146
+ base_score = 50
147
+ else:
148
+ base_score = 20
149
+
150
+ # 估值维度:PEG
151
+ pe = to_float(quote.get("pe", 0))
152
+ growth = to_float(fin.get("PARENTNETPROFITTZ", 0))
153
+ peg = pe / max(growth, 1) # 避免除零
154
+
155
+ if peg <= 0.5:
156
+ peg_score = 100
157
+ elif peg <= 1.0:
158
+ peg_score = 80
159
+ elif peg <= 1.5:
160
+ peg_score = 50
161
+ elif peg <= 2.0:
162
+ peg_score = 30
163
+ else:
164
+ peg_score = 0
165
+
166
+ # 风险维度:负债率
167
+ debt_ratio = to_float(fin.get("ZCFZL", 0))
168
+ if debt_ratio < 60:
169
+ risk_score = 100
170
+ elif debt_ratio < 100:
171
+ risk_score = 60
172
+ else:
173
+ risk_score = 0
174
+ ```
package/experts/soros.md CHANGED
@@ -87,3 +87,46 @@ _简化案例_:某银行 PB 0.5 倍,市场担忧不良资产→股价跌至
87
87
  ### 调用方式
88
88
 
89
89
  debate 模式中,重点关注:趋势是否明确(从 kline.py 20日均线判断)、市场情绪是否一致(从涨停家数/炸板率判断)、是否有反身性强化信号。个股基本面只作参考背景。
90
+
91
+ **代码示例:**
92
+
93
+ ```python
94
+ from data import get_quote, get_kline
95
+ from common import to_float
96
+ import statistics
97
+
98
+ # 获取K线数据判断趋势
99
+ bars = get_kline("sh600989", scale=240, datalen=30)
100
+ closes = [to_float(b["close"]) for b in bars if to_float(b.get("close", 0)) > 0]
101
+
102
+ if len(closes) >= 20:
103
+ ma20 = statistics.mean(closes[-20:])
104
+ current_price = closes[-1]
105
+
106
+ if current_price > ma20:
107
+ trend_score = 100 # 上升趋势
108
+ else:
109
+ trend_score = 0 # 下降趋势
110
+
111
+ # 技术面维度:趋势强度
112
+ if len(closes) >= 10:
113
+ recent = closes[-10:]
114
+ if all(recent[i] < recent[i+1] for i in range(len(recent)-1)):
115
+ tech_score = 100 # 明确上升趋势
116
+ elif all(recent[i] > recent[i+1] for i in range(len(recent)-1)):
117
+ tech_score = 0 # 明确下降趋势
118
+ else:
119
+ tech_score = 40 # 震荡
120
+ else:
121
+ tech_score = 40
122
+
123
+ # 情绪/反身性维度(需结合市场数据)
124
+ # 实际使用时从市场情绪接口获取涨停家数、炸板率等
125
+ market_up_count = 80 # 示例:涨停家数
126
+ if market_up_count > 80:
127
+ sentiment_score = 40 # 亢奋期,一致性过高
128
+ elif market_up_count > 40:
129
+ sentiment_score = 60 # 正常
130
+ else:
131
+ sentiment_score = 100 # 冰点,反向机会
132
+ ```
@@ -105,3 +105,47 @@
105
105
  ### 调用方式
106
106
 
107
107
  debate 模式中重点关注:涨停板基因(kline.py 近30日是否有9.5%+涨幅日)、板块联动(同板涨停数)、大盘趋势(所在指数是否站上20日线)。基本面只看EPS排雷,不深度分析。
108
+
109
+ **代码示例:**
110
+
111
+ ```python
112
+ from data import get_quote, get_kline
113
+ from common import to_float
114
+ import statistics
115
+
116
+ # 基础数据
117
+ quote = get_quote("sh600989")
118
+
119
+ # 技术面维度:涨停板基因
120
+ # 获取近30日K线,检查是否有接近涨停的交易日
121
+ bars = get_kline("sh600989", scale=240, datalen=30)
122
+ high_prices = [to_float(b.get("high", 0)) for b in bars]
123
+ prev_close = to_float(quote.get("prev_close", 0))
124
+
125
+ if prev_close > 0:
126
+ # 计算每日涨跌幅
127
+ limit_up_pct = 0.095 if "30" in quote.get("code", "") or "68" in quote.get("code", "") else 0.095
128
+ limit_up_count = sum(1 for hp in high_prices if hp / prev_close - 1 >= limit_up_pct * 0.9)
129
+
130
+ if limit_up_count >= 2:
131
+ tech_score = 100
132
+ elif limit_up_count >= 1:
133
+ tech_score = 50
134
+ else:
135
+ tech_score = 0
136
+
137
+ # 情绪/题材维度:板块联动(需外部市场数据)
138
+ # 示例:同板块涨停数量
139
+ sector_limit_up_count = 3 # 实际从市场数据获取
140
+
141
+ if sector_limit_up_count >= 3:
142
+ sentiment_score = 100
143
+ elif sector_limit_up_count >= 2:
144
+ sentiment_score = 60
145
+ else:
146
+ sentiment_score = 20
147
+
148
+ # 基本面维度:仅排雷
149
+ eps = to_float(quote.get("pe")) # 这里用PE作为占位,实际不深入分析
150
+ base_score = 60 if eps and eps > 0 else 0
151
+ ```
@@ -141,3 +141,56 @@
141
141
  ### 调用方式
142
142
 
143
143
  debate 模式中重点关注:kline.py 30日K线判断均线排列形态(MA5/MA10/MA20是否多头)、题材在生命周期中位置(启动→高潮→加速→分歧→退潮)、量价是否健康(放量上涨+缩量回踩=最佳)。流通市值取 quote.py 的 circulating_cap 字段。
144
+
145
+ **代码示例:**
146
+
147
+ ```python
148
+ from data import get_quote, get_kline
149
+ from common import to_float
150
+ import statistics
151
+
152
+ # 基础数据
153
+ quote = get_quote("sh600989")
154
+ circulating_cap = to_float(quote.get("circulating_cap", 0))
155
+
156
+ # 估值维度:流通市值
157
+ if 50 <= circulating_cap <= 300:
158
+ cap_score = 100
159
+ elif 30 <= circulating_cap <= 500:
160
+ cap_score = 60
161
+ else:
162
+ cap_score = 20
163
+
164
+ # 技术面维度:均线系统
165
+ bars = get_kline("sh600989", scale=240, datalen=30)
166
+ closes = [to_float(b["close"]) for b in bars if to_float(b.get("close", 0)) > 0]
167
+
168
+ if len(closes) >= 20:
169
+ ma5 = statistics.mean(closes[-5:])
170
+ ma10 = statistics.mean(closes[-10:])
171
+ ma20 = statistics.mean(closes[-20:])
172
+
173
+ if ma5 > ma10 > ma20:
174
+ tech_score = 100 # 多头排列
175
+ elif ma5 < ma10 < ma20:
176
+ tech_score = 0 # 空头排列
177
+ else:
178
+ tech_score = 70 # 均线粘合
179
+ else:
180
+ tech_score = 40
181
+
182
+ # 情绪/题材维度:量价配合
183
+ volumes = [to_float(b.get("volume", 0)) for b in bars]
184
+ if len(closes) >= 10 and len(volumes) >= 10:
185
+ recent_vol = statistics.mean(volumes[-5:])
186
+ prev_vol = statistics.mean(volumes[-10:-5])
187
+
188
+ if recent_vol > prev_vol * 1.2 and closes[-1] > statistics.mean(closes[-5:-1]):
189
+ sentiment_score = 100 # 放量上涨
190
+ elif recent_vol < prev_vol * 0.8:
191
+ sentiment_score = 60 # 缩量回调(健康)
192
+ else:
193
+ sentiment_score = 40
194
+ else:
195
+ sentiment_score = 50
196
+ ```
@@ -142,3 +142,69 @@
142
142
  ### 调用方式
143
143
 
144
144
  debate 模式中重点关注:kline.py 30日K线判断是否强势股(有≥4板记录)、当前是否缩量调整(量能<主升期50%)、关键K线反转信号(锤子线/早晨之星/阳包阴/缩量十字星)。流通市值取 quote.py circulating_cap,止损位取前低或20日线。
145
+
146
+ **代码示例:**
147
+
148
+ ```python
149
+ from data import get_quote, get_kline
150
+ from common import to_float
151
+ import statistics
152
+
153
+ # 基础数据
154
+ quote = get_quote("sh600989")
155
+ circulating_cap = to_float(quote.get("circulating_cap", 0))
156
+
157
+ # 估值维度:流通市值 + 回调深度
158
+ if 30 <= circulating_cap <= 200:
159
+ cap_score = 100
160
+ elif 10 <= circulating_cap <= 300:
161
+ cap_score = 50
162
+ else:
163
+ cap_score = 0
164
+
165
+ # 技术面维度:K线反转形态 + 缩量程度
166
+ bars = get_kline("sh600989", scale=240, datalen=30)
167
+ closes = [to_float(b["close"]) for b in bars if to_float(b.get("close", 0)) > 0]
168
+ volumes = [to_float(b.get("volume", 0)) for b in bars if to_float(b.get("volume", 0)) > 0]
169
+
170
+ if len(closes) >= 10 and len(volumes) >= 10:
171
+ # 计算缩量程度
172
+ peak_vol = max(volumes[-10:]) if volumes else 1
173
+ recent_vol = statistics.mean(volumes[-3:]) if len(volumes) >= 3 else 1
174
+ vol_ratio = recent_vol / peak_vol if peak_vol > 0 else 1
175
+
176
+ # K线反转形态识别(简化版)
177
+ last3 = closes[-3:]
178
+ if len(closes) >= 4:
179
+ prev3 = closes[-4:-1]
180
+ else:
181
+ prev3 = closes[:-1] if len(closes) > 1 else [closes[0]] * 3
182
+
183
+ # 锤子线/早晨之星形态
184
+ is_hammer = last3[-1] < last3[-2] and closes[-1] < statistics.mean(last3)
185
+ is_engulfing = last3[-1] > last3[-2] and last3[-2] < last3[-3] and last3[-1] > last3[-3]
186
+
187
+ if vol_ratio <= 0.5 and (is_hammer or is_engulfing):
188
+ tech_score = 100
189
+ elif vol_ratio <= 0.5:
190
+ tech_score = 60
191
+ else:
192
+ tech_score = 20
193
+ else:
194
+ tech_score = 40
195
+
196
+ # 情绪维度:调整阶段
197
+ if len(closes) >= 10:
198
+ # 计算调整天数(从最高点回调)
199
+ peak_idx = closes.index(max(closes))
200
+ adjust_days = len(closes) - peak_idx - 1
201
+
202
+ if 3 <= adjust_days <= 7:
203
+ sentiment_score = 100 # ���佳调整期
204
+ elif adjust_days < 3:
205
+ sentiment_score = 40 # 调整不足
206
+ else:
207
+ sentiment_score = 30 # 调整过长
208
+ else:
209
+ sentiment_score = 50
210
+ ```