fix: address PR review feedback for validation functions

1. Fix Friday logic edge case comment
   - Clarified get_default_backtest_dates() docstring
   - Removed confusing 'at least a week old' comment
   - Explicitly documented Friday behavior

2. Reorder validation checks in validate_date_range_for_symbol()
   - Now checks start > end first (most fundamental)
   - Then checks end > today (future dates)
   - Finally checks symbol-specific bounds
   - Users get most actionable error first

3. Add server-side numeric bounds validation
   - New validate_numeric_inputs() function
   - Validates units > 0, loan >= 0, 0 < LTV < 1
   - Called in run_backtest() before service call

4. Add boundary tests
   - Test start_date exactly at SYMBOL_MIN_DATES boundary
   - Test same-day date range (start == end)
   - Test end_date exactly today
   - Test end_date tomorrow (future)
   - Test validation order returns most actionable error
   - Test near-zero and large values for units calculation
   - Test LTV at boundaries (0, 1, 0.01, 0.99)

5. Add tests for validate_numeric_inputs
   - Valid inputs, zero/negative values
   - LTV boundary conditions
This commit is contained in:
Bu5hm4nn
2026-03-29 19:29:46 +02:00
parent f9ea7f0b67
commit 269745cd3e
2 changed files with 191 additions and 13 deletions

View File

@@ -49,13 +49,18 @@ SYMBOL_MIN_DATES = {
}
def get_default_backtest_dates() -> tuple[date, date]:
"""Get default backtest date range (last 2 years excluding current week)."""
"""Get default backtest date range (~2 years ending on most recent Friday or earlier).
Returns dates (start, end) where:
- end is the most recent Friday (including today if today is Friday)
- start is ~730 days before end
"""
today = date.today()
# Find the most recent Friday that's at least a week old
# Find days since most recent Friday
days_since_friday = (today.weekday() - 4) % 7
if days_since_friday == 0 and today.weekday() != 4:
# Not Friday yet, go back to previous Friday
days_since_friday = 7
# If today is Friday (weekday 4), days_since_friday is 0, so end = today
# If today is Saturday (weekday 5), days_since_friday is 1, so end = yesterday (Friday)
# etc.
end = today - timedelta(days=days_since_friday)
start = end - timedelta(days=730) # ~2 years
return start, end
@@ -69,14 +74,37 @@ def validate_date_range_for_symbol(start_date: date, end_date: date, symbol: str
"""Validate date range is within available data for symbol.
Returns error message if invalid, None if valid.
Validation order:
1. Logical order (start <= end)
2. End not in future
3. Symbol-specific data availability
"""
if start_date > end_date:
return "Start date must be before or equal to end date."
if end_date > date.today():
return "End date cannot be in the future."
min_date = SYMBOL_MIN_DATES.get(symbol)
if min_date and start_date < min_date:
return f"Start date must be on or after {min_date.strftime('%Y-%m-%d')} for {symbol} (data availability)."
if end_date > date.today():
return "End date cannot be in the future."
if start_date > end_date:
return "Start date must be before or equal to end date."
return None
def validate_numeric_inputs(
units: float,
loan_amount: float,
margin_call_ltv: float,
) -> str | None:
"""Validate numeric inputs for backtest scenario.
Returns error message if invalid, None if valid.
"""
if units <= 0:
return "Underlying units must be positive."
if loan_amount < 0:
return "Loan amount cannot be negative."
if not (0 < margin_call_ltv < 1):
return "Margin call LTV must be between 0 and 1 (exclusive)."
return None
@@ -781,6 +809,16 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
render_result_state("Scenario validation failed", date_range_error, tone="warning")
return
# Validate numeric inputs
units = float(units_input.value or 0.0)
loan = float(loan_input.value or 0.0)
ltv = float(ltv_input.value or 0.0)
numeric_error = validate_numeric_inputs(units, loan, ltv)
if numeric_error:
validation_label.set_text(numeric_error)
render_result_state("Input validation failed", numeric_error, tone="warning")
return
# Save settings before running
save_backtest_settings()
@@ -789,9 +827,9 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
start_date=start_date,
end_date=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),
underlying_units=units,
loan_amount=loan,
margin_call_ltv=ltv,
)
# Update cost in saved settings after successful run
if str(data_source_select.value) == "databento":