rebuild(stage-9): deliver deterministic intelligent screening

This commit is contained in:
leefer
2026-07-30 05:15:17 +08:00
parent 6cb52e864a
commit 158257ebb8
46 changed files with 7322 additions and 34 deletions
+172
View File
@@ -0,0 +1,172 @@
const fs = require("node:fs");
const path = require("node:path");
const { expect, test } = require("@playwright/test");
const evidence = path.resolve(__dirname, "../../docs/evidence/stage-9");
test.beforeAll(() => fs.mkdirSync(evidence, { recursive: true }));
async function authenticate(page, username, password) {
await page.goto("/");
await page.getByLabel("账号名").fill(username);
await page.getByLabel("密码").fill(password);
await page.getByRole("button", { name: "登录", exact: true }).click();
await expect(page.locator(".sidebar, .field-error")).toBeVisible();
if (!(await page.locator(".sidebar").isVisible())) {
await page.getByRole("tab", { name: "注册" }).click();
await page.getByRole("button", { name: "注册并登录" }).click();
}
}
const formula = {
universe: { exclude_st: true, listed_days_min: 120 },
filters: [{ field: "amount_billion", op: ">=", value: 1 }],
score: [
{ field: "return_20d", weight: 0.6, direction: "desc" },
{ field: "sector_strength", weight: 0.4, direction: "desc" },
],
limit: 30,
min_score: 0.5,
meta: {
category: "趋势追踪",
suitable_environment: "趋势明确且成交活跃的市场",
failure_risk: "震荡轮动过快时信号容易反复",
},
};
const stageStrategy = {
id: "stage-ice",
version: 1,
kind: "stage",
name: "冰点抗跌先手",
display_name: "冰点抗跌先手",
description: "寻找冰点中保持相对强度、低波动且有板块承接的个股,允许无结果。",
regimes: ["ice"],
formula,
};
const curated = [
{ ...stageStrategy, id: "curated-01", kind: "curated", name: "连续分红质量", display_name: "连续分红质量" },
{ ...stageStrategy, id: "curated-02", kind: "curated", name: "动态多因子(基础版)", display_name: "动态多因子(基础版)", formula: { ...formula, meta: { ...formula.meta, category: "多因子" } } },
];
const candidate = {
identifier: "000001.SZ",
code: "000001",
name: "平安银行",
sector: "银行",
close: 12.35,
pct_chg: 2.4,
amount_billion: 18.6,
score_display: 86.5,
reason: "中期动量与行业强度居前",
risk_flags: [],
};
function run(id, mode, name, status = "completed") {
return {
id,
mode,
strategy_id: mode === "custom" ? "custom-7" : mode === "stage" ? "stage-ice" : `curated-0${id - 1}`,
strategy_name: name,
selection_date: "2026-07-30",
status,
coverage: 1,
missing_fields: [],
items: status === "completed" ? [candidate] : [],
error_message: "",
};
}
const catalog = {
factor_groups: { "行情与动量": ["return_20d", "amount_billion"], "板块与行业": ["sector_strength"] },
factors: { return_20d: "20日涨幅", amount_billion: "成交额", sector_strength: "板块强度" },
stage: [stageStrategy],
curated,
};
const workspace = {
trade_date: "2026-07-30",
message: "",
catalog,
stage_runs: [run(1, "stage", "冰点抗跌先手")],
curated_runs: [run(2, "curated", "连续分红质量"), run(3, "curated", "动态多因子(基础版)", "no_signal")],
custom_strategies: [{ id: 7, name: "我的选股策略", version: 2, formula }],
custom_runs: [run(4, "custom", "我的选股策略")],
};
const track = {
id: 9,
code: "000001",
name: "平安银行",
sector: "银行",
selection_date: "2026-07-30",
strategy_name: "连续分红质量",
entry_price: 12.35,
t1_open_return: 1.2,
t1_return: 2.4,
t3_return: 4.1,
t5_return: 6.8,
max_gain: 8.2,
max_drawdown: -1.6,
observed_days: 5,
};
async function mockScreener(page) {
await page.route("**/api/screener/catalog", (route) => route.fulfill({ contentType: "application/json", body: JSON.stringify(catalog) }));
await page.route("**/api/screener?*", (route) => route.fulfill({ contentType: "application/json", body: JSON.stringify(workspace) }));
await page.route("**/api/screener/tracks", (route) => {
const body = route.request().method() === "GET" ? [track] : { id: 9 };
return route.fulfill({ contentType: "application/json", body: JSON.stringify(body) });
});
}
test("stage 9 screening preserves deterministic modes, explicit tracking and responsive layout", async ({ page }) => {
const consoleErrors = [];
page.on("console", (message) => {
if (message.type() === "error" && !message.text().includes("401 (Unauthorized)")) consoleErrors.push(message.text());
});
await mockScreener(page);
await authenticate(page, "stage4admin", "Stage4-pass-123!");
await page.goto("/workspace/screener");
await expect(page.getByText("当前阶段自动候选")).toBeVisible();
await expect(page.getByText("中期动量与行业强度居前")).toBeVisible();
await page.getByRole("button", { name: "加入跟踪" }).click();
await expect(page.getByRole("status")).toContainText("已加入策略跟踪");
await page.screenshot({ path: path.join(evidence, "stage-light-1920x1080.jpg"), type: "jpeg", quality: 82 });
await page.getByRole("button", { name: "策略选股" }).click();
await expect(page.getByText("策略库")).toBeVisible();
await expect(page.getByText("动态多因子(基础版)", { exact: true })).toBeVisible();
await page.getByTitle("图标排列").click();
await expect(page.locator(".strategy-items")).toHaveClass(/is-grid/);
await page.getByRole("button", { name: "自定义选股" }).click();
await expect(page.getByText("合计 100%")).toBeVisible();
await expect(page.getByRole("button", { name: "我的选股策略 第 2 版" })).toBeVisible();
await page.getByRole("button", { name: "夜间" }).click();
await page.setViewportSize({ width: 390, height: 844 });
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(0);
await page.screenshot({ path: path.join(evidence, "custom-dark-390x844.jpg"), type: "jpeg", quality: 82 });
await page.getByRole("button", { name: "持续 策略跟踪" }).click();
await expect(page.getByRole("heading", { name: "策略持续跟踪" })).toBeVisible();
await expect(page.locator(".topbar-title")).toHaveText("智能选股");
await expect(page.getByText("6.80", { exact: true })).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(0);
await page.screenshot({ path: path.join(evidence, "tracking-dark-390x844.jpg"), type: "jpeg", quality: 82 });
expect(consoleErrors).toEqual([]);
});
test("nonmembers see the same screening structure in a disabled state", async ({ page }) => {
await page.route("**/api/screener/catalog", (route) => route.fulfill({ contentType: "application/json", body: JSON.stringify(catalog) }));
await authenticate(page, "stage4user", "Stage4-user-123!");
await page.goto("/workspace/screener");
await expect(page.getByText("智能选股仅对会员开放")).toBeVisible();
await expect(page.getByText("当前阶段自动候选")).toBeVisible();
await expect(page.locator(".stage-overview")).toHaveAttribute("aria-disabled", "true");
await page.getByRole("button", { name: "自定义选股" }).click();
await expect(page.getByText("因子与权重")).toBeVisible();
await expect(page.locator(".custom-builder")).toHaveAttribute("aria-disabled", "true");
});