fix: fetch dataset scope options from WorldQuant platform
This commit is contained in:
@@ -35,6 +35,28 @@ type Scope = {
|
||||
universe: string;
|
||||
delay: number;
|
||||
};
|
||||
type PlatformOption = Omit<Scope, "universe"> & { universes: string[] };
|
||||
type PlatformOptions = { instrument_options: PlatformOption[] };
|
||||
function normalizeScope(rows: PlatformOption[], preferred: Scope): Scope {
|
||||
const typed = rows.filter(
|
||||
(r) => r.instrument_type === preferred.instrument_type,
|
||||
);
|
||||
const regional = typed.filter((r) => r.region === preferred.region);
|
||||
const row =
|
||||
regional.find((r) => r.delay === preferred.delay) ??
|
||||
regional[0] ??
|
||||
typed[0] ??
|
||||
rows[0];
|
||||
if (!row) return preferred;
|
||||
return {
|
||||
instrument_type: row.instrument_type,
|
||||
region: row.region,
|
||||
delay: row.delay,
|
||||
universe: row.universes.includes(preferred.universe)
|
||||
? preferred.universe
|
||||
: row.universes[0],
|
||||
};
|
||||
}
|
||||
type Note = { note: string; version: number; updated_at: string };
|
||||
type Entry = {
|
||||
id: string;
|
||||
@@ -136,7 +158,18 @@ export function DatasetPage({
|
||||
universe: "TOP3000",
|
||||
delay: 1,
|
||||
});
|
||||
const [scopes, setScopes] = useState<Record<string, string[]>>({});
|
||||
const scopeRef = useRef(scope);
|
||||
scopeRef.current = scope;
|
||||
const [scopes, setScopes] = useState<PlatformOption[]>([]);
|
||||
const [scopeError, setScopeError] = useState("");
|
||||
const [scopeRevision, setScopeRevision] = useState(0);
|
||||
const selectedOption = scopes.find(
|
||||
(r) =>
|
||||
r.instrument_type === scope.instrument_type &&
|
||||
r.region === scope.region &&
|
||||
r.delay === scope.delay,
|
||||
);
|
||||
const scopeValid = !!selectedOption?.universes.includes(scope.universe);
|
||||
const [browse, setBrowse] = useState<Browse>(initialBrowse);
|
||||
const [fieldBrowse, setFieldBrowse] = useState<Browse>(initialBrowse);
|
||||
const [size, setSize] = useState(25);
|
||||
@@ -370,11 +403,34 @@ export function DatasetPage({
|
||||
if (account) setSize(account.page_size);
|
||||
}, [account?.page_size]);
|
||||
useEffect(() => {
|
||||
if (active)
|
||||
api<Record<string, string[]>>("/catalog/scopes")
|
||||
.then(setScopes)
|
||||
.catch((e) => setError(e.message));
|
||||
}, [active]);
|
||||
if (!active) return;
|
||||
let live = true;
|
||||
setScopes([]);
|
||||
setScopeError("");
|
||||
api<PlatformOptions>("/catalog/scopes")
|
||||
.then((result) => {
|
||||
if (!live) return;
|
||||
if (!result.instrument_options.length)
|
||||
throw new Error("平台未返回可用研究范围");
|
||||
setScopes(result.instrument_options);
|
||||
const next = normalizeScope(
|
||||
result.instrument_options,
|
||||
scopeRef.current,
|
||||
);
|
||||
if (
|
||||
(Object.keys(next) as (keyof Scope)[]).some(
|
||||
(key) => next[key] !== scopeRef.current[key],
|
||||
)
|
||||
)
|
||||
changeScope(next);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (live) setScopeError(e.message);
|
||||
});
|
||||
return () => {
|
||||
live = false;
|
||||
};
|
||||
}, [active, version, scopeRevision]);
|
||||
useEffect(() => {
|
||||
if (!active) return;
|
||||
let live = true;
|
||||
@@ -535,6 +591,7 @@ export function DatasetPage({
|
||||
if (!field) setSelected(row);
|
||||
};
|
||||
async function sync(datasetId?: string) {
|
||||
if (!scopeValid) return;
|
||||
setBusy(true);
|
||||
try {
|
||||
await post("/catalog/sync-jobs", {
|
||||
@@ -848,6 +905,7 @@ export function DatasetPage({
|
||||
<>
|
||||
<section className="catalog-page">
|
||||
{[
|
||||
"InstrumentType",
|
||||
"Region",
|
||||
"Universe",
|
||||
"Delay",
|
||||
@@ -865,34 +923,68 @@ export function DatasetPage({
|
||||
</span>
|
||||
))}
|
||||
<div className="catalog-tools">
|
||||
<Select
|
||||
aria-labelledby="catalog-label-InstrumentType"
|
||||
value={scope.instrument_type}
|
||||
disabled={!scopes.length}
|
||||
optionList={options([
|
||||
...new Set(scopes.map((r) => r.instrument_type)),
|
||||
])}
|
||||
onChange={(v) =>
|
||||
changeScope(
|
||||
normalizeScope(scopes, {
|
||||
...scope,
|
||||
instrument_type: String(v),
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
aria-labelledby="catalog-label-Region"
|
||||
value={scope.region}
|
||||
optionList={options(Object.keys(scopes))}
|
||||
disabled={!scopes.length}
|
||||
optionList={options([
|
||||
...new Set(
|
||||
scopes
|
||||
.filter((r) => r.instrument_type === scope.instrument_type)
|
||||
.map((r) => r.region),
|
||||
),
|
||||
])}
|
||||
onChange={(v) =>
|
||||
changeScope({
|
||||
...scope,
|
||||
region: String(v),
|
||||
universe: scopes[String(v)][0],
|
||||
})
|
||||
changeScope(
|
||||
normalizeScope(scopes, { ...scope, region: String(v) }),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
aria-labelledby="catalog-label-Universe"
|
||||
value={scope.universe}
|
||||
optionList={options(scopes[scope.region] ?? [])}
|
||||
disabled={!selectedOption}
|
||||
optionList={options(selectedOption?.universes ?? [])}
|
||||
onChange={(v) => changeScope({ ...scope, universe: String(v) })}
|
||||
/>
|
||||
<Select
|
||||
aria-labelledby="catalog-label-Delay"
|
||||
value={scope.delay}
|
||||
optionList={[
|
||||
{ label: "Delay 0", value: 0 },
|
||||
{ label: "Delay 1", value: 1 },
|
||||
]}
|
||||
onChange={(v) => changeScope({ ...scope, delay: Number(v) })}
|
||||
disabled={!scopes.length}
|
||||
optionList={scopes
|
||||
.filter(
|
||||
(r) =>
|
||||
r.instrument_type === scope.instrument_type &&
|
||||
r.region === scope.region,
|
||||
)
|
||||
.map((r) => ({ label: `Delay ${r.delay}`, value: r.delay }))}
|
||||
onChange={(v) =>
|
||||
changeScope(
|
||||
normalizeScope(scopes, { ...scope, delay: Number(v) }),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button loading={busy} onClick={() => void sync()}>
|
||||
<Button
|
||||
loading={busy}
|
||||
disabled={!scopeValid}
|
||||
onClick={() => void sync()}
|
||||
>
|
||||
同步目录
|
||||
</Button>
|
||||
<Button
|
||||
@@ -907,6 +999,19 @@ export function DatasetPage({
|
||||
{formatTime(data.synced_at, account?.timezone)}
|
||||
</span>
|
||||
</div>
|
||||
{scopeError && (
|
||||
<Banner
|
||||
type="danger"
|
||||
description={
|
||||
<span>
|
||||
{scopeError}{" "}
|
||||
<Button onClick={() => setScopeRevision((v) => v + 1)}>
|
||||
重试获取选项
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{error && <Banner type="danger" description={error} />}
|
||||
<section className="library-panel">
|
||||
<div className="catalog-tools">
|
||||
|
||||
Reference in New Issue
Block a user