Skip to content

fix: Tushare 日线下载 stock_daily 列数不匹配并补充单元测试#33

Open
DrIsDr wants to merge 16 commits into
quant-king299:mainfrom
DrIsDr:fix/stock_daily-column-mismatch
Open

fix: Tushare 日线下载 stock_daily 列数不匹配并补充单元测试#33
DrIsDr wants to merge 16 commits into
quant-king299:mainfrom
DrIsDr:fix/stock_daily-column-mismatch

Conversation

@DrIsDr

@DrIsDr DrIsDr commented Jul 11, 2026

Copy link
Copy Markdown

问题描述

在 GUI 中运行 Tushare 日线下载时,DuckDB 报错:

Binder Error: table "excluded" has 12 columns available but 14 columns specified

原因是 stock_daily 表定义包含 adjust_typefactor 两列(共 14 列),但实际插入的 DataFrame 只有 12 列,导致 INSERT OR REPLACE INTO stock_daily SELECT * FROM _tmp_daily 列数不匹配。

修复内容

  1. 修复列数不匹配

    • stock_daily 建表语句中删除 adjust_typefactor 两列
    • 主键从 (stock_code, date, period, adjust_type) 改为 (stock_code, date, period)
    • INSERT 语句显式指定 12 个目标列
  2. 提取可测试的保存逻辑

    • 新增 TushareDownloadThread._save_daily_dataframe(conn, df) 静态方法
    • INSERT 列名从 DataFrame 列动态推导,避免 SQL 列名与 DataFrame 列顺序硬耦合
    • created_at 不再写入 INSERT,新建行使用 DEFAULT,冲突替换时保留原值

测试

新增单元测试目录:

unit_tests/gui_app/widgets/test_tushare_data_widget.py

覆盖 5 个场景:

  • 正确写入 12 列数据
  • 新表不含已废弃的 adjust_type / factor
  • DataFrame 列顺序打乱后仍能按列名正确写入
  • 冲突替换时 created_at 保留,updated_at 刷新
  • DataFrame 中多余列不干扰写入

运行结果:5 passed

其他变更

  • .gitignore:新增 unit_tests/ 例外规则
  • pyproject.toml:新增 pytest 配置,指定 testpaths = ["unit_tests"]

DrIsDr added 4 commits July 11, 2026 08:24
Tushare daily 接口返回的 DataFrame 使用 ts_code 列表示股票代码,
但 get_price 只重命名了 trade_date/vol,未将 ts_code 重命名为 symbol。
后续按 ['symbol', 'date', ...] 选取列时触发 KeyError,被外层捕获后返回 None,
使得 HybridDataManager 在 QMT 无数据时 fallback 到 Tushare 也失败,
最终导致回测获取不到价格、无法产生交易。
stock_daily 表定义包含 adjust_type、factor 两列(共14列),
但实际插入的 DataFrame 只有12列,导致 DuckDB 在执行
INSERT OR REPLACE ... SELECT * 时因 excluded 伪表列数不匹配而报错。

修复:
- 删除 stock_daily 表中无用的 adjust_type、factor 列
- 主键从 (stock_code, date, period, adjust_type) 改为 (stock_code, date, period)
- INSERT 语句显式指定12个目标列,兼容已存在的旧14列表
修复 Code Review 发现的两个问题:
1. INSERT 列顺序与 DataFrame 顺序强耦合:将保存逻辑提取为
   TushareDownloadThread._save_daily_dataframe(),INSERT 列名从
   DataFrame 列动态推导,避免硬编码列名导致的数据错位风险。
2. created_at 在冲突替换时被覆盖:INSERT 不再包含 created_at,
   新建行使用 DEFAULT CURRENT_TIMESTAMP,冲突行保留原 created_at。

新增测试:
- unit_tests/gui_app/widgets/test_tushare_data_widget.py
- 覆盖正确写入、无废弃列、列顺序无关、created_at 保留、忽略多余列

同时更新 .gitignore 与 pyproject.toml,使用 unit_tests/ 作为测试目录。
Copilot AI review requested due to automatic review settings July 11, 2026 03:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 修复了 GUI 中 Tushare 日线下载写入 DuckDB 时 stock_daily 表列数与写入 DataFrame 列数不一致导致的插入失败问题,并将日线保存逻辑抽取为可单测的静态方法,同时补齐单元测试与 pytest 配置以覆盖关键写入场景。

