diff --git a/backend/app/strategy/prompts/strategy-guide.md b/backend/app/strategy/prompts/strategy-guide.md index 74d377d7..acbcc7d9 100644 --- a/backend/app/strategy/prompts/strategy-guide.md +++ b/backend/app/strategy/prompts/strategy-guide.md @@ -37,6 +37,14 @@ META = { # 策略参数 (只把用户可能调节的阈值放这里,公式常数不必参数化) # type 支持: float / int / bool / select(带 options) / date(格式 "YYYY-MM-DD") / string(自由文本) # float/int 可带 min/max/step;select 带 options: [{label, value}];date/string 的 default 是字符串 + # + # 可选 UI 元数据 (只影响参数面板展示, 不影响策略语义; 后端原样透传): + # "group": "组名" — 相同组名的相邻参数归并为一节显示 + # "visible_if": {...} — 级联显隐: {"param": "pid", "in": [值...]} 单条件, + # 或 {"all": [...]} / {"any": [...]} 递归组合; + # in 值类型应与目标参数一致 (bool 用 True/False 而非字符串); + # 隐藏只是不渲染, 参数值仍按当前值随请求提交 + # "option_applies": {选项: {pid: 值}} — select 选中某选项时批量应用一组参数值 (如预设) "params": [ ], diff --git a/frontend/src/components/screener/StrategySettingsDialog.tsx b/frontend/src/components/screener/StrategySettingsDialog.tsx index 23e19114..52a55bbc 100644 --- a/frontend/src/components/screener/StrategySettingsDialog.tsx +++ b/frontend/src/components/screener/StrategySettingsDialog.tsx @@ -5,6 +5,8 @@ import { X, Settings2, RotateCcw, Save, Filter, Star, TrendingUp, Sparkles, Down import { api, type StrategyDetail, type StrategyParamDef, type CompositeChildInfo, type ScoringDirection, type CustomSignalCondition } from '@/lib/api' import { QK } from '@/lib/queryKeys' import { toPercentages, normalizeWeights } from '@/lib/weights' +import { groupParams, visibleParams } from '@/lib/strategyParams' +import { toast } from '@/components/Toast' import { BUILTIN_COLUMNS } from '@/lib/watchlist-columns' import { color } from '@/lib/colors' import { SignalPicker } from './SignalPicker' @@ -167,10 +169,12 @@ function RiskPctField({ label, value, onChange, min, max, step = 0.5, unit = '%' // 策略参数字段 // memo: 单参数修改只重渲本行 (父组件需传稳定回调, 否则 memo 失效) -const ParamField = memo(function ParamField({ def, value, onChange }: { +const ParamField = memo(function ParamField({ def, value, onChange, onApplyValues }: { def: StrategyParamDef value: any onChange: (pid: string, v: any) => void + /** select 带 option_applies 时, 选中某选项需批量应用一组参数值 (如标的池预设) */ + onApplyValues?: (values: Record) => void }) { if (def.type === 'bool') { const checked = value === true || value === 'true' || value === 'True' @@ -199,7 +203,15 @@ const ParamField = memo(function ParamField({ def, value, onChange }: { {def.label} onChange(param.id, e.target.value)} className={INPUT_CLS}> + @@ -2935,14 +2950,25 @@ export function StrategyBacktest({ loadCandidate, onLoadConsumed }: { actions={detail.params.length > 0 ? {detail.params.length} 项 : undefined} > {detail.params.length > 0 ? ( -
- {detail.params.map(param => ( - +
+ {groupParams(visibleParams(detail.params, strategyParams)).map((g, gi) => ( +
+ {g.name && ( +
{g.name}
+ )} + {/* 命名组横排(如「入池开关 + 止盈 + 锁定」同组一行);无名组回退网格 */} +
+ {g.items.map(param => ( + setStrategyParams(prev => ({ ...prev, ...values }))} + /> + ))} +
+
))}
) : ( diff --git a/frontend/src/pages/backtest/StrategyOptimizer.tsx b/frontend/src/pages/backtest/StrategyOptimizer.tsx index d89dc1da..8212fdcd 100644 --- a/frontend/src/pages/backtest/StrategyOptimizer.tsx +++ b/frontend/src/pages/backtest/StrategyOptimizer.tsx @@ -105,7 +105,7 @@ export function StrategyOptimizer() {
- + {task?.isPending ? ( diff --git a/frontend/src/pages/backtest/StrategyWalkForward.tsx b/frontend/src/pages/backtest/StrategyWalkForward.tsx index e6dde95c..a54b5c35 100644 --- a/frontend/src/pages/backtest/StrategyWalkForward.tsx +++ b/frontend/src/pages/backtest/StrategyWalkForward.tsx @@ -176,7 +176,7 @@ export function StrategyWalkForward() { - +
每折跑 {sweep.combos || 0} 组优化 × N 折,耗时较长
diff --git a/frontend/src/pages/backtest/components/paramSweep.tsx b/frontend/src/pages/backtest/components/paramSweep.tsx index f2c939d9..c2e3a736 100644 --- a/frontend/src/pages/backtest/components/paramSweep.tsx +++ b/frontend/src/pages/backtest/components/paramSweep.tsx @@ -1,5 +1,6 @@ import { useMemo, useState } from 'react' import type { StrategyDetail, StrategyParamDef } from '@/lib/api' +import { paramDisplayLabel, visibleParams } from '@/lib/strategyParams' /** 参数扫描配置的共享逻辑与 UI — 优化器与 walk-forward 复用。 */ @@ -69,7 +70,11 @@ export function useParamSweep(strategies: StrategyDetail[], onStrategyChange?: ( const [sweeps, setSweeps] = useState>({}) const selected = strategies.find(s => s.id === strategyId) - const params = selected?.params ?? [] + // 级联隐藏参数不参与扫描: 扫描面板未扫描参数固定为默认值, 按默认值判定显隐 + const params = useMemo( + () => visibleParams(selected?.params ?? [], selected?.params_defaults ?? {}), + [selected], + ) const selectStrategy = (id: string) => { setStrategyId(id) @@ -112,7 +117,14 @@ export function useParamSweep(strategies: StrategyDetail[], onStrategyChange?: ( return grid } - return { strategyId, selected, selectStrategy, params, sweeps, updateSweep, combos, gridError, buildGrid } + // 因级联规则 (visible_if) 在默认配置下隐藏、不参与扫描的参数, 供 UI 提示 + // (paramDisplayLabel: 平铺列表无组上下文, 短标签需带组名才可区分) + const hiddenParamLabels = useMemo( + () => (selected?.params ?? []).filter(p => !params.includes(p)).map(p => paramDisplayLabel(p)), + [selected, params], + ) + + return { strategyId, selected, selectStrategy, params, sweeps, updateSweep, combos, gridError, buildGrid, hiddenParamLabels } } /** 策略选择器。 */ @@ -130,15 +142,22 @@ export function StrategySelect({ strategies, value, onChange }: { } /** 可扫参数列表 (勾选 + min/max/step)。 */ -export function SweepParamList({ params, sweeps, updateSweep }: { +export function SweepParamList({ params, sweeps, updateSweep, hiddenLabels = [] }: { params: StrategyParamDef[] sweeps: Record updateSweep: (pid: string, patch: Partial) => void + /** 因级联规则 (visible_if) 在默认配置下隐藏的参数标签, 仅作提示 */ + hiddenLabels?: string[] }) { if (!params.length) return null return (
扫描参数 (勾选后设范围)
+ {hiddenLabels.length > 0 && ( +
+ {hiddenLabels.length} 个参数因级联规则在默认配置下隐藏,不参与扫描:{hiddenLabels.join('、')} +
+ )}
{params.map(p => { const s = sweeps[p.id] ?? defaultSweep(p) @@ -159,7 +178,7 @@ export function SweepParamList({ params, sweeps, updateSweep }: {
{s.enabled && numeric && (