Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions frontend/e2e/chain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,13 @@ test("compares every model on a narrow screen without nesting the disclosure tri
await page.getByRole("combobox", { name: "Stock forecast model" }).selectOption("student_t_ewma")
const first = page.locator(".mobile-option-row").first()
await expect(first.locator(".odds-physical")).toContainText("Student-t EWMA")
expect(await first.evaluate((row) => row.querySelector(".mobile-row-summary")!.contains(row.querySelector(".mobile-model-compare details")))).toBe(false)
expect(await first.evaluate((row) => row.querySelector(".mobile-row-summary")!.contains(row.querySelector(".mobile-model-compare button")))).toBe(false)
await first.getByText("Compare models").click()
await expect(first).toContainText("GJR-GARCH Student-t")
await expect(first).toContainText("N=0")
await expect(first).toContainText("sparse_strikes")
const comparison = page.getByRole("dialog", { name: "Compare models" })
await expect(comparison).toContainText("GJR-GARCH Student-t")
await comparison.locator(".model-result").nth(2).locator("summary.model-result-heading").click()
await expect(comparison).toContainText("N=0")
await expect(comparison).toContainText("sparse strikes")
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390)
})

Expand Down
46 changes: 44 additions & 2 deletions frontend/e2e/watchlist.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,46 @@ test("watches a contract from a mobile disclosure and reports deduplication", as
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true)
})

test("compares all models in a bounded dialog on desktop and mobile", async ({ page }, testInfo) => {
const comparisonItem = {
...item,
physical_models: [
{ method: "lognormal_ewma", status: "available", itm_pct_tenths: 612, otm_pct_tenths: 388, atm_pct_tenths: 0, price_basis: "completed_close", support: 60 },
{ method: "student_t_ewma", status: "pending", reason: "candidate_not_prepared" },
{ method: "gjr_garch_t", status: "unavailable", reason: "gjr_parameters_invalid" },
],
market_models: [
{ method: "regimelib", status: "available", itm_pct_tenths: 583, otm_pct_tenths: 417, bound_low_pct_tenths: 452, bound_high_pct_tenths: 753 },
{ method: "constrained_call_curve", status: "unavailable", reason: "clean_strikes_do_not_bracket_contract" },
],
}
await page.route("**/api/watchlist**", (route) => route.fulfill({ json: { items: [comparisonItem] } }))
for (const width of [1280, 390]) {
await page.setViewportSize({ width, height: 800 })
await page.goto("/watchlist")
await page.getByRole("button", { name: "Compare models" }).click()
const dialog = page.getByRole("dialog", { name: "Compare models" })
await expect(dialog).toBeVisible()
await expect(dialog.locator(".model-result")).toHaveCount(5)
await expect(dialog.locator(".model-result details[open]")).toHaveCount(0)
await expect(dialog).toContainText("gjr parameters invalid")
const bounds = await dialog.evaluate((element) => {
const rect = element.getBoundingClientRect()
return { width: rect.width, height: rect.height, scrollWidth: element.scrollWidth, clientWidth: element.clientWidth }
})
expect(bounds.width).toBeLessThanOrEqual(width - 16)
expect(bounds.height).toBeLessThanOrEqual(800)
expect(bounds.scrollWidth).toBeLessThanOrEqual(bounds.clientWidth + 1)
await page.screenshot({ path: testInfo.outputPath(`model-comparison-${width}.png`) })
await dialog.getByText("EWMA lognormal").click()
await expect(dialog.getByText("60 completed returns")).toBeVisible()
if (width === 390) await page.keyboard.press("Escape")
else await dialog.getByRole("button", { name: "Close model comparison" }).click()
await expect(dialog).toBeHidden()
await expect(page.getByRole("button", { name: "Compare models" })).toBeFocused()
}
})

