mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 16:54:20 +08:00
feat: 实现全量 A 股列表获取与行业信息自动挂载
1. 编解码器:新增 codec/industry.py 用于解析 tdxhy.cfg 行业配置。 2. 数据模型:SecurityInfo 增加 industry_tdx 和 industry_sw 字段。 3. 客户端 API:增加 get_security_list_all(),支持分页拉取全市场 A 股并自动匹配行业标签。 4. 通讯优化:更新 KNOWN_HOSTS 包含更稳定的华为云及招商证券节点。 5. 文档更新:README.md 同步 API 及数据模型变更。
This commit is contained in:
@@ -95,6 +95,7 @@ client = AsyncTdxClient.from_best_host(ping_timeout=5.0)
|
||||
|------|------|
|
||||
| `get_security_count(market)` | 市场证券总数 |
|
||||
| `get_security_list(market, start)` | 证券列表(每页 ~1000 条) |
|
||||
| `get_security_list_all()` | 全市场 A 股列表(自动挂载行业信息) |
|
||||
| `get_security_quotes([(market, code), ...])` | 批量实时五档行情(最多 80 只/次) |
|
||||
| `get_security_bars(market, code, category, start, count=800)` | K 线(股票) |
|
||||
| `get_index_bars(market, code, category, start, count=800)` | K 线(指数) |
|
||||
@@ -164,6 +165,7 @@ _raw
|
||||
|
||||
```
|
||||
market code name volunit decimal_point pre_close
|
||||
industry_tdx industry_sw
|
||||
```
|
||||
|
||||
### XdxrRecord(除权除息)
|
||||
|
||||
@@ -17,6 +17,7 @@ from .commands.security_quotes import GetSecurityQuotesCmd
|
||||
from .commands.transaction import GetHistoryTransactionDataCmd, GetTransactionDataCmd
|
||||
from .commands.xdxr_info import GetXdxrInfoCmd
|
||||
from .codec.block import parse_block_dat
|
||||
from .codec.industry import parse_tdxhy_cfg
|
||||
from .exceptions import TdxConnectionError
|
||||
from .models.bar import SecurityBar
|
||||
from .models.enums import KlineCategory, Market
|
||||
@@ -145,6 +146,41 @@ class TdxClient:
|
||||
"""获取证券列表(每页约1000条,按 start 分页)。"""
|
||||
return self._execute(GetSecurityListCmd(market, start))
|
||||
|
||||
def get_security_list_all(self) -> list[SecurityInfo]:
|
||||
"""获取全市场(沪深 A 股)完整证券列表,并自动挂载行业信息。"""
|
||||
# 1. 尝试获取行业配置
|
||||
industry_map = {}
|
||||
try:
|
||||
cfg_data = self.get_report_file("tdxhy.cfg")
|
||||
if cfg_data:
|
||||
industry_map = parse_tdxhy_cfg(cfg_data)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
all_stocks: list[SecurityInfo] = []
|
||||
for market in [Market.SH, Market.SZ]:
|
||||
count = self.get_security_count(market)
|
||||
for start in range(0, count, 1000):
|
||||
stocks = self.get_security_list(market, start)
|
||||
for s in stocks:
|
||||
# 精确 A 股过滤规则
|
||||
is_a_share = False
|
||||
if market == Market.SH:
|
||||
# 沪市 A 股:60xxxx, 68xxxx
|
||||
if s.code.startswith(("60", "68")):
|
||||
is_a_share = True
|
||||
elif market == Market.SZ:
|
||||
# 深市 A 股:00xxxx (主板), 30xxxx (创业板)
|
||||
if s.code.startswith(("00", "30")):
|
||||
is_a_share = True
|
||||
|
||||
if is_a_share:
|
||||
# 挂载行业信息
|
||||
if s.code in industry_map:
|
||||
s.industry_tdx, s.industry_sw = industry_map[s.code]
|
||||
all_stocks.append(s)
|
||||
return all_stocks
|
||||
|
||||
def get_security_quotes(
|
||||
self, stocks: list[tuple[Market, str]]
|
||||
) -> list[SecurityQuote]:
|
||||
@@ -416,6 +452,36 @@ class AsyncTdxClient:
|
||||
async def get_security_list(self, market: Market, start: int) -> list[SecurityInfo]:
|
||||
return await self._execute(GetSecurityListCmd(market, start))
|
||||
|
||||
async def get_security_list_all(self) -> list[SecurityInfo]:
|
||||
"""获取全市场完整证券列表,并自动挂载行业信息。"""
|
||||
industry_map = {}
|
||||
try:
|
||||
cfg_data = await self.get_report_file("tdxhy.cfg")
|
||||
if cfg_data:
|
||||
industry_map = parse_tdxhy_cfg(cfg_data)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
all_stocks: list[SecurityInfo] = []
|
||||
for market in [Market.SH, Market.SZ]:
|
||||
count = await self.get_security_count(market)
|
||||
for start in range(0, count, 1000):
|
||||
stocks = await self.get_security_list(market, start)
|
||||
for s in stocks:
|
||||
is_a_share = False
|
||||
if market == Market.SH:
|
||||
if s.code.startswith(("60", "68")):
|
||||
is_a_share = True
|
||||
elif market == Market.SZ:
|
||||
if s.code.startswith(("00", "30")):
|
||||
is_a_share = True
|
||||
|
||||
if is_a_share:
|
||||
if s.code in industry_map:
|
||||
s.industry_tdx, s.industry_sw = industry_map[s.code]
|
||||
all_stocks.append(s)
|
||||
return all_stocks
|
||||
|
||||
async def get_security_quotes(
|
||||
self, stocks: list[tuple[Market, str]]
|
||||
) -> list[SecurityQuote]:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""通达信行业配置文件 (tdxhy.cfg) 解析器。"""
|
||||
|
||||
def parse_tdxhy_cfg(content: bytes) -> dict[str, tuple[str, str]]:
|
||||
"""解析 tdxhy.cfg 字节内容。
|
||||
|
||||
返回字典: { "code": (tdx_industry, sw_industry), ... }
|
||||
"""
|
||||
results = {}
|
||||
try:
|
||||
text = content.decode("gbk", errors="replace")
|
||||
for line in text.splitlines():
|
||||
parts = line.strip().split("|")
|
||||
if len(parts) >= 3:
|
||||
# 格式: 市场|代码|行业1|||行业2
|
||||
# 我们只关心 A 股 6 位代码
|
||||
code = parts[1]
|
||||
if len(code) == 6:
|
||||
tdx_ind = parts[2]
|
||||
sw_ind = parts[5] if len(parts) >= 6 else ""
|
||||
results[code] = (tdx_ind, sw_ind)
|
||||
except Exception:
|
||||
pass
|
||||
return results
|
||||
@@ -16,4 +16,8 @@ class SecurityInfo:
|
||||
decimal_point: int # 价格小数位数
|
||||
pre_close: float # 昨收价(已修复 pytdx Bug #3:改用正确价格解码)
|
||||
|
||||
# 扩展字段(通过 get_security_list_all 关联 tdxhy.cfg 获得)
|
||||
industry_tdx: str = "" # 通达信行业代码 (如 T1001)
|
||||
industry_sw: str = "" # 申万行业代码 (如 X500102)
|
||||
|
||||
_raw: bytes = field(default=b"", repr=False, compare=False)
|
||||
|
||||
@@ -21,8 +21,10 @@ _DEFAULT_TIMEOUT = 15.0
|
||||
# 已知可用的通达信行情服务器(按优先级排序)
|
||||
KNOWN_HOSTS: list[str] = [
|
||||
"180.153.18.170",
|
||||
"124.71.187.122",
|
||||
"180.153.18.171",
|
||||
"180.153.18.172",
|
||||
"119.147.212.81",
|
||||
"115.238.56.198",
|
||||
"115.238.90.165",
|
||||
"218.75.126.9",
|
||||
|
||||
Reference in New Issue
Block a user