mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 15:44:15 +08:00
feat: add extended market, offline data reader, rewrite README
- Add ExTdxClient/AsyncExTdxClient for futures, HK stocks, etc (port 7727) - Add offline module: read daily bars, minute bars, blocks, gbbq, financials from local TDX installation directory (inspired by pytdx) - Add examples 09 (file download) and 10 (offline data reading) - Rewrite README with comprehensive API docs and code examples - Add TdxFileNotFoundError and TdxOfflineError exceptions Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
321043f9a5
commit
9c5672b4d2
@@ -0,0 +1,80 @@
|
||||
"""演示:板块数据读取(本地 + 网络自动回退)。
|
||||
|
||||
系统板块获取优先级:
|
||||
1. 本地 .dat 文件(离线读取)
|
||||
2. TDX 服务器在线获取(自动回退)
|
||||
|
||||
自定义板块仅支持本地读取。
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from xmtdx import TdxClient
|
||||
from xmtdx.models.finance import TdxBlock
|
||||
from xmtdx.offline import detect_tdx_home, read_block_dat, read_customer_blocks
|
||||
|
||||
|
||||
def _print_blocks(blocks: list[TdxBlock], title: str) -> None:
|
||||
print(f"\n{title} ({len(blocks)} 个板块):")
|
||||
for block in blocks[:5]:
|
||||
codes_preview = ", ".join(block.codes[:5])
|
||||
suffix = "..." if len(block.codes) > 5 else ""
|
||||
print(f" {block.name} ({block.count}只): {codes_preview}{suffix}")
|
||||
if len(blocks) > 5:
|
||||
print(f" ... 还有 {len(blocks) - 5} 个板块")
|
||||
|
||||
|
||||
home = detect_tdx_home()
|
||||
|
||||
# --- 系统板块 ---
|
||||
print("=" * 60)
|
||||
print("系统板块")
|
||||
print("=" * 60)
|
||||
|
||||
vipdoc = Path(home) / "vipdoc" if home else None
|
||||
block_names = ["block_zs.dat", "block_gn.dat", "block_fg.dat"]
|
||||
block_labels = {
|
||||
"block_zs.dat": "行业板块",
|
||||
"block_gn.dat": "概念板块",
|
||||
"block_fg.dat": "风格板块",
|
||||
}
|
||||
|
||||
need_fetch = []
|
||||
for name in block_names:
|
||||
local_path = vipdoc / name if vipdoc else None
|
||||
if local_path and local_path.is_file():
|
||||
blocks = read_block_dat(local_path)
|
||||
_print_blocks(blocks, f"{block_labels[name]} ({name}, 本地)")
|
||||
else:
|
||||
print(f"\n{block_labels[name]}: 本地文件不存在,将从服务器获取")
|
||||
need_fetch.append(name)
|
||||
|
||||
# 本地没有的板块,通过网络获取
|
||||
if need_fetch:
|
||||
print(f"\n正在连接服务器获取 {len(need_fetch)} 个板块文件...")
|
||||
with TdxClient.from_best_host() as c:
|
||||
for name in need_fetch:
|
||||
blocks = c.get_block_info(name)
|
||||
_print_blocks(blocks, f"{block_labels[name]} ({name}, 网络)")
|
||||
|
||||
# --- 自定义板块 ---
|
||||
print(f"\n{'=' * 60}")
|
||||
print("自定义板块")
|
||||
print("=" * 60)
|
||||
|
||||
if home:
|
||||
blocknew_dir = Path(home) / "T0002" / "blocknew"
|
||||
if blocknew_dir.is_dir():
|
||||
blocks = read_customer_blocks(blocknew_dir)
|
||||
if blocks:
|
||||
print(f"\n共 {len(blocks)} 个自定义板块:")
|
||||
for block in blocks[:10]:
|
||||
codes_preview = ", ".join(block.codes[:5])
|
||||
suffix = "..." if len(block.codes) > 5 else ""
|
||||
print(f" {block.blockname} ({len(block.codes)}只): {codes_preview}{suffix}")
|
||||
else:
|
||||
print("未找到自定义板块数据")
|
||||
else:
|
||||
print(f"自定义板块目录不存在: {blocknew_dir}")
|
||||
else:
|
||||
print("需要本地通达信安装目录才能读取自定义板块")
|
||||
@@ -0,0 +1,43 @@
|
||||
"""演示:从本地通达信目录读取日线 K 线数据。
|
||||
|
||||
两种用法:
|
||||
1. 直接指定 .day 文件路径
|
||||
2. 通过 市场+代码 自动定位文件(需要设置 TDX_HOME 环境变量)
|
||||
|
||||
需要本地已安装通达信并下载过日线数据。
|
||||
"""
|
||||
|
||||
from xmtdx.offline import detect_tdx_home, read_daily_bars, find_daily_bar_file
|
||||
from xmtdx import Market
|
||||
|
||||
home = detect_tdx_home()
|
||||
if home is None:
|
||||
print("未检测到通达信安装目录,请设置 TDX_HOME 环境变量")
|
||||
print("例如: set TDX_HOME=C:\\new_jyplug")
|
||||
raise SystemExit(1)
|
||||
|
||||
print(f"通达信目录: {home}")
|
||||
|
||||
# --- 方式1: 通过 市场+代码 自动定位文件 ---
|
||||
filepath = find_daily_bar_file(Market.SH, "600000")
|
||||
print(f"\n文件路径: {filepath}")
|
||||
|
||||
bars = read_daily_bars(filepath)
|
||||
if not bars:
|
||||
print("未读取到数据,请确认通达信已下载该股票的日线数据")
|
||||
raise SystemExit(0)
|
||||
|
||||
# 最近 10 个交易日
|
||||
print(f"\n浦发银行 日线 (最近 {min(10, len(bars))} 个交易日):")
|
||||
print(f"{'日期':>12s} {'开盘':>8s} {'最高':>8s} {'最低':>8s} {'收盘':>8s} {'成交量':>10s}")
|
||||
for bar in bars[-10:]:
|
||||
print(
|
||||
f"{bar.year}-{bar.month:02d}-{bar.day:02d} "
|
||||
f"{bar.open:>8.2f} {bar.high:>8.2f} "
|
||||
f"{bar.low:>8.2f} {bar.close:>8.2f} "
|
||||
f"{bar.vol:>10.0f}"
|
||||
)
|
||||
|
||||
# --- 方式2: 直接指定文件路径 ---
|
||||
# from pathlib import Path
|
||||
# bars2 = read_daily_bars(Path(r"C:\new_jyplug\vipdoc\sz\lday\sz000001.day"))
|
||||
@@ -0,0 +1,80 @@
|
||||
"""演示:检测通达信安装目录与路径解析。
|
||||
|
||||
offline 模块的路径检测优先级:
|
||||
1. TDX_HOME 环境变量
|
||||
2. 平台常见路径猜测 (Windows: C:\\new_jyplug, C:\\new_tdx, D:\\... 等)
|
||||
|
||||
vipdoc 目录结构:
|
||||
vipdoc/
|
||||
├── sh/lday/ 上海日线 sh600000.day
|
||||
├── sh/fzline/ 上海5分钟线 sh600000.5
|
||||
├── sh/fzline/ 上海分钟线 sh600000.lc1 / .lc5
|
||||
├── sz/lday/ 深圳日线 sz000001.day
|
||||
├── sz/fzline/ 深圳5分钟线 sz000001.5
|
||||
├── sz/fzline/ 深圳分钟线 sz000001.lc1 / .lc5
|
||||
└── ds/ 扩展市场 29#A1801.day
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from xmtdx.offline import detect_tdx_home, resolve_vipdoc
|
||||
from xmtdx.offline import find_daily_bar_file, find_5min_bar_file, find_lc1_bar_file
|
||||
from xmtdx import Market
|
||||
|
||||
# --- 检测安装目录 ---
|
||||
print("=" * 60)
|
||||
print("通达信安装目录检测")
|
||||
print("=" * 60)
|
||||
|
||||
home = detect_tdx_home()
|
||||
if home:
|
||||
print(f"检测到: {home}")
|
||||
else:
|
||||
print("未检测到,可通过以下方式指定:")
|
||||
print(f" set TDX_HOME=C:\\new_jyplug")
|
||||
|
||||
# --- 手动指定路径 ---
|
||||
print(f"\n{'=' * 60}")
|
||||
print("手动指定 vipdoc 路径")
|
||||
print("=" * 60)
|
||||
|
||||
if home:
|
||||
vipdoc = resolve_vipdoc()
|
||||
print(f"vipdoc 目录: {vipdoc}")
|
||||
|
||||
# 列出 vipdoc 子目录
|
||||
if vipdoc.is_dir():
|
||||
for d in sorted(vipdoc.iterdir()):
|
||||
if d.is_dir():
|
||||
files = list(d.rglob("*"))
|
||||
print(f" {d.name}/ ({len(files)} 个文件)")
|
||||
else:
|
||||
print("(需要 TDX_HOME 才能自动解析)")
|
||||
|
||||
# --- 文件定位示例 ---
|
||||
print(f"\n{'=' * 60}")
|
||||
print("通过 市场+代码 定位文件")
|
||||
print("=" * 60)
|
||||
|
||||
if home:
|
||||
examples = [
|
||||
("浦发银行 日线", lambda: find_daily_bar_file(Market.SH, "600000")),
|
||||
("平安银行 日线", lambda: find_daily_bar_file(Market.SZ, "000001")),
|
||||
("浦发银行 5分钟", lambda: find_5min_bar_file(Market.SH, "600000")),
|
||||
("平安银行 1分钟", lambda: find_lc1_bar_file(Market.SZ, "000001")),
|
||||
]
|
||||
for label, finder in examples:
|
||||
p = finder()
|
||||
exists = "存在" if p.is_file() else "不存在"
|
||||
print(f" {label}: {p} ({exists})")
|
||||
else:
|
||||
print("(需要 TDX_HOME)")
|
||||
|
||||
# --- 设置环境变量的方式 ---
|
||||
print(f"\n{'=' * 60}")
|
||||
print("如何设置 TDX_HOME")
|
||||
print("=" * 60)
|
||||
print(" Windows CMD: set TDX_HOME=C:\\new_jyplug")
|
||||
print(" Windows PS: $env:TDX_HOME = 'C:\\new_jyplug'")
|
||||
print(" Linux/macOS: export TDX_HOME=/opt/new_tdx")
|
||||
@@ -0,0 +1,69 @@
|
||||
"""演示:从本地通达信目录读取扩展市场日线数据。
|
||||
|
||||
扩展市场包括:期货、港股、外盘等。
|
||||
文件位于 vipdoc/ds/ 目录下,如 29#A1801.day
|
||||
|
||||
需要本地已安装通达信并下载过扩展市场数据。
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from xmtdx.offline import detect_tdx_home, read_ex_daily_bars
|
||||
|
||||
home = detect_tdx_home()
|
||||
if home is None:
|
||||
print("未检测到通达信安装目录,请设置 TDX_HOME 环境变量")
|
||||
raise SystemExit(1)
|
||||
|
||||
vipdoc = Path(home) / "vipdoc" / "ds" / "lday"
|
||||
|
||||
# 列出 ds 目录下可用的 .day 文件
|
||||
day_files = sorted(vipdoc.glob("*.day")) if vipdoc.is_dir() else []
|
||||
if not day_files:
|
||||
print(f"扩展市场目录为空或不存在: {vipdoc}")
|
||||
print("请在通达信中下载扩展市场数据后再试")
|
||||
raise SystemExit(0)
|
||||
|
||||
print(f"可用文件 ({len(day_files)} 个):")
|
||||
for f in day_files[:10]:
|
||||
print(f" {f.name}")
|
||||
if len(day_files) > 10:
|
||||
print(f" ... 还有 {len(day_files) - 10} 个")
|
||||
|
||||
# 读取第一个文件作为示例
|
||||
sample = day_files[5]
|
||||
"""
|
||||
可用文件 (211 个):
|
||||
12#A_IXIC.day
|
||||
38#1_GDP.day
|
||||
38#1_GDPI.day
|
||||
38#1_MSR.day
|
||||
38#2_CGPI.day
|
||||
38#2_CPI.day
|
||||
38#2_PPCI.day
|
||||
38#2_PPI.day
|
||||
38#2_PPPI.day
|
||||
38#3_BCI.day
|
||||
... 还有 201 个
|
||||
|
||||
读取: 38#2_CPI.day
|
||||
共 250 条记录,最后 5 条:
|
||||
日期 开盘 最高 最低 收盘 结算
|
||||
2025-12-31 100.80 100.80 100.80 100.80 0.00
|
||||
2026-01-31 100.20 100.20 100.20 100.20 0.00
|
||||
2026-02-28 101.30 101.30 101.30 101.30 0.00
|
||||
2026-03-31 101.00 101.00 101.00 101.00 0.00
|
||||
2026-04-30 101.20 101.20 101.20 101.20 0.00
|
||||
"""
|
||||
print(f"\n读取: {sample.name}")
|
||||
bars = read_ex_daily_bars(sample)
|
||||
|
||||
if bars:
|
||||
print(f"共 {len(bars)} 条记录,最后 5 条:")
|
||||
print(f" {'日期':>12s} {'开盘':>8s} {'最高':>8s} {'最低':>8s} {'收盘':>8s} {'结算':>8s}")
|
||||
for bar in bars[-5:]:
|
||||
print(
|
||||
f" {bar.year}-{bar.month:02d}-{bar.day:02d} "
|
||||
f"{bar.open:>8.2f} {bar.high:>8.2f} "
|
||||
f"{bar.low:>8.2f} {bar.close:>8.2f} {bar.settlement:>8.2f}"
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
"""演示:从本地通达信目录读取股本变迁数据。
|
||||
|
||||
股本变迁文件包含分红、送股、配股、扩缩股等历史记录。
|
||||
数据使用 XOR 加密存储,读取时会自动解密。
|
||||
|
||||
需要本地已安装通达信。
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from xmtdx.offline import detect_tdx_home, read_gbbq
|
||||
|
||||
home = detect_tdx_home()
|
||||
if home is None:
|
||||
print("未检测到通达信安装目录,请设置 TDX_HOME 环境变量")
|
||||
raise SystemExit(1)
|
||||
|
||||
gbbq_path = Path(home) / "T0002" / "hq_cache" / "gbbq"
|
||||
if not gbbq_path.is_file():
|
||||
# 尝试其他可能的路径
|
||||
gbbq_path = Path(home) / "T0002" / "gbbq"
|
||||
|
||||
if not gbbq_path.is_file():
|
||||
print(f"股本变迁文件不存在")
|
||||
print(f" 尝试过: {Path(home) / 'T0002' / 'hq_cache' / 'gbbq'}")
|
||||
print(f" 尝试过: {Path(home) / 'T0002' / 'gbbq'}")
|
||||
print("请在通达信中确认 gbbq 文件的位置")
|
||||
raise SystemExit(0)
|
||||
|
||||
print(f"读取: {gbbq_path}")
|
||||
records = read_gbbq(gbbq_path)
|
||||
|
||||
if not records:
|
||||
print("未读取到数据")
|
||||
raise SystemExit(0)
|
||||
|
||||
print(f"共 {len(records)} 条股本变迁记录\n")
|
||||
|
||||
# 按代码分组统计
|
||||
from collections import Counter
|
||||
|
||||
code_counts = Counter(r.code for r in records)
|
||||
print(f"涉及 {len(code_counts)} 只股票")
|
||||
|
||||
# 显示前 20 条记录
|
||||
print(f"\n前 20 条记录:")
|
||||
print(f" {'市场':>4s} {'代码':>8s} {'日期':>10s} {'类别':>4s} {'红利/盘前流通':>12s} {'配股价/前总股本':>14s}")
|
||||
for rec in records[:20]:
|
||||
print(
|
||||
f" {rec.market:>4d} {rec.code:>8s} {rec.datetime:>10d} "
|
||||
f"{rec.category:>4d} {rec.hongli_panqianliutong:>12.4f} "
|
||||
f"{rec.peigujia_qianzongguben:>14.4f}"
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
"""演示:从本地通达信目录读取历史财务数据。
|
||||
|
||||
支持两种文件格式:
|
||||
- .dat 文件: 直接读取
|
||||
- .zip 文件: 自动解压后读取(如 gpcw20260331.zip)
|
||||
|
||||
文件可通过 TdxClient.get_financial_file_list() + download_file() 获取,
|
||||
也可从 calc 服务器下载。
|
||||
|
||||
需要本地有 gpcw*.dat 或 gpcw*.zip 文件。
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from xmtdx.offline import detect_tdx_home, read_history_financial
|
||||
|
||||
home = detect_tdx_home()
|
||||
if home is None:
|
||||
print("未检测到通达信安装目录,请设置 TDX_HOME 环境变量")
|
||||
raise SystemExit(1)
|
||||
|
||||
# 常见的历史财务数据存放位置
|
||||
candidates = [
|
||||
Path(home) / "vipdoc" / "fin",
|
||||
Path(home) / "T0002" / "fin",
|
||||
Path.home() / "Downloads",
|
||||
Path("."),
|
||||
]
|
||||
|
||||
# 查找可用的财务数据文件
|
||||
fin_files: list[Path] = []
|
||||
for d in candidates:
|
||||
if d.is_dir():
|
||||
fin_files.extend(d.glob("gpcw*.dat"))
|
||||
fin_files.extend(d.glob("gpcw*.zip"))
|
||||
|
||||
if not fin_files:
|
||||
print("未找到历史财务数据文件 (gpcw*.dat 或 gpcw*.zip)")
|
||||
print("\n获取方式:")
|
||||
print(" 1. 使用 TdxClient.get_financial_file_list() 查询可用文件")
|
||||
print(" 2. 使用 TdxClient.download_file() 下载到本地")
|
||||
raise SystemExit(0)
|
||||
|
||||
print(f"找到 {len(fin_files)} 个财务数据文件:")
|
||||
for f in fin_files:
|
||||
print(f" {f}")
|
||||
|
||||
# 读取第一个文件
|
||||
sample = fin_files[0]
|
||||
print(f"\n读取: {sample.name}")
|
||||
records = read_history_financial(sample)
|
||||
|
||||
if not records:
|
||||
print("未读取到数据")
|
||||
raise SystemExit(0)
|
||||
|
||||
print(f"共 {len(records)} 条记录")
|
||||
print(f"\n前 10 条:")
|
||||
print(f" {'代码':>8s} {'市场':>4s} {'报告期':>10s} {'字段数':>6s}")
|
||||
for rec in records[:10]:
|
||||
print(f" {rec.code:>8s} {rec.market.name:>4s} {rec.report_date:>10d} {len(rec.fields):>6d}")
|
||||
|
||||
# 展示一只股票的详细数据
|
||||
if records:
|
||||
rec = records[0]
|
||||
print(f"\n{rec.code} ({rec.market.name}) 报告期 {rec.report_date} 的前 20 个字段:")
|
||||
for i, val in enumerate(rec.fields[:20]):
|
||||
print(f" 字段{i + 1:3d}: {val:>15.4f}")
|
||||
@@ -0,0 +1,83 @@
|
||||
"""演示:从本地通达信目录读取分钟 K 线数据。
|
||||
|
||||
支持三种文件格式:
|
||||
- .5 文件: vipdoc/{sh,sz}/fzline/ (OHLC 为整数÷100)
|
||||
- .lc1 文件: vipdoc/{sh,sz}/fzline/ (OHLC 为浮点数)
|
||||
- .lc5 文件: vipdoc/{sh,sz}/fzline/ (OHLC 为浮点数)
|
||||
|
||||
需要本地已安装通达信并下载过分钟数据。
|
||||
"""
|
||||
|
||||
from xmtdx.offline import (
|
||||
detect_tdx_home,
|
||||
read_5min_bars,
|
||||
read_lc_min_bars,
|
||||
find_5min_bar_file,
|
||||
find_lc1_bar_file,
|
||||
find_lc5_bar_file,
|
||||
)
|
||||
from xmtdx import Market
|
||||
|
||||
home = detect_tdx_home()
|
||||
if home is None:
|
||||
print("未检测到通达信安装目录,请设置 TDX_HOME 环境变量")
|
||||
raise SystemExit(1)
|
||||
|
||||
"""
|
||||
# --- .5 文件 (5 分钟线) ---
|
||||
print("=" * 60)
|
||||
print("5 分钟线 (.5 文件)")
|
||||
print("=" * 60)
|
||||
|
||||
filepath = find_5min_bar_file(Market.SH, "600000")
|
||||
bars = read_5min_bars(filepath)
|
||||
if bars:
|
||||
print(f"共 {len(bars)} 条记录,最后 5 条:")
|
||||
for bar in bars[-5:]:
|
||||
print(
|
||||
f" {bar.year}-{bar.month:02d}-{bar.day:02d} "
|
||||
f"{bar.hour:02d}:{bar.minute:02d} "
|
||||
f"开{bar.open:>7.2f} 高{bar.high:>7.2f} "
|
||||
f"低{bar.low:>7.2f} 收{bar.close:>7.2f} 量{bar.vol:>8.0f}"
|
||||
)
|
||||
else:
|
||||
print("未读取到数据")
|
||||
|
||||
# --- .lc1 文件 (1 分钟线) ---
|
||||
print(f"\n{'=' * 60}")
|
||||
print("1 分钟线 (.lc1 文件)")
|
||||
print("=" * 60)
|
||||
|
||||
filepath = find_lc1_bar_file(Market.SH, "600000")
|
||||
bars = read_lc_min_bars(filepath)
|
||||
if bars:
|
||||
print(f"共 {len(bars)} 条记录,最后 5 条:")
|
||||
for bar in bars[-5:]:
|
||||
print(
|
||||
f" {bar.year}-{bar.month:02d}-{bar.day:02d} "
|
||||
f"{bar.hour:02d}:{bar.minute:02d} "
|
||||
f"开{bar.open:>7.2f} 高{bar.high:>7.2f} "
|
||||
f"低{bar.low:>7.2f} 收{bar.close:>7.2f} 量{bar.vol:>8.0f}"
|
||||
)
|
||||
else:
|
||||
print("未读取到数据")
|
||||
"""
|
||||
|
||||
# --- .lc5 文件 (5 分钟线) ---
|
||||
print(f"\n{'=' * 60}")
|
||||
print("5 分钟线 (.lc5 文件)")
|
||||
print("=" * 60)
|
||||
|
||||
filepath = find_lc5_bar_file(Market.SZ, "002176")
|
||||
bars = read_lc_min_bars(filepath)
|
||||
if bars:
|
||||
print(f"共 {len(bars)} 条记录,最后 5 条:")
|
||||
for bar in bars[-5:]:
|
||||
print(
|
||||
f" {bar.year}-{bar.month:02d}-{bar.day:02d} "
|
||||
f"{bar.hour:02d}:{bar.minute:02d} "
|
||||
f"开{bar.open:>7.2f} 高{bar.high:>7.2f} "
|
||||
f"低{bar.low:>7.2f} 收{bar.close:>7.2f} 量{bar.vol:>8.0f}"
|
||||
)
|
||||
else:
|
||||
print("未读取到数据")
|
||||
Reference in New Issue
Block a user