test("shows dated market odds and close-based outcomes, then tracks the result check", async ({ page }) => {
let completeJob = false
await page.route("**/api/watchlist", async (route) => {
Expand Down Expand Up @@ -217,8 +257,10 @@ test("shows shared model evidence in the mobile watchlist comparison", async ({
await page.goto("/watchlist")
const odds = page.getByRole("region", { name: "Odds estimates" })
await odds.getByText("Compare models").click()
await expect(odds).toContainText("N=0")
await expect(odds).toContainText("sparse_strikes")
const comparison = page.getByRole("dialog", { name: "Compare models" })
await comparison.locator(".model-result").first().locator("summary.model-result-heading").click()
await expect(comparison).toContainText("N=0")
await expect(comparison).toContainText("sparse strikes")
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(320)
})

Expand Down
7 changes: 3 additions & 4 deletions frontend/src/ItmChain.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,10 @@ describe("chain interactions", () => {
expect(first.textContent).toContain("70.0% ITM")
expect(first.textContent).toContain("Student-t EWMA")
const trigger = first.querySelector(".mobile-row-summary")!
const compare = first.querySelector(".mobile-model-compare details")!
const compare = first.querySelector(".mobile-model-compare button")!
expect(trigger.contains(compare)).toBe(false)
fireEvent.click(compare.querySelector("summary")!)
expect((compare as HTMLDetailsElement).open).toBe(true)
expect(compare.textContent).toContain("quotes_pending")
fireEvent.click(compare)
expect(screen.getByRole("dialog", { name: "Compare models" }).textContent).toContain("quotes_pending")
})

it("includes odds in the mobile contract summary before expanding details", async () => {
Expand Down
27 changes: 19 additions & 8 deletions frontend/src/ModelComparison.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,26 @@ it("shows all physical and market methods, including pending and unavailable res
{ method: "regimelib", status: "available", itm_pct_tenths: 520, otm_pct_tenths: 480, bound_low_pct_tenths: 400, bound_high_pct_tenths: 600 },
{ method: "constrained_call_curve", status: "unavailable", reason: "clean_strikes_do_not_bracket_contract" },
]
const { container } = render(<ModelComparison physical={physical} market={market} selected="student_t_ewma" />)
const disclosure = container.querySelector("details")!
expect(disclosure.open).toBe(false)
fireEvent.click(screen.getByText("Compare models"))
expect(disclosure.open).toBe(true)
expect(screen.getByRole("region", { name: "Physical forecast models" }).textContent).toContain("Student-t EWMA")
expect(screen.getByRole("region", { name: "Physical forecast models" }).textContent).toContain("Student-t EWMASelected")
render(<ModelComparison physical={physical} market={market} selected="student_t_ewma" />)
const trigger = screen.getByRole("button", { name: "Compare models" })
expect(screen.queryByRole("dialog")).toBeNull()
fireEvent.click(trigger)
expect(screen.getByRole("dialog", { name: "Compare models" })).toBeTruthy()
const physicalSection = screen.getByRole("region", { name: "Physical forecast models" })
expect(physicalSection.textContent).toContain("Student-t EWMA")
expect(physicalSection.textContent).toContain("Student-t EWMASelected")
expect(screen.getByRole("region", { name: "Physical forecast models" }).textContent).toContain("Model spread: 3.0 percentage points across 2 completed-close models")
expect(physicalSection.querySelectorAll(".model-result details[open]")).toHaveLength(0)
fireEvent.click(screen.getByText("Scaled empirical"))
expect(screen.getByRole("region", { name: "Physical forecast models" }).textContent).toContain("Independent history blocks 35 (minimum 30)")
expect(screen.getByRole("region", { name: "Physical forecast models" }).textContent).toContain("needs_500_sessions")
expect(screen.getByRole("region", { name: "Risk-neutral market models" }).textContent).toContain("Regimelib")
expect(screen.getByRole("region", { name: "Risk-neutral market models" }).textContent).toContain("clean_strikes_do_not_bracket_contract")
expect(disclosure.textContent).toContain("Quote bounds")
fireEvent.click(screen.getByText("Regimelib"))
expect(screen.getByRole("dialog").textContent).toContain("Quote bounds")
fireEvent.click(screen.getByRole("button", { name: "Close model comparison" }))
expect(screen.queryByRole("dialog")).toBeNull()
expect(document.activeElement).toBe(trigger)
})

it("reports simulation precision as sampling error rather than forecast confidence", () => {
Expand All @@ -41,6 +48,7 @@ it("reports simulation precision as sampling error rather than forecast confiden
fit_ms: 13, lookup_ms: 2, data_hash: "abc123456789more",
}]} market={[]} selected="student_t_ewma" />)
fireEvent.click(screen.getByText("Compare models"))
fireEvent.click(screen.getByText("Student-t EWMA"))
const physical = screen.getByRole("region", { name: "Physical forecast models" }).textContent
expect(physical).toContain("4,096 simulated paths")
expect(physical).toContain("Maximum 95% simulation error ±1.6 percentage points; model uncertainty excluded")
Expand All @@ -65,6 +73,9 @@ it("keeps retrospective and as-issued evidence distinct and reports N=0 without
const replay = { ...empty, independent_date_blocks: 20, ticker_origin_horizon_units: 500, tickers: 20, coverage_basis: "recorded_contract_cells_with_baseline_issuance", replay_scheduled_units: 25, replay_baseline_available_units: 20, replay_fit_coverage: 0.8, replay_rejection_reasons: { split_affected: 5 }, brier: { baseline: 0.22, candidate: 0.20, paired_delta: -0.02, bootstrap_95: [-0.03, -0.01] } }
render(<ModelComparison physical={physical} market={[]} selected="student_t_ewma" evidenceIndex={{ "student_t_ewma:2-5": { prospective: empty, retrospective: replay } }} />)
fireEvent.click(screen.getByText("Compare models"))
fireEvent.click(screen.getByText("Student-t EWMA"))
fireEvent.click(screen.getByText(/Prospective as-issued · N=0/))
fireEvent.click(screen.getByText(/Retrospective replay · N=500/))
const prospective = screen.getByRole("region", { name: "Prospective as-issued evidence" })
const replayPanel = screen.getByRole("region", { name: "Retrospective replay evidence" })
expect(prospective.textContent).toContain("N=0")
Expand Down
Loading
Loading