feat: add local correlation filters and full recheck action
Deploy production / deploy (push) Successful in 54s
Deploy production / deploy (push) Successful in 54s
This commit is contained in:
@@ -88,6 +88,7 @@ export const jobLabels: Record<string, string> = {
|
||||
full_sync: "全量同步 Alpha",
|
||||
daily_sync: "按天同步 Alpha",
|
||||
self_correlation: "本地自相关检测",
|
||||
self_correlation_recheck: "重检自相关",
|
||||
submission_check: "写回 Description 并检查提交",
|
||||
alpha_refresh: "导入 / 刷新 Alpha",
|
||||
pnl_refresh: "获取 PnL",
|
||||
|
||||
@@ -108,14 +108,17 @@ export function JobPanel({
|
||||
: `仅补取缺失的 PnL${job.checkpoint?.alpha_id ? ` · ${job.checkpoint.alpha_id}` : ""}`}
|
||||
</p>
|
||||
)}
|
||||
{job.kind === "self_correlation" && job.checkpoint?.alpha_id && (
|
||||
<p className="muted">
|
||||
{job.checkpoint.alpha_id} ·{" "}
|
||||
{job.checkpoint.phase === "calculating"
|
||||
? "计算相关性"
|
||||
: `准备 PnL:${job.checkpoint.references_loaded ?? 0} / ${job.checkpoint.references_total ?? 0} 个基准`}
|
||||
</p>
|
||||
)}
|
||||
{["self_correlation", "self_correlation_recheck"].includes(
|
||||
job.kind,
|
||||
) &&
|
||||
job.checkpoint?.alpha_id && (
|
||||
<p className="muted">
|
||||
{job.checkpoint.alpha_id} ·{" "}
|
||||
{job.checkpoint.phase === "calculating"
|
||||
? "计算相关性"
|
||||
: `准备 PnL:${job.checkpoint.references_loaded ?? 0} / ${job.checkpoint.references_total ?? 0} 个基准`}
|
||||
</p>
|
||||
)}
|
||||
<div className="job-numbers">
|
||||
<span>
|
||||
已处理 <b>{job.processed}</b>
|
||||
|
||||
@@ -376,6 +376,8 @@ export function AlphaPage({
|
||||
setIdText("");
|
||||
if (kind === "pnl_backfill" && job.total === 0)
|
||||
Toast.success("已提交 Alpha 的 PnL 已齐全,无需补取");
|
||||
if (kind === "self_correlation_recheck" && job.total === 0)
|
||||
Toast.success("没有预检通过或检查通过的 Alpha,无需重检");
|
||||
onTask();
|
||||
} catch (e) {
|
||||
Toast.error((e as Error).message);
|
||||
@@ -766,6 +768,24 @@ export function AlphaPage({
|
||||
onChange={(v) => updateDraft("check_type", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
本地自相关状态
|
||||
<Select
|
||||
aria-label="本地自相关状态"
|
||||
placeholder="全部状态"
|
||||
showClear
|
||||
value={draft.local_correlation_status || undefined}
|
||||
optionList={[
|
||||
{ value: "not_cached", label: "未检测" },
|
||||
{ value: "stale", label: "待重算" },
|
||||
...Object.entries(correlationLabels).map(([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
})),
|
||||
]}
|
||||
onChange={(v) => updateDraft("local_correlation_status", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
自定义标签
|
||||
<Select
|
||||
@@ -864,7 +884,10 @@ export function AlphaPage({
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
{Object.entries(metricLabels).map(([key, label]) => (
|
||||
{Object.entries({
|
||||
...metricLabels,
|
||||
local_correlation: "本地自相关",
|
||||
}).map(([key, label]) => (
|
||||
<label key={key}>
|
||||
{label}
|
||||
<div className="range-inputs">
|
||||
@@ -896,6 +919,7 @@ export function AlphaPage({
|
||||
</Checkbox>
|
||||
</div>
|
||||
<p className="muted">
|
||||
本地自相关范围为 -1~1,按缓存数值筛选,可搭配状态排除待重算记录。
|
||||
数值筛选使用平台原始值,例如 Turnover 0.15 表示 15%。
|
||||
检查结果仅汇总已同步的平台检查;预检通过不代表可以提交,活动资格可用自定义标签记录。
|
||||
</p>
|
||||
@@ -1127,6 +1151,15 @@ export function AlphaPage({
|
||||
>
|
||||
导入 Alpha ID
|
||||
</Button>
|
||||
<Button
|
||||
icon={<IconRefresh aria-hidden="true" />}
|
||||
loading={busy === "self_correlation_recheck"}
|
||||
disabled={Boolean(busy)}
|
||||
title="重新计算全部预检通过和检查通过的 Alpha 本地自相关,不受筛选和分页影响"
|
||||
onClick={() => void newTask("self_correlation_recheck")}
|
||||
>
|
||||
重检自相关
|
||||
</Button>
|
||||
<Button
|
||||
icon={<IconRefresh aria-hidden="true" />}
|
||||
theme="solid"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("recheck button precedes daily sync and submits an unrestricted job", async ({
|
||||
page,
|
||||
}) => {
|
||||
let requestBody: unknown;
|
||||
await page.route(/\/api\/v1\/sync-jobs$/, async (route) => {
|
||||
if (route.request().method() !== "POST") return route.continue();
|
||||
requestBody = route.request().postDataJSON();
|
||||
await route.fulfill({
|
||||
status: 202,
|
||||
json: {
|
||||
id: "recheck-test",
|
||||
kind: "self_correlation_recheck",
|
||||
status: "completed",
|
||||
total: 0,
|
||||
processed: 0,
|
||||
failed: 0,
|
||||
payload: { alpha_ids: [] },
|
||||
checkpoint: {},
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
});
|
||||
await page.goto("/#alphas");
|
||||
await page.getByLabel("密码", { exact: true }).fill("browser-test-password");
|
||||
await page.getByRole("button", { name: "进入工作空间" }).click();
|
||||
await page.getByRole("tab", { name: "提交受阻", exact: true }).click();
|
||||
await page.getByRole("button", { name: "筛选 Alpha", exact: true }).click();
|
||||
await page
|
||||
.getByRole("textbox", { name: "搜索 Alpha", exact: true })
|
||||
.fill("only-this-page");
|
||||
await page.getByRole("button", { name: "查询", exact: true }).click();
|
||||
const recheck = page.getByRole("button", { name: "重检自相关", exact: true });
|
||||
const daily = page.getByRole("button", { name: "按天同步", exact: true });
|
||||
await expect(recheck).toBeEnabled();
|
||||
const left = (await recheck.boundingBox())!;
|
||||
const right = (await daily.boundingBox())!;
|
||||
expect(left.x + left.width).toBeLessThanOrEqual(right.x);
|
||||
expect(Math.abs(left.y - right.y)).toBeLessThan(3);
|
||||
await recheck.click();
|
||||
await expect
|
||||
.poll(() => requestBody)
|
||||
.toEqual({ kind: "self_correlation_recheck", alpha_ids: [] });
|
||||
await expect(
|
||||
page.getByText("没有预检通过或检查通过的 Alpha,无需重检"),
|
||||
).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("local correlation filters combine with tabs and survive saved views", async ({
|
||||
page,
|
||||
}) => {
|
||||
let params = new URLSearchParams();
|
||||
await page.route(/\/api\/v1\/alphas\?/, async (route) => {
|
||||
params = new URL(route.request().url()).searchParams;
|
||||
await route.fulfill({
|
||||
json: { items: [], total: 0, limit: 25, offset: 0 },
|
||||
});
|
||||
});
|
||||
await page.goto("/#alphas");
|
||||
await page.getByLabel("密码", { exact: true }).fill("browser-test-password");
|
||||
await page.getByRole("button", { name: "进入工作空间" }).click();
|
||||
const blocked = page.getByRole("tab", { name: "提交受阻", exact: true });
|
||||
await blocked.click();
|
||||
await expect.poll(() => params.get("submission_blocked")).toBe("true");
|
||||
expect(params.get("submission")).toBe("UNSUBMITTED");
|
||||
await page.getByRole("button", { name: "筛选 Alpha", exact: true }).click();
|
||||
await page
|
||||
.getByRole("textbox", { name: "搜索 Alpha", exact: true })
|
||||
.fill("candidate");
|
||||
await page
|
||||
.getByText("全部状态", { exact: true })
|
||||
.click();
|
||||
await page.getByRole("option", { name: /相关性偏高/ }).click();
|
||||
await page.getByText("更多筛选", { exact: true }).click();
|
||||
await page
|
||||
.getByRole("spinbutton", { name: "本地自相关 最小值", exact: true })
|
||||
.fill("0.7");
|
||||
await page
|
||||
.getByRole("spinbutton", { name: "本地自相关 最大值", exact: true })
|
||||
.fill("0.75");
|
||||
await page.getByRole("button", { name: "查询", exact: true }).click();
|
||||
await expect.poll(() => params.get("q")).toBe("candidate");
|
||||
expect(params.get("submission_blocked")).toBe("true");
|
||||
await page.getByRole("button", { name: "保存为新视图", exact: true }).click();
|
||||
await page
|
||||
.getByRole("textbox", { name: "视图名称", exact: true })
|
||||
.fill("相关性候选");
|
||||
await page.getByRole("button", { name: "保存视图", exact: true }).click();
|
||||
const saved = page.getByRole("tab", { name: "相关性候选" });
|
||||
await expect(saved).toBeVisible();
|
||||
await page.getByRole("tab", { name: "已提交", exact: true }).click();
|
||||
await expect.poll(() => params.get("submission")).toBe("SUBMITTED");
|
||||
expect(params.has("submission_blocked")).toBe(false);
|
||||
await saved.click();
|
||||
await expect.poll(() => params.get("submission_blocked")).toBe("true");
|
||||
expect(params.get("q")).toBe("candidate");
|
||||
expect(params.get("local_correlation_status")).toBe("high");
|
||||
expect(params.get("local_correlation_min")).toBe("0.7");
|
||||
expect(params.get("local_correlation_max")).toBe("0.75");
|
||||
await blocked.click();
|
||||
await page.getByRole("button", { name: "筛选 Alpha", exact: true }).click();
|
||||
await page.getByRole("button", { name: "重置", exact: true }).click();
|
||||
await expect.poll(() => params.has("q")).toBe(false);
|
||||
expect(params.get("submission_blocked")).toBe("true");
|
||||
expect(params.has("local_correlation_status")).toBe(false);
|
||||
expect(params.has("local_correlation_min")).toBe(false);
|
||||
await expect(blocked).toHaveAttribute("aria-selected", "true");
|
||||
});
|
||||
Reference in New Issue
Block a user