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,135 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"updated": "2026-06-05T16:07:35",
|
|
4
|
+
"source": "eastmoney_push2",
|
|
5
|
+
"total_sectors": 14,
|
|
6
|
+
"total_stocks": 99
|
|
7
|
+
},
|
|
8
|
+
"金融": [
|
|
9
|
+
"sh601398",
|
|
10
|
+
"sh601288",
|
|
11
|
+
"sh601939",
|
|
12
|
+
"sh601988",
|
|
13
|
+
"sh600036",
|
|
14
|
+
"sh601166",
|
|
15
|
+
"sh601328",
|
|
16
|
+
"sh601318",
|
|
17
|
+
"sh600016",
|
|
18
|
+
"sh600000",
|
|
19
|
+
"sh601169",
|
|
20
|
+
"sh601688",
|
|
21
|
+
"sh600030",
|
|
22
|
+
"sh601881",
|
|
23
|
+
"sh601601"
|
|
24
|
+
],
|
|
25
|
+
"消费": [
|
|
26
|
+
"sh600519",
|
|
27
|
+
"sz000858",
|
|
28
|
+
"sh600809",
|
|
29
|
+
"sz000568",
|
|
30
|
+
"sz002304",
|
|
31
|
+
"sh600887",
|
|
32
|
+
"sz002714",
|
|
33
|
+
"sh603288",
|
|
34
|
+
"sz000661"
|
|
35
|
+
],
|
|
36
|
+
"医药": [
|
|
37
|
+
"sz300760",
|
|
38
|
+
"sh600436",
|
|
39
|
+
"sz300015",
|
|
40
|
+
"sh600276",
|
|
41
|
+
"sh603259",
|
|
42
|
+
"sz300122",
|
|
43
|
+
"sz000538",
|
|
44
|
+
"sh600196",
|
|
45
|
+
"sz002007",
|
|
46
|
+
"sh688185"
|
|
47
|
+
],
|
|
48
|
+
"资源": [
|
|
49
|
+
"sz000807",
|
|
50
|
+
"sh603993",
|
|
51
|
+
"sh603799",
|
|
52
|
+
"sh600989",
|
|
53
|
+
"sh601088",
|
|
54
|
+
"sh601899",
|
|
55
|
+
"sh600489"
|
|
56
|
+
],
|
|
57
|
+
"电力": [
|
|
58
|
+
"sh600900",
|
|
59
|
+
"sh600025",
|
|
60
|
+
"sh600886",
|
|
61
|
+
"sh601985",
|
|
62
|
+
"sh600795",
|
|
63
|
+
"sh600011",
|
|
64
|
+
"sh600027"
|
|
65
|
+
],
|
|
66
|
+
"石化": [
|
|
67
|
+
"sh600028",
|
|
68
|
+
"sh601857",
|
|
69
|
+
"sh600346"
|
|
70
|
+
],
|
|
71
|
+
"高股息": [
|
|
72
|
+
"sh601398",
|
|
73
|
+
"sh600900",
|
|
74
|
+
"sh600028",
|
|
75
|
+
"sh601857",
|
|
76
|
+
"sh601088",
|
|
77
|
+
"sh601328",
|
|
78
|
+
"sz000807",
|
|
79
|
+
"sh600886"
|
|
80
|
+
],
|
|
81
|
+
"科技": [
|
|
82
|
+
"sz002415",
|
|
83
|
+
"sh603986",
|
|
84
|
+
"sh688981",
|
|
85
|
+
"sh600588",
|
|
86
|
+
"sz300496",
|
|
87
|
+
"sz002236"
|
|
88
|
+
],
|
|
89
|
+
"机器人": [
|
|
90
|
+
"sh688017",
|
|
91
|
+
"sz300124",
|
|
92
|
+
"sz002747",
|
|
93
|
+
"sz300503",
|
|
94
|
+
"sz002472",
|
|
95
|
+
"sh688698",
|
|
96
|
+
"sz002527",
|
|
97
|
+
"sz300024"
|
|
98
|
+
],
|
|
99
|
+
"PCB/AI算力": [
|
|
100
|
+
"sz300476",
|
|
101
|
+
"sz002463",
|
|
102
|
+
"sz002916",
|
|
103
|
+
"sh603228",
|
|
104
|
+
"sz002436"
|
|
105
|
+
],
|
|
106
|
+
"军工": [
|
|
107
|
+
"sh600760",
|
|
108
|
+
"sz000768",
|
|
109
|
+
"sh600893",
|
|
110
|
+
"sh600150",
|
|
111
|
+
"sh601989",
|
|
112
|
+
"sh600862"
|
|
113
|
+
],
|
|
114
|
+
"半导体": [
|
|
115
|
+
"sz002371",
|
|
116
|
+
"sh603501",
|
|
117
|
+
"sh688012",
|
|
118
|
+
"sz300661",
|
|
119
|
+
"sh688008",
|
|
120
|
+
"sz002049"
|
|
121
|
+
],
|
|
122
|
+
"新能源": [
|
|
123
|
+
"sz300750",
|
|
124
|
+
"sz300014",
|
|
125
|
+
"sz300438",
|
|
126
|
+
"sz300274",
|
|
127
|
+
"sz002459"
|
|
128
|
+
],
|
|
129
|
+
"光伏": [
|
|
130
|
+
"sh601012",
|
|
131
|
+
"sh600438",
|
|
132
|
+
"sz002459",
|
|
133
|
+
"sh688599"
|
|
134
|
+
]
|
|
135
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""统一数据类型定义。"""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Quote:
|
|
7
|
+
"""统一行情数据结构。"""
|
|
8
|
+
code: str = ""
|
|
9
|
+
name: str = ""
|
|
10
|
+
price: float = 0.0
|
|
11
|
+
prev_close: float = 0.0
|
|
12
|
+
open: float = 0.0
|
|
13
|
+
high: float = 0.0
|
|
14
|
+
low: float = 0.0
|
|
15
|
+
change_pct: float = 0.0
|
|
16
|
+
change_amt: float = 0.0
|
|
17
|
+
volume: int = 0 # 手(腾讯源)或 股(新浪源),data 层不归一化
|
|
18
|
+
amount: float = 0.0 # 万元
|
|
19
|
+
turnover: float = 0.0 # %
|
|
20
|
+
pe: float = 0.0
|
|
21
|
+
pb: float = 0.0
|
|
22
|
+
total_cap: float = 0.0 # 亿
|
|
23
|
+
circulating_cap: float = 0.0
|
|
24
|
+
source: str = ""
|
|
25
|
+
|
|
26
|
+
def has_basic_data(self) -> bool:
|
|
27
|
+
return self.price > 0
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict:
|
|
30
|
+
return self.__dict__.copy()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class KlineBar:
|
|
35
|
+
"""统一 K 线数据结构。"""
|
|
36
|
+
day: str = ""
|
|
37
|
+
open: float = 0.0
|
|
38
|
+
high: float = 0.0
|
|
39
|
+
low: float = 0.0
|
|
40
|
+
close: float = 0.0
|
|
41
|
+
volume: int = 0 # 手
|
|
42
|
+
amount: float = 0.0
|
|
43
|
+
pct_chg: float = 0.0
|
|
44
|
+
source: str = ""
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> dict:
|
|
47
|
+
return self.__dict__.copy()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class FinanceRecord:
|
|
52
|
+
"""统一财务数据结构。"""
|
|
53
|
+
report_date: str = ""
|
|
54
|
+
eps: float = 0.0 # 每股收益
|
|
55
|
+
roe: float = 0.0 # ROE(%)
|
|
56
|
+
revenue_yoy: float = 0.0 # 营收同比(%)
|
|
57
|
+
net_profit_yoy: float = 0.0 # 净利同比(%)
|
|
58
|
+
gross_margin: float = 0.0 # 毛利率(%)
|
|
59
|
+
net_margin: float = 0.0 # 净利率(%)
|
|
60
|
+
debt_ratio: float = 0.0 # 负债率(%)
|
|
61
|
+
bps: float = 0.0 # 每股净资产
|
|
62
|
+
ocf_per_share: float = 0.0 # 每股经营现金流
|
|
63
|
+
source: str = ""
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> dict:
|
|
66
|
+
return self.__dict__.copy()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
数据源集合:自动发现并加载可用的数据源。
|
|
3
|
+
依赖包未安装时自动跳过对应数据源。
|
|
4
|
+
"""
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# 添加 scripts 目录到 path
|
|
9
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
10
|
+
|
|
11
|
+
from common import BaseFetcher, DataFetcherManager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _try_import(module_name, class_name):
|
|
15
|
+
"""尝试导入模块,失败返回 None。"""
|
|
16
|
+
try:
|
|
17
|
+
import importlib
|
|
18
|
+
mod = importlib.import_module(f".{module_name}", package=__name__)
|
|
19
|
+
return getattr(mod, class_name, None)
|
|
20
|
+
except Exception:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_quote_fetchers() -> list:
|
|
25
|
+
"""获取所有可用的行情数据源。"""
|
|
26
|
+
fetchers = []
|
|
27
|
+
|
|
28
|
+
# 直接 HTTP 数据源(无依赖)
|
|
29
|
+
from .tencent_quote import TencentQuoteFetcher
|
|
30
|
+
from .eastmoney_quote import EastmoneyQuoteFetcher
|
|
31
|
+
from .sina_quote import SinaQuoteFetcher
|
|
32
|
+
fetchers.extend([TencentQuoteFetcher(), EastmoneyQuoteFetcher(), SinaQuoteFetcher()])
|
|
33
|
+
|
|
34
|
+
# 可选依赖数据源
|
|
35
|
+
cls = _try_import("efinance_quote", "EfinanceQuoteFetcher")
|
|
36
|
+
if cls:
|
|
37
|
+
fetchers.append(cls())
|
|
38
|
+
|
|
39
|
+
cls = _try_import("akshare_quote", "AkshareQuoteFetcher")
|
|
40
|
+
if cls:
|
|
41
|
+
fetchers.append(cls())
|
|
42
|
+
|
|
43
|
+
cls = _try_import("tushare_quote", "TushareQuoteFetcher")
|
|
44
|
+
if cls:
|
|
45
|
+
fetchers.append(cls())
|
|
46
|
+
|
|
47
|
+
cls = _try_import("pytdx_quote", "PytdxQuoteFetcher")
|
|
48
|
+
if cls:
|
|
49
|
+
fetchers.append(cls())
|
|
50
|
+
|
|
51
|
+
return fetchers
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_kline_fetchers() -> list:
|
|
55
|
+
"""获取所有可用的 K 线数据源。"""
|
|
56
|
+
fetchers = []
|
|
57
|
+
|
|
58
|
+
# 直接 HTTP 数据源(无依赖)
|
|
59
|
+
from .sina_kline import SinaKlineFetcher
|
|
60
|
+
from .eastmoney_kline import EastmoneyKlineFetcher
|
|
61
|
+
from .tencent_kline import TencentKlineFetcher
|
|
62
|
+
fetchers.extend([SinaKlineFetcher(), EastmoneyKlineFetcher(), TencentKlineFetcher()])
|
|
63
|
+
|
|
64
|
+
# 可选依赖数据源
|
|
65
|
+
cls = _try_import("efinance_kline", "EfinanceKlineFetcher")
|
|
66
|
+
if cls:
|
|
67
|
+
fetchers.append(cls())
|
|
68
|
+
|
|
69
|
+
cls = _try_import("akshare_kline", "AkshareKlineFetcher")
|
|
70
|
+
if cls:
|
|
71
|
+
fetchers.append(cls())
|
|
72
|
+
|
|
73
|
+
cls = _try_import("tushare_kline", "TushareKlineFetcher")
|
|
74
|
+
if cls:
|
|
75
|
+
fetchers.append(cls())
|
|
76
|
+
|
|
77
|
+
cls = _try_import("baostock_kline", "BaostockKlineFetcher")
|
|
78
|
+
if cls:
|
|
79
|
+
fetchers.append(cls())
|
|
80
|
+
|
|
81
|
+
cls = _try_import("yfinance_kline", "YfinanceKlineFetcher")
|
|
82
|
+
if cls:
|
|
83
|
+
fetchers.append(cls())
|
|
84
|
+
|
|
85
|
+
return fetchers
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_finance_fetchers() -> list:
|
|
89
|
+
"""获取所有可用的财务数据源。"""
|
|
90
|
+
fetchers = []
|
|
91
|
+
|
|
92
|
+
from .eastmoney_finance import EastmoneyFinanceFetcher
|
|
93
|
+
fetchers.append(EastmoneyFinanceFetcher())
|
|
94
|
+
|
|
95
|
+
cls = _try_import("efinance_finance", "EfinanceFinanceFetcher")
|
|
96
|
+
if cls:
|
|
97
|
+
fetchers.append(cls())
|
|
98
|
+
|
|
99
|
+
cls = _try_import("akshare_finance", "AkshareFinanceFetcher")
|
|
100
|
+
if cls:
|
|
101
|
+
fetchers.append(cls())
|
|
102
|
+
|
|
103
|
+
return fetchers
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# 全局管理器(延迟初始化)
|
|
107
|
+
_quote_manager = None
|
|
108
|
+
_kline_manager = None
|
|
109
|
+
_finance_manager = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_quote_manager() -> DataFetcherManager:
|
|
113
|
+
global _quote_manager
|
|
114
|
+
if _quote_manager is None:
|
|
115
|
+
_quote_manager = DataFetcherManager(get_quote_fetchers())
|
|
116
|
+
return _quote_manager
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_kline_manager() -> DataFetcherManager:
|
|
120
|
+
global _kline_manager
|
|
121
|
+
if _kline_manager is None:
|
|
122
|
+
_kline_manager = DataFetcherManager(get_kline_fetchers())
|
|
123
|
+
return _kline_manager
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_finance_manager() -> DataFetcherManager:
|
|
127
|
+
global _finance_manager
|
|
128
|
+
if _finance_manager is None:
|
|
129
|
+
_finance_manager = DataFetcherManager(get_finance_fetchers())
|
|
130
|
+
return _finance_manager
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""akshare 财务数据源(需要 akshare 包)。"""
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
5
|
+
|
|
6
|
+
from common import BaseFetcher
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import akshare as ak
|
|
10
|
+
HAS_AKSHARE = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
HAS_AKSHARE = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AkshareFinanceFetcher(BaseFetcher):
|
|
16
|
+
"""akshare 财务数据源 (优先级 3) - 需要安装 akshare 包。"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__("akshare_finance", priority=3)
|
|
20
|
+
|
|
21
|
+
def fetch(self, code: str, **kwargs) -> list | None:
|
|
22
|
+
if not HAS_AKSHARE:
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
plain = code.lstrip("shszSHSZbjBJ")
|
|
26
|
+
df = ak.stock_financial_abstract(symbol=plain)
|
|
27
|
+
if df is None or df.empty:
|
|
28
|
+
return None
|
|
29
|
+
# 返回最近 4 季
|
|
30
|
+
result = []
|
|
31
|
+
for _, row in df.head(4).iterrows():
|
|
32
|
+
result.append(row.to_dict())
|
|
33
|
+
return result if result else None
|
|
34
|
+
except Exception:
|
|
35
|
+
return None
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""akshare K 线数据源(需要 akshare 包)。"""
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
5
|
+
|
|
6
|
+
from common import BaseFetcher
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import akshare as ak
|
|
10
|
+
HAS_AKSHARE = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
HAS_AKSHARE = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AkshareKlineFetcher(BaseFetcher):
|
|
16
|
+
"""akshare K 线数据源 (优先级 1) - 需要安装 akshare 包。"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__("akshare_kline", priority=1)
|
|
20
|
+
|
|
21
|
+
def fetch(self, code: str, **kwargs) -> list | None:
|
|
22
|
+
if not HAS_AKSHARE:
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
scale = kwargs.get("scale", 240)
|
|
26
|
+
datalen = kwargs.get("datalen", 30)
|
|
27
|
+
plain = code.lstrip("shszSHSZbjBJ")
|
|
28
|
+
|
|
29
|
+
if scale == 240:
|
|
30
|
+
# 日 K
|
|
31
|
+
df = ak.stock_zh_a_hist(symbol=plain, period="daily", adjust="qfq")
|
|
32
|
+
elif scale == 60:
|
|
33
|
+
df = ak.stock_zh_a_hist(symbol=plain, period="60", adjust="qfq")
|
|
34
|
+
elif scale == 30:
|
|
35
|
+
df = ak.stock_zh_a_hist(symbol=plain, period="30", adjust="qfq")
|
|
36
|
+
elif scale == 15:
|
|
37
|
+
df = ak.stock_zh_a_hist(symbol=plain, period="15", adjust="qfq")
|
|
38
|
+
elif scale == 5:
|
|
39
|
+
df = ak.stock_zh_a_hist(symbol=plain, period="5", adjust="qfq")
|
|
40
|
+
else:
|
|
41
|
+
df = ak.stock_zh_a_hist(symbol=plain, period="daily", adjust="qfq")
|
|
42
|
+
|
|
43
|
+
if df is None or df.empty:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
df = df.tail(datalen)
|
|
47
|
+
result = []
|
|
48
|
+
for _, row in df.iterrows():
|
|
49
|
+
result.append({
|
|
50
|
+
"day": str(row.get("日期", ""))[:10],
|
|
51
|
+
"open": str(row.get("开盘", 0)),
|
|
52
|
+
"close": str(row.get("收盘", 0)),
|
|
53
|
+
"high": str(row.get("最高", 0)),
|
|
54
|
+
"low": str(row.get("最低", 0)),
|
|
55
|
+
"volume": str(row.get("成交量", 0)),
|
|
56
|
+
})
|
|
57
|
+
return result if result else None
|
|
58
|
+
except Exception:
|
|
59
|
+
return None
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""akshare 行情数据源(需要 akshare 包)。"""
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
5
|
+
|
|
6
|
+
from common import BaseFetcher
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import akshare as ak
|
|
10
|
+
HAS_AKSHARE = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
HAS_AKSHARE = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AkshareQuoteFetcher(BaseFetcher):
|
|
16
|
+
"""akshare 行情数据源 (优先级 1) - 需要安装 akshare 包。"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__("akshare_quote", priority=1)
|
|
20
|
+
|
|
21
|
+
def fetch(self, code: str, **kwargs) -> dict | None:
|
|
22
|
+
if not HAS_AKSHARE:
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
plain = code.lstrip("shszSHSZbjBJ")
|
|
26
|
+
df = ak.stock_zh_a_spot_em()
|
|
27
|
+
if df is None or df.empty:
|
|
28
|
+
return None
|
|
29
|
+
row = df[df["代码"] == plain]
|
|
30
|
+
if row.empty:
|
|
31
|
+
return None
|
|
32
|
+
r = row.iloc[0]
|
|
33
|
+
return {
|
|
34
|
+
"code": str(r.get("代码", "")),
|
|
35
|
+
"name": str(r.get("名称", "")),
|
|
36
|
+
"price": str(r.get("最新价", 0)),
|
|
37
|
+
"prev_close": str(r.get("昨收", 0)),
|
|
38
|
+
"open": str(r.get("今开", 0)),
|
|
39
|
+
"change_pct": str(r.get("涨跌幅", 0)),
|
|
40
|
+
"change_amt": str(r.get("涨跌额", 0)),
|
|
41
|
+
"high": str(r.get("最高", 0)),
|
|
42
|
+
"low": str(r.get("最低", 0)),
|
|
43
|
+
"volume": str(r.get("成交量", 0)),
|
|
44
|
+
"amount": str(r.get("成交额", 0)),
|
|
45
|
+
"turnover": str(r.get("换手率", 0)),
|
|
46
|
+
"pe": str(r.get("市盈率-动态", 0)),
|
|
47
|
+
"pb": str(r.get("市净率", 0)),
|
|
48
|
+
"total_cap": str(r.get("总市值", 0)),
|
|
49
|
+
"circulating_cap": str(r.get("流通市值", 0)),
|
|
50
|
+
}
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Baostock K 线数据源(需要 baostock 包)。"""
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
5
|
+
|
|
6
|
+
from common import BaseFetcher
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import baostock as bs
|
|
10
|
+
HAS_BAOSTOCK = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
HAS_BAOSTOCK = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaostockKlineFetcher(BaseFetcher):
|
|
16
|
+
"""Baostock K 线数据源 (优先级 3) - 需要安装 baostock 包。"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__("baostock_kline", priority=3)
|
|
20
|
+
|
|
21
|
+
def fetch(self, code: str, **kwargs) -> list | None:
|
|
22
|
+
if not HAS_BAOSTOCK:
|
|
23
|
+
return None
|
|
24
|
+
scale = kwargs.get("scale", 240)
|
|
25
|
+
datalen = kwargs.get("datalen", 30)
|
|
26
|
+
|
|
27
|
+
# baostock 格式: sh.600989
|
|
28
|
+
plain = code.lstrip("shszSHSZbjBJ").zfill(6)
|
|
29
|
+
if plain.startswith(("60", "68", "51", "56", "58")):
|
|
30
|
+
bs_code = f"sh.{plain}"
|
|
31
|
+
else:
|
|
32
|
+
bs_code = f"sz.{plain}"
|
|
33
|
+
|
|
34
|
+
if scale != 240:
|
|
35
|
+
return None # baostock 只支持日线
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
lg = bs.login()
|
|
39
|
+
rs = bs.query_history_k_data_plus(
|
|
40
|
+
bs_code,
|
|
41
|
+
"date,open,high,low,close,volume",
|
|
42
|
+
count=datalen,
|
|
43
|
+
frequency="d",
|
|
44
|
+
adjustflag="2",
|
|
45
|
+
)
|
|
46
|
+
if rs.error_code != "0":
|
|
47
|
+
bs.logout()
|
|
48
|
+
return None
|
|
49
|
+
result = []
|
|
50
|
+
while rs.next():
|
|
51
|
+
row = rs.get_row_data()
|
|
52
|
+
if len(row) >= 6:
|
|
53
|
+
result.append({
|
|
54
|
+
"day": row[0],
|
|
55
|
+
"open": row[1],
|
|
56
|
+
"high": row[2],
|
|
57
|
+
"low": row[3],
|
|
58
|
+
"close": row[4],
|
|
59
|
+
"volume": row[5],
|
|
60
|
+
})
|
|
61
|
+
bs.logout()
|
|
62
|
+
return result if result else None
|
|
63
|
+
except Exception:
|
|
64
|
+
return None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""东方财富财务数据源。"""
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
6
|
+
|
|
7
|
+
from common import BaseFetcher, http_get
|
|
8
|
+
|
|
9
|
+
URL = "https://emweb.securities.eastmoney.com/PC_HSF10/NewFinanceAnalysis/ZYZBAjaxNew?type=0&code={code}"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EastmoneyFinanceFetcher(BaseFetcher):
|
|
13
|
+
"""东方财富财务数据源 (优先级 10)。缓存由 data 层统一管理。"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__("eastmoney_finance", priority=10)
|
|
17
|
+
|
|
18
|
+
def fetch(self, code: str, **kwargs) -> list | None:
|
|
19
|
+
raw = http_get(URL.format(code=code))
|
|
20
|
+
try:
|
|
21
|
+
data = json.loads(raw)
|
|
22
|
+
except json.JSONDecodeError:
|
|
23
|
+
return None
|
|
24
|
+
if not data or "data" not in data or not data["data"]:
|
|
25
|
+
return None
|
|
26
|
+
result = data["data"][:4]
|
|
27
|
+
for r in result:
|
|
28
|
+
r["source"] = "eastmoney"
|
|
29
|
+
return result
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""东方财富 K 线数据源。"""
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
6
|
+
|
|
7
|
+
from common import BaseFetcher, http_get, to_secid
|
|
8
|
+
|
|
9
|
+
EASTMONEY_KLINE_URL = "https://push2his.eastmoney.com/api/qt/stock/kline/get?cb=&secid={secid}&ut=fa5fd1943c7b386f172d6893dbbd4dc1&fields1=f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13&fields2=f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61&klt={klt}&fqt=1&end=20500101&lmt={lmt}"
|
|
10
|
+
KLT_MAP = {5: 5, 15: 15, 30: 30, 60: 60, 240: 101}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EastmoneyKlineFetcher(BaseFetcher):
|
|
14
|
+
"""东方财富 K 线数据源 (优先级 8)。"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
super().__init__("eastmoney_kline", priority=8)
|
|
18
|
+
|
|
19
|
+
def fetch(self, code: str, **kwargs) -> list | None:
|
|
20
|
+
scale = kwargs.get("scale", 240)
|
|
21
|
+
datalen = kwargs.get("datalen", 30)
|
|
22
|
+
secid = to_secid(code)
|
|
23
|
+
klt = KLT_MAP.get(scale, 101)
|
|
24
|
+
url = EASTMONEY_KLINE_URL.format(secid=secid, klt=klt, lmt=datalen)
|
|
25
|
+
raw = http_get(url, timeout=10)
|
|
26
|
+
try:
|
|
27
|
+
data = json.loads(raw)
|
|
28
|
+
except json.JSONDecodeError:
|
|
29
|
+
return None
|
|
30
|
+
if not data or data.get("rc") != 0 or "data" not in data:
|
|
31
|
+
return None
|
|
32
|
+
klines = data["data"].get("klines", [])
|
|
33
|
+
if not klines:
|
|
34
|
+
return None
|
|
35
|
+
result = []
|
|
36
|
+
for line in klines:
|
|
37
|
+
parts = line.split(",")
|
|
38
|
+
if len(parts) >= 6:
|
|
39
|
+
result.append({
|
|
40
|
+
"day": parts[0],
|
|
41
|
+
"open": parts[1],
|
|
42
|
+
"close": parts[2],
|
|
43
|
+
"high": parts[3],
|
|
44
|
+
"low": parts[4],
|
|
45
|
+
"volume": parts[5],
|
|
46
|
+
"source": "eastmoney",
|
|
47
|
+
})
|
|
48
|
+
return result if result else None
|