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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -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