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.
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/PKG-INFO +49 -1
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/README.md +48 -0
- dragon_quant-0.2.4/dragon_quant/_version.py +1 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/cli.py +59 -0
- dragon_quant-0.2.4/dragon_quant/config/__init__.py +4 -0
- dragon_quant-0.2.4/dragon_quant/config/api_config.py +173 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/data.py +12 -2
- dragon_quant-0.2.4/dragon_quant/fix_api.py +411 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/logging/reporter.py +42 -14
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/base.py +3 -1
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/tencent.py +3 -1
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/xueqiu.py +3 -1
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/rate_limit.py +12 -7
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/review.py +34 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/scorers/absorption.py +182 -43
- dragon_quant-0.2.4/dragon_quant/scorers/anti_drop.py +408 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/scorers/drive.py +58 -18
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/storage/db.py +359 -21
- dragon_quant-0.2.4/dragon_quant/vpa/__init__.py +14 -0
- dragon_quant-0.2.4/dragon_quant/vpa/engine.py +111 -0
- dragon_quant-0.2.4/dragon_quant/vpa/factors/__init__.py +23 -0
- dragon_quant-0.2.4/dragon_quant/vpa/factors/base.py +51 -0
- dragon_quant-0.2.4/dragon_quant/vpa/factors/breakout.py +72 -0
- dragon_quant-0.2.4/dragon_quant/vpa/factors/divergence.py +72 -0
- dragon_quant-0.2.4/dragon_quant/vpa/factors/trend_verify.py +85 -0
- dragon_quant-0.2.4/dragon_quant/vpa/factors/vol_amount.py +83 -0
- dragon_quant-0.2.4/dragon_quant/vpa/report.py +85 -0
- dragon_quant-0.2.4/dragon_quant/vpa/types.py +59 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/PKG-INFO +49 -1
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/SOURCES.txt +15 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/pyproject.toml +1 -1
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_logging.py +70 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_providers_parsing.py +26 -1
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_rate_limit.py +20 -1
- dragon_quant-0.2.4/tests/test_scorers_absorption.py +201 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_scorers_anti_drop.py +102 -3
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_scorers_drive.py +70 -2
- dragon_quant-0.2.4/tests/test_storage.py +286 -0
- dragon_quant-0.2.4/tests/test_vpa.py +126 -0
- dragon_quant-0.2.4/web_ui/index.html +426 -0
- dragon_quant-0.2.2/dragon_quant/_version.py +0 -1
- dragon_quant-0.2.2/dragon_quant/scorers/anti_drop.py +0 -206
- dragon_quant-0.2.2/tests/test_scorers_absorption.py +0 -50
- dragon_quant-0.2.2/tests/test_storage.py +0 -125
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/LICENSE +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/__init__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/__main__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/analyze.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/cache/__init__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/cache/data_cache.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/logging/__init__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/logging/logger.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/logging/query.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/models/__init__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/models/types.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/orchestrator.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/__init__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/browser.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/cookie.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/providers/eastmoney.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/scorers/__init__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/scorers/leadership.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/storage/__init__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/storage/manager.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/storage/paths.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/utils/__init__.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant/utils/trading.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/dependency_links.txt +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/entry_points.txt +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/requires.txt +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/dragon_quant.egg-info/top_level.txt +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/setup.cfg +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_cache.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_models.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_orchestrator_helpers.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_providers_cookie.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_review.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/tests/test_scorers_leadership.py +0 -0
- {dragon_quant-0.2.2 → dragon_quant-0.2.4}/web_ui/__init__.py +0 -0
- {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.
|
|
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,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
|
|
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(
|
|
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]:
|