02-数据架构与表结构设计
围绕趋势定投分析需求设计表结构。核心原则:按标的类型分表、字段含义明确、索引贴合查询模式、历史深度按数据类型分级。
整体架构
数据库按业务域分八组,共 23 张表:
分组逻辑:维度表(dim_)存标的基础信息,事实表存时间序列数据。分析查询时先从维度表筛标的,再关联事实表取数据。
标的维度表
dim_index(指数基本信息)
CREATE TABLE IF NOT EXISTS dim_index (
index_code TEXT PRIMARY KEY, -- 指数代码,如 000300
index_name TEXT NOT NULL, -- 指数简称,如 沪深300
index_full_name TEXT, -- 全称
category TEXT NOT NULL, -- 分类:宽基/行业/主题/策略/海外
sub_category TEXT, -- 子分类,如 行业下分 半导体/医药
publisher TEXT, -- 发布机构,如 中证指数公司
base_date TEXT, -- 基准日
base_point REAL, -- 基准点数
updated_at TEXT NOT NULL -- 最后更新时间
);
dim_fund(基金基本信息)
CREATE TABLE IF NOT EXISTS dim_fund (
fund_code TEXT PRIMARY KEY, -- 基金代码,如 510300
fund_name TEXT NOT NULL, -- 基金简称
fund_type TEXT NOT NULL, -- 类型:ETF/LOF/场外指数/主动
tracking_index TEXT, -- 跟踪指数代码(指数基金)
management_company TEXT, -- 基金公司
manager TEXT, -- 基金经理
management_fee REAL, -- 管理费率
custody_fee REAL, -- 托管费率
inception_date TEXT, -- 成立日
updated_at TEXT NOT NULL
);
dim_stock(股票基本信息)
CREATE TABLE IF NOT EXISTS dim_stock (
stock_code TEXT PRIMARY KEY, -- 股票代码,如 600519
stock_name TEXT NOT NULL, -- 股票简称
exchange TEXT NOT NULL, -- 交易所:SH/SZ/BJ
industry_code TEXT, -- 所属申万行业代码
industry_name TEXT, -- 所属申万行业名称
list_date TEXT, -- 上市日
updated_at TEXT NOT NULL,
FOREIGN KEY (industry_code) REFERENCES dim_industry(industry_code)
);
dim_industry(行业基本信息)
CREATE TABLE IF NOT EXISTS dim_industry (
industry_code TEXT PRIMARY KEY, -- 申万行业代码,如 801080
industry_name TEXT NOT NULL, -- 行业名称,如 电子
industry_level INTEGER NOT NULL, -- 行业级别:1/2/3
parent_code TEXT, -- 父行业代码
updated_at TEXT NOT NULL
);
关注指数清单
dim_index 表存储全市场指数基本信息,但实际跟踪的指数按趋势定投分析需求精选。清单分四组,对应不同的分析场景。
宽基指数(必抓)
定投组合的核心配置,覆盖 A 股主要市值区间。
策略指数(推荐)
基于因子策略的指数,用于增强收益或降低波动。
海外指数(推荐)
跨境配置,分散单一市场风险。
海外指数的 symbol_em 格式与 A 股不同,akshare 用 stock_us_index_daily_em 或 index_global_spot_em 接口获取。
行业/主题指数(按关注方向抓)
对应 03-中间段/02-行业分析/案例库 的 9 个方向,每个方向选 1-2 个代表指数:
低空经济等新兴主题指数尚在发展中,可先用相关 ETF 的跟踪指数替代,待官方指数成熟后切换。
指数清单维护
- 清单变更需同步修改
update_all.py 的 WATCH_INDEXES 列表
- 新增行业指数需对应到 03-中间段/02-行业分析/案例库 的具体案例
- 退市或合并的指数需从清单移除,并清理本地库历史数据
行情数据表
index_daily(指数日线)
CREATE TABLE IF NOT EXISTS index_daily (
index_code TEXT NOT NULL,
trade_date TEXT NOT NULL, -- YYYY-MM-DD
open REAL,
close REAL,
high REAL,
low REAL,
volume REAL, -- 成交量(万手)
amount REAL, -- 成交额(万元)
pct_change REAL, -- 涨跌幅 %
PRIMARY KEY (index_code, trade_date),
FOREIGN KEY (index_code) REFERENCES dim_index(index_code)
);
CREATE INDEX IF NOT EXISTS idx_index_daily_date
ON index_daily(trade_date);
fund_daily(基金日线)
CREATE TABLE IF NOT EXISTS fund_daily (
fund_code TEXT NOT NULL,
trade_date TEXT NOT NULL,
open REAL,
close REAL,
high REAL,
low REAL,
volume REAL,
amount REAL,
pct_change REAL,
unit_nav REAL, -- 单位净值(场外基金)
accum_nav REAL, -- 累计净值
PRIMARY KEY (fund_code, trade_date),
FOREIGN KEY (fund_code) REFERENCES dim_fund(fund_code)
);
CREATE INDEX IF NOT EXISTS idx_fund_daily_date
ON fund_daily(trade_date);
stock_daily(股票日线)
CREATE TABLE IF NOT EXISTS stock_daily (
stock_code TEXT NOT NULL,
trade_date TEXT NOT NULL,
open REAL,
close REAL,
high REAL,
low REAL,
volume REAL, -- 成交量(手)
amount REAL, -- 成交额(元)
pct_change REAL,
turnover_rate REAL, -- 换手率 %
pe_ttm REAL, -- 滚动市盈率
pb REAL, -- 市净率
total_market_cap REAL, -- 总市值(元)
circ_market_cap REAL, -- 流通市值(元)
PRIMARY KEY (stock_code, trade_date),
FOREIGN KEY (stock_code) REFERENCES dim_stock(stock_code)
);
CREATE INDEX IF NOT EXISTS idx_stock_daily_date
ON stock_daily(trade_date);
industry_daily(行业日线)
CREATE TABLE IF NOT EXISTS industry_daily (
industry_code TEXT NOT NULL,
trade_date TEXT NOT NULL,
open REAL,
close REAL,
high REAL,
low REAL,
volume REAL,
amount REAL,
pct_change REAL,
PRIMARY KEY (industry_code, trade_date),
FOREIGN KEY (industry_code) REFERENCES dim_industry(industry_code)
);
CREATE INDEX IF NOT EXISTS idx_industry_daily_date
ON industry_daily(trade_date);
估值数据表
index_valuation(指数估值)
CREATE TABLE IF NOT EXISTS index_valuation (
index_code TEXT NOT NULL,
trade_date TEXT NOT NULL,
pe REAL, -- 市盈率
pe_ttm REAL, -- 滚动市盈率
pb REAL, -- 市净率
ps REAL, -- 市销率
dividend_yield REAL, -- 股息率 %
pe_percentile REAL, -- PE 历史分位(0-1)
pb_percentile REAL, -- PB 历史分位
PRIMARY KEY (index_code, trade_date),
FOREIGN KEY (index_code) REFERENCES dim_index(index_code)
);
CREATE INDEX IF NOT EXISTS idx_index_val_date
ON index_valuation(trade_date);
pe_percentile、pb_percentile 由脚本计算后写入,计算方式见 04-终点段/01-基金标的研究/02-估值分位计算方法。
stock_valuation(股票估值)
CREATE TABLE IF NOT EXISTS stock_valuation (
stock_code TEXT NOT NULL,
trade_date TEXT NOT NULL,
pe_ttm REAL,
pb REAL,
ps_ttm REAL,
dividend_yield REAL,
PRIMARY KEY (stock_code, trade_date),
FOREIGN KEY (stock_code) REFERENCES dim_stock(stock_code)
);
基本面数据表
fund_holdings(基金持仓)
CREATE TABLE IF NOT EXISTS fund_holdings (
fund_code TEXT NOT NULL,
report_date TEXT NOT NULL, -- 报告期,如 2025-06-30
stock_code TEXT NOT NULL,
stock_name TEXT,
hold_ratio REAL, -- 占净值比例 %
hold_amount REAL, -- 持仓股数
hold_value REAL, -- 持仓市值
rank INTEGER, -- 持仓排名
PRIMARY KEY (fund_code, report_date, stock_code),
FOREIGN KEY (fund_code) REFERENCES dim_fund(fund_code)
);
CREATE INDEX IF NOT EXISTS idx_fund_holdings_stock
ON fund_holdings(stock_code);
stock_financial(股票财报)
CREATE TABLE IF NOT EXISTS stock_financial (
stock_code TEXT NOT NULL,
report_date TEXT NOT NULL, -- 报告期
revenue REAL, -- 营业收入
net_profit REAL, -- 净利润
revenue_yoy REAL, -- 营收同比 %
net_profit_yoy REAL, -- 净利润同比 %
roe REAL, -- 净资产收益率 %
roa REAL, -- 总资产收益率 %
gross_margin REAL, -- 毛利率 %
net_margin REAL, -- 净利率 %
debt_ratio REAL, -- 资产负债率 %
current_ratio REAL, -- 流动比率
quick_ratio REAL, -- 速动比率
eps REAL, -- 每股收益
bps REAL, -- 每股净资产
operating_cash_flow REAL, -- 经营性现金流
PRIMARY KEY (stock_code, report_date),
FOREIGN KEY (stock_code) REFERENCES dim_stock(stock_code)
);
宏观与资金流向表
macro_indicator(宏观经济指标)
CREATE TABLE IF NOT EXISTS macro_indicator (
indicator_code TEXT NOT NULL, -- 指标代码,如 CPI_YOY
indicator_name TEXT NOT NULL, -- 指标名称
report_date TEXT NOT NULL, -- 数据日期
value REAL, -- 指标值
unit TEXT, -- 单位
frequency TEXT, -- 频率:日/月/季
source TEXT, -- 数据来源
PRIMARY KEY (indicator_code, report_date)
);
indicator_code 采用统一编码,按类别分组。完整清单与解读指引如下:
通胀类
CPI - PPI 剪刀差扩大通常意味着下游盈利承压。
增长类
PMI 连续 3 个月同方向变化通常预示趋势。
货币与流动性类
M1 - M2 剪刀差转负且扩大通常反映企业投资意愿弱。
利率类
汇率与商品类(辅助参考)
money_flow(资金流向)
CREATE TABLE IF NOT EXISTS money_flow (
flow_code TEXT NOT NULL, -- 流向代码,如 NORTH_FLOW
flow_name TEXT NOT NULL, -- 流向名称
trade_date TEXT NOT NULL,
value REAL, -- 流入/流出金额(万元)
cumulative REAL, -- 累计金额
PRIMARY KEY (flow_code, trade_date)
);
常用 flow_code:
数据治理表
trade_calendar(交易日历)
CREATE TABLE IF NOT EXISTS trade_calendar (
trade_date TEXT PRIMARY KEY,
is_open INTEGER NOT NULL, -- 1=交易日, 0=非交易日
exchange TEXT NOT NULL -- SSE/SZSE
);
update_failure_log(更新失败日志)
CREATE TABLE IF NOT EXISTS update_failure_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT NOT NULL, -- 本次更新批次 ID(时间戳)
fetcher TEXT NOT NULL, -- 失败的 fetcher 名
target_code TEXT, -- 失败的标的代码
api_name TEXT, -- 接口名
error_type TEXT, -- 错误类型:network/parse/empty/unknown
error_message TEXT, -- 错误详情
occurred_at TEXT NOT NULL -- 发生时间
);
CREATE INDEX IF NOT EXISTS idx_failure_run
ON update_failure_log(run_id);
CREATE INDEX IF NOT EXISTS idx_failure_target
ON update_failure_log(target_code);
validation_result(校验结果)
CREATE TABLE IF NOT EXISTS validation_result (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT NOT NULL,
table_name TEXT NOT NULL,
check_type TEXT NOT NULL, -- integrity/accuracy/consistency/timeliness/cross_validation
check_name TEXT NOT NULL,
status TEXT NOT NULL, -- pass/warning/fail
detail TEXT,
checked_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_validation_run
ON validation_result(run_id);
cross_validation_log(多源交叉验证日志)
CREATE TABLE IF NOT EXISTS cross_validation_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT NOT NULL,
target_code TEXT NOT NULL, -- 标的代码
trade_date TEXT NOT NULL, -- 验证日期
field_name TEXT NOT NULL, -- 验证字段,如 close/pe/pb
source_primary TEXT NOT NULL, -- 主源,如 em(东方财富)
source_secondary TEXT NOT NULL, -- 备源,如 sina(新浪)
value_primary REAL, -- 主源值
value_secondary REAL, -- 备源值
diff REAL, -- 绝对差
diff_pct REAL, -- 相对差 %
status TEXT NOT NULL, -- match/mismatch/missing/single_source
checked_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_xval_run
ON cross_validation_log(run_id);
CREATE INDEX IF NOT EXISTS idx_xval_target
ON cross_validation_log(target_code);
CREATE INDEX IF NOT EXISTS idx_xval_status
ON cross_validation_log(status);
update_progress(断点续传进度)
CREATE TABLE IF NOT EXISTS update_progress (
fetcher_name TEXT NOT NULL, -- fetcher 名称,如 fetch_index_daily
target_code TEXT NOT NULL, -- 标的代码
last_trade_date TEXT, -- 最后成功更新的交易日
last_updated_at TEXT NOT NULL, -- 最后更新时间
status TEXT NOT NULL, -- pending/in_progress/completed/failed
run_id TEXT, -- 最近一次运行的 run_id
PRIMARY KEY (fetcher_name, target_code)
);
CREATE INDEX IF NOT EXISTS idx_progress_status
ON update_progress(status);
CREATE INDEX IF NOT EXISTS idx_progress_run
ON update_progress(run_id);
status 取值:pending(待处理)/ in_progress(处理中,中断续传判断依据)/ completed(已完成,跳过)/ failed(失败,下次优先重试)。断点续传机制详见 04-数据更新与调度策略。
行为数据表
行为数据记录投资者自身的决策、持仓与关注清单,与市场数据分离。这三张表打通路线图中"决策复盘""估值跟踪""机会清单"等闭环环节,支撑命中率统计与错题本模式识别。
decision_log(决策日志)
CREATE TABLE IF NOT EXISTS decision_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
decision_date TEXT NOT NULL, -- 决策日期 YYYY-MM-DD
action TEXT NOT NULL, -- buy/sell/hold/stop_loss/take_profit
target_type TEXT NOT NULL, -- index/fund/stock
target_code TEXT NOT NULL,
target_name TEXT,
amount REAL, -- 成交金额(元)
shares REAL, -- 成交份额/股数
price REAL, -- 成交价格
pe_percentile REAL, -- 决策时 PE 分位快照
pb_percentile REAL, -- 决策时 PB 分位快照
reason TEXT, -- 决策理由
strategy TEXT, -- 策略标签
emotion TEXT, -- 情绪标签
linked_doc TEXT, -- 关联的复盘文档路径
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_decision_date
ON decision_log(decision_date);
CREATE INDEX IF NOT EXISTS idx_decision_target
ON decision_log(target_code);
CREATE INDEX IF NOT EXISTS idx_decision_action
ON decision_log(action);
pe_percentile、pb_percentile 在决策时从 index_valuation 表快照写入,确保复盘时能还原"当时看到的估值位置",不因后续数据更新而丢失决策依据。
portfolio_snapshot(组合快照)
CREATE TABLE IF NOT EXISTS portfolio_snapshot (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_date TEXT NOT NULL, -- 快照日期 YYYY-MM-DD
target_code TEXT NOT NULL,
target_name TEXT,
target_type TEXT NOT NULL, -- index/fund/stock
shares REAL, -- 持有份额
cost_price REAL, -- 成本价
current_price REAL, -- 当前价格
market_value REAL, -- 当前市值
cost_value REAL, -- 成本市值
profit_loss REAL, -- 盈亏金额
profit_loss_pct REAL, -- 盈亏比例 %
weight REAL, -- 占组合比例 %
pe_percentile REAL, -- 快照时 PE 分位
pb_percentile REAL, -- 快照时 PB 分位
created_at TEXT NOT NULL,
UNIQUE (snapshot_date, target_code)
);
CREATE INDEX IF NOT EXISTS idx_portfolio_date
ON portfolio_snapshot(snapshot_date);
CREATE INDEX IF NOT EXISTS idx_portfolio_target
ON portfolio_snapshot(target_code);
建议每月末生成一次快照,支撑"6 个月估值跟踪"完成标志。UNIQUE (snapshot_date, target_code) 保证同一日同一标的不重复快照。
watchlist(可投标的清单)
CREATE TABLE IF NOT EXISTS watchlist (
id INTEGER PRIMARY KEY AUTOINCREMENT,
added_date TEXT NOT NULL, -- 加入清单日期
target_type TEXT NOT NULL, -- index/fund
target_code TEXT NOT NULL,
target_name TEXT,
source TEXT NOT NULL, -- 来源:政策分析/行业分析/产业趋势/机会分析
source_doc TEXT, -- 来源文档路径
thesis TEXT, -- 投资逻辑
priority TEXT, -- high/medium/low
status TEXT NOT NULL, -- pending/watching/entered/exited/rejected
pe_percentile_at_add REAL, -- 加入时 PE 分位
expected_entry_pe_pct REAL, -- 预期入场 PE 分位
notes TEXT,
UNIQUE (target_code, source)
);
CREATE INDEX IF NOT EXISTS idx_watchlist_status
ON watchlist(status);
CREATE INDEX IF NOT EXISTS idx_watchlist_priority
ON watchlist(priority);
source 对应中间段各分析模块,source_doc 记录具体文档路径,便于回溯"为什么把这个标的加入清单"。status 流转:pending(待观察)-> watching(观察中)-> entered(已建仓)/ rejected(放弃)-> exited(已清仓)。
政策数据边界说明
政策-行业-基金框架中"政策"层的数据不进入数据库,以 03-中间段/01-政策分析 下的 Markdown 文档形式管理。原因:
- 政策文件、会议精神、监管动作为定性内容,结构化成本高、收益低
- 政策传导回测依赖人工判断与 LLM 辅助,不适合自动化入库
watchlist.source 字段以"政策分析"作为来源标签,关联到具体政策分析文档路径,不存储政策内容本身
数据库只存储政策传导后的"结果数据"(行业/指数/资金流向变化),不存储政策"原因数据"。
字段枚举值与取值范围
各表的枚举字段与数值字段取值范围集中说明如下,用于数据校验、查询过滤与写入校验。
标的分类字段
dim_index.category(指数分类)
dim_index.sub_category:行业类下按申万一级分类(如 电子/医药生物/食品饮料),主题类下按主题方向(如 新能源/AI/低空经济),其他类别为 NULL。
dim_fund.fund_type(基金类型)
dim_stock.exchange(交易所)
dim_industry.industry_level(行业级别)
数据质量字段
所有业务表(index_daily/fund_daily/stock_daily/industry_daily/index_valuation/stock_valuation)含 data_quality 字段:
查询接口默认过滤 data_quality IN ('ok', 'manual_fixed'),详见 04-查询接口/01-连接与工具函数。
数值字段取值范围
行情数据(OHLCV)
估值数据
分位数解读约定:
基本面数据(stock_financial)
宏观指标(macro_indicator)
日志与状态字段
update_failure_log.error_type
validation_result.check_type
validation_result.status
cross_validation_log.status
update_progress.status
行为数据字段
decision_log.action(决策动作)
decision_log.target_type / portfolio_snapshot.target_type / watchlist.target_type
decision_log.strategy(策略标签)
decision_log.emotion(情绪标签,用于心态纪律复盘)
watchlist.priority(关注优先级)
watchlist.status(标的清单状态)
watchlist.source(清单来源,对应中间段分析模块)
日期格式约定
索引策略
主键已覆盖主要的"按标的 + 按日期"查询。额外索引按实际查询模式补充:
避免过度索引。每个索引增加写入成本和存储占用,只在确有查询需求时添加。
历史深度要求
不同数据类型的历史深度要求,已在 数据库概览 定义,此处汇总建表时的填充策略:
建库时首次灌入按"理想深度"获取,后续按"增量策略"更新。
字段命名约定
- 日期统一
YYYY-MM-DD 字符串格式,避免时区问题
- 百分比字段存数值(5% 存 5.0),字段名带
_pct 或 _ratio 后缀
- 金额单位在字段注释或维度表声明,不写进字段名
- 布尔值用 INTEGER 0/1
- 缺失值存 NULL,不用 0 或 -1 填充