dragon-quant 0.2.2__tar.gz → 0.2.4__tar.gz

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 (80) hide show
  1. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/PKG-INFO +49 -1
  2. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/README.md +48 -0
  3. dragon_quant-0.2.4/dragon_quant/_version.py +1 -0
  4. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/cli.py +59 -0
  5. dragon_quant-0.2.4/dragon_quant/config/__init__.py +4 -0
  6. dragon_quant-0.2.4/dragon_quant/config/api_config.py +173 -0
  7. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/data.py +12 -2
  8. dragon_quant-0.2.4/dragon_quant/fix_api.py +411 -0
  9. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/logging/reporter.py +42 -14
  10. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/base.py +3 -1
  11. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/tencent.py +3 -1
  12. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/xueqiu.py +3 -1
  13. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/rate_limit.py +12 -7
  14. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/review.py +34 -0
  15. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/scorers/absorption.py +182 -43
  16. dragon_quant-0.2.4/dragon_quant/scorers/anti_drop.py +408 -0
  17. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/scorers/drive.py +58 -18
  18. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/storage/db.py +359 -21
  19. dragon_quant-0.2.4/dragon_quant/vpa/__init__.py +14 -0
  20. dragon_quant-0.2.4/dragon_quant/vpa/engine.py +111 -0
  21. dragon_quant-0.2.4/dragon_quant/vpa/factors/__init__.py +23 -0
  22. dragon_quant-0.2.4/dragon_quant/vpa/factors/base.py +51 -0
  23. dragon_quant-0.2.4/dragon_quant/vpa/factors/breakout.py +72 -0
  24. dragon_quant-0.2.4/dragon_quant/vpa/factors/divergence.py +72 -0
  25. dragon_quant-0.2.4/dragon_quant/vpa/factors/trend_verify.py +85 -0
  26. dragon_quant-0.2.4/dragon_quant/vpa/factors/vol_amount.py +83 -0
  27. dragon_quant-0.2.4/dragon_quant/vpa/report.py +85 -0
  28. dragon_quant-0.2.4/dragon_quant/vpa/types.py +59 -0
  29. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/PKG-INFO +49 -1
  30. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/SOURCES.txt +15 -0
  31. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/pyproject.toml +1 -1
  32. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_logging.py +70 -0
  33. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_providers_parsing.py +26 -1
  34. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_rate_limit.py +20 -1
  35. dragon_quant-0.2.4/tests/test_scorers_absorption.py +201 -0
  36. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_scorers_anti_drop.py +102 -3
  37. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_scorers_drive.py +70 -2
  38. dragon_quant-0.2.4/tests/test_storage.py +286 -0
  39. dragon_quant-0.2.4/tests/test_vpa.py +126 -0
  40. dragon_quant-0.2.4/web_ui/index.html +426 -0
  41. dragon_quant-0.2.2/dragon_quant/_version.py +0 -1
  42. dragon_quant-0.2.2/dragon_quant/scorers/anti_drop.py +0 -206
  43. dragon_quant-0.2.2/tests/test_scorers_absorption.py +0 -50
  44. dragon_quant-0.2.2/tests/test_storage.py +0 -125
  45. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/LICENSE +0 -0
  46. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/__init__.py +0 -0
  47. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/__main__.py +0 -0
  48. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/analyze.py +0 -0
  49. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/cache/__init__.py +0 -0
  50. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/cache/data_cache.py +0 -0
  51. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/logging/__init__.py +0 -0
  52. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/logging/logger.py +0 -0
  53. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/logging/query.py +0 -0
  54. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/models/__init__.py +0 -0
  55. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/models/types.py +0 -0
  56. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/orchestrator.py +0 -0
  57. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/__init__.py +0 -0
  58. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/browser.py +0 -0
  59. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/cookie.py +0 -0
  60. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/eastmoney.py +0 -0
  61. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/scorers/__init__.py +0 -0
  62. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/scorers/leadership.py +0 -0
  63. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/storage/__init__.py +0 -0
  64. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/storage/manager.py +0 -0
  65. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/storage/paths.py +0 -0
  66. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/utils/__init__.py +0 -0
  67. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/utils/trading.py +0 -0
  68. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/dependency_links.txt +0 -0
  69. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/entry_points.txt +0 -0
  70. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/requires.txt +0 -0
  71. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/top_level.txt +0 -0
  72. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/setup.cfg +0 -0
  73. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_cache.py +0 -0
  74. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_models.py +0 -0
  75. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_orchestrator_helpers.py +0 -0
  76. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_providers_cookie.py +0 -0
  77. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_review.py +0 -0
  78. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_scorers_leadership.py +0 -0
  79. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/web_ui/__init__.py +0 -0
  80. {dragon_quant-0.2.2 → dragon_quant-0.2.4}/web_ui/server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dragon-quant
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: 龙头战法四维量化筛选系统 — A股涨停板龙头识别工具
5
5
  Author: gitBingxu
