rebuild(audit): complete entity detail and market preview
This commit is contained in:
@@ -130,3 +130,57 @@ test("regular users do not receive administrator controls and see intelligent lo
|
||||
await expect(page.getByText("智能选股仅对会员开放")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "查看会员状态" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("empty bootstrap, latest default and background refresh preserve the current workspace", async ({ page }) => {
|
||||
let synchronized = false;
|
||||
await page.route("**/api/market/summary", (route) => route.fulfill({
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(synchronized
|
||||
? {
|
||||
context: {
|
||||
requested_date: "2026-07-30", actual_date: "2026-07-29", previous_date: "2026-07-28",
|
||||
observed_at: "2026-07-29T15:00:00+08:00", state: "final", carried_forward: true,
|
||||
message: "沿用最近真实收盘快照",
|
||||
},
|
||||
values: { temperature: 42, limit_up: 68, limit_down: 4, broken: 24, seal_rate: 73.9, amount: 1628000000000 },
|
||||
}
|
||||
: {
|
||||
context: {
|
||||
requested_date: "2026-07-30", actual_date: null, previous_date: null,
|
||||
observed_at: null, state: null, carried_forward: false, message: "等待管理员首次同步",
|
||||
},
|
||||
values: null,
|
||||
}),
|
||||
}));
|
||||
await page.route("**/api/market/workspaces/emotion?*", (route) => route.fulfill({
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(synchronized
|
||||
? {
|
||||
trade_date: "2026-07-29", observed_at: "2026-07-29T15:00:00+08:00", carried_forward: true,
|
||||
message: "沿用最近真实收盘快照", overview: { limit_up: 68, limit_down: 4, broken: 24, seal_rate: 73.9, amount: 1628000000000 },
|
||||
sentiment: { score: 42, phase: "退潮", direction: "降温", confidence: 92, stats: {}, components: [] }, history: [],
|
||||
}
|
||||
: { trade_date: null, carried_forward: false, message: "等待管理员首次同步真实收盘行情", overview: {} }),
|
||||
}));
|
||||
await page.route("**/api/market/snapshot-sync?*", (route) => {
|
||||
synchronized = true;
|
||||
return route.fulfill({ contentType: "application/json", body: JSON.stringify({ trade_date: "2026-07-29", temperature: 42 }) });
|
||||
});
|
||||
|
||||
await authenticate(page, "stage4admin", "Stage4-pass-123!");
|
||||
await expect(page).toHaveURL(/\/workspace\/emotion$/);
|
||||
await expect(page.getByText("等待管理员首次同步真实收盘行情")).toBeVisible();
|
||||
await expect(page.locator(".market-strip-row")).not.toContainText(/涨停\s+\d/);
|
||||
|
||||
await page.locator(".page-frame").evaluate((element) => { element.dataset.acceptanceMarker = "preserved"; });
|
||||
await page.getByRole("button", { name: "后台刷新" }).click();
|
||||
await expect(page.getByRole("status")).toContainText("刷新页面后读取新数据");
|
||||
await expect(page).toHaveURL(/\/workspace\/emotion$/);
|
||||
await expect(page.locator('[data-acceptance-marker="preserved"]')).toBeVisible();
|
||||
await expect(page.getByText("等待管理员首次同步真实收盘行情")).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
await expect(page.getByRole("heading", { name: "情绪周期" })).toBeVisible();
|
||||
await expect(page.locator(".topbar .date-input")).toHaveValue("2026-07-29");
|
||||
await expect(page.locator(".market-strip-row")).toContainText("涨停 68");
|
||||
});
|
||||
|
||||
@@ -86,6 +86,35 @@ test("latest snapshot, grouped search, chart preview and entity detail", async (
|
||||
const interval = route.request().url().endsWith("/minute") ? "minute" : "day";
|
||||
return route.fulfill({ contentType: "application/json", body: JSON.stringify(chartPayload(interval)) });
|
||||
});
|
||||
await page.route("**/api/market/entities/stock/000001.SZ/charts/*", (route) => {
|
||||
const interval = route.request().url().endsWith("/minute") ? "minute" : "day";
|
||||
return route.fulfill({ contentType: "application/json", body: JSON.stringify({ ...chartPayload(interval), entity_type: "stock", identifier: "000001.SZ", code: "000001", name: "平安银行" }) });
|
||||
});
|
||||
await page.route("**/api/market/entities/*/*/detail?*", (route) => {
|
||||
const stock = route.request().url().includes("/stock/");
|
||||
return route.fulfill({
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
entity: stock
|
||||
? { entity_type: "stock", identifier: "000001.SZ", code: "000001", name: "平安银行", sector: "银行" }
|
||||
: { entity_type: "index", identifier: "000001.SH", code: "000001", name: "上证指数", sector: null },
|
||||
trade_date: "2026-07-29", observed_at: "2026-07-29T15:00:00+08:00",
|
||||
price: stock ? 12.35 : 3604, previous_close: stock ? 12.05 : 3594,
|
||||
change: stock ? 2.4896 : 0.2782,
|
||||
metrics: [
|
||||
{ key: "open", label: "开盘", value: stock ? 12.1 : 3585, unit: "元" },
|
||||
{ key: "high", label: "最高", value: stock ? 12.5 : 3612, unit: "元" },
|
||||
{ key: "low", label: "最低", value: stock ? 12.0 : 3570, unit: "元" },
|
||||
{ key: "amount", label: "成交额", value: 1860000000, unit: "元" },
|
||||
],
|
||||
money_flow: stock ? { available: true, net_million: 12.5, large_million: 8.1, net_5d_million: 35.2, flow_to_circ_mv_5d: 0.0185 } : null,
|
||||
event: stock ? { status: "涨停", reason: "银行板块走强", streak: 1, first_time: "09:35", last_time: "14:20", open_times: 0, seal_amount: 50000000 } : null,
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.route("**/api/review?*", (route) => route.fulfill({ contentType: "application/json", body: JSON.stringify({ trade_date: "2026-07-29", watchlist: [], daily: null, history: [], trades: [], trade_summary: { total: 0, realized: 0, win_rate: null, pnl_amount: null, average_position: null } }) }));
|
||||
await page.route("**/api/review/stock-notes/*", (route) => route.fulfill({ contentType: "application/json", body: "[]" }));
|
||||
await page.route("**/api/review/watchlist", (route) => route.fulfill({ contentType: "application/json", body: JSON.stringify({ identifier: "000001.SZ", name: "平安银行" }) }));
|
||||
|
||||
await authenticate(page);
|
||||
await expect(page.locator(".market-strip-row")).toContainText("涨停 68");
|
||||
@@ -97,19 +126,57 @@ test("latest snapshot, grouped search, chart preview and entity detail", async (
|
||||
await expect(dialog.getByRole("button", { name: /上证指数/ })).toBeVisible();
|
||||
await expect(dialog.locator(".market-chart")).toBeVisible();
|
||||
await expect(dialog).toContainText("数据日期 2026-07-29");
|
||||
const risingCandle = dialog.locator(".candle-up").first();
|
||||
const candleGeometry = await risingCandle.evaluate((group) => {
|
||||
const lines = [...group.querySelectorAll("line")];
|
||||
const rect = group.querySelector("rect");
|
||||
return {
|
||||
upperEnd: Number(lines[0]?.getAttribute("y2")),
|
||||
bodyTop: Number(rect?.getAttribute("y")),
|
||||
lowerStart: Number(lines[1]?.getAttribute("y1")),
|
||||
bodyBottom: Number(rect?.getAttribute("y")) + Number(rect?.getAttribute("height")),
|
||||
bodyFill: getComputedStyle(rect).fill,
|
||||
bodyStroke: getComputedStyle(rect).stroke,
|
||||
};
|
||||
});
|
||||
expect(candleGeometry.upperEnd).toBeCloseTo(candleGeometry.bodyTop, 5);
|
||||
expect(candleGeometry.lowerStart).toBeCloseTo(candleGeometry.bodyBottom, 5);
|
||||
expect(candleGeometry.bodyFill).not.toBe(candleGeometry.bodyStroke);
|
||||
await page.screenshot({ path: path.join(evidence, "search-preview-light-1920x1080.jpg"), type: "jpeg", quality: 82 });
|
||||
|
||||
await dialog.getByRole("button", { name: /上证指数/ }).click();
|
||||
await expect(page).toHaveURL(/\/market\/index\/000001.SH/);
|
||||
await expect(page.getByRole("heading", { name: "上证指数" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "资金流" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "事件逻辑" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "个股复盘笔记" })).toHaveCount(0);
|
||||
await page.getByRole("button", { name: "分时" }).click();
|
||||
await expect(page.locator(".chart-zero")).toHaveCount(1);
|
||||
await expect(page.locator(".preview-meta")).toContainText("09:30–15:00");
|
||||
|
||||
await page.getByRole("button", { name: "夜间" }).click();
|
||||
const darkSurfaces = await page.evaluate(() => ({
|
||||
chart: getComputedStyle(document.querySelector(".market-chart")).backgroundColor,
|
||||
panel: getComputedStyle(document.querySelector(".market-preview")).backgroundColor,
|
||||
canvas: getComputedStyle(document.documentElement).backgroundColor,
|
||||
}));
|
||||
expect(darkSurfaces.chart).toBe(darkSurfaces.panel);
|
||||
expect(darkSurfaces.chart).not.toBe("rgb(255, 255, 255)");
|
||||
expect(darkSurfaces.canvas).not.toBe("rgb(255, 255, 255)");
|
||||
await page.screenshot({ path: path.join(evidence, "entity-detail-dark-1920x1080.jpg"), type: "jpeg", quality: 82 });
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBe(0);
|
||||
|
||||
await page.goto("/market/stock/000001.SZ?code=000001&name=%E5%B9%B3%E5%AE%89%E9%93%B6%E8%A1%8C§or=%E9%93%B6%E8%A1%8C");
|
||||
await expect(page.getByRole("heading", { name: "平安银行" })).toBeVisible();
|
||||
await expect(page.locator(".entity-detail-price")).toContainText("+2.49%");
|
||||
await expect(page.getByRole("heading", { name: "资金流" })).toBeVisible();
|
||||
await expect(page.getByText("银行板块走强")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "个股复盘笔记" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "加入自选" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "进入观势" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "加入自选" }).click();
|
||||
await expect(page.getByRole("button", { name: "移出自选" })).toBeVisible();
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await expect(page.locator(".market-chart")).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBe(0);
|
||||
|
||||
@@ -89,19 +89,38 @@ test("emotion and pool workspaces remain usable across desktop and mobile", asyn
|
||||
|
||||
await expect(page.getByRole("heading", { name: "情绪周期" })).toBeVisible();
|
||||
await expect(page.getByText("情绪指标继续走弱")).toBeVisible();
|
||||
await expect(page.locator(".phase-warning")).not.toContainText("筛选门槛");
|
||||
await page.getByRole("button", { name: "60日" }).click();
|
||||
await expect(page.locator(".emotion-history-table tbody tr")).toHaveCount(60);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollHeight > window.innerHeight)).toBe(true);
|
||||
const finalHistoryRow = page.locator(".emotion-history-table tbody tr").last();
|
||||
await finalHistoryRow.scrollIntoViewIfNeeded();
|
||||
expect(await finalHistoryRow.evaluate((row) => row.getBoundingClientRect().bottom <= window.innerHeight - 30)).toBe(true);
|
||||
await page.screenshot({ path: path.join(evidence, "emotion-light-1920x1080.jpg"), type: "jpeg", quality: 82 });
|
||||
|
||||
await page.getByRole("link", { name: /涨停池/ }).click();
|
||||
await page.getByRole("button", { name: "3板+" }).click();
|
||||
await expect(page.locator(".data-table tbody tr")).toHaveCount(10);
|
||||
await page.getByLabel("搜索股池").fill("样本股票5");
|
||||
await expect(page.locator(".data-table tbody tr")).toHaveCount(1);
|
||||
const statusbar = await page.locator(".statusbar").evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
const rect = element.getBoundingClientRect();
|
||||
return { position: style.position, bottom: Math.round(window.innerHeight - rect.bottom) };
|
||||
});
|
||||
expect(statusbar).toEqual({ position: "fixed", bottom: 0 });
|
||||
await expect(page.getByText("5板")).toBeVisible();
|
||||
await page.getByRole("button", { name: "夜间" }).click();
|
||||
await page.screenshot({ path: path.join(evidence, "pool-dark-1920x1080.jpg"), type: "jpeg", quality: 82 });
|
||||
await page.setViewportSize({ width: 3840, height: 2160 });
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBe(0);
|
||||
const density = await page.evaluate(() => ({
|
||||
contentWidth: document.querySelector(".page-frame").getBoundingClientRect().width,
|
||||
fontSize: getComputedStyle(document.body).fontSize,
|
||||
topbarHeight: document.querySelector(".topbar").getBoundingClientRect().height,
|
||||
}));
|
||||
expect(density.contentWidth).toBeLessThanOrEqual(2200);
|
||||
expect(density.fontSize).toBe("13px");
|
||||
expect(density.topbarHeight).toBe(46);
|
||||
await page.screenshot({ path: path.join(evidence, "pool-dark-3840x2160.jpg"), type: "jpeg", quality: 82 });
|
||||
|
||||
await page.getByRole("link", { name: /涨停表现/ }).click();
|
||||
|
||||
@@ -45,6 +45,14 @@ const stageStrategy = {
|
||||
formula,
|
||||
};
|
||||
|
||||
const stageStrategyTwo = {
|
||||
...stageStrategy,
|
||||
id: "stage-divergence",
|
||||
name: "分化去弱留强",
|
||||
display_name: "分化去弱留强",
|
||||
regimes: ["divergence"],
|
||||
};
|
||||
|
||||
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: "多因子" } } },
|
||||
@@ -63,17 +71,21 @@ const candidate = {
|
||||
risk_flags: [],
|
||||
};
|
||||
|
||||
function run(id, mode, name, status = "completed") {
|
||||
function candidateNamed(code, name, reason) {
|
||||
return { ...candidate, identifier: `${code}.SZ`, code, name, reason };
|
||||
}
|
||||
|
||||
function run(id, mode, name, status = "completed", strategyId = "", item = candidate) {
|
||||
return {
|
||||
id,
|
||||
mode,
|
||||
strategy_id: mode === "custom" ? "custom-7" : mode === "stage" ? "stage-ice" : `curated-0${id - 1}`,
|
||||
strategy_id: strategyId || (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] : [],
|
||||
items: status === "completed" ? [item] : [],
|
||||
error_message: "",
|
||||
};
|
||||
}
|
||||
@@ -81,7 +93,7 @@ function run(id, mode, name, status = "completed") {
|
||||
const catalog = {
|
||||
factor_groups: { "行情与动量": ["return_20d", "amount_billion"], "板块与行业": ["sector_strength"] },
|
||||
factors: { return_20d: "20日涨幅", amount_billion: "成交额", sector_strength: "板块强度" },
|
||||
stage: [stageStrategy],
|
||||
stage: [stageStrategy, stageStrategyTwo],
|
||||
curated,
|
||||
};
|
||||
|
||||
@@ -89,10 +101,16 @@ const workspace = {
|
||||
trade_date: "2026-07-30",
|
||||
message: "",
|
||||
catalog,
|
||||
stage_runs: [run(1, "stage", "冰点抗跌先手")],
|
||||
curated_runs: [run(2, "curated", "连续分红质量"), run(3, "curated", "动态多因子(基础版)", "no_signal")],
|
||||
stage_runs: [
|
||||
run(1, "stage", "冰点抗跌先手", "completed", "stage-ice", candidateNamed("000011", "冰点样本", "冰点阶段相对抗跌")),
|
||||
run(5, "stage", "分化去弱留强", "completed", "stage-divergence", candidateNamed("000012", "分化样本", "分化阶段承接较强")),
|
||||
],
|
||||
curated_runs: [
|
||||
run(2, "curated", "连续分红质量", "completed", "curated-01", candidateNamed("000021", "分红样本", "分红与质量条件满足")),
|
||||
run(3, "curated", "动态多因子(基础版)", "completed", "curated-02", candidateNamed("000022", "多因子样本", "综合因子得分居前")),
|
||||
],
|
||||
custom_strategies: [{ id: 7, name: "我的选股策略", version: 2, formula }],
|
||||
custom_runs: [run(4, "custom", "我的选股策略")],
|
||||
custom_runs: [run(4, "custom", "我的选股策略", "completed", "custom-7", candidateNamed("000031", "自定义样本", "用户条件确定性命中"))],
|
||||
};
|
||||
|
||||
const track = {
|
||||
@@ -131,7 +149,10 @@ test("stage 9 screening preserves deterministic modes, explicit tracking and res
|
||||
await page.goto("/workspace/screener");
|
||||
|
||||
await expect(page.getByText("当前阶段自动候选")).toBeVisible();
|
||||
await expect(page.getByText("中期动量与行业强度居前")).toBeVisible();
|
||||
await expect(page.locator(".screener-results")).toContainText("冰点样本");
|
||||
await page.getByRole("button", { name: "分化去弱留强", exact: true }).click();
|
||||
await expect(page.locator(".screener-results")).toContainText("分化样本");
|
||||
await expect(page.locator(".screener-results")).not.toContainText("冰点样本");
|
||||
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 });
|
||||
@@ -139,12 +160,18 @@ test("stage 9 screening preserves deterministic modes, explicit tracking and res
|
||||
await page.getByRole("button", { name: "策略选股" }).click();
|
||||
await expect(page.getByText("策略库")).toBeVisible();
|
||||
await expect(page.getByText("动态多因子(基础版)", { exact: true })).toBeVisible();
|
||||
await expect(page.locator(".screener-results")).toContainText("分红样本");
|
||||
await page.locator(".strategy-items").getByRole("button", { name: /动态多因子/ }).click();
|
||||
await expect(page.locator(".screener-results")).toContainText("多因子样本");
|
||||
await expect(page.locator(".screener-results")).not.toContainText("分红样本");
|
||||
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 expect(page.locator(".screener-results")).toContainText("自定义样本");
|
||||
await expect(page.locator(".screener-results")).not.toContainText("多因子样本");
|
||||
await page.getByRole("button", { name: "夜间" }).click();
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(0);
|
||||
@@ -166,6 +193,7 @@ test("nonmembers see the same screening structure in a disabled state", async ({
|
||||
await expect(page.getByText("智能选股仅对会员开放")).toBeVisible();
|
||||
await expect(page.getByText("当前阶段自动候选")).toBeVisible();
|
||||
await expect(page.locator(".stage-overview")).toHaveAttribute("aria-disabled", "true");
|
||||
await expect(page.locator(".stage-flow .done")).toHaveCount(0);
|
||||
await page.getByRole("button", { name: "自定义选股" }).click();
|
||||
await expect(page.getByText("因子与权重")).toBeVisible();
|
||||
await expect(page.locator(".custom-builder")).toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
@@ -204,6 +204,87 @@ def test_latest_daily_chart_drops_empty_premarket_bar(tmp_path) -> None:
|
||||
assert series.points[-1].amount == 13320
|
||||
|
||||
|
||||
def test_entity_detail_uses_selected_bar_previous_close_and_complete_stock_sections(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
market = gateway(tmp_path)
|
||||
database = market._database
|
||||
repository = MarketRepository()
|
||||
with database.transaction() as connection:
|
||||
connection.execute(
|
||||
"UPDATE market_summaries SET payload_json = ? WHERE trade_date = ?",
|
||||
(
|
||||
json.dumps(
|
||||
{
|
||||
"overview": {},
|
||||
"limits": [
|
||||
{
|
||||
"identifier": "000001.SZ", "code": "000001",
|
||||
"reason": "银行板块走强", "streak": 1,
|
||||
"first_time": "09:35", "last_time": "14:20", "open_times": 0,
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
"2026-07-29",
|
||||
),
|
||||
)
|
||||
snapshot_id = connection.execute(
|
||||
"""
|
||||
INSERT INTO screener_factor_snapshots
|
||||
(trade_date, version, observed_at, state, source_set_json,
|
||||
coverage_json, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
,
|
||||
(
|
||||
"2026-07-29",
|
||||
"detail-v1",
|
||||
"2026-07-29T15:05:00+08:00",
|
||||
"final",
|
||||
'["tushare"]',
|
||||
"{}",
|
||||
"2026-07-29T15:05:00+08:00",
|
||||
),
|
||||
).lastrowid
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO screener_factor_values
|
||||
(snapshot_id, identifier, code, name, sector, listed_days, is_st, payload_json)
|
||||
VALUES (?, '000001.SZ', '000001', '平安银行', '银行', 1000, 0, ?)
|
||||
""",
|
||||
(
|
||||
snapshot_id,
|
||||
json.dumps(
|
||||
{
|
||||
"name": "平安银行", "sector": "银行", "turnover_rate": 2.5,
|
||||
"return_5d": 3.2, "return_20d": 8.6, "total_mv_billion": 2100,
|
||||
"circ_mv_billion": 1900, "net_flow_million": 12.5,
|
||||
"large_flow_million": 8.1, "net_flow_5d_million": 35.2,
|
||||
"flow_to_circ_mv_5d": 0.0185,
|
||||
}
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
detail = MarketSnapshotService(database, repository, market).entity_detail(
|
||||
"stock", "000001.SZ", "2026-07-29"
|
||||
)
|
||||
|
||||
assert detail["trade_date"] == "2026-07-29"
|
||||
assert detail["price"] == 11.1
|
||||
assert detail["previous_close"] == 10.8
|
||||
assert detail["change"] == pytest.approx(2.7778)
|
||||
assert detail["entity"]["name"] == "平安银行"
|
||||
assert detail["money_flow"]["available"] is True
|
||||
assert detail["money_flow"]["net_million"] == 12.5
|
||||
assert detail["event"] == {
|
||||
"status": "涨停", "reason": "银行板块走强", "streak": 1,
|
||||
"first_time": "09:35", "last_time": "14:20", "open_times": 0,
|
||||
"seal_amount": None,
|
||||
}
|
||||
|
||||
|
||||
def test_minute_chart_contract_has_real_session_bounds_and_hides_source(tmp_path) -> None:
|
||||
application = create_application(Settings.for_test(tmp_path))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user