mirror of
https://github.com/shy3130/tick-stock-panel.git
synced 2026-11-02 10:26:13 +00:00
Merge pull request #446 from intfoo/pr/strategy-param-string-type
feat(strategy): 支持 string 类型策略参数
This commit is contained in:
@@ -28,7 +28,7 @@ _SYSTEM_PREFIX = """你是A股量化策略设计专家。根据用户描述的
|
||||
4. polars 策略只 import polars 和 datetime;matrix_native 策略只允许 import numpy 以及 from app.backtest.matrix import 所需矩阵协议和算子
|
||||
|
||||
要求:
|
||||
1. 用户可能调整的策略阈值通过 META["params"] 暴露,每项使用 id/label/type/default/min/max/step;公式常数、固定窗口边界、布尔开关不必强行参数化
|
||||
1. 用户可能调整的策略阈值通过 META["params"] 暴露,每项使用 id/label/type/default/min/max/step(type 支持 float/int/bool/select/date/string);公式常数、固定窗口边界、布尔开关不必强行参数化
|
||||
2. 遵循指南中的文件结构,但优先贴合用户规则,不要为了套模板歪曲策略含义
|
||||
3. ENTRY_SIGNALS/EXIT_SIGNALS 根据策略逻辑自行选择匹配的信号列,不要照搬示例
|
||||
4. scoring 权重根据策略核心逻辑定制,总和 = 1.0;键只能使用指南中的真实数值字段或受控虚拟评分字段 ma20_bias,不得创造条件名称作为评分列
|
||||
|
||||
@@ -35,7 +35,7 @@ META = {
|
||||
"exclude_st": True,
|
||||
"exclude_new_days": 30,
|
||||
},
|
||||
"params": [], # type: float/int/bool/select/date;float/int 带 min/max/step
|
||||
"params": [], # type: float/int/bool/select/date/string;float/int 带 min/max/step
|
||||
"scoring": {},
|
||||
"order_by": "score",
|
||||
"descending": True,
|
||||
|
||||
@@ -35,8 +35,8 @@ META = {
|
||||
},
|
||||
|
||||
# 策略参数 (只把用户可能调节的阈值放这里,公式常数不必参数化)
|
||||
# type 支持: float / int / bool / select(带 options) / date(格式 "YYYY-MM-DD")
|
||||
# float/int 可带 min/max/step;select 带 options: [{label, value}];date 的 default 是字符串
|
||||
# type 支持: float / int / bool / select(带 options) / date(格式 "YYYY-MM-DD") / string(自由文本)
|
||||
# float/int 可带 min/max/step;select 带 options: [{label, value}];date/string 的 default 是字符串
|
||||
"params": [
|
||||
],
|
||||
|
||||
|
||||
@@ -663,7 +663,9 @@ export function StrategyBuilderDialog({ open, onClose, onSavedId, mode = 'create
|
||||
) : (
|
||||
<>
|
||||
<span className="text-[11px] font-mono text-foreground">{p.default}</span>
|
||||
<span className="text-[10px] text-muted">{p.min} ~ {p.max}{p.type === 'float' || p.type === 'int' ? ' · 步长 ' + p.step : ''}</span>
|
||||
{(p.type === 'float' || p.type === 'int') && (
|
||||
<span className="text-[10px] text-muted">{p.min} ~ {p.max} · 步长 {p.step}</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -155,6 +155,20 @@ function ParamField({ def, value, onChange }: {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (def.type === 'string') {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[11px] text-secondary w-16 shrink-0 text-right">{def.label}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={value ?? def.default ?? ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
className="flex-1 min-w-0 px-1.5 py-0.5 rounded bg-base border border-border text-[11px] font-mono text-foreground focus:outline-none focus:border-accent/50"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -759,7 +759,7 @@ export interface AuctionBenchmarkPayload {
|
||||
export interface StrategyParamDef {
|
||||
id: string
|
||||
label: string
|
||||
type: 'float' | 'int' | 'select' | 'bool'
|
||||
type: 'float' | 'int' | 'select' | 'bool' | 'string'
|
||||
default: number | string | boolean
|
||||
min?: number
|
||||
max?: number
|
||||
|
||||
@@ -747,6 +747,19 @@ function StrategyParamInput({ param, value, onChange }: {
|
||||
</label>
|
||||
)
|
||||
}
|
||||
if (param.type === 'string') {
|
||||
return (
|
||||
<label className="block">
|
||||
<span className="mb-1 block text-[11px] text-secondary">{param.label}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={value ?? param.default ?? ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
className={INPUT_CLS}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<label className="block">
|
||||
<span className="mb-1 block text-[11px] text-secondary">{param.label}</span>
|
||||
|
||||
@@ -106,7 +106,8 @@ export function useParamSweep(strategies: StrategyDetail[], onStrategyChange?: (
|
||||
if (!s?.enabled) continue
|
||||
if (p.type === 'bool') grid[p.id] = [true, false]
|
||||
else if (p.type === 'select') grid[p.id] = p.options ?? []
|
||||
else grid[p.id] = { min: Number(s.min), max: Number(s.max), step: Number(s.step) }
|
||||
else if (p.type === 'float' || p.type === 'int') grid[p.id] = { min: Number(s.min), max: Number(s.max), step: Number(s.step) }
|
||||
// string 等类型不支持范围展开, 跳过不可扫描参数
|
||||
}
|
||||
return grid
|
||||
}
|
||||
@@ -142,6 +143,18 @@ export function SweepParamList({ params, sweeps, updateSweep }: {
|
||||
{params.map(p => {
|
||||
const s = sweeps[p.id] ?? defaultSweep(p)
|
||||
const numeric = p.type === 'float' || p.type === 'int'
|
||||
const sweepable = numeric || p.type === 'bool' || p.type === 'select'
|
||||
if (!sweepable) {
|
||||
return (
|
||||
<div key={p.id} className="rounded-input border border-border/60 p-2">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="font-medium text-muted">{p.label}</span>
|
||||
<span className="text-muted">({p.type})</span>
|
||||
<span className="text-[11px] text-muted">文本参数不可扫描</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div key={p.id} className="rounded-input border border-border/60 p-2">
|
||||
<label className="flex items-center gap-2 text-xs">
|
||||
|
||||
Reference in New Issue
Block a user