6
6
  License-Expression: MIT
@@ -138,6 +138,25 @@ dragon-quant data batch-quote --codes 600172,000001,002409
138
138
  dragon-quant data cookie-status # 查看 Cookie 状态
139
139
  dragon-quant data cookie-fetch # 刷新全部 Cookie
140
140
  dragon-quant data cookie-fetch --source xueqiu # 只刷新雪球
141
+
142
+ # 手动设置 Cookie(推荐兜底方案)
143
+ # 适用场景:Playwright 自动获取失败/被风控;或你已经在浏览器里抓到了可用 Cookie。
144
+ #
145
+ # 1) 从浏览器开发者工具(Network)里复制请求头中的 Cookie(整段)
146
+ # 2) 写入本地(注意:Cookie 属于敏感信息,请勿提交到仓库/群聊)
147
+
148
+ # 设置东财 Cookie
149
+ python3 -m dragon_quant.providers.cookie set --source em --cookie 'qgqp_b_id=...; st_nvi=...; nid18=...'
150
+
151
+ # 设置雪球 Cookie
152
+ python3 -m dragon_quant.providers.cookie set --source xq --cookie 'xq_a_token=...; xq_is_login=1; u=...'
153
+
154
+ # 查看是否生效
155
+ python3 -m dragon_quant.providers.cookie status --show
156
+
157
+ # Cookie 文件默认位置(macOS)
158
+ # ~/Library/Application Support/dragon-quant/cookies/eastmoney
159
+ # ~/Library/Application Support/dragon-quant/cookies/xueqiu
141
160
  ```
142
161
 
143
162
  ### `review` — 龙头回测
@@ -161,6 +180,30 @@ dragon-quant review --ui-only --port 8765
161
180
 
162
181
  从 `dragons` 表中读取 pending 龙头 → 自动过滤入选日距今约 5~20 个交易日的记录 → 拉取日K → 找入选后第一个非一字板日(`high != low`)以最低价买入 → 用收益观察窗口计算 `max_return_5d` 与 `max_return_hold_days` → 再按“买入日至最大收益出现日”窗口计算 `max_drawdown_5d` → 写入 DB。每条 dragon 记录入库时的 `dragon_quant` 版本号会写入 `version` 字段,方便按版本回溯策略效果。
163
182
 
183
+ 回测时会对每只 pending 个股自动追加一段**量价分析**(见下方 `vpa`),结论同步写入 `vpa_analysis` 表。
184
+
185
+ ### `vpa` — 量价分析
186
+
187
+ ```bash
188
+ # 对单只个股做量价分析(默认写入 vpa_analysis 表)
189
+ dragon-quant vpa --code 600519
190
+
191
+ # 指定数据源与拉取根数
192
+ dragon-quant vpa --code 600519 --source xueqiu --days 60
193
+
194
+ # 仅输出不写库
195
+ dragon-quant vpa --code 600519 --no-save
196
+ ```
197
+
198
+ | 参数 | 默认 | 说明 |
199
+ |---|---|---|
200
+ | `--code` | (必填)| 股票代码,如 600519 |
201
+ | `--source` | xueqiu | 数据源 xueqiu / tencent |
202
+ | `--days` | 60 | 拉取日 K 线根数 |
203
+ | `--no-save` | - | 不写入数据库 |
204
+
205
+ 独立于四维评分体系与编排器的量价分析模块,基于「多空博弈 + 量能验证」原则,对个股做量价健康度验证。当前内置 4 个因子:**量额灵敏度**(高位看额、低位看量)、**趋势量价验证**(涨放量/调缩量)、**突破放量验证**、**量价背离**(缩量新高=动能衰竭)。输出量价健康度(0-100)+ 偏多/中性/偏空信号 + 每个因子的判断依据,定位为「验证器」而非买卖指令。因子以插件式注册表组织,新增因子无需改动引擎/CLI/review。
206
+
164
207
  ### `storage` — 数据管理
165
208
 
166
209
  ```bash