Changes:

  • 修订 stock_daily 建表结构与主键,移除遗留列,并将写入改为显式列插入以避免列数不匹配。
  • 抽取 _save_daily_dataframe(conn, df),统一并测试按列名对齐写入、忽略多余列、以及冲突替换时的时间戳行为。
  • 新增 unit_tests/ 测试目录与 pytest 配置,使测试可在仓库内稳定运行。

Reviewed changes

Copilot reviewed 3 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
gui_app/widgets/tushare_data_widget.py 修复 stock_daily 建表/写入逻辑,新增可测试的 _save_daily_dataframe 保存方法
unit_tests/gui_app/widgets/test_tushare_data_widget.py 新增 5 个场景的单元测试覆盖日线写入与冲突替换行为
unit_tests/gui_app/widgets/init.py 使测试目录成为可导入包结构的一部分
unit_tests/gui_app/init.py 使测试目录成为可导入包结构的一部分
unit_tests/init.py 使测试目录成为可导入包结构的一部分
pyproject.toml 增加 pytest 配置(testpaths 指向 unit_tests)
.gitignore 增加对 unit_tests 目录的例外规则,避免被忽略

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gui_app/widgets/tushare_data_widget.py Outdated
将日线 DataFrame 保存到 stock_daily 表。

设计要点:
1. INSERT 列名从 DataFrame 列动态推导,避免 SQL 列名与 DataFrame 列顺序硬耦合。
Comment on lines 1063 to 1067
amount DOUBLE,
adjust_type VARCHAR DEFAULT 'none',
factor DOUBLE DEFAULT 1.0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (stock_code, date, period, adjust_type)
PRIMARY KEY (stock_code, date, period)
)
DrIsDr added 12 commits July 11, 2026 11:30
SmallCapStrategy 和 SmallCapStrategyV2 中使用了 logger.info,
但模块未导入 logging 也未定义 logger,导致实例化时抛出 NameError。
新增测试验证:
- 模块已正确定义 logger
- SmallCapStrategy 实例化时不会因缺失 logger 而抛 NameError
- 自定义参数实例化时 logger 正常工作

同时配置 unit_tests/ 目录与 pytest:
- .gitignore 新增 unit_tests/ 例外规则
- pyproject.toml 新增 pytest 配置
- 验证 is_available() 仅依据 is_connected 返回状态
- 验证 is_available() 不再调用 xtdata API 做真实探测
- 验证 close() 正确重置连接状态、清空缓存、重置 _last_used
- 配置 pytest 入口:pyproject.toml [tool.pytest.ini_options]
- gitignore 保留 unit_tests/ 目录,忽略 .worktrees/
回测引擎记录的交易方向为 'long'/'short',但展示代码按 'buy'/'sell' 过滤,导致买入/卖出次数始终为 0。
stock_daily 表定义包含 adjust_type、factor 两列(共14列),
但实际插入的 DataFrame 只有12列,导致 DuckDB 在执行
INSERT OR REPLACE ... SELECT * 时因 excluded 伪表列数不匹配而报错。

修复:
- 删除 stock_daily 表中无用的 adjust_type、factor 列
- 主键从 (stock_code, date, period, adjust_type) 改为 (stock_code, date, period)
- INSERT 语句显式指定12个目标列,兼容已存在的旧14列表
修复 Code Review 发现的两个问题:
1. INSERT 列顺序与 DataFrame 顺序强耦合:将保存逻辑提取为
   TushareDownloadThread._save_daily_dataframe(),INSERT 列名从
   DataFrame 列动态推导,避免硬编码列名导致的数据错位风险。
2. created_at 在冲突替换时被覆盖:INSERT 不再包含 created_at,
   新建行使用 DEFAULT CURRENT_TIMESTAMP,冲突行保留原 created_at。

新增测试:
- unit_tests/gui_app/widgets/test_tushare_data_widget.py
- 覆盖正确写入、无废弃列、列顺序无关、created_at 保留、忽略多余列

同时更新 .gitignore 与 pyproject.toml,使用 unit_tests/ 作为测试目录。
- 将 docstring 中“动态推导 INSERT 列名”改为“预定义白名单 data_cols”,与实现一致。
- 移除 local_data_manager_widget.py 中 stock_daily 写入残留的 adjust_type 列,
  避免对新版 stock_daily 表(无主键 adjust_type)写入时报 column not found。
…-mismatch

# Conflicts:
#	.gitignore
#	gui_app/widgets/tushare_data_widget.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants