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,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
买卖信号汇总。
|
|
3
|
+
无内部依赖。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _generate_signals(features):
|
|
8
|
+
"""汇总买卖信号。"""
|
|
9
|
+
buy, sell = [], []
|
|
10
|
+
ma = features.get("ma_system", {})
|
|
11
|
+
macd = features.get("macd") or {}
|
|
12
|
+
kdj = features.get("kdj") or {}
|
|
13
|
+
boll = features.get("bollinger") or {}
|
|
14
|
+
rsi_data = features.get("rsi", {})
|
|
15
|
+
vol = features.get("volume") or {}
|
|
16
|
+
vol_price = vol.get("volume_price", "")
|
|
17
|
+
vol_vp = vol.get("volume_price_signal", 0)
|
|
18
|
+
divergence = macd.get("divergence", "")
|
|
19
|
+
|
|
20
|
+
# 买入信号
|
|
21
|
+
if macd.get("signal") == 1:
|
|
22
|
+
buy.append("MACD金叉")
|
|
23
|
+
if divergence == "底背离(看涨)":
|
|
24
|
+
buy.append("MACD底背离")
|
|
25
|
+
if "金叉" in kdj.get("signal", "") and "超卖" in kdj.get("signal", ""):
|
|
26
|
+
buy.append("KDJ超卖区金叉")
|
|
27
|
+
if boll.get("position", 0.5) < 0.2 and "收窄" in boll.get("bandwidth_desc", ""):
|
|
28
|
+
buy.append("BOLL下轨+收窄(变盘)")
|
|
29
|
+
if rsi_data.get("rsi", 50) < 35:
|
|
30
|
+
buy.append(f"RSI超卖({rsi_data.get('rsi')})")
|
|
31
|
+
if vol_vp == 1 and "放量上涨" in vol_price:
|
|
32
|
+
buy.append("放量上涨(资金介入)")
|
|
33
|
+
|
|
34
|
+
# 缠论买卖点信号
|
|
35
|
+
chan_data = features.get("chan_theory") or {}
|
|
36
|
+
if chan_data.get("valid"):
|
|
37
|
+
maidain = chan_data.get("maidian", {})
|
|
38
|
+
for bp in maidain.get("buy_points", []):
|
|
39
|
+
buy.append(f"缠论{bp['type']}")
|
|
40
|
+
for sp in maidain.get("sell_points", []):
|
|
41
|
+
sell.append(f"缠论{sp['type']}")
|
|
42
|
+
beichi = chan_data.get("beichi", {})
|
|
43
|
+
if beichi.get("summary", "").startswith("检测到底背驰"):
|
|
44
|
+
buy.append("缠论底背驰")
|
|
45
|
+
elif "顶背驰" in beichi.get("summary", ""):
|
|
46
|
+
sell.append("缠论顶背驰")
|
|
47
|
+
|
|
48
|
+
# 本土战法信号
|
|
49
|
+
local_patterns = features.get("local_patterns") or {}
|
|
50
|
+
for lp in local_patterns.get("patterns", []):
|
|
51
|
+
if lp["type"] == "看涨":
|
|
52
|
+
buy.append(lp["name"])
|
|
53
|
+
elif lp["type"] == "看跌":
|
|
54
|
+
sell.append(lp["name"])
|
|
55
|
+
|
|
56
|
+
# 卖出信号
|
|
57
|
+
if macd.get("signal") == -1:
|
|
58
|
+
sell.append("MACD死叉")
|
|
59
|
+
if divergence == "顶背离(看跌)":
|
|
60
|
+
sell.append("MACD顶背离")
|
|
61
|
+
if "死叉" in kdj.get("signal", "") or "超买" in kdj.get("signal", ""):
|
|
62
|
+
sell.append(f"KDJ{kdj.get('signal')}")
|
|
63
|
+
if boll.get("position", 0.5) > 0.8:
|
|
64
|
+
sell.append("BOLL触及上轨")
|
|
65
|
+
if rsi_data.get("rsi", 50) > 70:
|
|
66
|
+
sell.append(f"RSI超买({rsi_data.get('rsi')})")
|
|
67
|
+
if vol_vp == -1 and "出货" in vol_price:
|
|
68
|
+
sell.append("放量下跌(主力出货)")
|
|
69
|
+
|
|
70
|
+
return buy, sell
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
趋势与结构分析(支撑阻力、箱体、突破、波浪)。
|
|
3
|
+
依赖: core (_find_swing_points)
|
|
4
|
+
"""
|
|
5
|
+
import statistics
|
|
6
|
+
|
|
7
|
+
from .core import _find_swing_points
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def support_resistance(closes, highs, lows, ma_info):
|
|
11
|
+
"""关键支撑/阻力位。"""
|
|
12
|
+
if len(highs) < 10 or len(lows) < 10:
|
|
13
|
+
return {"supports": [], "resistances": []}
|
|
14
|
+
|
|
15
|
+
last = closes[-1]
|
|
16
|
+
|
|
17
|
+
# 前高前低
|
|
18
|
+
lookback = min(60, len(highs))
|
|
19
|
+
recent_highs = highs[-lookback:]
|
|
20
|
+
recent_lows = lows[-lookback:]
|
|
21
|
+
|
|
22
|
+
# 找局部摇摆点
|
|
23
|
+
ph, pl = _find_swing_points(recent_highs, window=3)
|
|
24
|
+
swing_highs = sorted(set(round(recent_highs[i], 2) for i in ph if recent_highs[i] > last))
|
|
25
|
+
swing_lows = sorted(set(round(recent_lows[i], 2) for i in pl if recent_lows[i] < last), reverse=True)
|
|
26
|
+
|
|
27
|
+
supports = []
|
|
28
|
+
resistances = []
|
|
29
|
+
|
|
30
|
+
# 均线支撑/阻力
|
|
31
|
+
for name, price in ma_info.get("ma_supports", [])[:3]:
|
|
32
|
+
supports.append({"level": price, "source": name, "strength": "中"})
|
|
33
|
+
for name, price in ma_info.get("ma_resistances", [])[:3]:
|
|
34
|
+
resistances.append({"level": price, "source": name, "strength": "中"})
|
|
35
|
+
|
|
36
|
+
# 前低支撑
|
|
37
|
+
for lv in swing_lows[:2]:
|
|
38
|
+
supports.append({"level": lv, "source": "前低", "strength": "强"})
|
|
39
|
+
|
|
40
|
+
# 前高阻力
|
|
41
|
+
for hv in swing_highs[-2:]:
|
|
42
|
+
resistances.append({"level": hv, "source": "前高", "strength": "强"})
|
|
43
|
+
|
|
44
|
+
# 整数关口
|
|
45
|
+
round_num = round(last, -1 if last >= 10 else 0)
|
|
46
|
+
if round_num < last:
|
|
47
|
+
base = round_num
|
|
48
|
+
for i in range(1, 4):
|
|
49
|
+
r = base - i * (10 if last >= 50 else 1)
|
|
50
|
+
if r > 0:
|
|
51
|
+
supports.append({"level": r, "source": "整数关口", "strength": "弱"})
|
|
52
|
+
else:
|
|
53
|
+
base = round_num + (10 if last >= 50 else 1)
|
|
54
|
+
for i in range(3):
|
|
55
|
+
resistances.append({"level": base + i * (10 if last >= 50 else 1), "source": "整数关口", "strength": "弱"})
|
|
56
|
+
|
|
57
|
+
# 去重排序
|
|
58
|
+
supports = sorted(supports, key=lambda x: x["level"], reverse=True)[:5]
|
|
59
|
+
resistances = sorted(resistances, key=lambda x: x["level"])[:5]
|
|
60
|
+
|
|
61
|
+
nearest_support = supports[0]["level"] if supports else None
|
|
62
|
+
nearest_resistance = resistances[0]["level"] if resistances else None
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"supports": supports[:3],
|
|
66
|
+
"resistances": resistances[:3],
|
|
67
|
+
"nearest_support": nearest_support,
|
|
68
|
+
"nearest_resistance": nearest_resistance,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def box_detection(highs, lows, closes, window=20):
|
|
73
|
+
"""箱体震荡检测。"""
|
|
74
|
+
if len(closes) < window:
|
|
75
|
+
return None
|
|
76
|
+
hh = max(highs[-window:])
|
|
77
|
+
ll = min(lows[-window:])
|
|
78
|
+
avg = statistics.mean(closes[-window:])
|
|
79
|
+
range_pct = (hh - ll) / avg if avg > 0 else 0
|
|
80
|
+
|
|
81
|
+
if range_pct < 0.03:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
mid = (hh + ll) / 2
|
|
85
|
+
in_box = sum(1 for c in closes[-window:] if ll + (hh - ll) * 0.1 < c < hh - (hh - ll) * 0.1)
|
|
86
|
+
if in_box / window >= 0.6:
|
|
87
|
+
return {
|
|
88
|
+
"top": round(hh, 2),
|
|
89
|
+
"bottom": round(ll, 2),
|
|
90
|
+
"mid": round(mid, 2),
|
|
91
|
+
"range_pct": round(range_pct * 100, 1),
|
|
92
|
+
"days": window,
|
|
93
|
+
"status": "箱体震荡",
|
|
94
|
+
"position": round((closes[-1] - ll) / (hh - ll) * 100) if hh != ll else 50,
|
|
95
|
+
}
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def breakout_check(closes, highs, volumes, resistance):
|
|
100
|
+
"""突破检测。"""
|
|
101
|
+
if len(closes) < 21:
|
|
102
|
+
return {"status": "数据不足"}
|
|
103
|
+
last = closes[-1]
|
|
104
|
+
prev = closes[-2]
|
|
105
|
+
avg_vol20 = statistics.mean(volumes[-21:-1]) if len(volumes) >= 21 else statistics.mean(volumes[:-1])
|
|
106
|
+
last_vol = volumes[-1]
|
|
107
|
+
|
|
108
|
+
broke = last > resistance and prev <= resistance
|
|
109
|
+
if not broke:
|
|
110
|
+
# 之前突破现在回踩
|
|
111
|
+
recent_above = all(c > resistance for c in closes[-5:])
|
|
112
|
+
if recent_above and last < resistance * 1.01:
|
|
113
|
+
return {"status": "回踩确认中", "resistance": round(resistance, 2)}
|
|
114
|
+
return {"status": "未突破"}
|
|
115
|
+
|
|
116
|
+
vol_confirm = last_vol > 1.5 * avg_vol20
|
|
117
|
+
return {
|
|
118
|
+
"status": "突破确认(放量)" if vol_confirm else "突破待确认(缩量)",
|
|
119
|
+
"resistance": round(resistance, 2),
|
|
120
|
+
"volume_ratio": round(last_vol / avg_vol20, 2) if avg_vol20 > 0 else 0,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def wave_state(closes, highs, lows):
|
|
125
|
+
"""简易波浪状态。"""
|
|
126
|
+
if len(closes) < 40:
|
|
127
|
+
return "数据不足"
|
|
128
|
+
lookback = min(60, len(closes))
|
|
129
|
+
c = closes[-lookback:]
|
|
130
|
+
ph, pl = _find_swing_points(c, window=5)
|
|
131
|
+
|
|
132
|
+
if len(ph) >= 2 and len(pl) >= 2:
|
|
133
|
+
recent_ph = sorted(ph[-3:]) if len(ph) >= 3 else sorted(ph)
|
|
134
|
+
recent_pl = sorted(pl[-3:]) if len(pl) >= 3 else sorted(pl)
|
|
135
|
+
if recent_ph[-1] > recent_ph[0] and recent_pl[-1] > recent_pl[0]:
|
|
136
|
+
return "上升浪(高点抬高+低点抬高)"
|
|
137
|
+
elif recent_ph[-1] < recent_ph[0] and recent_pl[-1] < recent_pl[0]:
|
|
138
|
+
return "下跌浪(高点降低+低点降低)"
|
|
139
|
+
elif recent_ph[-1] > recent_ph[0]:
|
|
140
|
+
return "可能有顶部结构(高点抬高但MACD需确认)"
|
|
141
|
+
elif recent_pl[-1] > recent_pl[0]:
|
|
142
|
+
return "可能有底部结构(低点抬高)"
|
|
143
|
+
return "盘整"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
成交量分析(量价配合、OBV)。
|
|
3
|
+
依赖: core (_find_swing_points)
|
|
4
|
+
"""
|
|
5
|
+
import statistics
|
|
6
|
+
|
|
7
|
+
from .core import _find_swing_points
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def volume_analysis(closes, volumes):
|
|
11
|
+
"""量价分析:量比、天量/地量、量价配合、OBV。"""
|
|
12
|
+
if len(closes) < 6 or len(volumes) < 6:
|
|
13
|
+
return None
|
|
14
|
+
|
|
15
|
+
last = closes[-1]
|
|
16
|
+
recent_vol_avg = statistics.mean(volumes[-5:]) if len(volumes) >= 5 else volumes[-1]
|
|
17
|
+
base_vol_avg = statistics.mean(volumes[-20:-5]) if len(volumes) >= 20 else recent_vol_avg
|
|
18
|
+
volume_ratio = recent_vol_avg / base_vol_avg if base_vol_avg > 0 else 1
|
|
19
|
+
|
|
20
|
+
# 量比定性
|
|
21
|
+
if volume_ratio < 0.3:
|
|
22
|
+
vr_desc = "地量(底部信号)"
|
|
23
|
+
elif volume_ratio < 0.5:
|
|
24
|
+
vr_desc = "极度缩量"
|
|
25
|
+
elif volume_ratio < 0.8:
|
|
26
|
+
vr_desc = "缩量"
|
|
27
|
+
elif volume_ratio < 1.2:
|
|
28
|
+
vr_desc = "正常"
|
|
29
|
+
elif volume_ratio < 2.0:
|
|
30
|
+
vr_desc = "放量"
|
|
31
|
+
elif volume_ratio < 3.0:
|
|
32
|
+
vr_desc = "显著放量"
|
|
33
|
+
else:
|
|
34
|
+
vr_desc = "巨量(警惕短期高点)"
|
|
35
|
+
|
|
36
|
+
# 量价配合
|
|
37
|
+
mid = max(len(closes) // 2, 3)
|
|
38
|
+
recent_c = closes[-mid:]
|
|
39
|
+
prev_c = closes[:mid]
|
|
40
|
+
recent_v = volumes[-mid:]
|
|
41
|
+
prev_v = volumes[:mid]
|
|
42
|
+
|
|
43
|
+
price_chg = statistics.mean(recent_c) / max(statistics.mean(prev_c), 0.01) - 1
|
|
44
|
+
vol_chg = statistics.mean(recent_v) / max(statistics.mean(prev_v), 0.01) - 1
|
|
45
|
+
|
|
46
|
+
if price_chg > 0.01 and vol_chg > 0:
|
|
47
|
+
vp_desc = "放量上涨(资金介入)"
|
|
48
|
+
vp_signal = 1
|
|
49
|
+
elif price_chg < -0.01 and vol_chg < 0:
|
|
50
|
+
vp_desc = "缩量下跌(抛压减轻)"
|
|
51
|
+
vp_signal = 1
|
|
52
|
+
elif price_chg > 0.01 and vol_chg < 0:
|
|
53
|
+
vp_desc = "缩量上涨(量价背离)"
|
|
54
|
+
vp_signal = -1
|
|
55
|
+
elif price_chg < -0.01 and vol_chg > 0:
|
|
56
|
+
vp_desc = "放量下跌(主力出货)"
|
|
57
|
+
vp_signal = -1
|
|
58
|
+
else:
|
|
59
|
+
vp_desc = "量价中性"
|
|
60
|
+
vp_signal = 0
|
|
61
|
+
|
|
62
|
+
# OBV 及背离
|
|
63
|
+
obv_values = _obv_series(closes, volumes)
|
|
64
|
+
obv_now = obv_values[-1] if obv_values else 0
|
|
65
|
+
obv_div = _detect_obv_divergence(closes, obv_values)
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"volume_ratio": round(volume_ratio, 2),
|
|
69
|
+
"volume_ratio_desc": vr_desc,
|
|
70
|
+
"volume_price": vp_desc,
|
|
71
|
+
"volume_price_signal": vp_signal,
|
|
72
|
+
"obv_divergence": obv_div,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _obv_series(closes, volumes):
|
|
77
|
+
"""OBV 序列。"""
|
|
78
|
+
obv = [0]
|
|
79
|
+
for i in range(1, len(closes)):
|
|
80
|
+
if closes[i] > closes[i - 1]:
|
|
81
|
+
obv.append(obv[-1] + volumes[i])
|
|
82
|
+
elif closes[i] < closes[i - 1]:
|
|
83
|
+
obv.append(obv[-1] - volumes[i])
|
|
84
|
+
else:
|
|
85
|
+
obv.append(obv[-1])
|
|
86
|
+
return obv
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _detect_obv_divergence(closes, obv_series):
|
|
90
|
+
"""OBV 顶/底背离。"""
|
|
91
|
+
if len(closes) < 40 or len(obv_series) < 40:
|
|
92
|
+
return None
|
|
93
|
+
lookback = min(40, len(closes))
|
|
94
|
+
c = closes[-lookback:]
|
|
95
|
+
o = obv_series[-lookback:]
|
|
96
|
+
|
|
97
|
+
price_highs, price_lows = _find_swing_points(c, window=5)
|
|
98
|
+
obv_highs, obv_lows = _find_swing_points(o, window=5)
|
|
99
|
+
|
|
100
|
+
if len(price_highs) >= 2 and len(obv_highs) >= 2:
|
|
101
|
+
last2_p = sorted(price_highs[-2:])
|
|
102
|
+
if last2_p[1] - last2_p[0] >= 8 and c[last2_p[1]] > c[last2_p[0]]:
|
|
103
|
+
relevant = sorted([i for i in obv_highs if abs(i - last2_p[0]) <= 5 or abs(i - last2_p[1]) <= 5])
|
|
104
|
+
if len(relevant) >= 2 and o[relevant[-1]] < o[relevant[0]]:
|
|
105
|
+
return "OBV顶背离"
|
|
106
|
+
|
|
107
|
+
if len(price_lows) >= 2 and len(obv_lows) >= 2:
|
|
108
|
+
last2_p = sorted(price_lows[-2:])
|
|
109
|
+
if last2_p[1] - last2_p[0] >= 8 and c[last2_p[1]] < c[last2_p[0]]:
|
|
110
|
+
relevant = sorted([i for i in obv_lows if abs(i - last2_p[0]) <= 5 or abs(i - last2_p[1]) <= 5])
|
|
111
|
+
if len(relevant) >= 2 and o[relevant[-1]] > o[relevant[0]]:
|
|
112
|
+
return "OBV底背离"
|
|
113
|
+
return None
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
兼容入口:import technical 包后转发 CLI。
|
|
4
|
+
用法:
|
|
5
|
+
technical.py sh600989 # 完整技术分析报告
|
|
6
|
+
technical.py sh600989 --quick # 快速摘要
|
|
7
|
+
technical.py sh600989 --scale 60 # 60分钟K线
|
|
8
|
+
technical.py sh600989 -j # JSON 输出
|
|
9
|
+
technical.py sh600989 --quick -j # JSON 快速摘要
|
|
10
|
+
technical.py sh600989 --classify # 含个股分类+缠论+本土战法+市场自适应
|
|
11
|
+
technical.py sh600989 --classify --no-chan # 跳过缠论
|
|
12
|
+
"""
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
from common import (
|
|
19
|
+
board_type,
|
|
20
|
+
clamp,
|
|
21
|
+
normalize_quote_code,
|
|
22
|
+
normalize_finance_code,
|
|
23
|
+
to_float,
|
|
24
|
+
)
|
|
25
|
+
from kline import fetch as fetch_kline
|
|
26
|
+
from quote import fetch_batch
|
|
27
|
+
|
|
28
|
+
# 从 technical 包导入所有公开函数
|
|
29
|
+
from technical import * # noqa: F401,F403
|
|
30
|
+
from technical.core import _parse_records
|
|
31
|
+
from technical.moving_average import ma_system, _MA_PERIODS
|
|
32
|
+
from technical.macd import macd_full
|
|
33
|
+
from technical.kdj import kdj_full
|
|
34
|
+
from technical.boll import bollinger
|
|
35
|
+
from technical.rsi import rsi_features
|
|
36
|
+
from technical.volume import volume_analysis
|
|
37
|
+
from technical.candlestick import detect_candle_patterns
|
|
38
|
+
from technical.trend import support_resistance, box_detection, breakout_check, wave_state
|
|
39
|
+
from technical.astock import limit_analysis
|
|
40
|
+
from technical.scoring import (
|
|
41
|
+
composite_score,
|
|
42
|
+
detect_market_environment,
|
|
43
|
+
_market_weight_adjustments,
|
|
44
|
+
)
|
|
45
|
+
from technical.report import render_report, render_quick
|
|
46
|
+
from technical.core import sma
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _compute_all(closes, opens, highs, lows, volumes, records, board, quote, args=None):
|
|
50
|
+
"""计算所有技术指标。args 为 CLI 参数,用于控制可选模块。"""
|
|
51
|
+
features = {}
|
|
52
|
+
|
|
53
|
+
features["ma_system"] = ma_system(closes)
|
|
54
|
+
features["macd"] = macd_full(closes)
|
|
55
|
+
features["kdj"] = kdj_full(closes, highs, lows)
|
|
56
|
+
features["bollinger"] = bollinger(closes) or {}
|
|
57
|
+
features["rsi"] = rsi_features(closes)
|
|
58
|
+
features["volume"] = volume_analysis(closes, volumes) or {}
|
|
59
|
+
features["patterns"] = detect_candle_patterns(records)
|
|
60
|
+
features["support_resistance"] = support_resistance(closes, highs, lows, features["ma_system"])
|
|
61
|
+
features["box"] = box_detection(highs, lows, closes)
|
|
62
|
+
nearest_r = features["support_resistance"].get("nearest_resistance")
|
|
63
|
+
features["breakout"] = breakout_check(closes, highs, volumes, nearest_r) if nearest_r else {}
|
|
64
|
+
features["wave"] = wave_state(closes, highs, lows)
|
|
65
|
+
features["limit_analysis"] = limit_analysis(records, board, quote)
|
|
66
|
+
|
|
67
|
+
# ── 可选增强模块(--classify 时启用)──
|
|
68
|
+
do_classify = args and getattr(args, "classify", False)
|
|
69
|
+
|
|
70
|
+
# 均线序列(供本土战法使用)
|
|
71
|
+
mas = {}
|
|
72
|
+
for p in [5, 10, 20, 60]:
|
|
73
|
+
mas[f"ma{p}"] = [sma(closes[:i + 1], p) if i + 1 >= p else closes[i]
|
|
74
|
+
for i in range(len(closes))]
|
|
75
|
+
|
|
76
|
+
# 本土战法(始终运行,计算成本低)
|
|
77
|
+
try:
|
|
78
|
+
from patterns_local import detect_all_local_patterns
|
|
79
|
+
local_result = detect_all_local_patterns(records, closes, highs, lows, volumes, mas,
|
|
80
|
+
code=quote.get("code", ""))
|
|
81
|
+
features["local_patterns"] = local_result
|
|
82
|
+
except Exception:
|
|
83
|
+
features["local_patterns"] = {"patterns": [], "summary": "本土战法计算失败", "count": 0}
|
|
84
|
+
|
|
85
|
+
# 个股分类(需要财务数据)
|
|
86
|
+
if do_classify:
|
|
87
|
+
try:
|
|
88
|
+
from classifier import classify_stock
|
|
89
|
+
fin_record = None
|
|
90
|
+
try:
|
|
91
|
+
from finance import fetch as fetch_finance
|
|
92
|
+
fn_code = normalize_finance_code(quote.get("code", ""))
|
|
93
|
+
fin_data = fetch_finance(fn_code)
|
|
94
|
+
fin_record = fin_data[0] if fin_data else None
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
features["classification"] = classify_stock(fin_record, quote, records)
|
|
98
|
+
except Exception:
|
|
99
|
+
features["classification"] = {"type": "普通股", "confidence": "低",
|
|
100
|
+
"reasons": ["分类计算失败"], "priority_indicators": [],
|
|
101
|
+
"deprioritized": []}
|
|
102
|
+
|
|
103
|
+
# 缠论分析(需要较长K线历史)
|
|
104
|
+
do_chan = do_classify and not (args and getattr(args, "no_chan", False))
|
|
105
|
+
if do_chan and len(records) >= 30:
|
|
106
|
+
try:
|
|
107
|
+
from chan import chan_full_analysis
|
|
108
|
+
features["chan_theory"] = chan_full_analysis(records)
|
|
109
|
+
except Exception:
|
|
110
|
+
features["chan_theory"] = {"valid": False, "error": "缠论计算失败"}
|
|
111
|
+
else:
|
|
112
|
+
features["chan_theory"] = {"valid": False, "error": "未启用" if not do_classify else "数据不足"}
|
|
113
|
+
|
|
114
|
+
# 市场环境
|
|
115
|
+
if do_classify:
|
|
116
|
+
market_index = getattr(args, "market_index", None)
|
|
117
|
+
if market_index:
|
|
118
|
+
try:
|
|
119
|
+
idx_quotes = fetch_batch([normalize_quote_code(market_index)])
|
|
120
|
+
idx_quote = idx_quotes[0] if idx_quotes else None
|
|
121
|
+
features["market_environment"] = detect_market_environment(idx_quote)
|
|
122
|
+
except Exception:
|
|
123
|
+
features["market_environment"] = detect_market_environment()
|
|
124
|
+
else:
|
|
125
|
+
features["market_environment"] = detect_market_environment()
|
|
126
|
+
else:
|
|
127
|
+
features["market_environment"] = {"state": "震荡", "confidence": "低",
|
|
128
|
+
"signals": ["未启用市场检测"],
|
|
129
|
+
"weight_adjustments": _market_weight_adjustments("震荡")}
|
|
130
|
+
|
|
131
|
+
return features
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def main():
|
|
135
|
+
parser = argparse.ArgumentParser(description="A 股纯技术分析")
|
|
136
|
+
parser.add_argument("code", help="证券代码,如 sh600989")
|
|
137
|
+
parser.add_argument("--scale", "-s", type=int, default=240, help="K线周期: 240=日K, 60=60分钟, 30=30分钟, 15=15分钟, 5=5分钟")
|
|
138
|
+
parser.add_argument("--quick", "-q", action="store_true", help="快速摘要模式")
|
|
139
|
+
parser.add_argument("--json", "-j", action="store_true", help="JSON 输出")
|
|
140
|
+
parser.add_argument("--datalen", type=int, default=250, help="K线数量(默认250)")
|
|
141
|
+
parser.add_argument("--classify", action="store_true", help="启用个股分类+缠论+本土战法+市场自适应")
|
|
142
|
+
parser.add_argument("--no-chan", action="store_true", help="跳过缠论分析(仅与 --classify 配合)")
|
|
143
|
+
parser.add_argument("--market-index", type=str, default=None, help="市场环境参考指数(默认无,如 sh000001)")
|
|
144
|
+
args = parser.parse_args()
|
|
145
|
+
|
|
146
|
+
code = normalize_quote_code(args.code)
|
|
147
|
+
board = board_type(code)
|
|
148
|
+
|
|
149
|
+
# 获取数据
|
|
150
|
+
records = fetch_kline(code, args.scale, args.datalen)
|
|
151
|
+
if not records:
|
|
152
|
+
sys.exit(f"❌ 无法获取 {code} 的 K 线数据")
|
|
153
|
+
|
|
154
|
+
quotes = fetch_batch([code])
|
|
155
|
+
quote = quotes[0] if quotes else {}
|
|
156
|
+
if not quote:
|
|
157
|
+
sys.exit(f"❌ 无法获取 {code} 的实时行情")
|
|
158
|
+
|
|
159
|
+
# 解析数值
|
|
160
|
+
closes, opens, highs, lows, volumes = _parse_records(records)
|
|
161
|
+
if len(closes) < 10:
|
|
162
|
+
sys.exit(f"❌ {code} K 线数据不足(需≥10根,当前{len(closes)})")
|
|
163
|
+
|
|
164
|
+
# 计算所有指标
|
|
165
|
+
features = _compute_all(closes, opens, highs, lows, volumes, records, board, quote, args)
|
|
166
|
+
|
|
167
|
+
# 综合评分(自适应)
|
|
168
|
+
stock_type = "普通股"
|
|
169
|
+
market_state = None
|
|
170
|
+
if args.classify:
|
|
171
|
+
classification = features.get("classification") or {}
|
|
172
|
+
stock_type = classification.get("type", "普通股")
|
|
173
|
+
market_env = features.get("market_environment") or {}
|
|
174
|
+
market_state = market_env.get("state")
|
|
175
|
+
score = composite_score(features, stock_type=stock_type, market_state=market_state)
|
|
176
|
+
|
|
177
|
+
# 元数据
|
|
178
|
+
price_num = to_float(quote.get("price"))
|
|
179
|
+
meta = {
|
|
180
|
+
"code": code,
|
|
181
|
+
"name": quote.get("name", ""),
|
|
182
|
+
"price": quote.get("price", "-"),
|
|
183
|
+
"price_num": price_num,
|
|
184
|
+
"change_pct": quote.get("change_pct", "-"),
|
|
185
|
+
"board": board,
|
|
186
|
+
"scale": args.scale,
|
|
187
|
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M"),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# 查找止损位
|
|
191
|
+
sr = features.get("support_resistance", {})
|
|
192
|
+
nearest_support = sr.get("nearest_support")
|
|
193
|
+
if nearest_support and price_num > 0:
|
|
194
|
+
features["stop_loss_pct"] = round((price_num - nearest_support) / price_num * 100, 1)
|
|
195
|
+
|
|
196
|
+
if args.json:
|
|
197
|
+
feature_keys = {"ma_system", "macd", "kdj", "bollinger", "rsi", "volume",
|
|
198
|
+
"patterns", "support_resistance", "box", "breakout", "wave",
|
|
199
|
+
"limit_analysis"}
|
|
200
|
+
if args.classify:
|
|
201
|
+
feature_keys.update({"classification", "chan_theory", "local_patterns", "market_environment"})
|
|
202
|
+
output = {
|
|
203
|
+
"meta": meta,
|
|
204
|
+
"score": score,
|
|
205
|
+
"features": {k: v for k, v in features.items() if k in feature_keys},
|
|
206
|
+
}
|
|
207
|
+
print(json.dumps(output, ensure_ascii=False, indent=2, default=str))
|
|
208
|
+
elif args.quick:
|
|
209
|
+
print(render_quick(features, score, meta))
|
|
210
|
+
else:
|
|
211
|
+
print(render_report(features, score, {}, meta))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
main()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: financial-analyst
|
|
3
|
+
description: 财务分析 agent,专注于财务建模、预测、场景分析和数据驱动决策支持。完全自包含——使用 stock-analyzer-skill 包的 scripts/ 工具。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Financial Analyst
|
|
7
|
+
|
|
8
|
+
财务分析 agent,专注于财务建模、预测、场景分析和数据驱动决策支持。
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
/financial-analyst <任务描述>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 前置依赖
|
|
17
|
+
|
|
18
|
+
- 本 skill 是 [stock-analyzer-skill](https://github.com/) 的一部分
|
|
19
|
+
- 工具脚本位于包根目录 `scripts/`;Claude Code 运行时工作目录即为项目根目录
|
|
20
|
+
- 调用方式:直接运行 `python3 scripts/<name>.py <args>`
|
|
21
|
+
- 不依赖任何外部 Python 库
|
|
22
|
+
|
|
23
|
+
## Instructions
|
|
24
|
+
|
|
25
|
+
使用简洁中文。先给结论和置信度,再给关键数据、模型假设、敏感性和行动项。涉及最新数据时必须运行脚本或明确说明数据不可得。
|
|
26
|
+
|
|
27
|
+
## Workflow Coordination
|
|
28
|
+
|
|
29
|
+
完整链路见包根目录 `workflow.md`。本 skill 是财务深挖和建模环节:
|
|
30
|
+
|
|
31
|
+
- 上游来自 `stock`:当估值分歧、盈利质量异常、增长预测不清时进入财务建模。
|
|
32
|
+
- 上游来自 `investment-researcher`:作为尽调报告的财务证据模块。
|
|
33
|
+
- 下游回到 `stock`:交接盈利质量、估值区间、关键假设和敏感性。
|
|
34
|
+
- 下游到 `portfolio`:当财务风险影响持仓时,交接降仓/回避触发条件。
|
|
35
|
+
|
|
36
|
+
输出必须包含核心假设、敏感性、数据缺口和 `fundamental_rating`。不单独给交易买点。
|
|
37
|
+
|
|
38
|
+
### Step 1: 理解分析需求
|
|
39
|
+
|
|
40
|
+
- 明确分析目标和范围
|
|
41
|
+
- 确定所需财务数据和指标
|
|
42
|
+
- 确认时间范围和频率
|
|
43
|
+
|
|
44
|
+
### Step 2: 数据收集与验证
|
|
45
|
+
|
|
46
|
+
#### 2.1 数据获取方式(按优先级)
|
|
47
|
+
|
|
48
|
+
**方式一:包内脚本(推荐)**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
|
|
52
|
+
# 实时行情
|
|
53
|
+
python3 scripts/quote.py sh600989
|
|
54
|
+
|
|
55
|
+
# 财务数据(最近 4 季)
|
|
56
|
+
python3 scripts/finance.py SH600989
|
|
57
|
+
|
|
58
|
+
# 批量财务对比
|
|
59
|
+
python3 scripts/finance.py -c SH600989,SZ000807,SH603993
|
|
60
|
+
|
|
61
|
+
# 研报
|
|
62
|
+
python3 scripts/announcements.py 600989 reports
|
|
63
|
+
|
|
64
|
+
# 公司公告
|
|
65
|
+
python3 scripts/announcements.py 600989
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**方式二:直接 curl(脚本不可用时兜底)**
|
|
69
|
+
|
|
70
|
+
优先参考 `scripts/common.py` 的字段映射,不要手写腾讯字段索引。
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# 财务摘要(东方财富)
|
|
74
|
+
curl -s "https://emweb.securities.eastmoney.com/PC_HSF10/NewFinanceAnalysis/ZYZBAjaxNew?type=0&code=SH600989"
|
|
75
|
+
|
|
76
|
+
# 实时估值(腾讯)
|
|
77
|
+
curl -s "https://qt.gtimg.cn/q=sh600989" | iconv -f GBK -t UTF-8
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**方式三:WebSearch/WebFetch(边界)**
|
|
81
|
+
|
|
82
|
+
不可靠,国内环境下优先用方式一、二。WebSearch 失败不要重试超过 2 次。
|
|
83
|
+
|
|
84
|
+
#### 2.2 关键财务数据字段
|
|
85
|
+
|
|
86
|
+
| 数据类型 | 字段 | 来源 |
|
|
87
|
+
| -------- | ---- | ---- |
|
|
88
|
+
| 盈利能力 | 营收、净利润、毛利率、净利率、ROE | `scripts/finance.py` |
|
|
89
|
+
| 成长性 | 营收增速、净利润增速、EPS增速 | `scripts/finance.py` |
|
|
90
|
+
| 估值 | PE、PB、PS、市值 | `scripts/quote.py` |
|
|
91
|
+
| 偿债能力 | 资产负债率、流动比率、速动比率 | `scripts/finance.py` |
|
|
92
|
+
| 现金流 | 经营现金流、自由现金流 | `scripts/finance.py` |
|
|
93
|
+
| 分红 | 每股股利、股息率、分红率 | `scripts/announcements.py` |
|
|
94
|
+
|
|
95
|
+
字段详细含义见 `methodology.md` §2.2。
|
|
96
|
+
|
|
97
|
+
#### 2.3 数据验证清单
|
|
98
|
+
|
|
99
|
+
- [ ] 同比数据一致性(本期 vs 去年同期)
|
|
100
|
+
- [ ] 环比数据合理性(本期 vs 上期)
|
|
101
|
+
- [ ] 异常值识别(毛利率突变、非经常性损益占比)
|
|
102
|
+
- [ ] 审计意见(标准无保留 vs 带强调事项)
|
|
103
|
+
- [ ] 数据来源交叉验证(至少两个独立来源)
|
|
104
|
+
|
|
105
|
+
### Step 3: 分析
|
|
106
|
+
|
|
107
|
+
- 财务指标计算:收入、利润、ROE、毛利率、净利率、资产负债率、经营现金流
|
|
108
|
+
- 趋势分析:同比、环比、连续季度变化、异常值
|
|
109
|
+
- 对比分析:同业、历史区间、估值和盈利质量
|
|
110
|
+
- 场景分析:基准/乐观/悲观,列明假设
|
|
111
|
+
- 风险评估:财务风险、估值风险、数据缺口
|
|
112
|
+
|
|
113
|
+
**五层分析框架**(详见 `methodology.md` §2):
|
|
114
|
+
- 基本面:ROE > 15% 优秀,> 20% 顶级
|
|
115
|
+
- 估值:PE/ROE < 3 为好,PEG < 1 低估
|
|
116
|
+
- 技术面:30 日 K 线趋势 + 关键支撑/阻力
|
|
117
|
+
- 板块:所属板块今日表现
|
|
118
|
+
- 风险收益比:情景分析 + 凯利公式仓位
|
|
119
|
+
|
|
120
|
+
### Step 4: 输出
|
|
121
|
+
|
|
122
|
+
- 分析报告
|
|
123
|
+
- 关键发现
|
|
124
|
+
- 建议和行动项
|
|
125
|
+
|
|
126
|
+
## Allowed Auto-Actions (No Confirmation Needed)
|
|
127
|
+
|
|
128
|
+
- 运行 scripts/ 下的查询脚本
|
|
129
|
+
- 读取本地 data/ 下的参考数据
|
|
130
|
+
- 读取 `methodology.md`
|
|
131
|
+
|
|
132
|
+
## Actions Requiring Confirmation
|
|
133
|
+
|
|
134
|
+
1. 执行 `git commit`、`git push`
|
|
135
|
+
2. 修改 scripts/ 或 data/ 文件
|
|
136
|
+
|
|
137
|
+
## Guardrails
|
|
138
|
+
|
|
139
|
+
- 不要把脚本未返回的数据包装成事实;缺失项标注为“未覆盖/未获取”。
|
|
140
|
+
- 财务模型必须列出关键假设,尤其是收入增速、利润率、折现率或估值倍数。
|
|
141
|
+
- 输出投资相关建议时使用 buy/hold/sell 加风险条件,不给保证收益。
|