refactor(factor): 完成因子框架 DSL 化重构

- 重构 FactorEngine 实现完整的 DSL 表达式执行链路
- 新增 DataRouter 数据路由器,支持内存模式和核心宽表组装
- 新增 ExecutionPlanner 执行计划生成器,整合编译器和翻译器
- 新增 ComputeEngine 计算引擎,支持并行运算
- 完善 factors/__init__.py 公开 API 导出
- 新增 test_factor_engine.py 引擎单元测试
- 移除旧引擎实现和废弃的 DSL promotion 测试
- 更新 AGENTS.md 添加 v2.2 架构变更历史和 Factors 框架设计说明
This commit is contained in:
2026-03-01 15:03:56 +08:00
parent 84479ee9ff
commit b461a4940d
5 changed files with 1087 additions and 663 deletions

214
AGENTS.md
View File

@@ -82,34 +82,34 @@ ProStock/
│ │
│ ├── data/ # 数据获取与存储
│ │ ├── api_wrappers/ # Tushare API 封装
│ │ │ ├── API_INTERFACE_SPEC.md # 接口规范文档
│ │ │ ├── api.md # API 接口定义
│ │ │ ├── api_daily.py # 日线数据接口
│ │ │ ├── base_sync.py # 同步基础抽象类(BaseDataSync/StockBasedSync/DateBasedSync)
│ │ │ ├── api_daily.py # 日线数据接口(DailySync)
│ │ │ ├── api_pro_bar.py # Pro Bar 数据接口(ProBarSync)
│ │ │ ├── api_stock_basic.py # 股票基础信息接口
│ │ │ ├── api_trade_cal.py # 交易日历接口
│ │ │ ├── api_bak_basic.py # 历史股票列表接口(BakBasicSync)
│ │ │ ├── api_namechange.py # 股票名称变更接口
│ │ │ ├── financial_data/ # 财务数据接口
│ │ │ │ ├── api_income.py # 利润表接口
│ │ │ │ └── api_financial_sync.py # 财务数据同步
│ │ │ └── __init__.py
│ │ ├── __init__.py
│ │ ├── client.py # Tushare API 客户端带速率限制
│ │ ├── client.py # Tushare API 客户端(带速率限制)
│ │ ├── config.py # 数据模块配置
│ │ ├── db_inspector.py # 数据库信息查看工具
│ │ ├── db_manager.py # DuckDB 表管理和同步
│ │ ├── rate_limiter.py # 令牌桶速率限制器
│ │ ├── storage.py # 数据存储核心
│ │ ── sync.py # 数据同步主逻辑
│ │ ── sync.py # 数据同步调度中心
│ │ └── utils.py # 数据模块工具函数
│ │
│ ├── factors/ # 因子计算框架
│ │ ├── __init__.py
│ │ ├── base.py # 因子基类(截面/时序)
│ │ ├── composite.py # 组合因子和标量运算
│ │ ├── data_loader.py # 数据加载器
│ │ ├── data_spec.py # 数据规格定义
│ │ ── engine.py # 因子执行引擎
│ │ ├── momentum/ # 动量因子
│ │ │ ├── __init__.py
│ │ │ ├── ma.py # 移动平均线
│ │ │ └── return_rank.py # 收益排名
│ │ └── financial/ # 财务因子
│ │ └── __init__.py
│ ├── factors/ # 因子计算框架DSL 表达式驱动)
│ │ ├── __init__.py # 导出所有公开 API
│ │ ├── dsl.py # DSL 表达式层 - 节点定义和运算符重载
│ │ ├── api.py # API 层 - 常用符号(close/open等)和函数(ts_mean/cs_rank等)
│ │ ├── compiler.py # AST 编译器 - 依赖提取
│ │ ├── translator.py # Polars 表达式翻译器
│ │ ── engine.py # 因子执行引擎 - 统一入口
│ │
│ ├── pipeline/ # 模型训练管道
│ │ ├── __init__.py
@@ -296,9 +296,48 @@ uv run python -c "from src.data.sync import sync_all; sync_all(max_workers=20)"
## 架构变更历史
### v2.1 (2026-02-28) - 同步模块规范更新
### v2.2 (2026-03-01) - 因子框架 DSL 化重构
#### sync.py 职责划分
#### 因子计算框架重构
**变更**: 从基类继承方式迁移到 DSL 表达式方式
**原因**:
- 提供更直观的数学公式表达方式
- 支持因子表达式的组合和嵌套
- 更好的类型安全和编译期检查
**架构变化**:
- 新增 `dsl.py`: 表达式节点基类和运算符重载Symbol、FunctionNode等
- 新增 `api.py`: 常用符号close/open/volume等和函数ts_mean/cs_rank等
- 新增 `compiler.py`: AST 编译器,提取表达式依赖
- 新增 `translator.py`: 将 DSL 表达式翻译为 Polars 表达式
- 重构 `engine.py`: 统一执行引擎入口,整合 DataRouter、ExecutionPlanner、ComputeEngine
- 移除: `base.py``composite.py``data_loader.py``data_spec.py`
- 移除: `factors/momentum/``factors/financial/` 子目录
**使用方式对比**:
```python
# 旧方式(基类继承)
class MA20Factor(TimeSeriesFactor):
name = "ma20"
data_specs = [DataSpec("daily", ["close"], 20)]
def compute(self, data):
return data.get_column("close").rolling_mean(20)
# 新方式DSL 表达式)
from src.factors.api import close, ts_mean
ma20 = ts_mean(close, 20) # 直接编写数学表达式
engine = FactorEngine()
engine.register("ma20", ma20)
result = engine.compute(["ma20"], "20240101", "20240131")
```
#### data 模块补充完善
**新增文件**:
- `api_wrappers/base_sync.py`: 数据同步基础抽象类BaseDataSync、StockBasedSync、DateBasedSync
- `data_router.py`: 数据路由器(已集成到 factors/engine.py 中的 DataRouter
- `utils.py`: 日期工具函数get_today_date、get_next_date、is_quarter_end等
**影响**: 数据同步逻辑更加规范化,支持按股票和按日期两种同步模式
### v2.1 (2026-02-28) - 同步模块规范更新
**变更**: 明确 `sync.py` 只包含每日更新的数据同步
**原因**: 区分高频(每日)和低频(季度/年度)数据,避免不必要的 API 调用
**规范**:
@@ -353,7 +392,120 @@ uv run python -c "from src.data.sync import sync_all; sync_all(max_workers=20)"
`get_today_date()`、`get_next_date()`、`DEFAULT_START_DATE` 等函数统一在 `src/data/utils.py` 中管理
其他模块应从 `utils.py` 导入这些函数,避免重复定义
其他模块应从 `utils.py` 导入这些函数,避免重复定义
## Factors 框架设计说明
### 架构层次
因子框架采用分层设计,从上到下依次是:
```
API 层 (api.py)
|
v
DSL 层 (dsl.py) <- 因子表达式 (Node)
|
v
Compiler (compiler.py) <- AST 依赖提取
|
v
Translator (translator.py) <- 翻译为 Polars 表达式
|
v
Engine (engine.py) <- 执行引擎 (DataRouter/ExecutionPlanner/ComputeEngine)
|
v
数据层 (data_router.py + DuckDB) <- 数据获取和存储
```
### 使用方式
#### 1. 基础表达式
```python
from src.factors.api import close, open, ts_mean, cs_rank
# 定义因子表达式(惰性计算)
ma20 = ts_mean(close, 20) # 20日移动平均
price_rank = cs_rank(close) # 收盘价截面排名
# 组合运算
alpha = ma20 * 0.6 + price_rank * 0.4
```
#### 2. 注册和执行
```python
from src.factors import FactorEngine
engine = FactorEngine()
engine.register("ma20", ma20)
engine.register("price_rank", price_rank)
# 执行计算
result = engine.compute(
factor_names=["ma20", "price_rank"],
start_date="20240101",
end_date="20240131",
)
```
### 支持的函数
**时间序列函数 (ts_*)**
- `ts_mean(x, window)` - 滚动均值
- `ts_std(x, window)` - 滚动标准差
- `ts_max(x, window)` - 滚动最大值
- `ts_min(x, window)` - 滚动最小值
- `ts_sum(x, window)` - 滚动求和
- `ts_delay(x, periods)` - 滞后 N 期
- `ts_delta(x, periods)` - 差分 N 期
- `ts_corr(x, y, window)` - 滚动相关系数
- `ts_cov(x, y, window)` - 滚动协方差
- `ts_rank(x, window)` - 滚动排名
**截面函数 (cs_*)**
- `cs_rank(x)` - 截面排名(分位数)
- `cs_zscore(x)` - Z-Score 标准化
- `cs_neutralize(x, group)` - 行业/市值中性化
- `cs_winsorize(x, lower, upper)` - 缩尾处理
- `cs_demean(x)` - 去均值
**数学函数**
- `log(x)` - 自然对数
- `exp(x)` - 指数函数
- `sqrt(x)` - 平方根
- `sign(x)` - 符号函数
- `abs(x)` - 绝对值
- `max_(x, y)` / `min_(x, y)` - 逐元素最值
- `clip(x, lower, upper)` - 数值裁剪
**条件函数**
- `if_(condition, true_val, false_val)` - 条件选择
- `where(condition, true_val, false_val)` - if_ 的别名
### 运算符支持
DSL 表达式支持完整的 Python 运算符:
```python
# 算术运算: +, -, *, /, //, %, **
expr1 = (close - open) / open * 100 # 涨跌幅
# 比较运算: ==, !=, <, <=, >, >=
expr2 = close > open # 是否上涨
# 一元运算: -, +, abs()
expr3 = -change # 涨跌额取反
# 链式调用
expr4 = ts_mean(cs_rank(close), 20) # 排名后的20日平滑
```
## AI 行为准则
## AI 行为准则
### LSP 检测报错处理
@@ -384,3 +536,25 @@ LSP 报错Syntax error on line 45
✅ 正确做法:读取文件第 45 行,发现少了一个右括号,添加后重新检测
❌ 错误做法:删除文件重新写、或者忽略错误继续
```
### Emoji 表情禁用规则
**⚠️ 强制要求:代码和测试文件中禁止出现 emoji 表情。**
1. **禁止范围**
- 所有 `.py` 源代码文件
- 所有测试文件 (`tests/` 目录)
- 配置文件、脚本文件
2. **替代方案**
- ❌ 禁止使用:`print("✅ 成功")`、`print("❌ 失败")`、`# 📝 注释`
- ✅ 应使用:`print("[成功]")`、`print("[失败]")`、`# 注释`
- 使用方括号 `[成功]`、`[警告]`、`[错误]` 等文字标记代替 emoji
3. **唯一例外**
- AGENTS.md 文件本身可以使用 emoji 进行文档强调(如本文件中的 ⚠️)
- 项目文档、README 等对外展示文件可以酌情使用
4. **检查方法**
- 使用正则表达式搜索 emoji`[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF\u2600-\u26FF\u2700-\u27BF]`
- 提交前自查,确保无 emoji 混入代码