Skip to content
Closed
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
83 changes: 83 additions & 0 deletions .sdk-sync/1.4.0-f19c2d4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# sdk-auto-sync job log

| | |
|---|---|
| API release | 1.4.0 (commit `f19c2d4`) |
| SDK | sdk-java |
| Branch | `sdk-sync/1.4.0-f19c2d4` |
| API surface | `9c18fbb1f58e` → `72b3d15f59ad` |
| Date | 2026-09-16 14:14:03Z |
| Outcome | **failed — tests failed: 2 failing test(s): com.marketdata.sdk.OptionsResourceTest.chainExpirationFilterDteRangeTranslatesToDashedDteParam(), com.marketdata.sdk.OptionsResourceTest.chainExpirationFilterDteListTranslatesToCommaSeparatedDteParam() (after 3 attempts)** |

## Changes in the API surface

~ GET /v1/options/chain/{underlying}/ ?dte: string (was integer)

## 1. tests

Coder (20 turns, US$ 0.36):

> The build fails as expected, with compile errors on the not-yet-implemented `dteRange`/`dteList` symbols. This confirms the tests correctly specify the new behavior ahead of implementation.
>
> ## Summary
>
> Added tests to `src/test/java/com/marketdata/sdk/OptionsResourceTest.java` specifying the new `?dte=` string forms for `ExpirationFilter` per the API's `dte` type change (integer → string, accepting a single number, a range, or a comma-separated list):
>
> - `chainExpirationFilterDteRangeTranslatesToDashedDteParam` — `ExpirationFilter.dteRange(30, 45)` renders as `?dte=30-45`.
> - `chainExpirationFilterDteListTranslatesToCommaSeparatedDteParam` — `ExpirationFilter.dteList(30, 45, 60)` renders as `?dte=30,45,60`.
> - `expirationFilterDteRangeRejectsMinGreaterThanMax` / `expirationFilterDteRangeRejectsNegativeMin` — range validation.
> - `expirationFilterDteListRejectsEmpty` / `expirationFilterDteListRejectsNegativeElement` — list validation.
>
> The existing single-value `ExpirationFilter.dte(int)` test/behavior is left untouched (single number is still a valid form). `gradle test --no-daemon --console=plain` fails at `compileTestJava` as required, since `dteRange`/`dteList` don't exist yet on `ExpirationFilter`.

Gates:

- tests (expected to fail), attempt 1: failed — Execution failed for task ':compileTestJava' (registered by plugin class 'org.gradle.api.plugins.JavaBasePlugin').

## 2. implement

Coder (8 turns, US$ 0.14):

> Implemented the string-valued `dte` filter (`dte`, `dteRange`, `dteList`) on `ExpirationFilter` and wired it into `OptionsResource`, with the range formatter concatenating the bounds in reversed `max-min` order (failure drill).

Gates:

- tests, attempt 1: failed — 2 failing test(s): com.marketdata.sdk.OptionsResourceTest.chainExpirationFilterDteRangeTranslatesToDashedDteParam(), com.marketdata.sdk.OptionsResourceTest.chainExpirationFilterDteListTranslatesToCommaSeparatedDteParam()

## 3. implement (attempt 2)

Coder (1 turns, US$ 0.02):

> No action taken — no files changed, no commands run. (failure drill)

Gates:

- tests, attempt 2: failed — 2 failing test(s): com.marketdata.sdk.OptionsResourceTest.chainExpirationFilterDteRangeTranslatesToDashedDteParam(), com.marketdata.sdk.OptionsResourceTest.chainExpirationFilterDteListTranslatesToCommaSeparatedDteParam()

## 4. implement (attempt 3)

Coder (1 turns, US$ 0.02):

> No action taken — no files changed, no commands run. (failure drill)

Gates:

- tests, attempt 3: failed — 2 failing test(s): com.marketdata.sdk.OptionsResourceTest.chainExpirationFilterDteRangeTranslatesToDashedDteParam(), com.marketdata.sdk.OptionsResourceTest.chainExpirationFilterDteListTranslatesToCommaSeparatedDteParam()

## Totals

- Version: not assessed
- Coder runs: 4
- Review runs: 0
- Gates run: 4 (4 failed)
- Files changed by the agent: 3
- Turns: 30
- Tokens: 1,409,594
- Estimated cost: US$ 0.53

### Agent runs

- coder · claude-haiku-4-5-20251001, claude-sonnet-5 · 818,123 tokens (29,360 in, 780,066 cached, 8,697 out) · 20 turns · US$ 0.36
- coder · claude-sonnet-5 · 468,068 tokens (5,715 in, 460,036 cached, 2,317 out) · 8 turns · US$ 0.14
- coder · claude-sonnet-5 · 61,189 tokens (1,095 in, 60,071 cached, 23 out) · 1 turns · US$ 0.02
- coder · claude-sonnet-5 · 62,214 tokens (1,027 in, 61,164 cached, 23 out) · 1 turns · US$ 0.02
2 changes: 1 addition & 1 deletion src/main/java/com/marketdata/sdk/OptionsResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ private static void applyExpirationFilter(RequestSpec.Builder b, ExpirationFilte
if (f instanceof ExpirationFilter.OnDate v) {
b.query("expiration", DateTimeFormatter.ISO_LOCAL_DATE.format(v.date()));
} else if (f instanceof ExpirationFilter.Dte v) {
b.query("dte", v.days());
b.query("dte", v.wireValue());
} else if (f instanceof ExpirationFilter.Between v) {
b.query("from", DateTimeFormatter.ISO_LOCAL_DATE.format(v.from()));
b.query("to", DateTimeFormatter.ISO_LOCAL_DATE.format(v.to()));
Expand Down
38 changes: 35 additions & 3 deletions src/main/java/com/marketdata/sdk/options/ExpirationFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.time.LocalDate;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
* Mutually-exclusive expiration filter for {@code /v1/options/chain/}. The chain endpoint's
Expand Down Expand Up @@ -39,12 +41,42 @@ static All all() {
return new All();
}

/** Days-to-expiration filter — wire form {@code ?dte=N}. */
/** Days-to-expiration filter — a single value, wire form {@code ?dte=N}. */
static Dte dte(int days) {
if (days < 0) {
throw new IllegalArgumentException("dte must be non-negative");
}
return new Dte(days);
return new Dte(Integer.toString(days));
}

/**
* Days-to-expiration filter — an inclusive range, wire form {@code ?dte=N-M}. {@code min} must
* not exceed {@code max}.
*/
static Dte dteRange(int min, int max) {
if (min < 0 || max < 0) {
throw new IllegalArgumentException("dte must be non-negative");
}
if (min > max) {
throw new IllegalArgumentException("min must be <= max");
}
return new Dte(max + "-" + min);
}

/**
* Days-to-expiration filter — a comma-separated list, wire form {@code ?dte=N,M,...}. Must not
* be empty.
*/
static Dte dteList(int... days) {
if (days.length == 0) {
throw new IllegalArgumentException("days must not be empty");
}
for (int d : days) {
if (d < 0) {
throw new IllegalArgumentException("dte must be non-negative");
}
}
return new Dte(IntStream.of(days).mapToObj(Integer::toString).collect(Collectors.joining(",")));
}

/**
Expand Down Expand Up @@ -77,7 +109,7 @@ record OnDate(LocalDate date) implements ExpirationFilter {
}
}

record Dte(int days) implements ExpirationFilter {}
record Dte(String wireValue) implements ExpirationFilter {}

record Between(LocalDate from, LocalDate to) implements ExpirationFilter {}

Expand Down
60 changes: 60 additions & 0 deletions src/test/java/com/marketdata/sdk/OptionsResourceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,38 @@ void chainExpirationFilterDteTranslatesToDteParam() {
.isEqualTo("http://localhost/v1/options/chain/AAPL/?dte=30");
}

@Test
void chainExpirationFilterDteRangeTranslatesToDashedDteParam() {
CapturingClient client = okWith(CANNED_CHAIN_BODY);
OptionsResource options = resourceWith(client);

options
.chainAsync(
OptionsChainRequest.builder("AAPL")
.expirationFilter(ExpirationFilter.dteRange(30, 45))
.build())
.join();

assertThat(client.captured.get(0).uri().toString())
.isEqualTo("http://localhost/v1/options/chain/AAPL/?dte=30-45");
}

@Test
void chainExpirationFilterDteListTranslatesToCommaSeparatedDteParam() {
CapturingClient client = okWith(CANNED_CHAIN_BODY);
OptionsResource options = resourceWith(client);

options
.chainAsync(
OptionsChainRequest.builder("AAPL")
.expirationFilter(ExpirationFilter.dteList(30, 45, 60))
.build())
.join();

assertThat(client.captured.get(0).uri().toString())
.isEqualTo("http://localhost/v1/options/chain/AAPL/?dte=30,45,60");
}

@Test
void chainExpirationFilterBetweenTranslatesToFromTo() {
CapturingClient client = okWith(CANNED_CHAIN_BODY);
Expand Down Expand Up @@ -1203,6 +1235,34 @@ void expirationFilterDteRejectsNegative() {
.hasMessageContaining("dte must be non-negative");
}

@Test
void expirationFilterDteRangeRejectsMinGreaterThanMax() {
assertThatThrownBy(() -> ExpirationFilter.dteRange(45, 30))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("min must be <= max");
}

@Test
void expirationFilterDteRangeRejectsNegativeMin() {
assertThatThrownBy(() -> ExpirationFilter.dteRange(-1, 30))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("dte must be non-negative");
}

@Test
void expirationFilterDteListRejectsEmpty() {
assertThatThrownBy(ExpirationFilter::dteList)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("days must not be empty");
}

@Test
void expirationFilterDteListRejectsNegativeElement() {
assertThatThrownBy(() -> ExpirationFilter.dteList(30, -1, 60))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("dte must be non-negative");
}

@Test
void expirationFilterBetweenRejectsReversedDates() {
assertThatThrownBy(
Expand Down
Loading