feat: unify scope settings and catalog table interactions
Deploy production / deploy (push) Successful in 54s

This commit is contained in:
yuxuanhui
2026-09-12 12:11:53 +08:00
parent 9dee2de039
commit 8981c24fda
29 changed files with 2521 additions and 796 deletions
+24 -57
View File
@@ -1,3 +1,6 @@
import { SimulationSettingsEditor } from "../settings/SimulationSettingsEditor";
import { useSettingsOptions } from "../settings/useSettingsOptions";
import { initialSettings } from "../backtests/types";
import {
ResearchDataInput,
researchSelection,
@@ -40,21 +43,9 @@ export function FlowLaunchForm({
const [seed, setSeed] = useState(0);
const [confirmation, setConfirmation] = useState<FlowLaunch | null>(null);
const [busy, setBusy] = useState(false);
const [settings, setSettings] = useState<SimulationSettings>({
instrumentType: "EQUITY",
region: "USA",
universe: "TOP3000",
delay: 1,
decay: 0,
neutralization: "INDUSTRY",
truncation: 0.08,
pasteurization: "ON",
unitHandling: "VERIFY",
nanHandling: "OFF",
language: "FASTEXPR",
visualization: false,
maxTrade: "OFF",
});
const [settings, setSettings] = useState<SimulationSettings>(initialSettings);
const options = useSettingsOptions();
const [settingsValid, setSettingsValid] = useState(false);
useEffect(() => {
const c = new AbortController();
Promise.all([
@@ -169,47 +160,17 @@ export function FlowLaunchForm({
onChange={(v) => setTemplate(v ? String(v) : undefined)}
/>
</label>
<p>
{settings.region} / {settings.universe} / D{settings.delay} · REGULAR /
FASTEXPR / EQUITY
</p>
<div className="research-form-grid">
<label>
中性化
<Input
aria-label="自动研究中性化"
value={settings.neutralization}
onChange={(neutralization) =>
setSettings({ ...settings, neutralization })
}
/>
</label>
<label>
Decay
<InputNumber
aria-label="自动研究 Decay"
min={0}
value={settings.decay}
onChange={(v) => {
if (typeof v === "number") setSettings({ ...settings, decay: v });
}}
/>
</label>
<label>
Truncation
<InputNumber
aria-label="自动研究 Truncation"
min={0}
max={1}
step={0.01}
value={settings.truncation}
onChange={(v) => {
if (typeof v === "number")
setSettings({ ...settings, truncation: v });
}}
/>
</label>
</div>
<SimulationSettingsEditor
value={settings}
onChange={setSettings}
{...options}
onRefresh={() => void options.refresh()}
fixedScopes={inputs
.filter((r) => ids.includes(r.id))
.map((r) => r.scope)}
disabled={busy}
onValidityChange={setSettingsValid}
/>
<h4>本次授权预算</h4>
<div className="research-form-grid">
{(
@@ -263,7 +224,13 @@ export function FlowLaunchForm({
/>
<Button
theme="solid"
disabled={!ids.length || !hypothesis.trim() || !name.trim()}
disabled={
!ids.length ||
!hypothesis.trim() ||
!name.trim() ||
!settingsValid ||
busy
}
onClick={preview}
>
核对并启动研究
+20 -103
View File
@@ -1,3 +1,5 @@
import { SimulationSettingsEditor } from "../settings/SimulationSettingsEditor";
import { useSettingsOptions } from "../settings/useSettingsOptions";
import {
ResearchDataInput,
researchSelection,
@@ -67,7 +69,8 @@ export function ResearchWorkspace({
const [parent, setParent] = useState("");
const [method, setMethod] = useState("structure");
const [settings, setSettings] = useState<SimulationSettings>(initialSettings);
const [neutralizations, setNeutralizations] = useState<string[]>([]);
const options = useSettingsOptions(active && detailOpen);
const [settingsValid, setSettingsValid] = useState(false);
const [mode, setMode] = useState("all");
const [limit, setLimit] = useState(100);
const [seed, setSeed] = useState(0);
@@ -188,28 +191,6 @@ export function ResearchWorkspace({
setMethod("structure");
}
}, [active, action]);
async function refreshSettings() {
const data = await post<{
content: {
items: {
region: string;
universe: string;
delay: number;
neutralizations: string[];
}[];
};
}>("/catalog/setting-options/refresh");
const options = data.content.items
.filter(
(row) =>
row.region === settings.region &&
row.universe === settings.universe &&
row.delay === settings.delay,
)
.flatMap((row) => row.neutralizations);
setNeutralizations([...new Set(options)]);
Toast.success("合法设置已同步");
}
async function save() {
const next = asset
? await api<Asset>(`/research/assets/${asset.id}`, {
@@ -759,88 +740,24 @@ export function ResearchWorkspace({
/>
</label>
</div>
<div className="research-section-heading">
<h4>模拟设置</h4>
<Button
disabled={!inputIds.length || !!busy}
onClick={() => void task("同步合法设置", refreshSettings)}
>
同步合法设置
</Button>
</div>
<p>
{settings.region || "未选择市场"} /{" "}
{settings.universe || "未选择股票池"} / D{settings.delay}
(来自固定输入)
</p>
<div className="research-form-grid">
<label>
中性化
<Input
list={`${page}-neutralizations`}
aria-label="中性化"
value={settings.neutralization}
onChange={(neutralization) =>
setSettings({ ...settings, neutralization })
}
/>
<datalist id={`${page}-neutralizations`}>
{neutralizations.map((value) => (
<option key={value} value={value} />
))}
</datalist>
</label>
<label>
Decay
<InputNumber
aria-label="Decay"
min={0}
value={settings.decay}
onChange={(decay) =>
setSettings({ ...settings, decay: Number(decay) })
}
/>
</label>
<label>
Truncation
<InputNumber
aria-label="Truncation"
min={0}
max={1}
step={0.01}
value={settings.truncation}
onChange={(truncation) =>
setSettings({
...settings,
truncation: Number(truncation),
})
}
/>
</label>
</div>
<div className="research-form-grid">
{(["pasteurization", "nanHandling", "maxTrade"] as const).map(
(key) => (
<label key={key}>
{key}
<ResearchSelect
label={key}
value={settings[key]}
optionList={["ON", "OFF"].map((value) => ({
value,
label: value,
}))}
onChange={(value) =>
setSettings({ ...settings, [key]: value })
}
/>
</label>
),
)}
</div>
<SimulationSettingsEditor
value={settings}
onChange={setSettings}
{...options}
onRefresh={() => void options.refresh()}
fixedScopes={selectedInputs.map((r) => r.scope)}
disabled={!!busy}
onValidityChange={setSettingsValid}
/>
<Button
theme="solid"
disabled={dirty || !inputIds.length || !hypothesis || !!busy}
disabled={
dirty ||
!inputIds.length ||
!hypothesis ||
!!busy ||
!settingsValid
}
onClick={() => void task("生成候选", expand)}
>
保存候选研究记录