feat: defer entry spot derivation to backtest run (BT-005)

- Remove async refresh_workspace_seeded_units from date change handlers
- Date changes now only call on_form_change() (updates cost estimates, marks results stale)
- Entry spot is derived only when user clicks Run button
- Form remains responsive during configuration
- No more API errors when changing dates while configuring other fields
This commit is contained in:
Bu5hm4nn
2026-04-06 11:14:51 +02:00
parent 4af7a09c6e
commit aff4df325d
3 changed files with 12 additions and 39 deletions

View File

@@ -5,7 +5,7 @@ from datetime import date, datetime
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from nicegui import run, ui from nicegui import ui
from app.domain.backtesting_math import asset_quantity_from_workspace_config from app.domain.backtesting_math import asset_quantity_from_workspace_config
from app.models.backtest import ProviderRef from app.models.backtest import ProviderRef
@@ -784,32 +784,6 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
logger.warning(f"Failed to save backtest settings for workspace {workspace_id}: {e}") logger.warning(f"Failed to save backtest settings for workspace {workspace_id}: {e}")
return None return None
async def refresh_workspace_seeded_units() -> None:
"""Derive entry spot asynchronously and update units when dates change."""
validation_label.set_text("")
progress_label.set_text("Fetching entry spot...")
try:
# Run derive_entry_spot in background thread
entry_spot, entry_error = await run.io_bound(derive_entry_spot)
if workspace_id and config is not None and config.gold_value is not None and entry_spot is not None:
units_input.value = asset_quantity_from_workspace_config(
config, entry_spot=entry_spot, symbol=get_symbol_from_dataset()
)
update_cost_estimate()
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(entry_spot=entry_spot)
if validation_error:
validation_label.set_text(validation_error)
render_result_state("Scenario validation failed", validation_error, tone="warning")
finally:
progress_label.set_text("")
def on_form_change() -> None: def on_form_change() -> None:
"""Handle form changes with minimal API calls.""" """Handle form changes with minimal API calls."""
validation_label.set_text("") validation_label.set_text("")
@@ -1061,13 +1035,14 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
update_cost_estimate() update_cost_estimate()
# Wire up event handlers # Wire up event handlers
# Only call expensive derive_entry_spot on date changes # Date changes should NOT trigger expensive derive_entry_spot API call
# Entry spot derivation happens when user clicks Run
data_source_select.on_value_change(lambda e: on_form_change()) data_source_select.on_value_change(lambda e: on_form_change())
dataset_select.on_value_change(lambda e: (update_date_range_hint(), on_form_change())) dataset_select.on_value_change(lambda e: (update_date_range_hint(), on_form_change()))
schema_select.on_value_change(lambda e: on_form_change()) schema_select.on_value_change(lambda e: on_form_change())
symbol_select.on_value_change(lambda e: update_date_range_hint()) symbol_select.on_value_change(lambda e: update_date_range_hint())
start_input.on_value_change(lambda e: refresh_workspace_seeded_units()) start_input.on_value_change(lambda e: on_form_change())
end_input.on_value_change(lambda e: refresh_workspace_seeded_units()) end_input.on_value_change(lambda e: on_form_change())
# Don't trigger API calls on these changes # Don't trigger API calls on these changes
start_price_input.on_value_change(lambda e: mark_results_stale()) start_price_input.on_value_change(lambda e: mark_results_stale())
template_select.on_value_change(lambda e: mark_results_stale()) template_select.on_value_change(lambda e: mark_results_stale())

View File

@@ -13,7 +13,6 @@ notes:
- Pre-alpha policy: we may cut or replace old features without backward compatibility until alpha is declared. - Pre-alpha policy: we may cut or replace old features without backward compatibility until alpha is declared.
- Alpha migration policy: once alpha is declared, compatibility only needs to move forward; backward migrations are not required. - Alpha migration policy: once alpha is declared, compatibility only needs to move forward; backward migrations are not required.
priority_queue: priority_queue:
- BT-005
- BT-004 - BT-004
- EXEC-002 - EXEC-002
- DATA-DB-005 - DATA-DB-005
@@ -25,6 +24,7 @@ priority_queue:
- GCF-001 - GCF-001
- DATA-DB-006 - DATA-DB-006
recently_completed: recently_completed:
- BT-005
- CORE-003 - CORE-003
- CONV-001 - CONV-001
- DATA-DB-004 - DATA-DB-004
@@ -50,7 +50,6 @@ recently_completed:
- CORE-002B - CORE-002B
states: states:
backlog: backlog:
- BT-005
- BT-004 - BT-004
- DATA-DB-005 - DATA-DB-005
- DATA-DB-006 - DATA-DB-006
@@ -63,6 +62,7 @@ states:
- GCF-001 - GCF-001
in_progress: [] in_progress: []
done: done:
- BT-005
- CORE-003 - CORE-003
- CONV-001 - CONV-001
- DATA-DB-003 - DATA-DB-003

View File

@@ -1,6 +1,6 @@
id: BT-005 id: BT-005
title: Defer Entry Spot Derivation to Backtest Run title: Defer Entry Spot Derivation to Backtest Run
status: backlog status: done
priority: P1 priority: P1
effort: S effort: S
depends_on: [] depends_on: []
@@ -14,9 +14,7 @@ acceptance_criteria:
- Clear loading indicator shows when entry spot is being fetched during run. - Clear loading indicator shows when entry spot is being fetched during run.
- Previous entry spot value is retained until new one is derived. - Previous entry spot value is retained until new one is derived.
notes: notes:
Current behavior calls derive_entry_spot on every date change which causes Removed async refresh_workspace_seeded_units from date change handlers.
API errors if user is still configuring other fields. Entry spot derivation now only happens inside run_read_only_scenario when
the user clicks Run. Form changes now call on_form_change() which only
The refresh_workspace_seeded_units function should not be called on date changes. updates cost estimates and marks results stale.
Entry spot derivation should happen inside start_backtest or as a separate
explicit "fetch spot" button if user wants to preview.