Skip to content

Fix: days_to_expiration sent under wrong query param name (dte)#31

Open
MarketDataDev03 wants to merge 2 commits into
mainfrom
day_to_expiration
Open

Fix: days_to_expiration sent under wrong query param name (dte)#31
MarketDataDev03 wants to merge 2 commits into
mainfrom
day_to_expiration

Conversation

@MarketDataDev03
Copy link
Copy Markdown
Collaborator

Fix: days_to_expiration sent under wrong query param name (dte)

Closes #30

Summary

The days_to_expiration filter on client.options.chain(...) was silently
ignored by the API. The URL builder serializes input models with
by_alias=True (src/marketdata/resources/base.py:45), but the
days_to_expiration field had no Pydantic alias, so it was emitted to the
wire under its Python name (days_to_expiration=30) instead of the parameter
the API expects (dte=30). The backend doesn't recognize that key, so the
filter was dropped without error and the full chain was returned.

client.options.chain(symbol="AAPL", days_to_expiration=30)
# before: ...?days_to_expiration=30   -> ignored by API
# after:  ...?dte=30                  -> filter applied

Changes

1. The fix — src/marketdata/input_types/options.py

Added alias="dte" to the days_to_expiration field, matching the existing
convention used by every other renamed field in the model (strikeLimit,
minBid, from/to, etc.). Because BaseModelConfig sets
populate_by_name=True, callers keep using days_to_expiration=... while the
emitted query parameter is now dte.

2. Targeted regression test — src/tests/test_options_chain.py

test_options_chain_input_days_to_expiration_alias_on_wire asserts that a real
request carries dte=30 on the wire and never days_to_expiration, mirroring
the existing from_date/to_date alias test.

3. Preventive test for the whole bug class — src/tests/test_input_types.py

A parametrized test (test_snake_case_input_fields_have_api_alias)
auto-discovers every BaseInputType model in the SDK and asserts that any field
whose Python name contains an underscore is sent under an explicit alias
(or is exempt via GLOBAL_EXCLUDED_PARAMS, like output_format/filename,
which never reach the query string). A guard test ensures discovery isn't
silently empty. This catches the next field that forgets its alias, not just
this one.

Audit

I audited every input model across options.py, funds.py, markets.py,
stocks.py, and base.py. days_to_expiration was the only field missing
an alias — all other renamed fields (strike_limit, min_bid/max_bid,
min_ask/max_ask, max_bid_ask_spread[_pct], min_open_interest,
min_volume, from_date/to_date, use_52_week, adjust_splits,
report_type, date_format, add_headers, use_human_readable) already
carry the correct alias. The new preventive test now enforces this invariant.

Testing

  • Verified the new alias test fails when the alias="dte" fix is reverted,
    confirming it isn't a false green.
  • Full suite: 328 passed.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (fb1ea8e) to head (b101f92).

Additional details and impacted files
@@            Coverage Diff            @@
##              main       #31   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           52        52           
  Lines         2281      2281           
=========================================
  Hits          2281      2281           
Flag Coverage Δ
unittests 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

days_to_expiration on options.chain sent under wrong query param name (dte)

1 participant