feat(ext-data): 扩展表字段接入信号与因子(数值因子/评分 + string 归属筛选)

数值字段(int/float) → 信号+因子双通道:
- ext_factors: 帧组装时 join 扩展列并注册 kind=base 因子(分组「扩展数据」),
  时序模式按 (symbol,交易日) 精确对齐无未来函数, 快照模式仅当日单日帧
  注入(历史帧跳过防未来函数)
- registry.all_factors 惰性同步(配置目录签名幂等, 以注册表为权威增删);
  custom_signals.allowed_fields 自动并入 → 信号下拉/因子库/AI提示词/检验
  同一份清单; factor 补算入口按需注入
- 失效链: 上传/拉取/配置变更自动清扩展帧缓存+策略缓存, API层补
  repo.clear_cache; 写入后下一次计算立即生效
- 列名保留中文(预设表字段名), 非ASCII数值字段只进信号不注册因子
  (DSL标识符ASCII-only)

string 字段(概念/行业归属) → 仅信号条件通道:
- 运算符 包含(contains,字面量匹配非正则)/等于/不等于, 右值为字符串字面量,
  可与数值条件混合(强势板块归属 AND 热度阈值)
- 前端信号编辑器按字段类型切换运算符与右值输入; /options 暴露
  stringFields; AI 提示词含字符串字段清单与 contains 用法
- string 不注册为因子(数值口径), 空值不误报

测试: test_ext_factors 18个(PIT对齐/跨日不泄露/快照门控/写入失效/
contains字面量/中文列名端到端等); 存量因子计数测试补 data/ 运行时隔离
夹具(黄金断言不依赖本机扩展表); 受影响回归148个全过; pnpm build 通过;
ruff 对齐 main 基线
This commit is contained in:
shy3130
2026-09-06 18:27:33 +08:00
parent 716897f41a
commit e3a2fbce13
15 changed files with 1008 additions and 18 deletions
+20
View File
@@ -273,6 +273,8 @@ class ExtConfigStore:
json.dumps(config.to_dict(), ensure_ascii=False, indent=2),
encoding="utf-8",
)
# 字段集/模式变化会改变扩展列集合: 失效扩展帧缓存与策略结果缓存
_invalidate_ext_derived(self._base.parent)
def delete(self, config_id: str) -> bool:
import shutil
@@ -283,6 +285,7 @@ class ExtConfigStore:
if not cp.exists():
return False
shutil.rmtree(cp.parent, ignore_errors=True)
_invalidate_ext_derived(self._base.parent)
return True
def _migrate_legacy(self, old_path: Path) -> None:
@@ -582,9 +585,25 @@ def write_ext_parquet(
df = cast_df_to_schema(df, config.fields)
df.write_parquet(out_path)
logger.info("扩展表写入: %s%s (%d 行)", config.id, out_path, len(df))
# 扩展列已接入 enriched 帧/因子注册表: 写入后必须失效相关缓存
_invalidate_ext_derived(data_dir)
return len(df)
def _invalidate_ext_derived(data_dir: Path) -> None:
"""扩展数据/配置变更 → 扩展帧缓存 + 因子同步状态 + 策略结果缓存。
惰性导入避免与 ext_factors (反向惰性引用本模块) 构成模块级环。
repo 内存 enriched 缓存由 API 层 repo.clear_cache() 补充清理。
"""
try:
from app.factors.ext_factors import invalidate_ext_caches
invalidate_ext_caches(data_dir)
except Exception as e:
logger.warning("扩展数据缓存失效失败: %s", e)
def delete_ext_parquet(config_id: str, data_dir: Path) -> None:
"""删除扩展数据源关联的所有 Parquet 数据(保留 config.json)。
@@ -601,6 +620,7 @@ def delete_ext_parquet(config_id: str, data_dir: Path) -> None:
if ts_dir.exists():
import shutil
shutil.rmtree(ts_dir, ignore_errors=True)
_invalidate_ext_derived(data_dir)
def fix_symbol_format(config: ExtConfig, data_dir: Path) -> int: