From 0d1d2ec701027cd5c3866257170c88e952ca5f01 Mon Sep 17 00:00:00 2001 From: Lucas Giordano Date: Fri, 26 Jun 2026 15:53:00 -0300 Subject: [PATCH] remove strikes --- README.md | 1 - .../sdk/OptionsIntegrationTest.java | 13 -- .../com/marketdata/sdk/MarketDataClient.java | 5 +- .../marketdata/sdk/OptionsCsvResource.java | 9 - .../marketdata/sdk/OptionsHtmlResource.java | 9 - .../com/marketdata/sdk/OptionsResource.java | 30 ---- .../sdk/OptionsStrikesDeserializer.java | 94 ----------- .../sdk/OptionsStrikesResponse.java | 31 ---- .../sdk/options/ExpirationStrikes.java | 24 --- .../sdk/options/OptionsStrikes.java | 29 ---- .../sdk/options/OptionsStrikesRequest.java | 75 --------- .../marketdata/sdk/OptionsResourceTest.java | 158 ------------------ .../marketdata/sdk/RequestValidationTest.java | 3 - 13 files changed, 1 insertion(+), 480 deletions(-) delete mode 100644 src/main/java/com/marketdata/sdk/OptionsStrikesDeserializer.java delete mode 100644 src/main/java/com/marketdata/sdk/OptionsStrikesResponse.java delete mode 100644 src/main/java/com/marketdata/sdk/options/ExpirationStrikes.java delete mode 100644 src/main/java/com/marketdata/sdk/options/OptionsStrikes.java delete mode 100644 src/main/java/com/marketdata/sdk/options/OptionsStrikesRequest.java diff --git a/README.md b/README.md index 48be050..dcaa75e 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ is uniform across the SDK regardless of how many parameters an endpoint has. |--------|---------| | `lookup` | Resolve a human description (`"AAPL 1/16/2026 $200 Call"`) to an OCC symbol | | `expirations` | Expiration dates for an underlying | -| `strikes` | Strike ladder per expiration | | `quote` | Quote for a single OCC option symbol | | `quotes` | Quotes for many symbols — fans out concurrently, returns a per-symbol map | | `chain` | Full option chain with the rich filter surface | diff --git a/src/integrationTest/java/com/marketdata/sdk/OptionsIntegrationTest.java b/src/integrationTest/java/com/marketdata/sdk/OptionsIntegrationTest.java index 64ebecb..4b8cff6 100644 --- a/src/integrationTest/java/com/marketdata/sdk/OptionsIntegrationTest.java +++ b/src/integrationTest/java/com/marketdata/sdk/OptionsIntegrationTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import com.marketdata.sdk.options.ExpirationFilter; -import com.marketdata.sdk.options.ExpirationStrikes; import com.marketdata.sdk.options.OptionQuote; import com.marketdata.sdk.options.OptionSide; import com.marketdata.sdk.options.OptionsChainRequest; @@ -11,7 +10,6 @@ import com.marketdata.sdk.options.OptionsLookupRequest; import com.marketdata.sdk.options.OptionsQuoteRequest; import com.marketdata.sdk.options.OptionsQuotesRequest; -import com.marketdata.sdk.options.OptionsStrikesRequest; import com.marketdata.sdk.options.StrikeRange; import java.time.LocalDate; import java.util.List; @@ -91,17 +89,6 @@ void expirationsReturnsAtLeastOneFutureDate() { assertThat(resp.updated().getZone().getId()).isEqualTo("America/New_York"); } - @Test - void strikesReturnsStrikesPerExpiration() { - OptionsStrikesResponse resp = client.options().strikes(OptionsStrikesRequest.of(UNDERLYING)); - - assertThat(resp.statusCode()).isIn(200, 203); - assertThat(resp.values()).isNotEmpty(); - ExpirationStrikes first = resp.values().get(0); - assertThat(first.strikes()).as("first expiration's strike ladder is non-empty").isNotEmpty(); - assertThat(first.expiration().getZone().getId()).isEqualTo("America/New_York"); - } - @Test void chainReturnsFilteredContracts() { // Light filter: a narrow strike-limit window keeps the response small without depending on diff --git a/src/main/java/com/marketdata/sdk/MarketDataClient.java b/src/main/java/com/marketdata/sdk/MarketDataClient.java index c99f460..d00221e 100644 --- a/src/main/java/com/marketdata/sdk/MarketDataClient.java +++ b/src/main/java/com/marketdata/sdk/MarketDataClient.java @@ -192,10 +192,7 @@ public UtilitiesResource utilities() { return utilities; } - /** - * Options endpoints: {@code lookup}, {@code expirations}, {@code strikes}, {@code quotes}, {@code - * chain}. - */ + /** Options endpoints: {@code lookup}, {@code expirations}, {@code quotes}, {@code chain}. */ public OptionsResource options() { return options; } diff --git a/src/main/java/com/marketdata/sdk/OptionsCsvResource.java b/src/main/java/com/marketdata/sdk/OptionsCsvResource.java index 642de72..21d71f0 100644 --- a/src/main/java/com/marketdata/sdk/OptionsCsvResource.java +++ b/src/main/java/com/marketdata/sdk/OptionsCsvResource.java @@ -4,7 +4,6 @@ import com.marketdata.sdk.options.OptionsExpirationsRequest; import com.marketdata.sdk.options.OptionsQuoteRequest; import com.marketdata.sdk.options.OptionsQuotesRequest; -import com.marketdata.sdk.options.OptionsStrikesRequest; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -84,14 +83,6 @@ public Map quotes(OptionsQuotesRequest request) { return transport.joinSync(quotesAsync(request)); } - public CompletableFuture strikesAsync(OptionsStrikesRequest request) { - return executeCsv(OptionsResource.strikesSpec(request)); - } - - public CsvResponse strikes(OptionsStrikesRequest request) { - return transport.joinSync(strikesAsync(request)); - } - public CompletableFuture expirationsAsync(OptionsExpirationsRequest request) { return executeCsv(OptionsResource.expirationsSpec(request)); } diff --git a/src/main/java/com/marketdata/sdk/OptionsHtmlResource.java b/src/main/java/com/marketdata/sdk/OptionsHtmlResource.java index ab8b2e6..9f0c1de 100644 --- a/src/main/java/com/marketdata/sdk/OptionsHtmlResource.java +++ b/src/main/java/com/marketdata/sdk/OptionsHtmlResource.java @@ -3,7 +3,6 @@ import com.marketdata.sdk.options.OptionsChainRequest; import com.marketdata.sdk.options.OptionsExpirationsRequest; import com.marketdata.sdk.options.OptionsQuoteRequest; -import com.marketdata.sdk.options.OptionsStrikesRequest; import java.util.concurrent.CompletableFuture; /** @@ -45,14 +44,6 @@ public HtmlResponse quote(OptionsQuoteRequest request) { return transport.joinSync(quoteAsync(request)); } - public CompletableFuture strikesAsync(OptionsStrikesRequest request) { - return executeHtml(OptionsResource.strikesSpec(request)); - } - - public HtmlResponse strikes(OptionsStrikesRequest request) { - return transport.joinSync(strikesAsync(request)); - } - public CompletableFuture expirationsAsync(OptionsExpirationsRequest request) { return executeHtml(OptionsResource.expirationsSpec(request)); } diff --git a/src/main/java/com/marketdata/sdk/OptionsResource.java b/src/main/java/com/marketdata/sdk/OptionsResource.java index 0bcc5c6..81fc7ee 100644 --- a/src/main/java/com/marketdata/sdk/OptionsResource.java +++ b/src/main/java/com/marketdata/sdk/OptionsResource.java @@ -17,8 +17,6 @@ import com.marketdata.sdk.options.OptionsQuoteRequest; import com.marketdata.sdk.options.OptionsQuotes; import com.marketdata.sdk.options.OptionsQuotesRequest; -import com.marketdata.sdk.options.OptionsStrikes; -import com.marketdata.sdk.options.OptionsStrikesRequest; import com.marketdata.sdk.options.StrikeFilter; import java.io.IOException; import java.time.LocalDate; @@ -115,21 +113,6 @@ public OptionsExpirationsResponse expirations(OptionsExpirationsRequest request) return transport.joinSync(expirationsAsync(request)); } - /** Async: fetch the strike prices available for each expiration on the request's underlying. */ - public CompletableFuture strikesAsync(OptionsStrikesRequest request) { - RequestSpec.Builder b = strikesSpec(request); - config.applyTo(b); - return execute( - b.build(), - OptionsStrikes.class, - (d, env, fmt) -> new OptionsStrikesResponse(d.expirations(), d.updated(), env, fmt)); - } - - /** Sync wrapper for {@link #strikesAsync(OptionsStrikesRequest)}. */ - public OptionsStrikesResponse strikes(OptionsStrikesRequest request) { - return transport.joinSync(strikesAsync(request)); - } - /** Async: fetch the current (or historical) quote for a single OCC option symbol. */ public CompletableFuture quoteAsync(OptionsQuoteRequest request) { RequestSpec.Builder b = @@ -229,18 +212,6 @@ static RequestSpec.Builder expirationsSpec(OptionsExpirationsRequest request) { return b; } - static RequestSpec.Builder strikesSpec(OptionsStrikesRequest request) { - RequestSpec.Builder b = - RequestSpec.get("options/strikes/" + PathSegments.encode(request.symbol())); - if (request.expiration() != null) { - b.query("expiration", DateTimeFormatter.ISO_LOCAL_DATE.format(request.expiration())); - } - if (request.date() != null) { - b.query("date", DateTimeFormatter.ISO_LOCAL_DATE.format(request.date())); - } - return b; - } - static RequestSpec.Builder quoteSpec( String optionSymbol, @Nullable LocalDate date, @@ -379,7 +350,6 @@ static SimpleModule wireFormatModule() { SimpleModule m = new SimpleModule("marketdata-options"); m.addDeserializer(OptionsLookup.class, new OptionsLookupDeserializer()); m.addDeserializer(OptionsExpirations.class, new OptionsExpirationsDeserializer()); - m.addDeserializer(OptionsStrikes.class, new OptionsStrikesDeserializer()); m.addDeserializer(OptionsQuotes.class, optionRowsDeserializer(OptionsQuotes::new)); m.addDeserializer(OptionsChain.class, optionRowsDeserializer(OptionsChain::new)); return m; diff --git a/src/main/java/com/marketdata/sdk/OptionsStrikesDeserializer.java b/src/main/java/com/marketdata/sdk/OptionsStrikesDeserializer.java deleted file mode 100644 index 47bc73d..0000000 --- a/src/main/java/com/marketdata/sdk/OptionsStrikesDeserializer.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.marketdata.sdk; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.marketdata.sdk.options.ExpirationStrikes; -import com.marketdata.sdk.options.OptionsStrikes; -import java.io.IOException; -import java.time.LocalDate; -import java.time.ZonedDateTime; -import java.time.format.DateTimeParseException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * Wire-format deserializer for {@link OptionsStrikes}. Unlike every other endpoint, the strikes - * body uses dynamic top-level keys — one per expiration date — alongside the fixed {@code - * s} envelope and {@code updated} timestamp. {@link ParallelArrays} cannot express this shape; the - * deserializer iterates the root's fields and treats any key that parses as an ISO date ({@code - * "yyyy-MM-dd"}) as an expiration entry. Envelope handling mirrors the rest of the SDK: - * - *
    - *
  • {@code "s":"error"} → {@link JsonMappingException} with {@code errmsg}. - *
  • {@code "s":"no_data"} → empty list, {@code updated} left null (the API typically attaches - * {@code nextTime} / {@code prevTime} hints in that envelope; this deserializer ignores them - * because they aren't part of the typed surface). - *
  • otherwise → strict validation: missing {@code updated}, missing/wrong-typed strike values, - * or unrecognized non-date keys all raise {@link JsonMappingException}. - *
- */ -final class OptionsStrikesDeserializer extends JsonDeserializer { - - private static final String ENVELOPE_STATUS = "s"; - private static final String ENVELOPE_ERRMSG = "errmsg"; - private static final String ENVELOPE_ERROR = "error"; - private static final String ENVELOPE_NO_DATA = "no_data"; - private static final String UPDATED_KEY = "updated"; - - @Override - public OptionsStrikes deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - JsonNode root = p.readValueAsTree(); - String envelopeStatus = root.path(ENVELOPE_STATUS).asText(""); - if (ENVELOPE_ERROR.equals(envelopeStatus)) { - throw new JsonMappingException( - p, "API responded with error: " + root.path(ENVELOPE_ERRMSG).asText("(no errmsg field)")); - } - if (ENVELOPE_NO_DATA.equals(envelopeStatus)) { - return new OptionsStrikes(List.of(), null); - } - - ZonedDateTime updated = - MarketDataDates.parseTimestampField(p, root.get(UPDATED_KEY), UPDATED_KEY); - - List rows = new ArrayList<>(); - Iterator> it = root.fields(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - String key = entry.getKey(); - if (ENVELOPE_STATUS.equals(key) || UPDATED_KEY.equals(key)) { - continue; - } - LocalDate expirationDate; - try { - expirationDate = LocalDate.parse(key); - } catch (DateTimeParseException e) { - // The endpoint is documented as `s` + `updated` + dynamic ISO-date keys; an unrecognized - // key signals either a server bug or a forward-compatible extension. Strict-by-default - // surfaces it. - throw new JsonMappingException(p, "unrecognized top-level key: " + key); - } - JsonNode arr = entry.getValue(); - if (!arr.isArray()) { - throw new JsonMappingException(p, "non-array value for expiration " + key); - } - List strikes = new ArrayList<>(arr.size()); - for (int i = 0; i < arr.size(); i++) { - JsonNode cell = arr.get(i); - if (!cell.isNumber()) { - throw new JsonMappingException( - p, "non-numeric strike at " + key + "[" + i + "]: " + cell.asText()); - } - strikes.add(cell.asDouble()); - } - rows.add( - new ExpirationStrikes(expirationDate.atStartOfDay(MarketDataDates.MARKET_ZONE), strikes)); - } - - return new OptionsStrikes(rows, updated); - } -} diff --git a/src/main/java/com/marketdata/sdk/OptionsStrikesResponse.java b/src/main/java/com/marketdata/sdk/OptionsStrikesResponse.java deleted file mode 100644 index 6f91a6b..0000000 --- a/src/main/java/com/marketdata/sdk/OptionsStrikesResponse.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.marketdata.sdk; - -import com.marketdata.sdk.options.ExpirationStrikes; -import java.time.ZonedDateTime; -import java.util.List; -import org.jspecify.annotations.Nullable; - -/** - * Response for {@code options.strikes}: {@link #values()} is the per-expiration strike lists. The - * endpoint also carries an {@code updated} timestamp, exposed via {@link #updated()} ({@code null} - * on the {@code no_data} envelope). Construct only through the resource façade. - */ -public final class OptionsStrikesResponse - extends AbstractMarketDataResponse> { - - private final @Nullable ZonedDateTime updated; - - OptionsStrikesResponse( - List values, - @Nullable ZonedDateTime updated, - HttpResponseEnvelope envelope, - Format format) { - super(values, envelope, format); - this.updated = updated; - } - - /** When the strike table was last refreshed, or {@code null} when the API omitted it. */ - public @Nullable ZonedDateTime updated() { - return updated; - } -} diff --git a/src/main/java/com/marketdata/sdk/options/ExpirationStrikes.java b/src/main/java/com/marketdata/sdk/options/ExpirationStrikes.java deleted file mode 100644 index 45819ad..0000000 --- a/src/main/java/com/marketdata/sdk/options/ExpirationStrikes.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.marketdata.sdk.options; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Objects; - -/** - * Strikes available for a single expiration in an option chain — one row of {@link OptionsStrikes}. - * - * @param expiration the expiration date as a midnight {@code America/New_York} moment. Wire-format - * comes through as an ISO date-only string ({@code "2025-01-17"}) regardless of the §3 {@code - * dateformat} parameter — the strikes endpoint uses the key itself as the expiration label, so - * it cannot vary by format. - * @param strikes strike prices for {@code expiration}, in ascending order as the API delivers them. - * Immutable; never {@code null}. - */ -public record ExpirationStrikes(ZonedDateTime expiration, List strikes) { - - public ExpirationStrikes { - Objects.requireNonNull(expiration, "expiration"); - Objects.requireNonNull(strikes, "strikes"); - strikes = List.copyOf(strikes); - } -} diff --git a/src/main/java/com/marketdata/sdk/options/OptionsStrikes.java b/src/main/java/com/marketdata/sdk/options/OptionsStrikes.java deleted file mode 100644 index 56ae18f..0000000 --- a/src/main/java/com/marketdata/sdk/options/OptionsStrikes.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.marketdata.sdk.options; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Objects; - -/** - * Response shape for {@code GET /v1/options/strikes/{underlying}/} — the strike prices available - * for each expiration in the underlying's option chain. - * - *

The wire-format is unique in the API: there is no parallel-arrays envelope, and the response - * carries one top-level key per expiration date (e.g. {@code "2025-01-17"} → {@code [140, 145, - * 150]}) plus the universal {@code "s"} and {@code "updated"} metadata. The SDK lifts those into a - * positional list of {@link ExpirationStrikes} rows so consumers iterate naturally; ordering - * follows what the API returns (the backend sorts expirations ascending). - * - * @param expirations one entry per expiration in chronological order; never {@code null}. Empty - * when the {@code "s":"no_data"} envelope is received. - * @param updated when the server last refreshed this strikes table, in {@code America/New_York}. - * {@code null} only when the no-data envelope omits the {@code updated} field. - */ -public record OptionsStrikes( - List expirations, @org.jspecify.annotations.Nullable ZonedDateTime updated) { - - public OptionsStrikes { - Objects.requireNonNull(expirations, "expirations"); - expirations = List.copyOf(expirations); - } -} diff --git a/src/main/java/com/marketdata/sdk/options/OptionsStrikesRequest.java b/src/main/java/com/marketdata/sdk/options/OptionsStrikesRequest.java deleted file mode 100644 index 46ae072..0000000 --- a/src/main/java/com/marketdata/sdk/options/OptionsStrikesRequest.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.marketdata.sdk.options; - -import java.time.LocalDate; -import java.util.Objects; -import org.jspecify.annotations.Nullable; - -/** - * Parameters for {@code GET /v1/options/strikes/{symbol}/}. Two optional filters narrow the result: - * {@code expiration} returns strikes only for that expiration date, {@code date} fetches the - * historical table as it stood on a previous trading day. - */ -public final class OptionsStrikesRequest { - - private final String symbol; - private final @Nullable LocalDate expiration; - private final @Nullable LocalDate date; - - private OptionsStrikesRequest(Builder b) { - this.symbol = b.symbol; - this.expiration = b.expiration; - this.date = b.date; - } - - /** Shortcut for {@code builder(symbol).build()}. */ - public static OptionsStrikesRequest of(String symbol) { - return builder(symbol).build(); - } - - public static Builder builder(String symbol) { - return new Builder(symbol); - } - - public String symbol() { - return symbol; - } - - /** Single-expiration filter, or {@code null} when unset. */ - public @Nullable LocalDate expiration() { - return expiration; - } - - /** Historical query date, or {@code null} for the current/last-trading-day table. */ - public @Nullable LocalDate date() { - return date; - } - - public static final class Builder { - private final String symbol; - private @Nullable LocalDate expiration; - private @Nullable LocalDate date; - - private Builder(String symbol) { - this.symbol = Objects.requireNonNull(symbol, "symbol"); - } - - /** Restrict the result to the single given expiration date. */ - public Builder expiration(LocalDate expiration) { - this.expiration = Objects.requireNonNull(expiration, "expiration"); - return this; - } - - /** Fetch the strikes table as it stood at end-of-day on {@code date}. */ - public Builder date(LocalDate date) { - this.date = Objects.requireNonNull(date, "date"); - return this; - } - - public OptionsStrikesRequest build() { - if (symbol.isEmpty()) { - throw new IllegalArgumentException("symbol must be non-empty"); - } - return new OptionsStrikesRequest(this); - } - } -} diff --git a/src/test/java/com/marketdata/sdk/OptionsResourceTest.java b/src/test/java/com/marketdata/sdk/OptionsResourceTest.java index 5284b8f..292ee83 100644 --- a/src/test/java/com/marketdata/sdk/OptionsResourceTest.java +++ b/src/test/java/com/marketdata/sdk/OptionsResourceTest.java @@ -5,7 +5,6 @@ import com.marketdata.sdk.exception.ParseError; import com.marketdata.sdk.options.ExpirationFilter; -import com.marketdata.sdk.options.ExpirationStrikes; import com.marketdata.sdk.options.Greek; import com.marketdata.sdk.options.OptionQuote; import com.marketdata.sdk.options.OptionSide; @@ -14,7 +13,6 @@ import com.marketdata.sdk.options.OptionsLookupRequest; import com.marketdata.sdk.options.OptionsQuoteRequest; import com.marketdata.sdk.options.OptionsQuotesRequest; -import com.marketdata.sdk.options.OptionsStrikesRequest; import com.marketdata.sdk.options.StrikeFilter; import com.marketdata.sdk.options.StrikeRange; import java.net.URI; @@ -354,160 +352,6 @@ void expirationsBooleanCellThrowsParseError() { .hasMessageContaining("non-string, non-numeric"); } - // ---------- strikes: URL & params ---------- - - @Test - void strikesHitsVersionedEndpoint() { - CapturingClient client = - okWith("{\"s\":\"ok\",\"updated\":1705449600,\"2025-01-17\":[140,145,150]}"); - OptionsResource options = resourceWith(client); - - options.strikesAsync(OptionsStrikesRequest.of("AAPL")).join(); - - String url = client.captured.get(0).uri().toString(); - assertThat(url).isEqualTo("http://localhost/v1/options/strikes/AAPL/"); - } - - @Test - void strikesAttachesExpirationAndDateFilters() { - CapturingClient client = - okWith("{\"s\":\"ok\",\"updated\":1705449600,\"2025-01-17\":[140,145]}"); - OptionsResource options = resourceWith(client); - - options - .strikesAsync( - OptionsStrikesRequest.builder("AAPL") - .expiration(LocalDate.of(2025, Month.JANUARY, 17)) - .date(LocalDate.of(2024, Month.DECEMBER, 16)) - .build()) - .join(); - - String url = client.captured.get(0).uri().toString(); - assertThat(url) - .isEqualTo( - "http://localhost/v1/options/strikes/AAPL/?expiration=2025-01-17&date=2024-12-16"); - } - - // ---------- strikes: response decoding ---------- - - @Test - void strikesDecodesMultipleExpirations() { - ZoneId et = ZoneId.of("America/New_York"); - CapturingClient client = - okWith( - "{\"s\":\"ok\",\"updated\":1705449600," - + "\"2025-01-17\":[140.0,145.0,150.0]," - + "\"2025-02-21\":[135.0,140.0,145.0,150.0]}"); - OptionsResource options = resourceWith(client); - - OptionsStrikesResponse resp = options.strikes(OptionsStrikesRequest.of("AAPL")); - List strikes = resp.values(); - - assertThat(strikes).hasSize(2); - ExpirationStrikes first = strikes.get(0); - assertThat(first.expiration()) - .isEqualTo(LocalDate.of(2025, Month.JANUARY, 17).atStartOfDay(et)); - assertThat(first.strikes()).containsExactly(140.0, 145.0, 150.0); - ExpirationStrikes second = strikes.get(1); - assertThat(second.expiration()) - .isEqualTo(LocalDate.of(2025, Month.FEBRUARY, 21).atStartOfDay(et)); - assertThat(second.strikes()).containsExactly(135.0, 140.0, 145.0, 150.0); - assertThat(resp.updated()).isNotNull(); - assertThat(resp.updated().getZone().getId()).isEqualTo("America/New_York"); - } - - @Test - void strikesAcceptsTimestampStringFormatForUpdated() { - // The expiration keys are ALWAYS literal ISO dates regardless of dateformat (the backend - // emits str(date) for the key, ignoring the format param). Only `updated` honors dateformat. - CapturingClient client = - okWith("{\"s\":\"ok\",\"updated\":\"2025-01-16 19:00:00 -05:00\",\"2025-01-17\":[150.0]}"); - OptionsResource options = resourceWith(client); - - OptionsStrikesResponse resp = options.strikes(OptionsStrikesRequest.of("AAPL")); - List strikes = resp.values(); - assertThat(strikes).hasSize(1); - assertThat(resp.updated()).isNotNull(); - assertThat(resp.updated().toLocalDate()).isEqualTo(LocalDate.of(2025, Month.JANUARY, 16)); - } - - // ---------- strikes: envelope handling ---------- - - @Test - void strikesNoDataEnvelopeYieldsEmptyList() { - // The strikes endpoint also attaches nextTime/prevTime hints in no_data envelopes; the - // deserializer ignores them (they aren't part of the typed surface). - CapturingClient client = okWith("{\"s\":\"no_data\",\"nextTime\":null,\"prevTime\":null}"); - OptionsResource options = resourceWith(client); - - OptionsStrikesResponse resp = options.strikes(OptionsStrikesRequest.of("BOGUS")); - List strikes = resp.values(); - assertThat(strikes).isEmpty(); - assertThat(resp.updated()).isNull(); - } - - @Test - void strikesErrorEnvelopeSurfacesAsParseError() { - CapturingClient client = okWith("{\"s\":\"error\",\"errmsg\":\"Symbol not found\"}"); - OptionsResource options = resourceWith(client); - - assertThatThrownBy(() -> options.strikes(OptionsStrikesRequest.of("BOGUS"))) - .isInstanceOf(ParseError.class) - .hasMessageContaining("Symbol not found"); - } - - @Test - void strikesMissingUpdatedThrowsParseError() { - CapturingClient client = okWith("{\"s\":\"ok\",\"2025-01-17\":[150.0]}"); - OptionsResource options = resourceWith(client); - - assertThatThrownBy(() -> options.strikes(OptionsStrikesRequest.of("AAPL"))) - .isInstanceOf(ParseError.class) - .hasMessageContaining("updated"); - } - - @Test - void strikesUnrecognizedTopLevelKeyThrowsParseError() { - // Strict-by-default — a non-date, non-{s,updated} key signals server change. Surfacing it - // gives us a diagnostic breadcrumb instead of silently dropping data. - CapturingClient client = okWith("{\"s\":\"ok\",\"updated\":1705449600,\"surprise\":[1.0]}"); - OptionsResource options = resourceWith(client); - - assertThatThrownBy(() -> options.strikes(OptionsStrikesRequest.of("AAPL"))) - .isInstanceOf(ParseError.class) - .hasMessageContaining("unrecognized top-level key: surprise"); - } - - @Test - void strikesNonNumericStrikeThrowsParseError() { - CapturingClient client = - okWith("{\"s\":\"ok\",\"updated\":1705449600,\"2025-01-17\":[\"oops\"]}"); - OptionsResource options = resourceWith(client); - - assertThatThrownBy(() -> options.strikes(OptionsStrikesRequest.of("AAPL"))) - .isInstanceOf(ParseError.class) - .hasMessageContaining("non-numeric strike"); - } - - @Test - void strikesNonArrayExpirationValueThrowsParseError() { - CapturingClient client = okWith("{\"s\":\"ok\",\"updated\":1705449600,\"2025-01-17\":150.0}"); - OptionsResource options = resourceWith(client); - - assertThatThrownBy(() -> options.strikes(OptionsStrikesRequest.of("AAPL"))) - .isInstanceOf(ParseError.class) - .hasMessageContaining("non-array value for expiration"); - } - - @Test - void strikesSyncMirrorsAsync() { - CapturingClient client = okWith("{\"s\":\"ok\",\"updated\":1705449600,\"2025-01-17\":[150.0]}"); - OptionsResource options = resourceWith(client); - - List strikes = options.strikes(OptionsStrikesRequest.of("AAPL")).values(); - assertThat(strikes).hasSize(1); - } - // ---------- quote (singular): URL & params ---------- private static final String CANNED_QUOTE_BODY = @@ -882,7 +726,6 @@ void csvFacetCoversEveryEndpoint() { assertThat(csv.chain(OptionsChainRequest.of("AAPL")).csv()).contains("a,b"); assertThat(csv.quote(OptionsQuoteRequest.of("AAPL250117C00150000")).csv()).contains("a,b"); - assertThat(csv.strikes(OptionsStrikesRequest.of("AAPL")).csv()).contains("a,b"); assertThat(csv.expirations(OptionsExpirationsRequest.of("AAPL")).csv()).contains("a,b"); Map fanout = @@ -899,7 +742,6 @@ void htmlFacetCoversEveryEndpoint() { assertThat(html.chain(OptionsChainRequest.of("AAPL")).html()).contains(""); assertThat(html.quote(OptionsQuoteRequest.of("AAPL250117C00150000")).html()).contains(""); - assertThat(html.strikes(OptionsStrikesRequest.of("AAPL")).html()).contains(""); assertThat(html.expirations(OptionsExpirationsRequest.of("AAPL")).html()).contains(""); } diff --git a/src/test/java/com/marketdata/sdk/RequestValidationTest.java b/src/test/java/com/marketdata/sdk/RequestValidationTest.java index 0a5f6b4..d1d8c05 100644 --- a/src/test/java/com/marketdata/sdk/RequestValidationTest.java +++ b/src/test/java/com/marketdata/sdk/RequestValidationTest.java @@ -10,7 +10,6 @@ import com.marketdata.sdk.options.OptionsLookupRequest; import com.marketdata.sdk.options.OptionsQuoteRequest; import com.marketdata.sdk.options.OptionsQuotesRequest; -import com.marketdata.sdk.options.OptionsStrikesRequest; import com.marketdata.sdk.stocks.StockCandlesRequest; import com.marketdata.sdk.stocks.StockEarningsRequest; import com.marketdata.sdk.stocks.StockNewsRequest; @@ -134,8 +133,6 @@ void optionsQuotesOfFactoryCarriesSymbolsAndDefaultsOptionals() { @Test void optionsRequestsRejectEmptyInputs() { - assertThatThrownBy(() -> OptionsStrikesRequest.of("")) - .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> OptionsExpirationsRequest.of("")) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> OptionsLookupRequest.of(""))