Skip to content

Bug: min/max price filters on options.chain are never validated (min_bid > max_bid passes silently) #32

@MarketDataDev03

Description

@MarketDataDev03

Bug: min/max price filters on options.chain are never validated (min_bid > max_bid passes silently)

Problem

OptionsChainInput declares a model_validator that is meant to enforce
min ≤ max on the bid/ask price filters. In practice the check never runs:
it reuses the date-only helper _validate_min_max_dates, which compares the two
values only when both look like dates. The bid/ask filters are floats, so the
comparison is skipped entirely and an inverted range is accepted without error.

This means an obviously invalid request such as min_bid=5, max_bid=1 is sent
to the API as-is instead of being rejected client-side, defeating the purpose of
the validator.

Reproduction

from marketdata.input_types.options import OptionsChainInput

# Expected: a validation error (min greater than max).
# Actual: constructs fine, no error raised.
OptionsChainInput(symbol="AAPL", min_bid=5.0, max_bid=1.0)
OptionsChainInput(symbol="AAPL", min_ask=10.0, max_ask=2.0)

Root Cause

src/marketdata/input_types/options.py:124

@model_validator(mode="after")
def validate_input(self) -> "OptionsChainInput":
    params_typles = [
        ("min_bid", "max_bid"),
        ("min_ask", "max_ask"),
    ]
    for min_param, max_param in params_typles:
        self._validate_min_max_dates(min_param, max_param)
    return self

The helper it calls (src/marketdata/input_types/base.py:14) gates the
comparison behind a date check:

def _validate_min_max_dates(self, min_param, max_param) -> None:
    min_value = getattr(self, min_param)
    max_value = getattr(self, max_param)
    min_is_date = check_is_date(min_value)
    max_is_date = check_is_date(max_value)
    if min_is_date and max_is_date:        # <- never True for float prices
        if min_value > max_value:
            raise MinMaxDateValidationError(...)

check_is_date (src/marketdata/utils.py:36) returns False for any non-date,
non-string value, so for float prices min_is_date/max_is_date are always
False and the min > max comparison is unreachable.

Minor: the local variable is also misspelled params_typles (should be
params_tuples).

Suggested Fix

Validate numeric ranges with a numeric-aware check rather than the date helper.
For example, add a generic min/max helper on BaseInputType:

def _validate_min_max(self, min_param: str, max_param: str) -> None:
    min_value = getattr(self, min_param)
    max_value = getattr(self, max_param)
    if min_value is not None and max_value is not None and min_value > max_value:
        raise ValueError(f"{min_param} must be less than or equal to {max_param}")

and call it from OptionsChainInput.validate_input for the price pairs (keeping
_validate_min_max_dates for genuine date ranges like from_date/to_date).

While here, consider extending the check to the other ordered numeric pairs in
the model that currently aren't validated at all, e.g. min_open_interest /
min_volume bounds if max counterparts exist, and fix the params_typles
typo.

Recommendation

Add tests asserting that:

  • OptionsChainInput(min_bid=5, max_bid=1) raises a validation error,
  • a valid range (min_bid=1, max_bid=5) constructs without error,
  • the existing date-range validation on from_date/to_date still works.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions