feat(stock): GatePanel 자동 폼 + GlobalControls (TopN/ATR/RR + 3버튼)
This commit is contained in:
@@ -1,3 +1,41 @@
|
|||||||
export default function GatePanel({ meta, value, onChange }) {
|
export default function GatePanel({ meta, value, onChange }) {
|
||||||
return <section className="screener-card"><h3>{meta?.label ?? '게이트'}</h3><p style={{fontSize: 12, color:'#9ca3af'}}>TODO: 게이트 파라미터 폼 (Task 4.5)</p></section>;
|
if (!meta) return null;
|
||||||
|
const props = meta.param_schema?.properties || {};
|
||||||
|
return (
|
||||||
|
<section className="screener-card">
|
||||||
|
<h3>{meta.label}</h3>
|
||||||
|
<p style={{ fontSize: 11, color: '#9ca3af', marginTop: 0 }}>
|
||||||
|
통과 조건 — 통과한 종목만 점수 노드에 전달
|
||||||
|
</p>
|
||||||
|
{Object.entries(props).map(([key, prop]) => (
|
||||||
|
<GateField key={key} paramKey={key} prop={prop}
|
||||||
|
value={value?.[key] ?? meta.default_params?.[key]}
|
||||||
|
onChange={(v) => onChange({ ...value, [key]: v })} />
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GateField({ paramKey, prop, value, onChange }) {
|
||||||
|
if (prop.type === 'integer') {
|
||||||
|
return (
|
||||||
|
<div className="param-row">
|
||||||
|
<label style={{ width: 160, fontSize: 12 }}>{paramKey}</label>
|
||||||
|
<input type="number" value={value ?? ''}
|
||||||
|
min={prop.minimum} onChange={(e) => onChange(parseInt(e.target.value, 10))}
|
||||||
|
style={{ flex: 1 }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (prop.type === 'boolean') {
|
||||||
|
return (
|
||||||
|
<div className="param-row">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={!!value} onChange={(e) => onChange(e.target.checked)} />
|
||||||
|
<span style={{ marginLeft: 6, fontSize: 12 }}>{paramKey}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,42 @@ export default function GlobalControls({ settings, setSettings, onRun, onSave, o
|
|||||||
return (
|
return (
|
||||||
<section className="screener-card">
|
<section className="screener-card">
|
||||||
<h3>실행 옵션</h3>
|
<h3>실행 옵션</h3>
|
||||||
<button onClick={onRun} disabled={running}>{running ? '실행 중…' : '지금 실행 (미리보기)'}</button>
|
<div className="param-row">
|
||||||
<button onClick={onSave} disabled={running} style={{ marginTop: 8 }}>스냅샷 저장</button>
|
<label style={{ width: 80, fontSize: 12 }}>Top N</label>
|
||||||
<button onClick={onPersist} disabled={!dirty} style={{ marginTop: 8 }}>설정 저장</button>
|
<input type="number" value={settings.top_n}
|
||||||
|
onChange={(e) => setSettings({ ...settings, top_n: parseInt(e.target.value, 10) })}
|
||||||
|
min={5} max={100} style={{ width: 80 }} />
|
||||||
|
</div>
|
||||||
|
<div className="param-row">
|
||||||
|
<label style={{ width: 80, fontSize: 12 }}>ATR window</label>
|
||||||
|
<input type="number" value={settings.atr_window}
|
||||||
|
onChange={(e) => setSettings({ ...settings, atr_window: parseInt(e.target.value, 10) })}
|
||||||
|
min={5} max={50} style={{ width: 80 }} />
|
||||||
|
</div>
|
||||||
|
<div className="param-row">
|
||||||
|
<label style={{ width: 80, fontSize: 12 }}>손절 ×ATR</label>
|
||||||
|
<input type="number" value={settings.atr_stop_mult} step={0.1}
|
||||||
|
onChange={(e) => setSettings({ ...settings, atr_stop_mult: parseFloat(e.target.value) })}
|
||||||
|
min={0.5} max={5} style={{ width: 80 }} />
|
||||||
|
</div>
|
||||||
|
<div className="param-row">
|
||||||
|
<label style={{ width: 80, fontSize: 12 }}>R:R 비율</label>
|
||||||
|
<input type="number" value={settings.rr_ratio} step={0.1}
|
||||||
|
onChange={(e) => setSettings({ ...settings, rr_ratio: parseFloat(e.target.value) })}
|
||||||
|
min={1} max={10} style={{ width: 80 }} />
|
||||||
|
</div>
|
||||||
|
<button onClick={onRun} disabled={running}
|
||||||
|
style={{ marginTop: 16, width: '100%', padding: 10, background: '#fbbf24', color: '#0b0f17', border: 'none', borderRadius: 6, fontWeight: 600 }}>
|
||||||
|
{running ? '실행 중…' : '지금 실행 (미리보기)'}
|
||||||
|
</button>
|
||||||
|
<button onClick={onSave} disabled={running}
|
||||||
|
style={{ marginTop: 8, width: '100%', padding: 8 }}>
|
||||||
|
스냅샷 저장
|
||||||
|
</button>
|
||||||
|
<button onClick={onPersist} disabled={!dirty}
|
||||||
|
style={{ marginTop: 8, width: '100%', padding: 8, opacity: dirty ? 1 : 0.5 }}>
|
||||||
|
설정 저장 (디폴트 갱신)
|
||||||
|
</button>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user