@aggroot-team/aggroot 1.7.2
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/.env.example +106 -0
- package/LICENSE +21 -0
- package/README.md +496 -0
- package/dist/cli.cjs +26 -0
- package/dist/index.cjs +45238 -0
- package/dist/native/better_sqlite3.node +0 -0
- package/dist/tree-sitter-c.wasm +0 -0
- package/dist/tree-sitter-cpp.wasm +0 -0
- package/dist/web-tree-sitter.wasm +0 -0
- package/install-linux.sh +123 -0
- package/install-windows.bat +121 -0
- package/package.json +155 -0
- package/scripts/build-all.mjs +42 -0
- package/scripts/fix-workspace-deps.mjs +41 -0
- package/scripts/generate-example-skills.ts +238 -0
- package/scripts/import-meta-shim.js +3 -0
- package/scripts/install-browsers.mjs +132 -0
- package/scripts/obfuscate.mjs +215 -0
- package/scripts/test-bash-parser.ts +37 -0
- package/scripts/test-parser2.ts +24 -0
- package/scripts/test-parser3.js +4 -0
- package/scripts/test-tokenizer.js +148 -0
- package/scripts/verify-no-source.mjs +83 -0
- package/share/agents/agent-coder.json +256 -0
- package/share/agents/agent-devops.json +61 -0
- package/share/agents/agent-writer.json +213 -0
- package/share/scripts/_utils.py +105 -0
- package/share/scripts/market_overview.py +244 -0
- package/share/scripts/stock_compare.py +244 -0
- package/share/scripts/stock_financial.py +211 -0
- package/share/scripts/stock_news.py +181 -0
- package/share/scripts/stock_quote.py +433 -0
- package/share/scripts/stock_technical.py +402 -0
- package/share/web/dist/assets/arc-DmQ0lviI.js +1 -0
- package/share/web/dist/assets/architectureDiagram-3BPJPVTR-KELFQLRt.js +36 -0
- package/share/web/dist/assets/blockDiagram-GPEHLZMM-D_2waPJB.js +132 -0
- package/share/web/dist/assets/c4Diagram-AAUBKEIU-BxaYriMP.js +10 -0
- package/share/web/dist/assets/channel-DppiAmuZ.js +1 -0
- package/share/web/dist/assets/chunk-2J33WTMH-D_wcMArK.js +1 -0
- package/share/web/dist/assets/chunk-4BX2VUAB-C4Mm8aE8.js +1 -0
- package/share/web/dist/assets/chunk-55IACEB6-Djo1jBPb.js +1 -0
- package/share/web/dist/assets/chunk-727SXJPM-D-9kzLrQ.js +206 -0
- package/share/web/dist/assets/chunk-AQP2D5EJ-BV58AHlG.js +231 -0
- package/share/web/dist/assets/chunk-FMBD7UC4-D3AHszVJ.js +15 -0
- package/share/web/dist/assets/chunk-ND2GUHAM-CCuQ63c7.js +1 -0
- package/share/web/dist/assets/chunk-QZHKN3VN-Dl9PXzs1.js +1 -0
- package/share/web/dist/assets/classDiagram-4FO5ZUOK-BnkzOZLJ.js +1 -0
- package/share/web/dist/assets/classDiagram-v2-Q7XG4LA2-BnkzOZLJ.js +1 -0
- package/share/web/dist/assets/cose-bilkent-S5V4N54A-BuRspznV.js +1 -0
- package/share/web/dist/assets/cytoscape.esm-CkSuTymj.js +321 -0
- package/share/web/dist/assets/dagre-BM42HDAG-D4fhww9y.js +4 -0
- package/share/web/dist/assets/defaultLocale-CrowFXzY.js +1 -0
- package/share/web/dist/assets/diagram-2AECGRRQ-dMi9m1J4.js +43 -0
- package/share/web/dist/assets/diagram-5GNKFQAL-CjRIIBA8.js +10 -0
- package/share/web/dist/assets/diagram-KO2AKTUF-gR1tXbmQ.js +3 -0
- package/share/web/dist/assets/diagram-LMA3HP47-CQkJCjQX.js +24 -0
- package/share/web/dist/assets/diagram-OG6HWLK6-DBEvIvu5.js +24 -0
- package/share/web/dist/assets/erDiagram-TEJ5UH35-ChwS3Oys.js +85 -0
- package/share/web/dist/assets/flowDiagram-I6XJVG4X-yYT7qyQr.js +162 -0
- package/share/web/dist/assets/ganttDiagram-6RSMTGT7-9TgEI2XC.js +292 -0
- package/share/web/dist/assets/gitGraphDiagram-PVQCEYII-PVcWiaLN.js +106 -0
- package/share/web/dist/assets/graph-D2o_JWn5.js +1 -0
- package/share/web/dist/assets/index-BWshepxw.js +407 -0
- package/share/web/dist/assets/index-aC35iubs.css +1 -0
- package/share/web/dist/assets/infoDiagram-5YYISTIA-C5_cYCEE.js +2 -0
- package/share/web/dist/assets/init-Gi6I4Gst.js +1 -0
- package/share/web/dist/assets/ishikawaDiagram-YF4QCWOH-Day2c-1K.js +70 -0
- package/share/web/dist/assets/journeyDiagram-JHISSGLW-BZdHoYic.js +139 -0
- package/share/web/dist/assets/kanban-definition-UN3LZRKU-DoEoEViV.js +89 -0
- package/share/web/dist/assets/katex-HP8lGamR.js +257 -0
- package/share/web/dist/assets/layout-UJbxhwF7.js +1 -0
- package/share/web/dist/assets/linear-z8LqGvHi.js +1 -0
- package/share/web/dist/assets/mindmap-definition-RKZ34NQL-DshJGF7V.js +96 -0
- package/share/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/share/web/dist/assets/pieDiagram-4H26LBE5-MKBdIMr0.js +30 -0
- package/share/web/dist/assets/quadrantDiagram-W4KKPZXB-Rc8VkAHd.js +7 -0
- package/share/web/dist/assets/requirementDiagram-4Y6WPE33-DIvF0TEG.js +84 -0
- package/share/web/dist/assets/sankeyDiagram-5OEKKPKP-FTZOsw4G.js +40 -0
- package/share/web/dist/assets/sequenceDiagram-3UESZ5HK-BXnFdz67.js +162 -0
- package/share/web/dist/assets/stateDiagram-AJRCARHV-N_DEea7F.js +1 -0
- package/share/web/dist/assets/stateDiagram-v2-BHNVJYJU-9RyR03R8.js +1 -0
- package/share/web/dist/assets/timeline-definition-PNZ67QCA-nelw3asj.js +120 -0
- package/share/web/dist/assets/vennDiagram-CIIHVFJN-BkfB2ea3.js +34 -0
- package/share/web/dist/assets/wardley-L42UT6IY-ChtTMLrY.js +161 -0
- package/share/web/dist/assets/wardleyDiagram-YWT4CUSO-CEsObJvL.js +78 -0
- package/share/web/dist/assets/xychartDiagram-2RQKCTM6-DNog7YuY.js +7 -0
- package/share/web/dist/index.html +13 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""
|
|
2
|
+
股票技术指标分析
|
|
3
|
+
|
|
4
|
+
用法: python stock_technical.py <stock_code> [--indicator <type>] [--period <days>]
|
|
5
|
+
stock_code: 6位A股代码(如 600519) 或 5位港股代码(如 03888)
|
|
6
|
+
--indicator: 指标类型 ma/macd/rsi/kdj/boll/all,默认all
|
|
7
|
+
--period: 历史天数,默认120
|
|
8
|
+
|
|
9
|
+
AKShare接口:
|
|
10
|
+
主: ak.stock_zh_a_daily(symbol) — 新浪日线行情(稳定可用,代码需带sh/sz前缀)
|
|
11
|
+
备: ak.stock_zh_a_hist(symbol) — 东方财富日线行情(当前不稳定)
|
|
12
|
+
港股主: ak.stock_hk_daily(symbol) — 新浪港股日线(快速)
|
|
13
|
+
港股备: ak.stock_hk_hist(symbol) — 东方财富港股日线
|
|
14
|
+
|
|
15
|
+
本地计算: MA(5/10/20/60), MACD(12,26,9), RSI(6/12/24), KDJ(9,3,3), BOLL(20,2)
|
|
16
|
+
|
|
17
|
+
注意: 新浪接口代码需带市场前缀(如 sh600519),频繁调用会被封IP
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
import argparse
|
|
22
|
+
from datetime import datetime, timedelta
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, '.')
|
|
25
|
+
from _utils import normalize_code, get_market_prefix, output_json, output_error, check_akshare, safe_float, is_hk_code
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def calc_ma(closes, periods=[5, 10, 20, 60]):
|
|
29
|
+
"""计算移动平均线"""
|
|
30
|
+
result = {}
|
|
31
|
+
for p in periods:
|
|
32
|
+
if len(closes) >= p:
|
|
33
|
+
result[f"ma{p}"] = round(sum(closes[-p:]) / p, 2)
|
|
34
|
+
else:
|
|
35
|
+
result[f"ma{p}"] = None
|
|
36
|
+
|
|
37
|
+
# 判断排列
|
|
38
|
+
vals = [result.get(f"ma{p}") for p in periods if result.get(f"ma{p}") is not None]
|
|
39
|
+
if len(vals) >= 2:
|
|
40
|
+
if all(vals[i] >= vals[i+1] for i in range(len(vals)-1)):
|
|
41
|
+
result["signal"] = "多头排列"
|
|
42
|
+
elif all(vals[i] <= vals[i+1] for i in range(len(vals)-1)):
|
|
43
|
+
result["signal"] = "空头排列"
|
|
44
|
+
else:
|
|
45
|
+
result["signal"] = "交叉排列"
|
|
46
|
+
else:
|
|
47
|
+
result["signal"] = "数据不足"
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def calc_macd(closes, fast=12, slow=26, signal=9):
|
|
52
|
+
"""计算MACD"""
|
|
53
|
+
if len(closes) < slow + signal:
|
|
54
|
+
return {"dif": None, "dea": None, "macd_hist": None, "signal": "数据不足"}
|
|
55
|
+
|
|
56
|
+
# EMA计算
|
|
57
|
+
def ema(data, period):
|
|
58
|
+
k = 2 / (period + 1)
|
|
59
|
+
result = data[0]
|
|
60
|
+
for val in data[1:]:
|
|
61
|
+
result = val * k + result * (1 - k)
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
# 计算DIF序列
|
|
65
|
+
dif_list = []
|
|
66
|
+
for i in range(slow - 1, len(closes)):
|
|
67
|
+
fast_ema = ema(closes[i-fast+1:i+1] if i >= fast-1 else closes[:i+1], fast)
|
|
68
|
+
slow_ema = ema(closes[i-slow+1:i+1], slow)
|
|
69
|
+
dif_list.append(fast_ema - slow_ema)
|
|
70
|
+
|
|
71
|
+
if len(dif_list) < signal:
|
|
72
|
+
return {"dif": None, "dea": None, "macd_hist": None, "signal": "数据不足"}
|
|
73
|
+
|
|
74
|
+
dea = ema(dif_list, signal)
|
|
75
|
+
dif = dif_list[-1]
|
|
76
|
+
hist = 2 * (dif - dea)
|
|
77
|
+
|
|
78
|
+
# 信号判断
|
|
79
|
+
prev_hist = 2 * (dif_list[-2] - ema(dif_list[:-1], signal)) if len(dif_list) >= 2 else 0
|
|
80
|
+
if hist > 0 and prev_hist <= 0:
|
|
81
|
+
sig = "金叉"
|
|
82
|
+
elif hist < 0 and prev_hist >= 0:
|
|
83
|
+
sig = "死叉"
|
|
84
|
+
elif hist > 0:
|
|
85
|
+
sig = "多头"
|
|
86
|
+
else:
|
|
87
|
+
sig = "空头"
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
"dif": round(dif, 3),
|
|
91
|
+
"dea": round(dea, 3),
|
|
92
|
+
"macd_hist": round(hist, 3),
|
|
93
|
+
"signal": sig,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def calc_rsi(closes, periods=[6, 12, 24]):
|
|
98
|
+
"""计算RSI"""
|
|
99
|
+
result = {}
|
|
100
|
+
for p in periods:
|
|
101
|
+
if len(closes) < p + 1:
|
|
102
|
+
result[f"rsi{p}"] = None
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
gains = []
|
|
106
|
+
losses = []
|
|
107
|
+
for i in range(-p, 0):
|
|
108
|
+
diff = closes[i] - closes[i-1]
|
|
109
|
+
gains.append(max(diff, 0))
|
|
110
|
+
losses.append(max(-diff, 0))
|
|
111
|
+
|
|
112
|
+
avg_gain = sum(gains) / p
|
|
113
|
+
avg_loss = sum(losses) / p
|
|
114
|
+
|
|
115
|
+
if avg_loss == 0:
|
|
116
|
+
rsi = 100
|
|
117
|
+
else:
|
|
118
|
+
rs = avg_gain / avg_loss
|
|
119
|
+
rsi = 100 - (100 / (1 + rs))
|
|
120
|
+
|
|
121
|
+
result[f"rsi{p}"] = round(rsi, 2)
|
|
122
|
+
|
|
123
|
+
# 信号判断
|
|
124
|
+
rsi6 = result.get("rsi6")
|
|
125
|
+
if rsi6 is not None:
|
|
126
|
+
if rsi6 > 80:
|
|
127
|
+
result["signal"] = "超买"
|
|
128
|
+
elif rsi6 > 60:
|
|
129
|
+
result["signal"] = "偏强"
|
|
130
|
+
elif rsi6 > 40:
|
|
131
|
+
result["signal"] = "中性"
|
|
132
|
+
elif rsi6 > 20:
|
|
133
|
+
result["signal"] = "偏弱"
|
|
134
|
+
else:
|
|
135
|
+
result["signal"] = "超卖"
|
|
136
|
+
else:
|
|
137
|
+
result["signal"] = "数据不足"
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def calc_kdj(highs, lows, closes, n=9, m1=3, m2=3):
|
|
143
|
+
"""计算KDJ"""
|
|
144
|
+
if len(closes) < n:
|
|
145
|
+
return {"k": None, "d": None, "j": None, "signal": "数据不足"}
|
|
146
|
+
|
|
147
|
+
# 取最近n天
|
|
148
|
+
recent_highs = highs[-n:]
|
|
149
|
+
recent_lows = lows[-n:]
|
|
150
|
+
close = closes[-1]
|
|
151
|
+
|
|
152
|
+
hn = max(recent_highs)
|
|
153
|
+
ln = min(recent_lows)
|
|
154
|
+
|
|
155
|
+
if hn == ln:
|
|
156
|
+
rsv = 50
|
|
157
|
+
else:
|
|
158
|
+
rsv = (close - ln) / (hn - ln) * 100
|
|
159
|
+
|
|
160
|
+
# 简化计算(使用前一天的K/D值默认50)
|
|
161
|
+
k = (2 / m1) * 50 + (1 / m1) * rsv
|
|
162
|
+
d = (2 / m2) * 50 + (1 / m2) * k
|
|
163
|
+
j = 3 * k - 2 * d
|
|
164
|
+
|
|
165
|
+
# 信号判断
|
|
166
|
+
if j > 100:
|
|
167
|
+
sig = "超买"
|
|
168
|
+
elif k > d and k < 80:
|
|
169
|
+
sig = "偏强"
|
|
170
|
+
elif k < d and k > 20:
|
|
171
|
+
sig = "偏弱"
|
|
172
|
+
elif j < 0:
|
|
173
|
+
sig = "超卖"
|
|
174
|
+
else:
|
|
175
|
+
sig = "中性"
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
"k": round(k, 2),
|
|
179
|
+
"d": round(d, 2),
|
|
180
|
+
"j": round(j, 2),
|
|
181
|
+
"signal": sig,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def calc_boll(closes, n=20, k=2):
|
|
186
|
+
"""计算布林带"""
|
|
187
|
+
if len(closes) < n:
|
|
188
|
+
return {"upper": None, "middle": None, "lower": None, "signal": "数据不足"}
|
|
189
|
+
|
|
190
|
+
recent = closes[-n:]
|
|
191
|
+
middle = sum(recent) / n
|
|
192
|
+
std = (sum((x - middle) ** 2 for x in recent) / n) ** 0.5
|
|
193
|
+
upper = middle + k * std
|
|
194
|
+
lower = middle - k * std
|
|
195
|
+
current = closes[-1]
|
|
196
|
+
|
|
197
|
+
# 位置判断
|
|
198
|
+
if current > upper:
|
|
199
|
+
pos = "上轨上方"
|
|
200
|
+
sig = "超买"
|
|
201
|
+
elif current > middle:
|
|
202
|
+
pos = "中轨上方"
|
|
203
|
+
sig = "中性偏强"
|
|
204
|
+
elif current > lower:
|
|
205
|
+
pos = "中轨下方"
|
|
206
|
+
sig = "中性偏弱"
|
|
207
|
+
else:
|
|
208
|
+
pos = "下轨下方"
|
|
209
|
+
sig = "超卖"
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
"upper": round(upper, 2),
|
|
213
|
+
"middle": round(middle, 2),
|
|
214
|
+
"lower": round(lower, 2),
|
|
215
|
+
"current": round(current, 2),
|
|
216
|
+
"current_position": pos,
|
|
217
|
+
"signal": sig,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def main():
|
|
222
|
+
parser = argparse.ArgumentParser(description='股票技术指标分析')
|
|
223
|
+
parser.add_argument('stock_code', help='股票代码')
|
|
224
|
+
parser.add_argument('--indicator', default='all',
|
|
225
|
+
choices=['ma', 'macd', 'rsi', 'kdj', 'boll', 'all'],
|
|
226
|
+
help='指标类型(默认all)')
|
|
227
|
+
parser.add_argument('--period', type=int, default=120, help='历史天数(默认120)')
|
|
228
|
+
args = parser.parse_args()
|
|
229
|
+
|
|
230
|
+
err = check_akshare()
|
|
231
|
+
if err:
|
|
232
|
+
output_error(err)
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
code = normalize_code(args.stock_code)
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
import akshare as ak
|
|
239
|
+
|
|
240
|
+
# 计算日期范围
|
|
241
|
+
end_date = datetime.now().strftime('%Y%m%d')
|
|
242
|
+
start_date = (datetime.now() - timedelta(days=args.period * 2)).strftime('%Y%m%d')
|
|
243
|
+
|
|
244
|
+
df = None
|
|
245
|
+
closes = []
|
|
246
|
+
highs = []
|
|
247
|
+
lows = []
|
|
248
|
+
latest_date = ''
|
|
249
|
+
|
|
250
|
+
# 港股:优先分钟线合成当日K线+新浪daily,失败回退东方财富hist
|
|
251
|
+
if is_hk_code(code):
|
|
252
|
+
# 1. 取新浪日线(T-1,稳定快速)
|
|
253
|
+
daily_df = None
|
|
254
|
+
try:
|
|
255
|
+
daily_df = ak.stock_hk_daily(symbol=code, adjust="qfq")
|
|
256
|
+
if not daily_df.empty:
|
|
257
|
+
daily_df = daily_df.tail(args.period * 2)
|
|
258
|
+
except Exception:
|
|
259
|
+
daily_df = None
|
|
260
|
+
|
|
261
|
+
# 2. 尝试分钟线合成当日K线(盘中实时)
|
|
262
|
+
intraday_appended = False
|
|
263
|
+
try:
|
|
264
|
+
min_df = ak.stock_hk_hist_min_em(symbol=code, period='1')
|
|
265
|
+
if not min_df.empty:
|
|
266
|
+
today_str = str(min_df.iloc[-1].get('时间', ''))[:10]
|
|
267
|
+
today_data = min_df[min_df['时间'].astype(str).str.startswith(today_str)]
|
|
268
|
+
if not today_data.empty and daily_df is not None:
|
|
269
|
+
last_daily_date = str(daily_df.iloc[-1].get('date', ''))
|
|
270
|
+
if today_str > last_daily_date:
|
|
271
|
+
# 当日数据比日线更新,合成当日K线追加
|
|
272
|
+
today_close = safe_float(today_data.iloc[-1].get('收盘'))
|
|
273
|
+
today_high = max(safe_float(r.get('最高')) for _, r in today_data.iterrows() if safe_float(r.get('最高')) is not None)
|
|
274
|
+
today_low = min(safe_float(r.get('最低')) for _, r in today_data.iterrows() if safe_float(r.get('最低')) is not None)
|
|
275
|
+
today_open = safe_float(today_data.iloc[0].get('开盘'))
|
|
276
|
+
if today_close is not None:
|
|
277
|
+
daily_df = daily_df._append({
|
|
278
|
+
'date': today_str, 'open': today_open,
|
|
279
|
+
'high': today_high, 'low': today_low,
|
|
280
|
+
'close': today_close, 'volume': 0, 'amount': 0,
|
|
281
|
+
}, ignore_index=True)
|
|
282
|
+
intraday_appended = True
|
|
283
|
+
except Exception:
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
if daily_df is not None and not daily_df.empty:
|
|
287
|
+
closes = [safe_float(x) for x in daily_df['close'].tolist() if safe_float(x) is not None]
|
|
288
|
+
highs = [safe_float(x) for x in daily_df['high'].tolist() if safe_float(x) is not None]
|
|
289
|
+
lows = [safe_float(x) for x in daily_df['low'].tolist() if safe_float(x) is not None]
|
|
290
|
+
latest_date = str(daily_df.iloc[-1].get('date', ''))
|
|
291
|
+
df = daily_df # 标记成功
|
|
292
|
+
|
|
293
|
+
# 3. 兜底:东方财富港股日线
|
|
294
|
+
if df is None or df.empty:
|
|
295
|
+
try:
|
|
296
|
+
df = ak.stock_hk_hist(
|
|
297
|
+
symbol=code,
|
|
298
|
+
period="daily",
|
|
299
|
+
start_date=start_date,
|
|
300
|
+
end_date=end_date,
|
|
301
|
+
adjust="qfq"
|
|
302
|
+
)
|
|
303
|
+
if not df.empty:
|
|
304
|
+
closes = [safe_float(x) for x in df['收盘'].tolist() if safe_float(x) is not None]
|
|
305
|
+
highs = [safe_float(x) for x in df['最高'].tolist() if safe_float(x) is not None]
|
|
306
|
+
lows = [safe_float(x) for x in df['最低'].tolist() if safe_float(x) is not None]
|
|
307
|
+
latest_date = str(df.iloc[-1].get('日期', ''))
|
|
308
|
+
except Exception:
|
|
309
|
+
df = None
|
|
310
|
+
|
|
311
|
+
if df is None or df.empty:
|
|
312
|
+
output_error(f"未找到港股 {code} 的历史数据,港股代码为5位数字(如 03888)")
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
else:
|
|
316
|
+
# A股:优先新浪(稳定),失败回退东方财富
|
|
317
|
+
try:
|
|
318
|
+
prefix = get_market_prefix(code)
|
|
319
|
+
sina_code = f"{prefix}{code}"
|
|
320
|
+
df = ak.stock_zh_a_daily(
|
|
321
|
+
symbol=sina_code,
|
|
322
|
+
start_date=start_date,
|
|
323
|
+
end_date=end_date,
|
|
324
|
+
adjust="qfq"
|
|
325
|
+
)
|
|
326
|
+
if not df.empty:
|
|
327
|
+
# 新浪列名: date/open/high/low/close/volume/amount
|
|
328
|
+
closes = [safe_float(x) for x in df['close'].tolist() if safe_float(x) is not None]
|
|
329
|
+
highs = [safe_float(x) for x in df['high'].tolist() if safe_float(x) is not None]
|
|
330
|
+
lows = [safe_float(x) for x in df['low'].tolist() if safe_float(x) is not None]
|
|
331
|
+
latest_date = str(df.iloc[-1].get('date', ''))
|
|
332
|
+
except Exception:
|
|
333
|
+
df = None
|
|
334
|
+
|
|
335
|
+
# 东方财富 fallback
|
|
336
|
+
if df is None or df.empty:
|
|
337
|
+
try:
|
|
338
|
+
df = ak.stock_zh_a_hist(
|
|
339
|
+
symbol=code,
|
|
340
|
+
period="daily",
|
|
341
|
+
start_date=start_date,
|
|
342
|
+
end_date=end_date,
|
|
343
|
+
adjust="qfq"
|
|
344
|
+
)
|
|
345
|
+
if not df.empty:
|
|
346
|
+
closes = [safe_float(x) for x in df['收盘'].tolist() if safe_float(x) is not None]
|
|
347
|
+
highs = [safe_float(x) for x in df['最高'].tolist() if safe_float(x) is not None]
|
|
348
|
+
lows = [safe_float(x) for x in df['最低'].tolist() if safe_float(x) is not None]
|
|
349
|
+
latest_date = str(df.iloc[-1].get('日期', ''))
|
|
350
|
+
except Exception:
|
|
351
|
+
df = None
|
|
352
|
+
|
|
353
|
+
if df is None or df.empty:
|
|
354
|
+
output_error(f"未找到股票 {code} 的历史数据")
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
indicators = {}
|
|
358
|
+
indicator = args.indicator
|
|
359
|
+
|
|
360
|
+
if indicator in ('ma', 'all'):
|
|
361
|
+
indicators["ma"] = calc_ma(closes)
|
|
362
|
+
if indicator in ('macd', 'all'):
|
|
363
|
+
indicators["macd"] = calc_macd(closes)
|
|
364
|
+
if indicator in ('rsi', 'all'):
|
|
365
|
+
indicators["rsi"] = calc_rsi(closes)
|
|
366
|
+
if indicator in ('kdj', 'all'):
|
|
367
|
+
indicators["kdj"] = calc_kdj(highs, lows, closes)
|
|
368
|
+
if indicator in ('boll', 'all'):
|
|
369
|
+
indicators["boll"] = calc_boll(closes)
|
|
370
|
+
|
|
371
|
+
data = {
|
|
372
|
+
"code": code,
|
|
373
|
+
"latest_date": latest_date,
|
|
374
|
+
"data_points": len(closes),
|
|
375
|
+
"data_time": latest_date + (" (盘中实时)" if intraday_appended else " (收盘)"),
|
|
376
|
+
"indicators": indicators,
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
# 时效性检查
|
|
380
|
+
if latest_date:
|
|
381
|
+
try:
|
|
382
|
+
from datetime import date as date_cls
|
|
383
|
+
today = date_cls.today()
|
|
384
|
+
data_date = date_cls.fromisoformat(latest_date[:10])
|
|
385
|
+
days_old = (today - data_date).days
|
|
386
|
+
if days_old > 0:
|
|
387
|
+
data["data_staleness"] = f"最新K线为{days_old}天前({latest_date})"
|
|
388
|
+
if not intraday_appended:
|
|
389
|
+
data["data_staleness"] += ",日线数据为T-1(最近交易日收盘)"
|
|
390
|
+
if days_old > 3:
|
|
391
|
+
data["data_staleness"] += ",数据可能过时,请确认是否为非交易日"
|
|
392
|
+
except (ValueError, TypeError):
|
|
393
|
+
pass
|
|
394
|
+
|
|
395
|
+
output_json(data)
|
|
396
|
+
|
|
397
|
+
except Exception as e:
|
|
398
|
+
output_error(f"获取技术指标失败: {str(e)}")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
if __name__ == '__main__':
|
|
402
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bm as ln,aj as an,X as y,be as tn,I as z,b9 as b,aJ as _,d as un,aP as rn,ba as F,n as o,G as O,k as sn,e as on,aI as fn}from"./index-BWshepxw.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,q,w,v,A,S,a){var D=q-l,i=w-h,n=S-v,d=a-A,u=d*D-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*D,h+u*i]}function Q(l,h,q,w,v,A,S){var a=l-q,D=h-w,i=(S?A:-A)/F(a*a+D*D),n=i*D,d=-i*a,u=l+n,s=h+d,f=q+n,c=w+d,X=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,E=(g<0?-1:1)*F(fn(0,T*T*R-P*P)),G=(P*g-m*E)/R,J=(-P*m-g*E)/R,I=(P*g+m*E)/R,p=(-P*m+g*E)/R,x=G-X,e=J-t,r=I-X,k=p-t;return x*x+e*e>r*r+k*k&&(G=I,J=p),{cx:G,cy:J,x01:-n,y01:-d,x11:G*(v/T-1),y11:J*(v/T-1)}}function hn(){var l=cn,h=yn,q=O(0),w=null,v=gn,A=dn,S=mn,a=null,D=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,X=un(c-f),t=c>f;if(a||(a=n=D()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(X>tn-y)a.moveTo(s*z(f),s*b(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*z(c),u*b(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=X,E=X,G=S.apply(this,arguments)/2,J=G>y&&(w?+w.apply(this,arguments):F(u*u+s*s)),I=_(un(s-u)/2,+q.apply(this,arguments)),p=I,x=I,e,r;if(J>y){var k=sn(J/u*b(G)),H=sn(J/s*b(G));(P-=k*2)>y?(k*=t?1:-1,R+=k,T-=k):(P=0,R=T=(f+c)/2),(E-=H*2)>y?(H*=t?1:-1,m+=H,g-=H):(E=0,m=g=(f+c)/2)}var B=s*z(m),C=s*b(m),K=u*z(T),L=u*b(T);if(I>y){var M=s*z(g),N=s*b(g),U=u*z(R),V=u*b(R),j;if(X<rn)if(j=pn(B,C,U,V,M,N,K,L)){var W=B-j[0],Y=C-j[1],Z=M-j[0],$=N-j[1],nn=1/b(on((W*Z+Y*$)/(F(W*W+Y*Y)*F(Z*Z+$*$)))/2),en=F(j[0]*j[0]+j[1]*j[1]);p=_(I,(u-en)/(nn-1)),x=_(I,(s-en)/(nn+1))}else p=x=0}E>y?x>y?(e=Q(U,V,B,C,s,x,t),r=Q(M,N,K,L,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<I?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(B,C),a.arc(0,0,s,m,g,!t)):a.moveTo(B,C),!(u>y)||!(P>y)?a.lineTo(K,L):p>y?(e=Q(K,L,M,N,u,-p,t),r=Q(B,C,U,V,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<I?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[z(d)*n,b(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:O(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:O(+n),i):h},i.cornerRadius=function(n){return arguments.length?(q=typeof n=="function"?n:O(+n),i):q},i.padRadius=function(n){return arguments.length?(w=n==null?null:typeof n=="function"?n:O(+n),i):w},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:O(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:O(+n),i):A},i.padAngle=function(n){return arguments.length?(S=typeof n=="function"?n:O(+n),i):S},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
|