diff --git a/src/marketdata/input_types/options.py b/src/marketdata/input_types/options.py index 220476d..b3587a6 100644 --- a/src/marketdata/input_types/options.py +++ b/src/marketdata/input_types/options.py @@ -38,10 +38,14 @@ class OptionsChainInput(BaseInputType): description="The number of days to expiration to filter by", default=None ) from_date: datetime.date | str | None = Field( - description="The start date to fetch options chain for", default=None + description="The start date to fetch options chain for", + default=None, + alias="from", ) to_date: datetime.date | str | None = Field( - description="The end date to fetch options chain for", default=None + description="The end date to fetch options chain for", + default=None, + alias="to", ) month: int | None = Field(description="The month to filter by", default=None) year: int | None = Field(description="The year to filter by", default=None) @@ -135,6 +139,20 @@ class OptionsQuotesInput(BaseInputType): symbols: str | list[str] = Field( description="A single symbol string or a list of symbol strings", min_length=1 ) + date: datetime.date | str | None = Field( + description="Historical end-of-day quote date (ISO 8601, unix, or spreadsheet)", + default=None, + ) + from_date: datetime.date | str | None = Field( + description="Start of date range, inclusive (ISO 8601, unix, or spreadsheet)", + default=None, + alias="from", + ) + to_date: datetime.date | str | None = Field( + description="End of date range, exclusive (ISO 8601, unix, or spreadsheet)", + default=None, + alias="to", + ) @field_validator("symbols") def validate_symbols(cls, value: str | list[str]) -> list[str]: @@ -142,6 +160,11 @@ def validate_symbols(cls, value: str | list[str]) -> list[str]: return value.split(",") if "," in value else [value] return value + @model_validator(mode="after") + def validate_input(self) -> "OptionsQuotesInput": + self._validate_min_max_dates("from_date", "to_date") + return self + class OptionsStrikesInput(BaseInputType): diff --git a/src/tests/test_options_chain.py b/src/tests/test_options_chain.py index 2e1c0ce..acf464f 100644 --- a/src/tests/test_options_chain.py +++ b/src/tests/test_options_chain.py @@ -287,3 +287,23 @@ def test_get_options_chain_response_200_csv(respx_mock, client): "AAPL", output_format=OutputFormat.CSV, filename="test.csv" ) assert pathlib.Path(output).read_text() == "AS RECEIVED FROM API" + + +def test_options_chain_input_date_range_aliases_on_wire(load_json, respx_mock, client): + mock_data = load_json("options_chain_response_200") + respx_mock.get("https://api.marketdata.app/v1/options/chain/AAPL/").respond( + json=mock_data, status_code=200 + ) + + client.options.chain( + "AAPL", + from_date="2026-04-14", + to_date="2026-04-18", + output_format=OutputFormat.INTERNAL, + ) + + params = respx_mock.calls.last.request.url.params + assert params.get("from") == "2026-04-14" + assert params.get("to") == "2026-04-18" + assert params.get("from_date") is None + assert params.get("to_date") is None diff --git a/src/tests/test_options_quotes.py b/src/tests/test_options_quotes.py index 77959bf..3737c59 100644 --- a/src/tests/test_options_quotes.py +++ b/src/tests/test_options_quotes.py @@ -2,9 +2,12 @@ import pathlib from unittest.mock import patch +import pytest import pytz +from marketdata.exceptions import MinMaxDateValidationError from marketdata.input_types.base import OutputFormat +from marketdata.input_types.options import OptionsQuotesInput from marketdata.output_types.options_quotes import ( OptionsQuotes, OptionsQuotesHumanReadable, @@ -512,3 +515,48 @@ def test_options_quotes_human_readable_get_null_csv_string(): + ",".join([""] * len(fields)).replace("_", " ") ) assert null_csv_string == expected_csv_string + + +def test_options_quotes_input_date_range_aliases_on_wire(load_json, respx_mock, client): + mock_data = load_json("options_quotes_response_200") + respx_mock.get( + "https://api.marketdata.app/v1/options/quotes/AAPL271217C00255000/" + ).respond(json=mock_data, status_code=200) + + client.options.quotes( + symbols="AAPL271217C00255000", + from_date="2026-04-14", + to_date="2026-04-18", + output_format=OutputFormat.INTERNAL, + ) + + params = respx_mock.calls.last.request.url.params + assert params.get("from") == "2026-04-14" + assert params.get("to") == "2026-04-18" + assert params.get("from_date") is None + assert params.get("to_date") is None + + +def test_options_quotes_input_date_param_on_wire(load_json, respx_mock, client): + mock_data = load_json("options_quotes_response_200") + respx_mock.get( + "https://api.marketdata.app/v1/options/quotes/AAPL271217C00255000/" + ).respond(json=mock_data, status_code=200) + + client.options.quotes( + symbols="AAPL271217C00255000", + date="2026-04-15", + output_format=OutputFormat.INTERNAL, + ) + + params = respx_mock.calls.last.request.url.params + assert params.get("date") == "2026-04-15" + + +def test_options_quotes_input_from_after_to_raises(): + with pytest.raises(MinMaxDateValidationError): + OptionsQuotesInput( + symbols="AAPL271217C00255000", + from_date=datetime.date(2026, 4, 18), + to_date=datetime.date(2026, 4, 14), + )