"""``hd_*`` 表结构定义(DDL 单一事实来源)。 设计约束(development-plan.md §3): - 所有表 ``hd_`` 前缀,与 qlib 既有表共存于同一 schema,但不注册进其 Alembic 版本链; - 只使用 ``CREATE TABLE IF NOT EXISTS`` 与 ``ALTER TABLE ... ADD COLUMN``,**从不 DROP/TRUNCATE**; - 汇总表(画像/回测/敏感性)以 ``run_id`` 为逻辑分区键,重跑产生新 run_id,旧结果永久保留。 DDL 是幂等的:``apply_ddl()`` 可反复调用。 """ from __future__ import annotations from dataclasses import dataclass, field CHARSET = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" @dataclass(frozen=True) class Migration: """一次性的结构变更(幂等、可重复执行)。 背景:MySQL/MariaDB 的 UNIQUE 索引**不约束 NULL**(多行 NULL 视为互不相同), 因此凡是参与唯一键的列都不能为 NULL,否则会产生静默重复。 """ name: str sql: str # 仅当该索引**不存在**时执行(用于 ADD UNIQUE KEY) when_index_missing: str | None = None # 仅当该索引**存在**时执行(用于 DROP 冗余索引) when_index_exists: str | None = None # 仅当该列**不存在**时执行(用于 ADD COLUMN) when_column_missing: str | None = None # 仅当该列**当前可为 NULL** 时执行(用于 MODIFY ... NOT NULL) when_column_nullable: str | None = None # 仅当该列当前最大长度**小于**给定值时执行(用于安全加长 VARCHAR) when_column_max_length_lt: tuple[str, int] | None = None # 仅当表为空时执行(杜绝任何数据损失) requires_empty: bool = False @dataclass(frozen=True) class Table: name: str ddl: str comment: str # 后续版本新增的列(用于对既有库做幂等 ADD COLUMN) added_columns: dict[str, str] = field(default_factory=dict) # 结构迁移(永不删除数据) migrations: tuple[Migration, ...] = () # --------------------------------------------------------------------------- # A. 数据同步层 # --------------------------------------------------------------------------- T_DIVIDEND = Table( name="hd_dividend", comment="分红送转全明细(plan.md §14/§30 的数据基座)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_dividend` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `symbol` VARCHAR(12) NOT NULL, `end_date` DATE NOT NULL COMMENT '分红对应报告期', `ann_date` DATE NULL COMMENT '预案公告日', `imp_ann_date` DATE NULL COMMENT '实施公告日 —— PIT 关键字段', `div_proc` VARCHAR(16) NOT NULL COMMENT '预案/股东大会通过/实施/取消', `dedup_key` VARCHAR(80) NOT NULL COMMENT '去重键:NULL 不参与 MySQL 唯一约束,故显式构造', `stk_div` DECIMAL(16,6) NULL COMMENT '每股送转股', `stk_bo_rate` DECIMAL(16,6) NULL COMMENT '每股送股比例', `stk_co_rate` DECIMAL(16,6) NULL COMMENT '每股转增比例', `cash_div` DECIMAL(16,6) NULL COMMENT '每股税后现金分红(元)', `cash_div_tax` DECIMAL(16,6) NULL COMMENT '每股税前现金分红(元)—— 连续性判定口径', `record_date` DATE NULL COMMENT '股权登记日', `ex_date` DATE NULL COMMENT '除权除息日', `pay_date` DATE NULL COMMENT '派息日', `div_listdate` DATE NULL COMMENT '红股上市日', `base_share` DECIMAL(24,4) NULL COMMENT '基准股本(万股)', `source` VARCHAR(16) NOT NULL DEFAULT 'tushare', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_div_key` (`dedup_key`), KEY `ix_hd_div_ex` (`symbol`,`ex_date`), KEY `ix_hd_div_pit` (`symbol`,`imp_ann_date`), KEY `ix_hd_div_proc` (`div_proc`,`ex_date`) ) {CHARSET} COMMENT='分红送转全明细' """, migrations=( Migration( name="dividend_dedup_key", sql=( "ALTER TABLE `hd_dividend` " "ADD COLUMN `dedup_key` VARCHAR(80) NOT NULL DEFAULT '' " "COMMENT '去重键:NULL 不参与 MySQL 唯一约束,故显式构造' AFTER `div_proc`" ), when_column_missing="dedup_key", ), Migration( name="dividend_unique_key", sql="ALTER TABLE `hd_dividend` ADD UNIQUE KEY `uq_hd_div_key` (`dedup_key`)", when_index_missing="uq_hd_div_key", ), Migration( name="dividend_drop_null_key", sql="ALTER TABLE `hd_dividend` DROP INDEX `uq_hd_div`", when_index_exists="uq_hd_div", ), ), ) T_FINA = Table( name="hd_fina_indicator", comment="扩展财务指标(G4;带 announce_date 以支持 PIT)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_fina_indicator` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `symbol` VARCHAR(12) NOT NULL, `end_date` DATE NOT NULL COMMENT '报告期', `ann_date` DATE NOT NULL COMMENT '公告日 —— PIT 关键字段', `eps` DECIMAL(16,6) NULL, `roe` DECIMAL(16,6) NULL COMMENT '净资产收益率 %', `roe_waa` DECIMAL(16,6) NULL COMMENT '加权平均 ROE %', `roe_dt` DECIMAL(16,6) NULL COMMENT '扣非 ROE %', `roa` DECIMAL(16,6) NULL, `roic` DECIMAL(16,6) NULL COMMENT '投入资本回报率 %', `grossprofit_margin` DECIMAL(16,6) NULL COMMENT '毛利率 %', `netprofit_margin` DECIMAL(16,6) NULL COMMENT '净利率 %', `debt_to_assets` DECIMAL(16,6) NULL COMMENT '资产负债率 %', `current_ratio` DECIMAL(16,6) NULL, `quick_ratio` DECIMAL(16,6) NULL, `ocf_to_or` DECIMAL(16,6) NULL COMMENT '经营现金流/营收 %', `ocf_to_opincome` DECIMAL(16,6) NULL COMMENT '经营现金流/经营利润 %', `ocf_to_profit` DECIMAL(16,6) NULL COMMENT '经营现金流/净利润(盈利质量)', `netdebt` DECIMAL(24,4) NULL, `interestdebt` DECIMAL(24,4) NULL, `ebit` DECIMAL(24,4) NULL, `ebitda` DECIMAL(24,4) NULL, `interest_cover` DECIMAL(16,6) NULL COMMENT '利息保障倍数', `fcff` DECIMAL(24,4) NULL COMMENT '企业自由现金流', `fcfe` DECIMAL(24,4) NULL COMMENT '股权自由现金流', `source` VARCHAR(16) NOT NULL DEFAULT 'tushare', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_fina` (`symbol`,`end_date`,`ann_date`), KEY `ix_hd_fina_pit` (`symbol`,`ann_date`), KEY `ix_hd_fina_end` (`end_date`) ) {CHARSET} COMMENT='扩展财务指标(PIT)' """, ) T_CASHFLOW = Table( name="hd_cashflow", comment="现金流量表(G4;FCF 与分红覆盖的来源)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_cashflow` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `symbol` VARCHAR(12) NOT NULL, `end_date` DATE NOT NULL, `ann_date` DATE NOT NULL, `report_type` VARCHAR(4) NOT NULL DEFAULT '1' COMMENT '1=合并报表 2=单季合并 6=母公司;实测全为 1', `n_cashflow_act` DECIMAL(24,4) NULL COMMENT '经营活动现金流净额', `n_cashflow_inv_act` DECIMAL(24,4) NULL COMMENT '投资活动现金流净额', `n_cash_flows_fnc_act` DECIMAL(24,4) NULL COMMENT '筹资活动现金流净额', `c_pay_dist_dpcp_int_exp` DECIMAL(24,4) NULL COMMENT '分配股利/利润/偿付利息支付的现金', `free_cashflow` DECIMAL(24,4) NULL COMMENT 'Tushare 口径自由现金流', `c_fr_sale_sg_ar` DECIMAL(24,4) NULL COMMENT '销售商品收到的现金', `source` VARCHAR(16) NOT NULL DEFAULT 'tushare', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_cf_key` (`symbol`,`end_date`,`ann_date`,`report_type`), KEY `ix_hd_cf_pit` (`symbol`,`ann_date`) ) {CHARSET} COMMENT='现金流量表' """, migrations=( Migration( name="cashflow_report_type_column", sql=( "ALTER TABLE `hd_cashflow` ADD COLUMN `report_type` VARCHAR(4) " "NOT NULL DEFAULT '1' " "COMMENT '1=合并报表 2=单季合并 6=母公司;实测全为 1' AFTER `ann_date`" ), when_column_missing="report_type", ), Migration( name="cashflow_report_type_key", sql="ALTER TABLE `hd_cashflow` ADD UNIQUE KEY `uq_hd_cf_key` " "(`symbol`,`end_date`,`ann_date`,`report_type`)", when_index_missing="uq_hd_cf_key", ), Migration( name="cashflow_drop_old_key", sql="ALTER TABLE `hd_cashflow` DROP INDEX `uq_hd_cf`", when_index_exists="uq_hd_cf", ), ), ) T_BALANCE = Table( name="hd_balancesheet", comment="资产负债表(G4;负债率与净资产判定的来源)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_balancesheet` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `symbol` VARCHAR(12) NOT NULL, `end_date` DATE NOT NULL, `ann_date` DATE NOT NULL, `report_type` VARCHAR(4) NOT NULL DEFAULT '1' COMMENT '1=合并报表 2=单季合并 6=母公司;实测全为 1', `total_assets` DECIMAL(24,4) NULL, `total_liab` DECIMAL(24,4) NULL, `total_hldr_eqy_exc_min_int` DECIMAL(24,4) NULL COMMENT '归母净资产', `total_cur_assets` DECIMAL(24,4) NULL, `total_cur_liab` DECIMAL(24,4) NULL, `money_cap` DECIMAL(24,4) NULL COMMENT '货币资金', `goodwill` DECIMAL(24,4) NULL COMMENT '商誉', `source` VARCHAR(16) NOT NULL DEFAULT 'tushare', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_bs_key` (`symbol`,`end_date`,`ann_date`,`report_type`), KEY `ix_hd_bs_pit` (`symbol`,`ann_date`) ) {CHARSET} COMMENT='资产负债表' """, migrations=( Migration( name="balancesheet_report_type_column", sql=( "ALTER TABLE `hd_balancesheet` ADD COLUMN `report_type` VARCHAR(4) " "NOT NULL DEFAULT '1' " "COMMENT '1=合并报表 2=单季合并 6=母公司;实测全为 1' AFTER `ann_date`" ), when_column_missing="report_type", ), Migration( name="balancesheet_report_type_key", sql="ALTER TABLE `hd_balancesheet` ADD UNIQUE KEY `uq_hd_bs_key` " "(`symbol`,`end_date`,`ann_date`,`report_type`)", when_index_missing="uq_hd_bs_key", ), Migration( name="balancesheet_drop_old_key", sql="ALTER TABLE `hd_balancesheet` DROP INDEX `uq_hd_bs`", when_index_exists="uq_hd_bs", ), ), ) T_INCOME = Table( name="hd_income", comment="利润表(G4;分红支付率的分母来源)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_income` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `symbol` VARCHAR(12) NOT NULL, `end_date` DATE NOT NULL, `ann_date` DATE NOT NULL, `report_type` VARCHAR(4) NOT NULL DEFAULT '1' COMMENT '1=合并报表 2=单季合并 6=母公司;实测全为 1', `total_revenue` DECIMAL(24,4) NULL, `revenue` DECIMAL(24,4) NULL, `operate_profit` DECIMAL(24,4) NULL, `n_income` DECIMAL(24,4) NULL COMMENT '净利润(含少数股东)', `n_income_attr_p` DECIMAL(24,4) NULL COMMENT '归母净利润', `basic_eps` DECIMAL(16,6) NULL, `source` VARCHAR(16) NOT NULL DEFAULT 'tushare', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_inc_key` (`symbol`,`end_date`,`ann_date`,`report_type`), KEY `ix_hd_inc_pit` (`symbol`,`ann_date`) ) {CHARSET} COMMENT='利润表' """, migrations=( Migration( name="income_report_type_column", sql=( "ALTER TABLE `hd_income` ADD COLUMN `report_type` VARCHAR(4) " "NOT NULL DEFAULT '1' " "COMMENT '1=合并报表 2=单季合并 6=母公司;实测全为 1' AFTER `ann_date`" ), when_column_missing="report_type", ), Migration( name="income_report_type_key", sql="ALTER TABLE `hd_income` ADD UNIQUE KEY `uq_hd_inc_key` " "(`symbol`,`end_date`,`ann_date`,`report_type`)", when_index_missing="uq_hd_inc_key", ), Migration( name="income_drop_old_key", sql="ALTER TABLE `hd_income` DROP INDEX `uq_hd_inc`", when_index_exists="uq_hd_inc", ), ), ) T_INDEX_DAILY = Table( name="hd_index_daily", comment="基准指数行情(G5;plan.md §28.4 基准比较)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_index_daily` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `index_code` VARCHAR(16) NOT NULL, `index_name` VARCHAR(64) NULL, `trade_date` DATE NOT NULL, `open` DECIMAL(16,4) NULL, `high` DECIMAL(16,4) NULL, `low` DECIMAL(16,4) NULL, `close` DECIMAL(16,4) NULL, `pre_close` DECIMAL(16,4) NULL, `pct_chg` DECIMAL(12,6) NULL, `vol` DECIMAL(24,4) NULL, `amount` DECIMAL(24,4) NULL, `source` VARCHAR(16) NOT NULL DEFAULT 'tushare', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_idx` (`index_code`,`trade_date`), KEY `ix_hd_idx_date` (`trade_date`) ) {CHARSET} COMMENT='基准指数行情' """, ) T_SUSPEND = Table( name="hd_suspend", comment="停牌记录(G6;成交可行性约束)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_suspend` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `symbol` VARCHAR(12) NOT NULL, `trade_date` DATE NOT NULL, `suspend_type` VARCHAR(4) NOT NULL COMMENT 'S=停牌 R=复牌', `suspend_timing` VARCHAR(48) NULL, `source` VARCHAR(16) NOT NULL DEFAULT 'tushare', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_susp` (`symbol`,`trade_date`,`suspend_type`), KEY `ix_hd_susp_date` (`trade_date`) ) {CHARSET} COMMENT='停牌记录' """, migrations=( Migration( name="suspend_timing_widen", sql=( "ALTER TABLE `hd_suspend` MODIFY COLUMN `suspend_timing` VARCHAR(48) NULL" ), when_column_max_length_lt=("suspend_timing", 48), ), ), ) T_LIMIT = Table( name="hd_limit", comment="涨跌停价(G6;plan.md §29 成交现实性)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_limit` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `symbol` VARCHAR(12) NOT NULL, `trade_date` DATE NOT NULL, `up_limit` DECIMAL(16,4) NULL, `down_limit` DECIMAL(16,4) NULL, `source` VARCHAR(16) NOT NULL DEFAULT 'tushare', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_limit` (`symbol`,`trade_date`), KEY `ix_hd_limit_date` (`trade_date`) ) {CHARSET} COMMENT='涨跌停价' """, ) T_SYNC_LOG = Table( name="hd_sync_log", comment="本项目同步台账(含回补前后行数基线,供审计)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_sync_log` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `job` VARCHAR(64) NOT NULL COMMENT '同步器名', `api` VARCHAR(96) NULL COMMENT 'Tushare 接口名(交错模式为 a+b+c 形式)', `params_json` TEXT NULL, `started_at` DATETIME NOT NULL, `finished_at` DATETIME NULL, `status` VARCHAR(16) NOT NULL DEFAULT 'RUNNING' COMMENT 'RUNNING/OK/FAIL/SKIP', `rows_written` BIGINT NOT NULL DEFAULT 0, `rows_before` BIGINT NULL COMMENT '回补前基线行数', `rows_after` BIGINT NULL COMMENT '回补后行数(审计:只增不减)', `data_start` DATE NULL, `data_end` DATE NULL, `failure_reason` TEXT NULL, PRIMARY KEY (`id`), KEY `ix_hd_synclog_job` (`job`,`started_at`), KEY `ix_hd_synclog_status` (`status`) ) {CHARSET} COMMENT='同步台账' """, migrations=( Migration( name="sync_log_api_widen", sql=( "ALTER TABLE `hd_sync_log` MODIFY COLUMN `api` VARCHAR(96) NULL " "COMMENT 'Tushare 接口名(交错模式为 a+b+c 形式)'" ), when_column_max_length_lt=("api", 96), ), ), ) T_AUDIT = Table( name="hd_data_audit", comment="数据完整性问题清单(P0 验收依据)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_data_audit` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `audit_id` VARCHAR(32) NOT NULL, `check_code` VARCHAR(32) NOT NULL COMMENT 'G1..G6 或自定义检查码', `check_name` VARCHAR(128) NOT NULL, `severity` VARCHAR(16) NOT NULL COMMENT 'OK/WARN/FAIL', `table_name` VARCHAR(64) NULL, `metric_json` TEXT NULL, `message` TEXT NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), KEY `ix_hd_audit_run` (`audit_id`), KEY `ix_hd_audit_code` (`check_code`,`severity`) ) {CHARSET} COMMENT='数据审计结果' """, ) # --------------------------------------------------------------------------- # B. 股票池层 # --------------------------------------------------------------------------- T_UNIVERSE_RUN = Table( name="hd_universe_run", comment="一次筛选运行(股票池快照头)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_universe_run` ( `run_id` VARCHAR(32) NOT NULL, `name` VARCHAR(64) NOT NULL, `asof_date` DATE NOT NULL, `mode` VARCHAR(16) NOT NULL COMMENT 'point_in_time/latest', `config_json` MEDIUMTEXT NOT NULL COMMENT '生效配置快照(含 override 后)', `config_hash` VARCHAR(64) NOT NULL, `data_version` VARCHAR(64) NULL, `code_version` VARCHAR(32) NULL, `member_count` INT NOT NULL DEFAULT 0, `candidate_count` INT NOT NULL DEFAULT 0 COMMENT '进入筛选的候选总数', `stats_json` TEXT NULL COMMENT '逐滤网淘汰统计', `status` VARCHAR(16) NOT NULL DEFAULT 'OK' COMMENT 'OK/WARN/FAIL', `display_name` VARCHAR(128) NULL COMMENT '用户在界面上给这条筛选记录起的名字', `notes` VARCHAR(500) NULL COMMENT '用户备注', `archived_at` DATETIME NULL COMMENT '归档时间;NULL=未归档', `deleted_at` DATETIME NULL COMMENT '软删除时间;NULL=未删除(不做物理删除)', `created_at` DATETIME NOT NULL, PRIMARY KEY (`run_id`), KEY `ix_hd_uni_run_asof` (`asof_date`), KEY `ix_hd_uni_run_hash` (`config_hash`), KEY `ix_hd_uni_run_state` (`deleted_at`,`archived_at`,`created_at`) ) {CHARSET} COMMENT='股票池运行头' """, added_columns={ "display_name": "VARCHAR(128) NULL COMMENT '用户在界面上给这条筛选记录起的名字'", "notes": "VARCHAR(500) NULL COMMENT '用户备注'", "archived_at": "DATETIME NULL COMMENT '归档时间;NULL=未归档'", "deleted_at": "DATETIME NULL COMMENT '软删除时间;NULL=未删除(不做物理删除)'", }, migrations=( Migration( name="universe_run_state_index", sql=( "ALTER TABLE `hd_universe_run` ADD KEY `ix_hd_uni_run_state` " "(`deleted_at`,`archived_at`,`created_at`)" ), when_index_missing="ix_hd_uni_run_state", ), ), ) T_UNIVERSE_MEMBER = Table( name="hd_universe_member", comment="股票池成员 + 逐滤网命中留痕(可回答「为什么没选上」)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_universe_member` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `symbol` VARCHAR(12) NOT NULL, `name` VARCHAR(64) NULL, `industry` VARCHAR(64) NULL, `passed` TINYINT(1) NOT NULL COMMENT '1=入选 0=被淘汰', `fail_stage` VARCHAR(32) NULL COMMENT '首个未通过的滤网', `fail_reason` VARCHAR(255) NULL, `values_json` TEXT NULL COMMENT '各滤网实际取值快照', `filter_json` TEXT NULL COMMENT '各滤网通过位', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_uni_mem` (`run_id`,`symbol`), KEY `ix_hd_uni_mem_pass` (`run_id`,`passed`), KEY `ix_hd_uni_mem_sym` (`symbol`) ) {CHARSET} COMMENT='股票池成员留痕' """, ) T_DAILY_UNIVERSE = Table( name="hd_daily_universe", comment="每日动态股票池成员(daily 模式「每日选股」的留痕)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_daily_universe` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL COMMENT '所属回测 run_id', `trade_date` DATE NOT NULL COMMENT '该交易日的选股结果', `symbol` VARCHAR(12) NOT NULL, `name` VARCHAR(64) NULL, `industry` VARCHAR(64) NULL, `dividend_yield` DECIMAL(18,8) NULL COMMENT '入选当日股息率(池内排序口径)', `total_mv` DECIMAL(24,4) NULL, `roe_avg` DECIMAL(18,8) NULL, `listed_count` INT NULL COMMENT '当日市场候选数(未预剪枝)', `candidate_count` INT NULL COMMENT '当日实际参与筛选的候选数(已预剪枝)', `values_json` TEXT NULL COMMENT '入选时的关键因子快照', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_daily_uni` (`run_id`,`trade_date`,`symbol`), KEY `ix_hd_daily_uni_date` (`trade_date`), KEY `ix_hd_daily_uni_sym` (`symbol`,`trade_date`) ) {CHARSET} COMMENT='每日动态股票池成员' """, # 建表时已含上述列;这里的 added_columns 是为了让**先建表、后加列**的库 # 也能幂等补上(CREATE TABLE IF NOT EXISTS 不会修改已存在的表)。 added_columns={ "listed_count": "INT NULL COMMENT '当日市场候选数(未预剪枝)'", }, ) # --------------------------------------------------------------------------- # C. 因子与画像层 # --------------------------------------------------------------------------- T_FACTOR_SNAPSHOT = Table( name="hd_factor_snapshot", comment="决策时点因子值(长表;避免 1.9 亿行日频面板,见 plan §3.2)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_factor_snapshot` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `snapshot_id` VARCHAR(32) NOT NULL COMMENT '一次快照(通常绑定 universe run 或 rebalance 日)', `asof_date` DATE NOT NULL, `symbol` VARCHAR(12) NOT NULL, `factor_code` VARCHAR(48) NOT NULL, `value` DECIMAL(24,8) NULL, `source` VARCHAR(32) NULL COMMENT 'computed/dv_ttm/daily_basic 等口径来源', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_fac` (`snapshot_id`,`symbol`,`factor_code`), KEY `ix_hd_fac_asof` (`asof_date`,`factor_code`), KEY `ix_hd_fac_sym` (`symbol`,`factor_code`,`asof_date`) ) {CHARSET} COMMENT='决策时点因子快照' """, ) T_PROFILE_RUN = Table( name="hd_profile_run", comment="画像运行头", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_profile_run` ( `run_id` VARCHAR(32) NOT NULL, `name` VARCHAR(64) NOT NULL, `asof_date` DATE NOT NULL, `universe_run_id` VARCHAR(32) NULL COMMENT '来源股票池 run', `config_json` MEDIUMTEXT NOT NULL, `config_hash` VARCHAR(64) NOT NULL, `data_version` VARCHAR(64) NULL, `symbol_count` INT NOT NULL DEFAULT 0, `status` VARCHAR(16) NOT NULL DEFAULT 'OK', `created_at` DATETIME NOT NULL, PRIMARY KEY (`run_id`), KEY `ix_hd_prof_run_asof` (`asof_date`), KEY `ix_hd_prof_run_hash` (`config_hash`) ) {CHARSET} COMMENT='画像运行头' """, ) T_PROFILE_STAT = Table( name="hd_profile_stat", comment="逐股逐指标历史分布统计(plan.md §9 明确要求 P10/P25/P50/P75/P90)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_profile_stat` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `symbol` VARCHAR(12) NOT NULL, `metric_code` VARCHAR(48) NOT NULL, `window_years` INT NOT NULL DEFAULT 0 COMMENT '0=全历史;NULL 会绕过唯一约束故禁用', `n_obs` INT NOT NULL DEFAULT 0, `min_value` DECIMAL(24,8) NULL, `max_value` DECIMAL(24,8) NULL, `mean_value` DECIMAL(24,8) NULL, `median_value` DECIMAL(24,8) NULL, `std_value` DECIMAL(24,8) NULL, `p10` DECIMAL(24,8) NULL, `p25` DECIMAL(24,8) NULL, `p50` DECIMAL(24,8) NULL, `p75` DECIMAL(24,8) NULL, `p90` DECIMAL(24,8) NULL, `extra_percentiles_json` TEXT NULL COMMENT '配置中超出默认 5 档的分位', `current_value` DECIMAL(24,8) NULL COMMENT 'asof 当日取值', `current_percentile` DECIMAL(12,6) NULL COMMENT '当前值在历史分布中的分位 %', `status` VARCHAR(16) NOT NULL DEFAULT 'OK' COMMENT 'OK/INSUFFICIENT', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_prof_stat` (`run_id`,`symbol`,`metric_code`,`window_years`), KEY `ix_hd_prof_stat_sym` (`symbol`,`metric_code`), KEY `ix_hd_prof_stat_q` (`run_id`,`metric_code`,`current_percentile`) ) {CHARSET} COMMENT='个股历史分布统计' """, migrations=( Migration( name="profile_stat_window_not_null", sql=( "ALTER TABLE `hd_profile_stat` MODIFY COLUMN `window_years` " "INT NOT NULL DEFAULT 0 COMMENT '0=全历史;NULL 会绕过唯一约束故禁用'" ), when_column_nullable="window_years", ), ), ) T_PROFILE_SERIES = Table( name="hd_profile_series", comment="画像时间序列(供 HTML 画图;仅筛选后股票,体积可控)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_profile_series` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `symbol` VARCHAR(12) NOT NULL, `metric_code` VARCHAR(48) NOT NULL, `series_date` DATE NOT NULL, `value` DECIMAL(24,8) NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_prof_ser` (`run_id`,`symbol`,`metric_code`,`series_date`), KEY `ix_hd_prof_ser_sym` (`run_id`,`symbol`,`metric_code`) ) {CHARSET} COMMENT='个股画像序列' """, ) T_PROFILE_SCORE = Table( name="hd_profile_score", comment="安全边际分项得分(plan.md §15)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_profile_score` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `symbol` VARCHAR(12) NOT NULL, `score_code` VARCHAR(48) NOT NULL COMMENT 'dividend_yield/valuation/financial_quality/balance_sheet/dividend_quality/composite', `score` DECIMAL(12,6) NULL, `detail_json` TEXT NULL COMMENT '分项明细与所用锚点', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_prof_score` (`run_id`,`symbol`,`score_code`), KEY `ix_hd_prof_score_v` (`run_id`,`score_code`,`score`) ) {CHARSET} COMMENT='安全边际得分' """, ) # --------------------------------------------------------------------------- # D. 策略层 # --------------------------------------------------------------------------- T_STRATEGY = Table( name="hd_strategy", comment="策略身份与版本(plan.md §38/§39 生命周期)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_strategy` ( `strategy_id` VARCHAR(32) NOT NULL, `name` VARCHAR(64) NOT NULL, `version` VARCHAR(16) NOT NULL, `status` VARCHAR(16) NOT NULL DEFAULT 'DRAFT' COMMENT 'DRAFT/RESEARCH/BACKTEST/VALIDATED/PAPER/LIVE/ARCHIVED', `description` VARCHAR(500) NOT NULL DEFAULT '', `yaml_path` VARCHAR(255) NULL, `config_json` MEDIUMTEXT NOT NULL, `config_hash` VARCHAR(64) NOT NULL, `parent_id` VARCHAR(32) NULL COMMENT '上一版本 strategy_id', `spec_type` VARCHAR(16) NOT NULL DEFAULT 'yaml', `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`strategy_id`,`version`), UNIQUE KEY `uq_hd_strat_hash` (`config_hash`), KEY `ix_hd_strat_name` (`name`) ) {CHARSET} COMMENT='策略版本' """, ) T_STRATEGY_PARAM = Table( name="hd_strategy_param", comment="策略参数扁平化(path → value),便于跨版本 diff 与敏感性扫描", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_strategy_param` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `strategy_id` VARCHAR(32) NOT NULL, `version` VARCHAR(16) NOT NULL, `param_path` VARCHAR(255) NOT NULL, `param_value` VARCHAR(500) NULL, `value_type` VARCHAR(16) NULL COMMENT 'int/float/str/bool/null', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_sparam` (`strategy_id`,`version`,`param_path`), KEY `ix_hd_sparam_path` (`param_path`) ) {CHARSET} COMMENT='策略参数扁平表' """, ) # --------------------------------------------------------------------------- # E. 回测层 # --------------------------------------------------------------------------- T_BT_RUN = Table( name="hd_backtest_run", comment="回测运行头(plan.md §47 原则 6:可复现四元组)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_backtest_run` ( `run_id` VARCHAR(32) NOT NULL, `strategy_id` VARCHAR(32) NOT NULL, `strategy_version` VARCHAR(16) NOT NULL, `mode` VARCHAR(16) NOT NULL DEFAULT 'single' COMMENT 'single/in_sample/out_of_sample/walkforward_window', `wf_id` VARCHAR(32) NULL, `window_index` INT NULL, `start_date` DATE NOT NULL, `end_date` DATE NOT NULL, `initial_capital` DECIMAL(24,4) NOT NULL, `final_capital` DECIMAL(24,4) NULL, `total_dividend_net` DECIMAL(24,4) NOT NULL DEFAULT 0 COMMENT '累计现金分红(税后)', `total_dividend_tax` DECIMAL(24,4) NOT NULL DEFAULT 0 COMMENT '累计代扣红利税', `total_fees` DECIMAL(24,4) NOT NULL DEFAULT 0 COMMENT '累计交易费用', `cash_final` DECIMAL(24,4) NULL COMMENT '期末现金(用于资金对账)', `strategy_config_json` MEDIUMTEXT NOT NULL, `config_hash` VARCHAR(64) NOT NULL, `backtest_config_json` TEXT NULL, `cost_json` TEXT NULL, `data_version` VARCHAR(64) NULL, `code_version` VARCHAR(32) NULL, `seed` INT NULL, `status` VARCHAR(16) NOT NULL DEFAULT 'OK' COMMENT 'OK/FAIL/PARTIAL', `unimplemented_json` TEXT NULL COMMENT '未建模部分显式声明(如涨跌停近似)', `display_name` VARCHAR(128) NULL COMMENT '用户命名', `notes` VARCHAR(500) NULL COMMENT '用户备注', `universe_run_id` VARCHAR(32) NULL COMMENT '关联的股票池筛选记录(--universe-run 指定时写入)', `archived_at` DATETIME NULL COMMENT '归档时间;NULL=未归档', `deleted_at` DATETIME NULL COMMENT '软删除时间;NULL=未删除', `created_at` DATETIME NOT NULL, PRIMARY KEY (`run_id`), KEY `ix_hd_bt_strategy` (`strategy_id`,`strategy_version`), KEY `ix_hd_bt_wf` (`wf_id`,`window_index`), KEY `ix_hd_bt_period` (`start_date`,`end_date`), KEY `ix_hd_bt_universe` (`universe_run_id`), KEY `ix_hd_bt_state` (`deleted_at`,`archived_at`,`created_at`) ) {CHARSET} COMMENT='回测运行头' """, # 既有库通过 added_columns 幂等补列(CREATE TABLE IF NOT EXISTS 不会改已存在的表) added_columns={ "total_dividend_net": "DECIMAL(24,4) NOT NULL DEFAULT 0 COMMENT '累计现金分红(税后)'", "total_dividend_tax": "DECIMAL(24,4) NOT NULL DEFAULT 0 COMMENT '累计代扣红利税'", "total_fees": "DECIMAL(24,4) NOT NULL DEFAULT 0 COMMENT '累计交易费用'", "cash_final": "DECIMAL(24,4) NULL COMMENT '期末现金(用于资金对账)'", "display_name": "VARCHAR(128) NULL COMMENT '用户命名'", "notes": "VARCHAR(500) NULL COMMENT '用户备注'", "universe_run_id": "VARCHAR(32) NULL COMMENT '关联的股票池筛选记录'", "archived_at": "DATETIME NULL COMMENT '归档时间;NULL=未归档'", "deleted_at": "DATETIME NULL COMMENT '软删除时间;NULL=未删除'", }, migrations=( Migration( name="bt_run_universe_index", sql="ALTER TABLE `hd_backtest_run` ADD KEY `ix_hd_bt_universe` (`universe_run_id`)", when_index_missing="ix_hd_bt_universe", ), Migration( name="bt_run_state_index", sql=( "ALTER TABLE `hd_backtest_run` ADD KEY `ix_hd_bt_state` " "(`deleted_at`,`archived_at`,`created_at`)" ), when_index_missing="ix_hd_bt_state", ), ), ) T_BT_EQUITY = Table( name="hd_backtest_equity", comment="逐日净值曲线(含基准对齐,plan.md §28.4)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_backtest_equity` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `trade_date` DATE NOT NULL, `nav` DECIMAL(24,8) NOT NULL COMMENT '组合净值(初始=1)', `total_value` DECIMAL(24,4) NOT NULL, `cash` DECIMAL(24,4) NOT NULL, `position_value` DECIMAL(24,4) NOT NULL, `daily_return` DECIMAL(18,10) NULL, `cum_return` DECIMAL(18,10) NULL, `drawdown` DECIMAL(18,10) NULL COMMENT '相对历史高点回撤(负值)', `holding_count` INT NOT NULL DEFAULT 0, `benchmark_code` VARCHAR(16) NULL, `benchmark_close` DECIMAL(16,4) NULL, `benchmark_nav` DECIMAL(24,8) NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_bt_eq` (`run_id`,`trade_date`), KEY `ix_hd_bt_eq_date` (`trade_date`) ) {CHARSET} COMMENT='回测净值曲线' """, ) T_BT_POSITION = Table( name="hd_backtest_position", comment="逐日持仓快照", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_backtest_position` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `trade_date` DATE NOT NULL, `symbol` VARCHAR(12) NOT NULL, `quantity` DECIMAL(20,4) NOT NULL, `avg_cost` DECIMAL(16,4) NULL, `close` DECIMAL(16,4) NULL, `market_value` DECIMAL(24,4) NULL, `weight` DECIMAL(12,8) NULL, `unrealized_pnl` DECIMAL(24,4) NULL, `holding_days` INT NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_bt_pos` (`run_id`,`trade_date`,`symbol`), KEY `ix_hd_bt_pos_sym` (`run_id`,`symbol`) ) {CHARSET} COMMENT='逐日持仓' """, ) T_BT_TRADE = Table( name="hd_backtest_trade", comment="逐笔成交(plan.md §32;reason_json 为 AI 可解释性关键)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_backtest_trade` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `trade_id` VARCHAR(32) NOT NULL, `symbol` VARCHAR(12) NOT NULL, `signal_date` DATE NOT NULL, `execution_date` DATE NOT NULL, `side` VARCHAR(4) NOT NULL COMMENT 'BUY/SELL', `price` DECIMAL(16,4) NOT NULL, `quantity` DECIMAL(20,4) NOT NULL, `amount` DECIMAL(24,4) NOT NULL, `commission` DECIMAL(16,4) NOT NULL DEFAULT 0, `stamp_tax` DECIMAL(16,4) NOT NULL DEFAULT 0, `transfer_fee` DECIMAL(16,4) NOT NULL DEFAULT 0, `slippage_cost` DECIMAL(16,4) NOT NULL DEFAULT 0, `total_cost` DECIMAL(16,4) NOT NULL DEFAULT 0, `realized_pnl` DECIMAL(24,4) NULL, `holding_days` INT NULL, `reason_json` TEXT NULL COMMENT 'BUY/SELL 的完整理由快照', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_bt_trade` (`run_id`,`trade_id`), KEY `ix_hd_bt_trade_sym` (`run_id`,`symbol`,`execution_date`), KEY `ix_hd_bt_trade_date` (`execution_date`) ) {CHARSET} COMMENT='回测成交流水' """, ) T_BT_SIGNAL = Table( name="hd_backtest_signal", comment="逐信号(含 executed/skip_reason,用于区分「想买」与「买到了」)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_backtest_signal` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `symbol` VARCHAR(12) NOT NULL, `signal_date` DATE NOT NULL, `signal_type` VARCHAR(8) NOT NULL COMMENT 'BUY/SELL/HOLD/WATCH', `yield_value` DECIMAL(18,8) NULL COMMENT '触发时的股息率', `yield_percentile` DECIMAL(12,6) NULL COMMENT '触发时的历史分位 %', `target_weight` DECIMAL(12,8) NULL, `price` DECIMAL(16,4) NULL, `executed` TINYINT(1) NOT NULL DEFAULT 0, `skip_reason` VARCHAR(64) NULL COMMENT 'LIMIT_UP/LIMIT_DOWN/SUSPENDED/NO_CASH/MIN_COMMISSION', `reason_json` TEXT NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), KEY `ix_hd_bt_sig` (`run_id`,`symbol`,`signal_date`), KEY `ix_hd_bt_sig_type` (`run_id`,`signal_type`,`executed`) ) {CHARSET} COMMENT='回测信号流水' """, ) T_BT_METRIC = Table( name="hd_backtest_metric", comment="绩效指标长表(scope 区分样本内/外/窗口;plan.md §28)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_backtest_metric` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `run_id` VARCHAR(32) NOT NULL, `scope` VARCHAR(24) NOT NULL COMMENT 'all/train/test/window_k/year_YYYY', `category` VARCHAR(16) NOT NULL COMMENT 'return/risk/trade/benchmark', `metric_code` VARCHAR(48) NOT NULL, `metric_value` DECIMAL(24,8) NULL, `benchmark_code` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '空串=策略自身;NULL 会绕过唯一约束故禁用', `unit` VARCHAR(16) NULL COMMENT 'ratio/pct/money/count/days', `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_bt_metric` (`run_id`,`scope`,`metric_code`,`benchmark_code`), KEY `ix_hd_bt_metric_code` (`metric_code`), KEY `ix_hd_bt_metric_scope` (`run_id`,`scope`,`category`) ) {CHARSET} COMMENT='回测绩效指标' """, migrations=( Migration( name="bt_metric_benchmark_not_null", sql=( "ALTER TABLE `hd_backtest_metric` MODIFY COLUMN `benchmark_code` " "VARCHAR(16) NOT NULL DEFAULT '' " "COMMENT '空串=策略自身;NULL 会绕过唯一约束故禁用'" ), when_column_nullable="benchmark_code", ), ), ) T_WF_RUN = Table( name="hd_walkforward_run", comment="Walk-forward 运行头(plan.md §23)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_walkforward_run` ( `wf_id` VARCHAR(32) NOT NULL, `strategy_id` VARCHAR(32) NOT NULL, `strategy_version` VARCHAR(16) NOT NULL, `config_json` MEDIUMTEXT NOT NULL, `config_hash` VARCHAR(64) NOT NULL, `scheme` VARCHAR(16) NOT NULL COMMENT 'rolling/expanding', `train_years` INT NOT NULL, `test_years` INT NOT NULL, `step_months` INT NOT NULL, `window_count` INT NOT NULL DEFAULT 0, `data_version` VARCHAR(64) NULL, `code_version` VARCHAR(32) NULL, `status` VARCHAR(16) NOT NULL DEFAULT 'OK', `created_at` DATETIME NOT NULL, PRIMARY KEY (`wf_id`), KEY `ix_hd_wf_strategy` (`strategy_id`,`strategy_version`) ) {CHARSET} COMMENT='Walk-forward 运行头' """, ) T_WF_WINDOW = Table( name="hd_walkforward_window", comment="Walk-forward 每个窗口(train 定参 → test 冻结,plan.md §25)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_walkforward_window` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `wf_id` VARCHAR(32) NOT NULL, `window_index` INT NOT NULL, `train_start` DATE NOT NULL, `train_end` DATE NOT NULL, `test_start` DATE NOT NULL, `test_end` DATE NOT NULL, `frozen_params_json` TEXT NOT NULL COMMENT '由 train 段确定、在 test 段冻结的参数', `train_run_id` VARCHAR(32) NULL, `test_run_id` VARCHAR(32) NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_wf_win` (`wf_id`,`window_index`), KEY `ix_hd_wf_win_period` (`test_start`,`test_end`) ) {CHARSET} COMMENT='Walk-forward 窗口' """, ) # --------------------------------------------------------------------------- # F. 分析层 # --------------------------------------------------------------------------- T_SENS_RUN = Table( name="hd_sensitivity_run", comment="参数敏感性扫描(plan.md §27 过拟合控制)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_sensitivity_run` ( `sens_id` VARCHAR(32) NOT NULL, `base_strategy_id` VARCHAR(32) NOT NULL, `base_version` VARCHAR(16) NOT NULL, `sweep_json` TEXT NOT NULL COMMENT '扫描网格定义', `point_count` INT NOT NULL DEFAULT 0, `period_start` DATE NULL, `period_end` DATE NULL, `data_version` VARCHAR(64) NULL, `code_version` VARCHAR(32) NULL, `status` VARCHAR(16) NOT NULL DEFAULT 'OK', `created_at` DATETIME NOT NULL, PRIMARY KEY (`sens_id`), KEY `ix_hd_sens_strategy` (`base_strategy_id`,`base_version`) ) {CHARSET} COMMENT='敏感性扫描头' """, ) T_SENS_POINT = Table( name="hd_sensitivity_point", comment="敏感性扫描的每个参数组合及其绩效", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_sensitivity_point` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `sens_id` VARCHAR(32) NOT NULL, `point_index` INT NOT NULL, `param_values_json` TEXT NOT NULL, `param_key` VARCHAR(128) NULL COMMENT '单参数扫描时的主键(便于画曲线)', `run_id` VARCHAR(32) NULL COMMENT '对应回测 run', `cagr` DECIMAL(18,8) NULL, `total_return` DECIMAL(18,8) NULL, `max_drawdown` DECIMAL(18,8) NULL, `sharpe` DECIMAL(18,8) NULL, `calmar` DECIMAL(18,8) NULL, `sortino` DECIMAL(18,8) NULL, `trade_count` INT NULL, `turnover` DECIMAL(18,8) NULL, `extra_json` TEXT NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_hd_sens_pt` (`sens_id`,`point_index`), KEY `ix_hd_sens_pt_key` (`sens_id`,`param_key`) ) {CHARSET} COMMENT='敏感性扫描点' """, ) # --------------------------------------------------------------------------- # G. 报告层 # --------------------------------------------------------------------------- T_REPORT = Table( name="hd_report", comment="报告元数据(含 SQL 溯源,保证页面数字可复现)", ddl=f""" CREATE TABLE IF NOT EXISTS `hd_report` ( `report_id` VARCHAR(32) NOT NULL, `report_type` VARCHAR(24) NOT NULL COMMENT 'audit/universe/profile/backtest/walkforward/sensitivity/strategy/index', `title` VARCHAR(255) NOT NULL, `html_path` VARCHAR(500) NOT NULL, `source_run_ids` VARCHAR(500) NULL COMMENT '逗号分隔的来源 run_id', `context_json` MEDIUMTEXT NULL COMMENT '渲染上下文快照', `sql_provenance_json` MEDIUMTEXT NULL COMMENT '页面数据的来源 SQL(可复现性)', `data_version` VARCHAR(64) NULL, `generated_at` DATETIME NOT NULL, `file_bytes` BIGINT NULL, PRIMARY KEY (`report_id`), KEY `ix_hd_report_type` (`report_type`,`generated_at`) ) {CHARSET} COMMENT='报告元数据' """, ) ALL_TABLES: tuple[Table, ...] = ( # A. 数据同步层 T_DIVIDEND, T_FINA, T_CASHFLOW, T_BALANCE, T_INCOME, T_INDEX_DAILY, T_SUSPEND, T_LIMIT, T_SYNC_LOG, T_AUDIT, # B. 股票池层 T_UNIVERSE_RUN, T_UNIVERSE_MEMBER, T_DAILY_UNIVERSE, # C. 因子与画像层 T_FACTOR_SNAPSHOT, T_PROFILE_RUN, T_PROFILE_STAT, T_PROFILE_SERIES, T_PROFILE_SCORE, # D. 策略层 T_STRATEGY, T_STRATEGY_PARAM, # E. 回测层 T_BT_RUN, T_BT_EQUITY, T_BT_POSITION, T_BT_TRADE, T_BT_SIGNAL, T_BT_METRIC, T_WF_RUN, T_WF_WINDOW, # F. 分析层 T_SENS_RUN, T_SENS_POINT, # G. 报告层 T_REPORT, ) TABLE_NAMES: tuple[str, ...] = tuple(t.name for t in ALL_TABLES)