# A股个人量化选股与回测平台架构 > 版本:v1.0 > 定位:个人开发、A股、中低频选股/回测、AI Agent 二次开发 > 数据源优先级:Tushare > 新浪财经 > 当前数据库:**本机 MariaDB 10.11(`127.0.0.1:3306/qlib`)** > 数据库目标约束:**只允许本机**;`192.168.1.10` 已禁止作为 DB 目标,由 > `app/core/config.py::assert_db_target_allowed` 在解析配置时硬拦截(见 `AGENT.md` §0.1) > 历史:SQLite(`data/quant.db`)→ 远端 MySQL → 本机 MariaDB(逐表一致副本) > 核心量化引擎:Qlib > 前端:React / Next.js > 后端:FastAPI + Python --- ## 1. 项目目标 本项目不是简单给 Qlib 套一个 Web UI,而是构建一个独立的个人量化研究平台: ```text Web 前端 ↓ FastAPI ↓ Research Specification ↓ 业务服务层 ├── 数据服务 ├── 股票池服务 ├── 因子服务 ├── 选股服务 ├── 模型服务 ├── 回测服务 └── 实验服务 ↓ Qlib / ML ↓ 标准化研究结果 ↓ SQLite / Parquet ↓ Web 前端 ``` AI Agent 作为独立能力层,通过受控 Tool 调用上述服务,而不是直接操作数据库或随意修改 Qlib 内部代码。 --- # 2. 总体架构 ```text ┌──────────────────────┐ │ React / Next │ │ │ │ Dashboard │ │ 股票池 │ │ 因子研究 │ │ 选股 │ │ 回测 │ │ Experiment │ │ AI Research │ └──────────┬───────────┘ │ REST / SSE │ ▼ ┌──────────────────────┐ │ FastAPI │ │ │ │ API / Auth / DTO │ │ Job / Validation │ └──────────┬───────────┘ │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ Data Service Research Agent Service │ Service │ │ │ │ ▼ ▼ ▼ DAO / Repo Qlib / ML Agent Tools │ │ │ └────────────┼──────────────┘ ▼ ┌──────────────────────┐ │ Persistence │ │ │ │ SQLite │ │ Parquet │ │ Qlib Dataset │ └──────────────────────┘ ``` --- # 3. 核心设计原则 ## 3.1 Qlib 不是系统数据库 Qlib 负责: - Feature / Dataset - 模型训练 - Prediction - Portfolio - Backtest - 部分分析工具 Qlib 不作为业务系统唯一数据存储。 系统数据分为: ```text 业务数据 → SQLite 历史/分析型大数据 → Parquet Qlib计算数据 → Qlib Dataset 实验结果 → SQLite ``` ## 3.2 前端不能直接调用 Qlib 必须经过: ```text Frontend ↓ FastAPI ↓ Application Service ↓ Qlib Adapter ↓ Qlib ``` 这样未来可以替换 Qlib,而不影响前端。 ## 3.3 DAO 必须与数据库实现解耦 业务代码禁止直接: ```python sqlite3.connect(...) ``` 也禁止在 Service 中写 SQL。 必须: ```text Service ↓ Repository / DAO Interface ↓ SQLite Repository ``` 未来: ```text Service ↓ Repository Interface ├── SQLite Repository └── MySQL Repository ``` 业务层不感知底层数据库。 --- # 4. 数据源架构 ## 4.1 数据源优先级 ```text Data Service │ ┌────────┴────────┐ ▼ ▼ Tushare Sina Primary Secondary │ │ └────────┬────────┘ ▼ Normalization │ Validation │ Storage ``` ### 第一优先级:Tushare 用于: - 股票基本信息 - 日线行情 - 复权因子 - 指数 - 财务数据 - 分红送转 - 停复牌 - 行业/概念 - 其他可用数据 ### 第二优先级:新浪财经 主要作为: - Tushare 数据缺失补充 - 行情数据校验 - 特定实时/准实时数据补充 新浪数据必须经过统一 Adapter,禁止业务代码直接调用新浪接口。 --- # 5. 数据采集流水线 ```text Scheduler ↓ Data Source Adapter ↓ Raw Response ↓ Schema Validation ↓ Normalization ↓ Deduplication ↓ Business Validation ↓ DAO ↓ SQLite ↓ Parquet Export ↓ Qlib Dataset ``` 每个数据源都必须有独立 Adapter: ```text data/ ├── sources/ │ ├── base.py │ ├── tushare.py │ └── sina.py ├── normalizers/ ├── validators/ └── service.py ``` 业务层只依赖: ```python MarketDataProvider ``` 而不是: ```python TushareClient ``` --- # 6. 数据库设计 > **目标库约束(硬性)**:一律 `127.0.0.1:3306/qlib`(本机 MariaDB 10.11)。 > `192.168.1.10` 禁止作为数据库目标 —— 连错库不会报错、界面也正常,却会把回测/归档/策略 > 静默写到另一台机器上,属于最难发现的一类故障。守卫在 `get_settings()` 里执行: > 命中禁用主机直接抛错(应用起不来),禁用列表可用 `QLIB_FORBIDDEN_DB_HOSTS` 覆盖。 > 启动日志会打印 `数据库目标:mysql qlib@127.0.0.1:3306/qlib`(不含密码),便于随时确认。 ## 6.1 SQLite 定位 SQLite 用于: - 股票基础信息 - 数据源状态 - 数据同步记录 - 因子定义 - 策略定义 - 回测配置 - Experiment metadata - Job - 用户配置 - 系统配置 SQLite 不建议长期承载超大规模原始行情明细。 历史行情和大量 Feature 优先使用 Parquet。 --- # 7. DAO / Repository 设计 推荐使用: ```text SQLAlchemy 2.x + Repository Pattern + Pydantic DTO ``` 目录: ```text backend/ ├── domain/ │ ├── entities/ │ └── repositories/ │ ├── infrastructure/ │ └── persistence/ │ ├── sqlalchemy/ │ │ ├── models/ │ │ ├── repositories/ │ │ └── session.py │ └── migrations/ │ └── application/ └── services/ ``` Repository 接口示例: ```python class StockRepository(Protocol): def get_by_symbol(self, symbol: str) -> Stock | None: ... def list(self, filters: StockFilter) -> list[Stock]: ... def save(self, stock: Stock) -> Stock: ... ``` SQLite: ```python class SqlAlchemyStockRepository(StockRepository): ... ``` 未来 MySQL: ```python class MySqlStockRepository(StockRepository): ... ``` 推荐实际上让两者都基于 SQLAlchemy,而不是分别维护两套 SQL。 这样数据库切换主要通过: ```text DATABASE_URL ``` 完成。 例如: ```text SQLite: sqlite:///./data/quant.db MySQL: mysql+pymysql://user:password@host/quant ``` 业务 Service 不需要修改。 --- # 8. 建议的核心数据库表 第一阶段至少包括: ```text stock stock_daily stock_adjust_factor industry stock_industry index index_daily trading_calendar suspend_data # ⚠️ 未实现(停牌以「当日无行情」近似,结果页如实标注) stock_st_data # → 由 stock_name_history(tushare namechange)落地时点 ST 判定 financial_indicator income_statement # ---- 后续增量(已落地,见 docs/DEV_PLAN_DIVIDEND_BACKTEST.md §10)---- daily_basic # 每日指标:dv_ratio/dv_ttm 股息率、PE/PB、市值(高股息选股) stock_name_history # 名称生效区间:exclude_st 的**时点**口径(股息陷阱可见) balance_sheet cashflow_statement factor_definition factor_test strategy strategy_factor backtest backtest_trade backtest_position backtest_metric experiment experiment_artifact job data_sync_log ``` 注意: `stock_daily` 等大量时序数据如果规模变大,应逐渐迁移到: ```text Parquet ``` SQLite 保留元数据和索引。 --- # 9. 时间与未来函数防护 所有研究数据必须明确: ```text trade_date report_date announce_date effective_date ``` 财务数据必须按照 `announce_date` 控制可见性。 禁止: ```text 使用未来公告的财务数据回测过去 ``` 所有数据查询必须明确: ```text as_of_date ``` 例如: ```python get_financial_data( symbol="600519.SH", as_of_date="2024-06-30" ) ``` 只允许返回当时市场已经知道的数据。 --- # 10. Domain 层 建议核心领域对象: ```text Stock Universe Factor FactorTest Model Strategy Portfolio Backtest Experiment Job ``` 例如: ```text Universe ↓ Factor ↓ Model ↓ Strategy ↓ Backtest ↓ Experiment ``` --- # 11. Research Specification 前端、AI Agent 和后端统一使用自己的研究描述对象。 示例: ```json { "type": "backtest", "universe": { "market": "CN_A", "exclude_st": true, "exclude_suspended": true, "min_listing_days": 250 }, "factors": [ { "name": "momentum_60", "weight": 0.3 }, { "name": "roe_ttm", "weight": 0.3 }, { "name": "low_volatility_60", "weight": 0.4 } ], "selection": { "top_n": 30 }, "rebalance": "monthly", "period": { "start": "2015-01-01", "end": "2026-08-31" } } ``` 然后: ```text Research Specification ↓ Validator ↓ Strategy Builder ↓ Qlib Adapter ``` --- # 12. 前端结构 第一阶段(**已实现**,侧栏按研究闭环分组): ```text 研究 总览 / 股票池 / 股票筛选 策略 策略库 / 选股回测 / 实验对比 因子与信号 因子研究 / 因子组合 / 交易信号 数据 数据同步 / 数据管理 ``` ## 12.1 表现层约定(本轮定型) | 约定 | 取值 | 理由 | |---|---|---| | 图表库 | **TradingView Lightweight Charts**(唯一) | 轻量(~45KB)、原生支持「在 series 上打买卖点标记」,金融图表交互(十字光标/缩放)开箱可用;ECharts 已下线(依赖已移除),避免两套图表基座带来的样式与交互分裂 | | 图表基座 | `components/charts/LwChart.tsx` | 折线/面积/柱状 + 标记 + tooltip + 可点击图例由一个组件承载;**chart 实例只在结构变化时重建**,数据变化只 `setData`,避免每次刷新重建 canvas | | 主题 | `components/charts/theme.ts` | 颜色/数值格式集中定义(涨绿跌红按 A 股习惯),组件内不出现硬编码色值 | | 标记约束 | 标记时间必须存在于对应 series 且**升序** | Lightweight Charts 硬约束:不满足会整组标记丢失。`LwChart` 内统一做「过滤到已有时间点 + 排序」,页面只负责给语义(BUY/SELL) | | 股票标识 | `SymbolLink`(`lib/symbols.tsx`) | 「有代码必有名称」是产品要求:后端填充 `name`(权威),前端 `GET /api/stocks/names` 缓存兜底;名称缺失显示「—」,**不猜不造**(§7 无静默行为) | | 参数模型 | `components/StrategyParamsForm.tsx` 单一 `StrategyParams` | 策略库/回测/直通入口共用同一参数模型与校验,避免三处各自维护字段(§6 依赖抽象) | | 策略说明 | `StrategyDoc`(后端 `describe_strategy` 推导) | 说明与公式**由 spec 真实推导**而非前端手写模板:参数一改,公式同步变;引擎未建模处进 `warnings` 如实暴露(§7/§24) | | 结果视图 | `components/BacktestResultView.tsx`(回测页与归档页**共用**) | 「刚跑完」和「翻回来看」必须是同一套图表与表格;两处各写一套必然漂移成「归档里少一张图」 | | 归档页 | `/experiments/{id}` 为 **Server Component**(`app/experiments/[id]/page.tsx`) | 归档是只读内容:选股条件/执行依据/元数据必须服务端直出(客户端渲染时 SSR HTML 里连「选股条件」都搜不到);交互(删除/导出/折叠 spec)隔离在 `components/ArchiveActions.tsx` | | 归档口径说明 | 归档页顶部「选股条件」+「交易执行依据」两块,取自**归档内 spec** | 说明必须与那次执行一致,不能读当前页面状态(否则「看归档」会看到今天的参数);字段逐项对应引擎实执行语义,不写泛泛的模板话 | | 归档列表分页 | body 保持 `list[...]`,总数放 `X-Total-Count` 响应头 | 不破坏既有前端契约,同时消除「硬编码 limit=50 静默截断」(§7):前端据此显示「显示 N 条 / 共 M 条」 | > 前端**只**依赖 `/api/*` 的 Domain 结构(§3.2):`name` 由后端填充而不是前端反查数据库, > 图表库可替换而不影响领域层,`/backtest` 的三种入口(策略/选股/实验)都只是把 URL 参数 > 映射成同一个 `StrategyParams`。 第二阶段: ```text 模型研究 Portfolio AI Research 数据管理 ``` --- # 13. 前端输入 ## 股票池 支持: - 市场 - 股票类型 - 行业 - 市值 - 流动性 - 上市时间 - ST - 停牌 - 指数成分 ## 因子 支持: - 内置因子 - 自定义表达式 - 因子组合 - 权重 - 标准化 - 中性化 ## 选股 支持: - Top N - Top % - Score - 等权 - Score 加权 - 行业约束 - 单股权重上限 ## 回测 支持: - 起止日期 - 调仓频率 - 初始资金 - 手续费 - 印花税 - 滑点 - 涨跌停限制 - 停牌限制 --- # 14. 前端输出 回测结果统一转换为: ```text BacktestResult ├── summary ├── equity_curve ├── drawdown ├── monthly_returns ├── yearly_returns ├── positions ├── trades ├── turnover ├── risk_metrics └── factor_exposure ``` 这样前端完全不需要理解 Qlib 内部对象。 --- # 15. 异步 Job 所有耗时任务必须异步: ```text POST /api/backtests ↓ Job Created ↓ Worker ↓ Qlib ↓ Result ``` 前端通过: ```text SSE ``` 接收: ```text queued running factor_calculation model_training backtesting analysis completed failed ``` 第一阶段可以使用: ```text FastAPI BackgroundTasks ``` 或简单本地 Worker。 系统复杂后再引入: ```text Redis + Celery/RQ ``` 不要第一版过度工程化。 --- # 16. Qlib Adapter Qlib 必须被封装: ```text quant/ ├── qlib_adapter/ │ ├── dataset.py │ ├── feature.py │ ├── model.py │ ├── backtest.py │ └── provider.py ``` 业务代码: ```python backtest_service.run(spec) ``` 而不是: ```python qlib.init(...) qlib.workflow(...) ``` 散落在项目各处。 --- # 17. AI Agent 架构 Agent 只能调用 Tool: ```text Agent ├── search_stocks ├── inspect_factor ├── test_factor ├── create_strategy ├── run_backtest ├── compare_experiments ├── get_backtest_result └── create_experiment ``` Agent 禁止: ```text 直接 SQL 直接修改数据库 直接删除数据 直接执行任意 shell 直接修改生产策略 ``` Agent: ```text 自然语言 ↓ Research Plan ↓ Tool Calls ↓ Research Specification ↓ 执行 ↓ Experiment ↓ 分析 ``` --- # 18. AI Research 示例 用户: > 帮我寻找适合A股月度调仓的低频选股因子。 Agent: ```text 1. 获取A股股票池 2. 创建候选因子 3. IC测试 4. RankIC测试 5. 分层测试 6. 相关性分析 7. 删除冗余因子 8. 组合因子 9. Walk Forward 10. 回测 11. 保存 Experiment 12. 输出结论 ``` Agent 不能因为一次回测表现好就直接认为策略有效。 必须关注: ```text 样本外 Walk Forward 不同市场阶段 交易成本 换手率 因子稳定性 ``` --- # 19. 推荐的开发目录 ```text quant-platform/ │ ├── frontend/ │ └── web/ │ ├── backend/ │ ├── app/ │ │ ├── api/ │ │ ├── application/ │ │ ├── domain/ │ │ ├── infrastructure/ │ │ ├── quant/ │ │ ├── agent/ │ │ └── core/ │ │ │ └── tests/ │ ├── data/ │ ├── raw/ │ ├── normalized/ │ ├── parquet/ │ └── qlib/ │ ├── experiments/ │ ├── scripts/ │ ├── docker/ │ ├── docs/ │ ├── ARCHITECTURE.md ├── AGENT.md └── README.md ``` --- # 20. 第一阶段 MVP 不要一开始做所有功能。 ### Phase 1:数据 ```text Tushare ↓ 标准化 ↓ SQLite ↓ Parquet ``` 完成: - 股票列表 - 交易日历 - 日线 - 复权因子 - 基本财务指标 ### Phase 2:Qlib ```text Parquet ↓ Qlib Dataset ↓ Alpha158 / 自定义因子 ↓ LightGBM ↓ Backtest ``` ### Phase 3:Web 完成: ```text 股票池 因子 选股 回测 结果 ``` ### Phase 4:Experiment 所有研究自动保存。 ### Phase 5:AI Agent 最后再接: ```text 自然语言 ↓ Research Plan ↓ Tool ↓ Experiment ``` --- # 21. 数据流总图 ```text Tushare │ ▼ Tushare Adapter │ │ 失败/缺失 ▼ Sina Adapter │ ▼ Raw Data │ ▼ Data Validator │ ▼ Normalization │ ┌─────┴──────┐ ▼ ▼ SQLite Parquet │ │ │ ▼ │ Qlib Dataset │ │ │ ┌─────┴─────┐ │ ▼ ▼ │ Factor Model │ │ │ │ └─────┬─────┘ │ ▼ │ Strategy │ │ │ ▼ │ Backtest │ │ └────────────┤ ▼ Experiment │ ┌─────────┴─────────┐ ▼ ▼ Web Output AI Analysis ``` --- # 22. 最终原则 这个系统必须坚持: 1. **Tushare 第一,新浪第二** 2. **数据源通过 Adapter 隔离** 3. **DAO / Repository 隔离数据库** 4. **SQLite 当前使用,SQLAlchemy 保证未来 MySQL 可切换** 5. **大量历史时序数据逐渐转 Parquet** 6. **Qlib 是计算引擎,不是整个系统** 7. **前端只面对自己的 Domain API** 8. **前后端通过 Research Specification 解耦** 9. **所有耗时任务异步化** 10. **所有研究产生 Experiment** 11. **严格防止未来函数** 12. **AI Agent 只能通过 Tool 使用研究能力** 13. **第一阶段不做实盘** 14. **第一阶段不做复杂分布式架构** 15. **每个模块都必须可以被单元测试** 最终目标不是: > Qlib + Web 而是: > **一个以 Qlib 为量化研究引擎、以 Tushare 为主要数据源、具备清晰 DAO 抽象和 AI Agent 接口的个人 A 股量化研究平台。**