diff --git a/app/pages/backtests.py b/app/pages/backtests.py index 0e69681..bc1b86a 100644 --- a/app/pages/backtests.py +++ b/app/pages/backtests.py @@ -289,6 +289,34 @@ def _render_backtests_page(workspace_id: str | None = None) -> None: row_key="date", ).classes("w-full") + def validate_current_scenario() -> str | None: + try: + service.run_read_only_scenario( + symbol=str(symbol_input.value or ""), + start_date=parse_iso_date(start_input.value, "Start date"), + end_date=parse_iso_date(end_input.value, "End date"), + template_slug=str(template_select.value or ""), + underlying_units=float(units_input.value or 0.0), + loan_amount=float(loan_input.value or 0.0), + margin_call_ltv=float(ltv_input.value or 0.0), + ) + except (ValueError, KeyError) as exc: + return str(exc) + except Exception: + logger.exception( + "Backtest preview failed for workspace=%s symbol=%s start=%s end=%s template=%s units=%s loan=%s margin_call_ltv=%s", + workspace_id, + symbol_input.value, + start_input.value, + end_input.value, + template_select.value, + units_input.value, + loan_input.value, + ltv_input.value, + ) + return "Backtest preview failed. Please verify the scenario inputs and try again." + return None + def refresh_workspace_seeded_units() -> None: validation_label.set_text("") entry_spot, entry_error = derive_entry_spot() @@ -304,7 +332,13 @@ def _render_backtests_page(workspace_id: str | None = None) -> None: entry_spot_hint.set_text("Entry spot unavailable until the scenario dates are valid.") render_seeded_summary(entry_spot=entry_spot, entry_spot_error=entry_error) if entry_error: + validation_label.set_text(entry_error) render_result_state("Scenario validation failed", entry_error, tone="warning") + return + validation_error = validate_current_scenario() + if validation_error: + validation_label.set_text(validation_error) + render_result_state("Scenario validation failed", validation_error, tone="warning") else: mark_results_stale() @@ -313,7 +347,14 @@ def _render_backtests_page(workspace_id: str | None = None) -> None: entry_spot, entry_error = derive_entry_spot() render_seeded_summary(entry_spot=entry_spot, entry_spot_error=entry_error) if entry_error: + entry_spot_hint.set_text("Entry spot unavailable until the scenario dates are valid.") + validation_label.set_text(entry_error) render_result_state("Scenario validation failed", entry_error, tone="warning") + return + validation_error = validate_current_scenario() + if validation_error: + validation_label.set_text(validation_error) + render_result_state("Scenario validation failed", validation_error, tone="warning") else: mark_results_stale() diff --git a/app/pages/event_comparison.py b/app/pages/event_comparison.py index d0bc8af..cbfbcf1 100644 --- a/app/pages/event_comparison.py +++ b/app/pages/event_comparison.py @@ -204,11 +204,12 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: template_slugs = selected_template_slugs() if not template_slugs: - message = "Select at least one strategy template." - metadata_label.set_text(f"Preset: {option['label']} — {option['description']}") - scenario_label.set_text(message) - render_selected_summary(entry_spot=None, entry_spot_error=message) - return message + template_slugs = tuple(service.default_template_selection(str(option["slug"]))) + syncing_controls["value"] = True + try: + template_select.value = list(template_slugs) + finally: + syncing_controls["value"] = False try: preview_units = float(units_input.value or 0.0) @@ -260,12 +261,15 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: def render_report() -> None: validation_label.set_text("") result_panel.clear() + option = preset_lookup.get(str(preset_select.value or "")) template_slugs = selected_template_slugs() - if not template_slugs: - message = "Select at least one strategy template." - validation_label.set_text(message) - render_result_state("Scenario validation failed", message, tone="warning") - return + if option is not None and not template_slugs: + template_slugs = tuple(service.default_template_selection(str(option["slug"]))) + syncing_controls["value"] = True + try: + template_select.value = list(template_slugs) + finally: + syncing_controls["value"] = False try: report = service.run_read_only_comparison( preset_slug=str(preset_select.value or ""), @@ -406,6 +410,7 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: validation_label.set_text("") preview_error = refresh_preview(reset_templates=True, reseed_units=True) if preview_error: + validation_label.set_text(preview_error) render_result_state("Scenario validation failed", preview_error, tone="warning") else: mark_results_stale() @@ -416,6 +421,7 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: validation_label.set_text("") preview_error = refresh_preview() if preview_error: + validation_label.set_text(preview_error) render_result_state("Scenario validation failed", preview_error, tone="warning") else: mark_results_stale()