Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/marketdata/input_types/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -135,13 +139,32 @@ 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]:
if isinstance(value, 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):

Expand Down
20 changes: 20 additions & 0 deletions src/tests/test_options_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
48 changes: 48 additions & 0 deletions src/tests/test_options_quotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
)
Loading