feat(backtest): add dataset-specific date validation and better error handling

- Add DATABENTO_DATASET_MIN_DATES for XNAS.BASIC (2024-07-01) and GLBX.MDP3 (2010-01-01)
- Validate start date against dataset minimum before running backtest
- Parse Databento API errors and show user-friendly messages
- Update date range hint to show dataset-specific availability
- Catch BentoClientError and show appropriate warning tone
This commit is contained in:
Bu5hm4nn
2026-03-30 14:37:04 +02:00
parent f31b83668e
commit 79980c33ec

View File

@@ -48,6 +48,12 @@ SYMBOL_MIN_DATES = {
"XAU": date(1970, 1, 1), # XAU index historical data
}
# Minimum dates for Databento datasets (when data became available)
DATABENTO_DATASET_MIN_DATES = {
"XNAS.BASIC": date(2024, 7, 1), # XNAS.BASIC data available from July 2024
"GLBX.MDP3": date(2010, 1, 1), # GLBX.MDP3 futures data from 2010
}
def get_default_backtest_dates() -> tuple[date, date]:
"""Get default backtest date range (~2 years ending on most recent Friday or earlier).
@@ -446,8 +452,21 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
return "GLD" # Default for XNAS.BASIC
def update_date_range_hint() -> None:
"""Update the date range hint based on selected symbol."""
"""Update the date range hint based on selected symbol and data source."""
symbol = get_symbol_from_dataset()
data_source = str(data_source_select.value)
# Use dataset-specific minimum for Databento
if data_source == "databento":
dataset = str(dataset_select.value)
min_date = DATABENTO_DATASET_MIN_DATES.get(dataset)
if min_date:
date_range_hint.set_text(
f"{dataset} data available from {min_date.strftime('%Y-%m-%d')}"
)
return
# Fall back to symbol minimum
min_date = SYMBOL_MIN_DATES.get(symbol)
if min_date:
date_range_hint.set_text(
@@ -805,6 +824,20 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
start_date = parse_iso_date(start_input.value, "Start date")
end_date = parse_iso_date(end_input.value, "End date")
symbol = get_symbol_from_dataset()
# Validate dataset-specific minimum dates for Databento
data_source = str(data_source_select.value)
if data_source == "databento":
dataset = str(dataset_select.value)
dataset_min = DATABENTO_DATASET_MIN_DATES.get(dataset)
if dataset_min and start_date < dataset_min:
validation_label.set_text(
f"Start date must be on or after {dataset_min.strftime('%Y-%m-%d')} for {dataset} dataset. "
f"Selected start date {start_date.strftime('%Y-%m-%d')} is before available data."
)
render_result_state("Invalid start date", validation_label.text, tone="warning")
return
date_range_error = validate_date_range_for_symbol(start_date, end_date, symbol)
if date_range_error:
validation_label.set_text(date_range_error)
@@ -845,25 +878,46 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
validation_label.set_text(str(exc))
render_result_state("Scenario validation failed", str(exc), tone="warning")
return
except Exception:
except Exception as exc:
entry_spot, entry_error = derive_entry_spot()
render_seeded_summary(entry_spot=entry_spot, entry_spot_error=entry_error)
if entry_spot is None:
entry_spot_hint.set_text("Entry spot unavailable until the scenario dates are valid.")
message = "Backtest failed. Please verify the scenario inputs and try again."
logger.exception(
"Backtest page run failed for workspace=%s symbol=%s start=%s end=%s template=%s units=%s loan=%s margin_call_ltv=%s",
workspace_id,
symbol_select.value,
start_input.value,
end_input.value,
template_select.value,
units_input.value,
loan_input.value,
ltv_input.value,
)
validation_label.set_text(message)
render_result_state("Backtest failed", message, tone="error")
# Check for Databento API errors
error_msg = str(exc)
if "data_start_before_available_start" in error_msg:
# Extract the available start date from the error message
import re
match = re.search(r"available start of dataset [^(]+\('([^']+)'\)", error_msg)
if match:
available_start = match.group(1).split()[0] # Extract date part
validation_label.set_text(
f"Data not available before {available_start}. Please set start date to {available_start} or later."
)
else:
validation_label.set_text(
"Selected start date is before data is available for this dataset. Please choose a later date."
)
render_result_state("Invalid start date", validation_label.text, tone="warning")
elif "BentoClientError" in error_msg or "422" in error_msg:
validation_label.set_text(f"Data source error: {error_msg}")
render_result_state("Data unavailable", validation_label.text, tone="warning")
else:
message = "Backtest failed. Please verify the scenario inputs and try again."
logger.exception(
"Backtest page run failed for workspace=%s symbol=%s start=%s end=%s template=%s units=%s loan=%s margin_call_ltv=%s",
workspace_id,
symbol_select.value,
start_input.value,
end_input.value,
template_select.value,
units_input.value,
loan_input.value,
ltv_input.value,
)
validation_label.set_text(message)
render_result_state("Backtest failed", message, tone="error")
return
render_result(result)