You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds the stocks resource (client.stocks()) on top of the response-model + parameter conventions established by options. Reuses the transport/retry/rate-limit/response foundation, with two additive shared-layer changes (a tolerant date parser, and a per-response rate-limit accessor on MarketDataResponse). Reviewer's companion: docs/STOCKS_REVIEW_GUIDE.md.
What's included
Six endpoints, each in sync + …Async form, every one taking a Builder-based per-endpoint request and returning a named MarketDataResponse<T> (payload via values()):
quotes / prices — multi-symbol, batched into one request (comma list), one row per symbol. Unlike options.quotes (per-contract fan-out → map), these return a single response.
news — articles + the feed's scalar updated() off the response.
earnings — EPS history + the forward calendar.
StockResolution — a value type (not an enum): DAILY/WEEKLY/MONTHLY/YEARLY constants + minutes(int)/hours(int)/days(int)/weeks(int)/months(int)/years(int) factories + of(String), since the API accepts an open-ended family of resolutions.
Nullable fields + columns + Option A — every row field is @Nullable boxed (so columns can project, and so the backend's NaN→null over closed/illiquid markets decodes cleanly); a requested-but-omitted required column still raises ParseError. Quote OHLC and 52-week columns are opt-in via candle / week52.
CSV facet (asCsv() → CsvResponse, adds human/headers) for every endpoint; HTML facet built but package-private (backend serves no HTML).
Wire-format: candles/quotes/prices/earnings decode via the generic parallel-arrays helper; news has a hand-written StockNewsDeserializer (per-row arrays + a scalar updated at the root). New MarketDataDates.parseDateOrTimestampField tolerates a daily candle's date-only t and an intraday full timestamp uniformly.
Candle auto-chunking (§12) — an intraday request with a from bound spanning > ~1 year is split into year-sized sub-requests (StockResolution.isIntraday() + candleChunks), fetched concurrently through the 50-permit pool and merged into one response (typed and CSV paths). Mirrors the Python SDK's split_dates_by_timeframe.
Per-response rate limits (§8.2) — MarketDataResponse.rateLimit() returns a RateLimitSnapshot parsed from that response's own x-api-ratelimit-* headers (request-scoped, distinct from client-level getRateLimits()). SDK-wide: also covers options/utilities and the CSV/HTML responses.
Examples: StocksApp (every endpoint + CSV facet + columns + Option A + chunking + per-response rate-limit, against the mock server; make demo-stocks); the QuickstartApp stocks section enabled.
MarketDataResponse gains rateLimit(); AbstractMarketDataResponse parses it once from the envelope headers, so every existing response type (options/utilities/CSV/HTML) gets it for free — the only implementor of the interface.
Everything else — transport, retry, rate-limit parsing (RateLimitHeaders), status-cache, exceptions, ParallelArrays, JsonResponseParser, RequestConfig, the response model — is reused as-is from the options PR.
The author of this PR, MarketDataDev03, is not an activated member of this organization on Codecov.
Please activate this user on Codecov to display this PR comment.
Coverage data is still being uploaded to Codecov.io for purposes of overall coverage calculations.
Please don't hesitate to email us at support@codecov.io with any questions.
Review finding: news silently breaks under a columns projection
columns(...) is offered uniformly on StocksResource, but news doesn't honor it the way the other four endpoints do. StockNewsDeserializer zips all five article columns as required (ParallelArrays.zip(p, root, ARTICLE_FIELDS, …)), so as soon as the API legitimately omits a column — which is exactly what a columns projection asks it to do — the zip aborts.
Repro (reduced from a StocksResourceTest-style mock; backend honors columns=headline and returns only that array):
The generic endpoints route through rowsDeserializer(...) with an empty required-list + validateRequestedColumns (Option A), so projected-away columns decode to null. news reads every article field strictly, so it can't. No existing test exercises the news + columns path, which is why it slipped through.
Severity: medium — not an exotic edge; it's the documented columns feature applied to one of the six endpoints.
Suggested fix: give StockNewsDeserializer the same all-optional + Option A treatment as the generic path (article fields optional at the wire level, validated against the requested column set), keeping only the scalar-updated handling hand-written. Plus a regression test for news + columns.
(Alternatively, if columns on news is intentionally unsupported, document that and fail with a clearer message.)
Review finding: news silently breaks under a columns projection
columns(...) is offered uniformly on StocksResource, but news doesn't honor it the way the other four endpoints do. StockNewsDeserializer zips all five article columns as required (ParallelArrays.zip(p, root, ARTICLE_FIELDS, …)), so as soon as the API legitimately omits a column — which is exactly what a columns projection asks it to do — the zip aborts.
Repro (reduced from a StocksResourceTest-style mock; backend honors columns=headline and returns only that array):
The generic endpoints route through rowsDeserializer(...) with an empty required-list + validateRequestedColumns (Option A), so projected-away columns decode to null. news reads every article field strictly, so it can't. No existing test exercises the news + columns path, which is why it slipped through.
Severity: medium — not an exotic edge; it's the documented columns feature applied to one of the six endpoints.
Suggested fix: give StockNewsDeserializer the same all-optional + Option A treatment as the generic path (article fields optional at the wire level, validated against the requested column set), keeping only the scalar-updated handling hand-written. Plus a regression test for news + columns.
(Alternatively, if columns on news is intentionally unsupported, document that and fail with a clearer message.)
Good catch. I didn't want to support it because StockNewsArticle is non-null by contract, and honoring a
projection would force me to make every field nullable for all callers — taxing the common path with
null-checks just to enable a niche projection on a 5-field endpoint.
So client.stocks().columns(...).news(...) now fails fast with a clear IllegalArgumentException pointing at
the CSV facet (asCsv().columns(...).news(...)), which still works since CSV is raw text. That also replaces
the opaque ParseError you hit. I surfaced it as a failed future (not a sync throw) so async callers catch it
normally, and added regression tests. If typed projection on news is ever needed, Option A is a clean
major-version change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: stocks resource
Adds the
stocksresource (client.stocks()) on top of the response-model + parameter conventions established byoptions. Reuses the transport/retry/rate-limit/response foundation, with two additive shared-layer changes (a tolerant date parser, and a per-response rate-limit accessor onMarketDataResponse). Reviewer's companion:docs/STOCKS_REVIEW_GUIDE.md.What's included
…Asyncform, every one taking a Builder-based per-endpoint request and returning a namedMarketDataResponse<T>(payload viavalues()):candles— OHLCV series for aStockResolution.quote— single-symbol quote (/stocks/quotes/{symbol}/).quotes/prices— multi-symbol, batched into one request (comma list), one row per symbol. Unlikeoptions.quotes(per-contract fan-out → map), these return a single response.news— articles + the feed's scalarupdated()off the response.earnings— EPS history + the forward calendar.StockResolution— a value type (not an enum):DAILY/WEEKLY/MONTHLY/YEARLYconstants +minutes(int)/hours(int)/days(int)/weeks(int)/months(int)/years(int)factories +of(String), since the API accepts an open-ended family of resolutions.columns+ Option A — every row field is@Nullableboxed (socolumnscan project, and so the backend'sNaN→nullover closed/illiquid markets decodes cleanly); a requested-but-omitted required column still raisesParseError. Quote OHLC and 52-week columns are opt-in viacandle/week52.asCsv()→CsvResponse, addshuman/headers) for every endpoint; HTML facet built but package-private (backend serves no HTML).newshas a hand-writtenStockNewsDeserializer(per-row arrays + a scalarupdatedat the root). NewMarketDataDates.parseDateOrTimestampFieldtolerates a daily candle's date-onlytand an intraday full timestamp uniformly.frombound spanning > ~1 year is split into year-sized sub-requests (StockResolution.isIntraday()+candleChunks), fetched concurrently through the 50-permit pool and merged into one response (typed and CSV paths). Mirrors the Python SDK'ssplit_dates_by_timeframe.MarketDataResponse.rateLimit()returns aRateLimitSnapshotparsed from that response's ownx-api-ratelimit-*headers (request-scoped, distinct from client-levelgetRateLimits()). SDK-wide: also covers options/utilities and the CSV/HTML responses.StocksApp(every endpoint + CSV facet + columns + Option A + chunking + per-response rate-limit, against the mock server;make demo-stocks); theQuickstartAppstocks section enabled.## Stockssection (Java + Kotlin),CHANGELOG,CLAUDE.mdstate.Shared layers (additive changes)
MarketDataClientwiresclient.stocks().MarketDataDatesgainsparseDateOrTimestampField(existing parsers untouched).MarketDataResponsegainsrateLimit();AbstractMarketDataResponseparses it once from the envelope headers, so every existing response type (options/utilities/CSV/HTML) gets it for free — the only implementor of the interface.RateLimitHeaders), status-cache, exceptions,ParallelArrays,JsonResponseParser,RequestConfig, the response model — is reused as-is from theoptionsPR.