From b3e492d890238ca12f7770cadf425c3e656c9027 Mon Sep 17 00:00:00 2001 From: shy3130 <415333856@qq.com> Date: Sat, 5 Sep 2026 18:19:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(factor-editor):=20=E7=AE=97=E5=AD=90/?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E9=9D=A2=E6=9D=BF=E4=B8=8E=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E5=BA=93=20UX=20=E8=BF=AD=E4=BB=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 算子区去重: 移除裸 chips 行, 速查表升级为唯一算子面板(默认展开, 挂载时置 open 避免与折叠态打架) - 速查表重设计: 时序(13)/截面(3)/工具(9)三张分组卡片, 每行=完整签名+中文说明+参数约束, 点击整行插入公式光标处; 与 chips 共用 OPERATOR_GROUPS 单一数据源 - 折叠标题突出: 白色前景色+medium 字重+accent 箭头(展开旋转), 悬停变色 - 可用字段: 展开高度 160→256px; 分组名提亮加粗; 字段英文 id 蓝色(与算子签名同色), 中文保持灰色 - 模板库 5→12: 新增 低波动/量比回落/RSI超卖/低振幅/隔夜反转/距高点回落/换手稳定, 全部经 /api/factors/validate 预校验 - 校验/试算按钮居右(ml-auto) - factor-platform-plan.md §5.2 模板计数同步 --- docs/factor-platform-plan.md | 2 +- frontend/src/pages/factors/FactorEditor.tsx | 118 ++++++++++++-------- 2 files changed, 74 insertions(+), 46 deletions(-) diff --git a/docs/factor-platform-plan.md b/docs/factor-platform-plan.md index fe6fc78..69255c3 100644 --- a/docs/factor-platform-plan.md +++ b/docs/factor-platform-plan.md @@ -258,7 +258,7 @@ P0–P3 已全部实现并通过验证(后端 98 项因子相关测试 + 全 2. 校验通过 → 展示编译产物:依赖列集合、warmup_bars(旧文 §3 规则 4)。 3. 试算:选近 40 交易日 + 当前资产,跑一次 Rank IC 快照(复用检验引擎计算路径,**不另写计算逻辑**);展示 IC 序列 sparkline 与均值。 4. 注册:状态 `draft`,写入注册表;后续在检验 tab 跑完整检验后转 `active`(§8 状态机)。编辑已有因子时公式变化保存为新版本并回草稿态。 -5. 模板起点:「从模板开始 / 我的因子」下拉——经典模板(5 个范式)+ 我的因子(已保存自定义因子公式导入);替换非初始公式前弹确认。 +5. 模板起点:「从模板开始 / 我的因子」下拉——经典模板(12 个范式, 覆盖反转/动量/波动/量价/换手/超卖/位置)+ 我的因子(已保存自定义因子公式导入);替换非初始公式前弹确认。 6. 点选插入:全部 25 个 DSL 算子按「时序(13)/截面(3)/工具(9)」分组,点击插入光标处(与后端 `dsl.OPERATORS` 同步维护),悬停显示签名与参数约束;79 个可用字段 chip 双语显示(英文 id 等宽 + 中文标签),点击插入。 ### 5.3 约束(红线) diff --git a/frontend/src/pages/factors/FactorEditor.tsx b/frontend/src/pages/factors/FactorEditor.tsx index b9e8d60..04d34c2 100644 --- a/frontend/src/pages/factors/FactorEditor.tsx +++ b/frontend/src/pages/factors/FactorEditor.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { CircleAlert, CircleCheck, FlaskConical, PenLine, Play, Save, ShieldQuestion } from 'lucide-react' +import { ChevronRight, CircleAlert, CircleCheck, FlaskConical, PenLine, Play, Save, ShieldQuestion } from 'lucide-react' import { toast } from '@/components/Toast' import { api, type FactorTrialResponse, type FactorValidateResponse } from '@/lib/api' import { QK } from '@/lib/queryKeys' @@ -17,45 +17,53 @@ const TEMPLATES: { label: string; formula: string; note: string }[] = [ { label: '波动变化', formula: 'ts_std(close / ts_delay(close, 1) - 1, 20)', note: '20 日滚动日收益波动' }, { label: '换手异动', formula: 'ts_zscore(turnover_rate, 60)', note: '换手率相对自身 60 日分布的 z 分' }, { label: '乖离组合', formula: 'zscore(close / ma20 - 1) - zscore(ts_mean(turnover_rate, 5))', note: '价格乖离与换手均值的截面差' }, + { label: '低波动', formula: 'rank(-ts_std(change_pct, 20))', note: '20 日日收益波动最低 (低波动异象)' }, + { label: '量比回落', formula: 'rank(-ts_zscore(vol_ratio_5d, 60))', note: '量比回落到自身 60 日低位' }, + { label: 'RSI超卖', formula: 'rank(-ts_mean(rsi_14, 5))', note: '近 5 日 RSI 均值最低的超卖标的' }, + { label: '低振幅', formula: 'rank(-ts_mean(amplitude, 20))', note: '20 日平均振幅最低 (缩量休整)' }, + { label: '隔夜反转', formula: 'rank(-overnight_ret_20d)', note: '20 日累计隔夜收益的反转' }, + { label: '距高点回落', formula: 'rank(-distance_to_high_60d)', note: '距 60 日高点回撤最深的标的' }, + { label: '换手稳定', formula: 'rank(-ts_std(turnover_rate, 20))', note: '换手率波动最低 (筹码稳定)' }, ] -/** 后端 dsl.OPERATORS 的全部 25 个算子, 按家族分组; 点击插入, 悬停显示签名与约束。 */ -const OPERATOR_GROUPS: { label: string; ops: { name: string; desc: string }[] }[] = [ +/** 后端 dsl.OPERATORS 的全部 25 个算子, 按家族分组; name=chip 名, sig=完整签名, desc=中文说明。 + * chips 面板负责点击插入, 速查表负责阅读 — 同一份数据两个视图。 */ +const OPERATOR_GROUPS: { label: string; ops: { name: string; sig: string; desc: string }[] }[] = [ { label: '时序', ops: [ - { name: 'ts_mean', desc: 'ts_mean(x,n) 滚动均值, n∈[2,512]' }, - { name: 'ts_sum', desc: 'ts_sum(x,n) 滚动求和, n∈[2,512]' }, - { name: 'ts_std', desc: 'ts_std(x,n) 滚动标准差, n∈[2,512]' }, - { name: 'ts_max', desc: 'ts_max(x,n) 滚动最大值, n∈[2,512]' }, - { name: 'ts_min', desc: 'ts_min(x,n) 滚动最小值, n∈[2,512]' }, - { name: 'ts_delay', desc: 'ts_delay(x,n) n 期前的值, n≤512 (禁止负数)' }, - { name: 'ts_delta', desc: 'ts_delta(x,n) n 期差分 (x − n 期前的值)' }, - { name: 'ts_rank', desc: 'ts_rank(x,n) 滚动分位' }, - { name: 'ts_zscore', desc: 'ts_zscore(x,n) 滚动 z 分' }, - { name: 'ts_corr', desc: 'ts_corr(x,y,n) 滚动相关系数' }, - { name: 'ts_cov', desc: 'ts_cov(x,y,n) 滚动协方差' }, - { name: 'ts_quantile', desc: 'ts_quantile(x,n,q) 滚动分位, q∈(0,1)' }, - { name: 'decay_linear', desc: 'decay_linear(x,n) 线性衰减加权均值 (近端权重大)' }, + { name: 'ts_mean', sig: 'ts_mean(x,n)', desc: '滚动均值 · n∈[2,512]' }, + { name: 'ts_sum', sig: 'ts_sum(x,n)', desc: '滚动求和 · n∈[2,512]' }, + { name: 'ts_std', sig: 'ts_std(x,n)', desc: '滚动标准差 · n∈[2,512]' }, + { name: 'ts_max', sig: 'ts_max(x,n)', desc: '滚动最大值 · n∈[2,512]' }, + { name: 'ts_min', sig: 'ts_min(x,n)', desc: '滚动最小值 · n∈[2,512]' }, + { name: 'ts_delay', sig: 'ts_delay(x,n)', desc: 'n 期前的值 · n≤512' }, + { name: 'ts_delta', sig: 'ts_delta(x,n)', desc: 'n 期差分 · x − n 期前' }, + { name: 'ts_rank', sig: 'ts_rank(x,n)', desc: '滚动分位' }, + { name: 'ts_zscore', sig: 'ts_zscore(x,n)', desc: '滚动 z 分' }, + { name: 'ts_corr', sig: 'ts_corr(x,y,n)', desc: '滚动相关系数' }, + { name: 'ts_cov', sig: 'ts_cov(x,y,n)', desc: '滚动协方差' }, + { name: 'ts_quantile', sig: 'ts_quantile(x,n,q)', desc: '滚动分位 · q∈(0,1)' }, + { name: 'decay_linear', sig: 'decay_linear(x,n)', desc: '线性衰减加权 · 近端权重大' }, ], }, { label: '截面', ops: [ - { name: 'rank', desc: 'rank(x) 当日横截面百分位' }, - { name: 'zscore', desc: 'zscore(x) 当日横截面 z 分' }, - { name: 'winsorize', desc: 'winsorize(x,k) 截面截尾至 μ±kσ, k∈[1,6] 默认 3' }, + { name: 'rank', sig: 'rank(x)', desc: '当日横截面百分位' }, + { name: 'zscore', sig: 'zscore(x)', desc: '当日横截面 z 分' }, + { name: 'winsorize', sig: 'winsorize(x,k)', desc: '截面截尾至 μ±kσ · k∈[1,6] 默认 3' }, ], }, { label: '工具', ops: [ - { name: 'if_else', desc: 'if_else(c,a,b) 条件选择' }, - { name: 'min', desc: 'min(a,b) 两值取小' }, - { name: 'max', desc: 'max(a,b) 两值取大' }, - { name: 'log', desc: 'log(x) 自然对数' }, - { name: 'abs', desc: 'abs(x) 绝对值' }, - { name: 'sign', desc: 'sign(x) 符号函数' }, - { name: 'sqrt', desc: 'sqrt(x) 平方根' }, - { name: 'power', desc: 'power(x,c) 幂运算, |c|≤4' }, - { name: 'clamp', desc: 'clamp(x,lo,hi) 截断到 [lo,hi]' }, + { name: 'if_else', sig: 'if_else(c,a,b)', desc: '条件选择' }, + { name: 'min', sig: 'min(a,b)', desc: '两值取小' }, + { name: 'max', sig: 'max(a,b)', desc: '两值取大' }, + { name: 'log', sig: 'log(x)', desc: '自然对数' }, + { name: 'abs', sig: 'abs(x)', desc: '绝对值' }, + { name: 'sign', sig: 'sign(x)', desc: '符号函数' }, + { name: 'sqrt', sig: 'sqrt(x)', desc: '平方根' }, + { name: 'power', sig: 'power(x,c)', desc: '幂运算 · |c|≤4' }, + { name: 'clamp', sig: 'clamp(x,lo,hi)', desc: '截断到 [lo,hi]' }, ], }, ] @@ -289,27 +297,47 @@ export function FactorEditor({ editId = '' }: { editId?: string }) { {/* 算子/字段点选插入: 写公式不用背名字 */}
-
-
算子 ({OPERATOR_COUNT}) · 点击插入 · 悬停看签名与约束
-
+
{ if (el) el.open = true }} + className="group rounded-btn border border-border/60 bg-base/40 px-2.5 py-1.5 text-[10px]" + > + + + 算子({OPERATOR_COUNT} 个 · 时序 / 截面 / 工具) + · 点击签名插入 + +
{OPERATOR_GROUPS.map(group => ( -
- {group.label} - {group.ops.map(op => ( - - ))} +
+
{group.label} ({group.ops.length})
+
+ {group.ops.map(op => ( + + ))} +
))}
-
-
- 可用字段 ({columns.data?.columns.length ?? 0}) · 点击插入 -
+
四则运算 + - * / · 除零→空值 · 括号可任意分组
+
+
+ + + 可用字段 ({columns.data?.columns.length ?? 0}) · 点击插入 + +
{fieldGroups.map(([group, cols]) => (
-
{group}
+
{group}
{cols.map(col => ( ))} @@ -341,7 +369,7 @@ export function FactorEditor({ editId = '' }: { editId?: string }) { type="button" onClick={runBoth} disabled={!formula.trim() || validate.isPending} - className="inline-flex h-8 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-btn border border-border bg-surface px-3 text-xs font-medium text-secondary transition-colors hover:border-accent/40 hover:text-accent disabled:cursor-not-allowed disabled:opacity-50" + className="ml-auto inline-flex h-8 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-btn border border-border bg-surface px-3 text-xs font-medium text-secondary transition-colors hover:border-accent/40 hover:text-accent disabled:cursor-not-allowed disabled:opacity-50" > {validate.isPending ? '校验中…' : '校验'}