@@ -344,6 +387,11 @@ dragon_quant/
344
387
  │ ├── anti_drop.py # 抗跌性
345
388
  │ ├── leadership.py # 领涨性
346
389
  │ └── absorption.py # 资金承接
390
+ ├── vpa/ # 量价分析(独立模块,插件式因子)
391
+ │ ├── engine.py # analyze() 编排
392
+ │ ├── report.py # 报告渲染
393
+ │ ├── types.py # FactorResult / VPAReport
394
+ │ └── factors/ # 量价因子(量额/趋势/突破/背离)
347
395
  ├── models/
348
396
  │ └── types.py # 数据模型(KBar, Quote, ScoreResult...)
349
397
  ├── cache/
@@ -119,6 +119,25 @@ dragon-quant data batch-quote --codes 600172,000001,002409
119
119
  dragon-quant data cookie-status # 查看 Cookie 状态
120
120
  dragon-quant data cookie-fetch # 刷新全部 Cookie
121
121
  dragon-quant data cookie-fetch --source xueqiu # 只刷新雪球
122
+
123
+ # 手动设置 Cookie(推荐兜底方案)
124
+ # 适用场景:Playwright 自动获取失败/被风控;或你已经在浏览器里抓到了可用 Cookie。
125
+ #
126
+ # 1) 从浏览器开发者工具(Network)里复制请求头中的 Cookie(整段)
127
+ # 2) 写入本地(注意:Cookie 属于敏感信息,请勿提交到仓库/群聊)
128
+
129
+ # 设置东财 Cookie
130
+ python3 -m dragon_quant.providers.cookie set --source em --cookie 'qgqp_b_id=...; st_nvi=...; nid18=...'
131
+
132
+ # 设置雪球 Cookie
133
+ python3 -m dragon_quant.providers.cookie set --source xq --cookie 'xq_a_token=...; xq_is_login=1; u=...'
134
+
135
+ # 查看是否生效
136
+ python3 -m dragon_quant.providers.cookie status --show
137
+
138
+ # Cookie 文件默认位置(macOS)
139
+ # ~/Library/Application Support/dragon-quant/cookies/eastmoney
140
+ # ~/Library/Application Support/dragon-quant/cookies/xueqiu
122
141
  ```
123
142
 
124
143
  ### `review` — 龙头回测
@@ -142,6 +161,30 @@ dragon-quant review --ui-only --port 8765
142
161
 
143
162
  从 `dragons` 表中读取 pending 龙头 → 自动过滤入选日距今约 5~20 个交易日的记录 → 拉取日K → 找入选后第一个非一字板日(`high != low`)以最低价买入 → 用收益观察窗口计算 `max_return_5d` 与 `max_return_hold_days` → 再按“买入日至最大收益出现日”窗口计算 `max_drawdown_5d` → 写入 DB。每条 dragon 记录入库时的 `dragon_quant` 版本号会写入 `version` 字段,方便按版本回溯策略效果。
144
163
 
164
+ 回测时会对每只 pending 个股自动追加一段**量价分析**(见下方 `vpa`),结论同步写入 `vpa_analysis` 表。
165
+
166
+ ### `vpa` — 量价分析
167
+
168
+ ```bash
169
+ # 对单只个股做量价分析(默认写入 vpa_analysis 表)
170
+ dragon-quant vpa --code 600519
171
+
172
+ # 指定数据源与拉取根数
173
+ dragon-quant vpa --code 600519 --source xueqiu --days 60
174
+
175
+ # 仅输出不写库
176
+ dragon-quant vpa --code 600519 --no-save
177
+ ```
178
+
179
+ | 参数 | 默认 | 说明 |
180
+ |---|---|---|
181
+ | `--code` | (必填)| 股票代码,如 600519 |
182
+ | `--source` | xueqiu | 数据源 xueqiu / tencent |
183
+ | `--days` | 60 | 拉取日 K 线根数 |
184
+ | `--no-save` | - | 不写入数据库 |
185
+
186
+ 独立于四维评分体系与编排器的量价分析模块,基于「多空博弈 + 量能验证」原则,对个股做量价健康度验证。当前内置 4 个因子:**量额灵敏度**(高位看额、低位看量)、**趋势量价验证**(涨放量/调缩量)、**突破放量验证**、**量价背离**(缩量新高=动能衰竭)。输出量价健康度(0-100)+ 偏多/中性/偏空信号 + 每个因子的判断依据,定位为「验证器」而非买卖指令。因子以插件式注册表组织,新增因子无需改动引擎/CLI/review。
187
+
145
188
  ### `storage` — 数据管理
146
189
 
147
190
  ```bash
@@ -325,6 +368,11 @@ dragon_quant/
325
368
  │ ├── anti_drop.py # 抗跌性
326
369
  │ ├── leadership.py # 领涨性
327
370
  │ └── absorption.py # 资金承接
371
+ ├── vpa/ # 量价分析(独立模块,插件式因子)
372
+ │ ├── engine.py # analyze() 编排
373
+ │ ├── report.py # 报告渲染
374
+ │ ├── types.py # FactorResult / VPAReport
375
+ │ └── factors/ # 量价因子(量额/趋势/突破/背离)
328
376
  ├── models/
329
377
  │ └── types.py # 数据模型(KBar, Quote, ScoreResult...)
330
378
  ├── cache/
@@ -0,0 +1 @@
1
+ __version__ = "0.2.4"
@@ -7,6 +7,7 @@ CLI 入口 — dragon-quant 命令行工具
7
7
  dragon-quant data {sector,components,kline,minute,quote,batch-quote} [options]
8
8
  dragon-quant review [--date DATE] [--top N] [--force]
9
9
  dragon-quant storage {status,size,clear} [options]
10
+ dragon-quant fix-api [--provider eastmoney] [--show-browser]
10
11
  """
11
12
 
12
13
  import argparse
@@ -222,6 +223,46 @@ def _cmd_review_ui(args):
222
223
  start_server(port=args.port, open_browser=not args.no_browser)
223
224
 
224
225
 
226
+ def _cmd_vpa(args):
227
+ """个股量价分析命令"""
228
+ import json
229
+ from datetime import datetime
230
+ from dragon_quant.vpa import analyze
231
+ from dragon_quant.vpa.report import render
232
+ from dragon_quant._version import __version__
233
+
234
+ report = analyze(args.code, source=args.source, days=args.days)
235
+ print(render(report))
236
+
237
+ if not args.no_save and not report.fallback:
238
+ from dragon_quant.storage import db
239
+ factors = [
240
+ {"name": f.name, "title": f.title, "signal": f.signal,
241
+ "score": f.score, "note": f.note,
242
+ "evidence": f.evidence, "details": f.details}
243
+ for f in report.factors
244
+ ]
245
+ try:
246
+ db.upsert_vpa(
247
+ trade_date=datetime.now().strftime("%Y-%m-%d"),
248
+ code=report.code,
249
+ source=report.source,
250
+ health_score=report.health_score,
251
+ signal=report.signal,
252
+ summary=report.summary,
253
+ factors_json=json.dumps(factors, ensure_ascii=False),
254
+ version=__version__,
255
+ )
256
+ except Exception as ex:
257
+ print(f"⚠️ 写入数据库失败: {ex}", file=sys.stderr)
258
+
259
+
260
+ def _cmd_fix_api(args):
261
+ """修复 API 配置命令"""
262
+ from dragon_quant.fix_api import fix_api
263
+ fix_api(provider=args.fix_provider, headless=not args.show_browser)
264
+
265
+
225
266
  def _cmd_storage(args):
226
267
  """存储管理命令"""
227
268
  mgr = StorageManager()
@@ -374,6 +415,13 @@ def main():
374
415
  rev_p.add_argument("--port", type=int, default=8765, help="Web UI 端口 (默认 8765)")
375
416
  rev_p.add_argument("--no-browser", action="store_true", help="不自动打开浏览器")
376
417
 
418
+ # vpa 子命令
419
+ vpa_p = sub.add_parser("vpa", help="个股量价分析")
420
+ vpa_p.add_argument("--code", required=True, help="股票代码,如 600519")
421
+ vpa_p.add_argument("--source", default="xueqiu", choices=["xueqiu", "tencent"])
422
+ vpa_p.add_argument("--days", type=int, default=60, help="拉取日K线根数 (默认60)")
423
+ vpa_p.add_argument("--no-save", action="store_true", help="不写入数据库")
424
+
377
425
  # storage 子命令
378
426
  st_p = sub.add_parser("storage", help="持久化数据管理")
379
427
  st_subs = st_p.add_subparsers(dest="storage_action")
@@ -388,6 +436,13 @@ def main():
388
436
  clear_p.add_argument("--logs", action="store_true", help="清理日志")
389
437
  clear_p.add_argument("--days", type=int, default=None, help="保留最近N天")
390
438
 
439
+ # fix-api 子命令
440
+ fix_p = sub.add_parser("fix-api", help="从目标网站自动捕获并修复 API 配置")
441
+ fix_p.add_argument("--provider", dest="fix_provider", default="eastmoney",
442
+ choices=["eastmoney"], help="要修复的 provider (默认: eastmoney)")
443
+ fix_p.add_argument("--show-browser", action="store_true",
444
+ help="显示浏览器窗口 (默认无头运行)")
445
+
391
446
  args = parser.parse_args()
392
447
 
393
448
  if args.command == "scan":
@@ -400,6 +455,10 @@ def main():
400
455
  _cmd_storage(args)
401
456
  elif args.command == "review":
402
457
  _cmd_review(args)
458
+ elif args.command == "vpa":
459
+ _cmd_vpa(args)
460
+ elif args.command == "fix-api":
461
+ _cmd_fix_api(args)
403
462
 
404
463
  else:
405
464
  parser.print_help()
@@ -0,0 +1,4 @@
1
+ """
2
+ 配置管理 — API 请求模板持久化与回退
3
+ """
4
+
@@ -0,0 +1,173 @@
1
+ """
2
+ API 请求模板 — 持久化配置管理
3
+
4
+ 每个 provider 下的每个 endpoint 对应一个 RequestTemplate,
5
+ 封装请求 URL 拼接和 Header 构建。
6
+
7
+ 配置存储路径: {DATA_DIR}/config/api_config.json
8
+ """
9
+
10
+ import json
11
+ import os
12
+ import threading
13
+ from dataclasses import dataclass, field
14
+ from typing import Optional
15
+
16
+ import urllib.parse
17
+ import time
18
+
19
+ from dragon_quant.storage.paths import DATA_DIR
20
+
21
+ CONFIG_DIR = DATA_DIR / "config"
22
+ CONFIG_PATH = CONFIG_DIR / "api_config.json"
23
+
24
+ CONFIG_VERSION = "1"
25
+
26
+
27
+ @dataclass
28
+ class RequestTemplate:
29
+ base: str
30
+ path: str
31
+ fixed_params: dict = field(default_factory=dict)
32
+ headers: dict = field(default_factory=dict)
33
+ referer: str = ""
34
+
35
+ def build_url(self, **dynamic_params) -> str:
36
+ params = {**self.fixed_params, **dynamic_params}
37
+ if "cb" not in params:
38
+ # 尽量对齐东财前端 JSONP callback 命名,降低风控概率
39
+ # 形如:jQuery112307663912278618489_1780992936600
40
+ # 注:callback 名称不会影响我们 _parse_jsonp 的解析。
41
+ ts = int(time.time() * 1000)
42
+ pseudo_rand = int(str(ts)[-6:]) * 123457 # 仅用于仿真格式,无安全意义
43
+ params["cb"] = f"jQuery{pseudo_rand}_{ts}"
44
+ if "_" not in params:
45
+ params["_"] = str(int(time.time() * 1000))
46
+ qs = urllib.parse.urlencode(params)
47
+ return f"{self.base}{self.path}?{qs}"
48
+
49
+ def build_headers(self, referer: str = "", cookie: str = "") -> dict:
50
+ headers = {}
51
+ if self.headers:
52
+ headers.update(self.headers)
53
+ if referer or self.referer:
54
+ headers["Referer"] = referer or self.referer
55
+ if cookie:
56
+ headers["Cookie"] = cookie
57
+ return headers
58
+
59
+
60
+ def _ensure_config_dir():
61
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
62
+
63
+
64
+ def load_templates(provider: str) -> Optional[dict[str, RequestTemplate]]:
65
+ if not CONFIG_PATH.exists():
66
+ return None
67
+ try:
68
+ with open(CONFIG_PATH) as f:
69
+ data = json.load(f)
70
+ provider_data = data.get(provider, {})
71
+ if not provider_data:
72
+ return None
73
+ templates = {}
74
+ for ep_name, ep_cfg in provider_data.items():
75
+ templates[ep_name] = RequestTemplate(
76
+ base=ep_cfg["base"],
77
+ path=ep_cfg["path"],
78
+ fixed_params=ep_cfg.get("fixed_params", {}),
79
+ headers=ep_cfg.get("headers", {}),
80
+ referer=ep_cfg.get("referer", ""),
81
+ )
82
+ return templates
83
+ except Exception:
84
+ return None
85
+
86
+
87
+ def save_templates(provider: str, templates: dict[str, RequestTemplate]):
88
+ _ensure_config_dir()
89
+ existing = {}
90
+ if CONFIG_PATH.exists():
91
+ try:
92
+ with open(CONFIG_PATH) as f:
93
+ existing = json.load(f)
94
+ except Exception:
95
+ existing = {}
96
+ existing.setdefault("version", CONFIG_VERSION)
97
+ provider_data = {}
98
+ for name, tpl in templates.items():
99
+ provider_data[name] = {
100
+ "base": tpl.base,
101
+ "path": tpl.path,
102
+ "fixed_params": tpl.fixed_params,
103
+ "headers": tpl.headers,
104
+ "referer": tpl.referer,
105
+ }
106
+ existing[provider] = provider_data
107
+ tmp_path = CONFIG_PATH.with_suffix(".tmp")
108
+ with open(tmp_path, "w") as f:
109
+ json.dump(existing, f, ensure_ascii=False, indent=2)
110
+ os.replace(tmp_path, CONFIG_PATH)
111
+
112
+
113
+ def save_single_template(provider: str, endpoint: str, tpl: RequestTemplate):
114
+ existing = {}
115
+ if CONFIG_PATH.exists():
116
+ try:
117
+ with open(CONFIG_PATH) as f:
118
+ existing = json.load(f)
119
+ except Exception:
120
+ existing = {}
121
+ existing.setdefault("version", CONFIG_VERSION)
122
+ existing.setdefault(provider, {})
123
+ existing[provider][endpoint] = {
124
+ "base": tpl.base,
125
+ "path": tpl.path,
126
+ "fixed_params": tpl.fixed_params,
127
+ "headers": tpl.headers,
128
+ "referer": tpl.referer,
129
+ }
130
+ _ensure_config_dir()
131
+ tmp_path = CONFIG_PATH.with_suffix(".tmp")
132
+ with open(tmp_path, "w") as f:
133
+ json.dump(existing, f, ensure_ascii=False, indent=2)
134
+ os.replace(tmp_path, CONFIG_PATH)
135
+
136
+
137
+ def hardcoded_templates(provider: str) -> dict[str, RequestTemplate]:
138
+ if provider == "eastmoney":
139
+ return {
140
+ "sector_ranking": RequestTemplate(
141
+ base="https://push2.eastmoney.com",
142
+ path="/webguest/api/qt/clist/get",
143
+ fixed_params={
144
+ "np": "1", "fltt": "1", "invt": "2",
145
+ "timil": "1", "dect": "1", "wbp2u": "|0|0|0|web",
146
+ },
147
+ headers={},
148
+ referer="https://quote.eastmoney.com/center/hsbk.html",
149
+ ),
150
+ "sector_components": RequestTemplate(
151
+ base="https://push2.eastmoney.com",
152
+ path="/api/qt/clist/get",
153
+ fixed_params={
154
+ "np": "1", "fltt": "2", "invt": "2",
155
+ "dect": "1",
156
+ },
157
+ headers={},
158
+ referer="https://quote.eastmoney.com/center/gridlist.html",
159
+ ),
160
+ "sector_5min_kline": RequestTemplate(
161
+ base="https://push2his.eastmoney.com",
162
+ path="/api/qt/stock/kline/get",
163
+ fixed_params={
164
+ "fqt": "1",
165
+ "beg": "0",
166
+ "end": "20500101",
167
+ "smplmt": "460",
168
+ },
169
+ headers={},
170
+ referer="https://quote.eastmoney.com/bk/90.{code}.html",
171
+ ),
172
+ }
173
+ return {}
@@ -81,17 +81,27 @@ def get_sector_ranking(asc: bool = False) -> list[SectorPerformance]:
81
81
  return em.get_sector_ranking(asc=asc)
82
82
 
83
83
 
84
- def get_sector_components(sector_code: str) -> list[StockInfo]:
84
+ def get_sector_components(sector_code: str, page: int = 1,
85
+ all_pages: bool = False,
86
+ page_size: int = 50) -> list[StockInfo]:
85
87
  """获取概念板块成分股列表(按涨跌幅降序)
86
88
 
87
89
  Args:
88
90
  sector_code: 板块代码,如 "BK0487"
91
+ page: 页码(默认第一页)
92
+ all_pages: 是否自动拉取全量分页
93
+ page_size: 每页大小
89
94
  Returns:
90
95
  list[StockInfo] 成分股列表
91
96
  """
92
97
  providers = _get_providers()
93
98
  em = providers["eastmoney"]
94
- return em.get_sector_components(sector_code)
99
+ return em.get_sector_components(
100
+ sector_code,
101
+ page=page,
102
+ all_pages=all_pages,
103
+ page_size=page_size,
104
+ )
95
105
 
96
106
 
97
107
  def get_sector_5min_kline(sector_code: str, bars: int = 100) -> list[KBar]: