Skip to content

Commit 67d94cc

Browse files
Merge pull request #10 from MarketDataApp/10_options_resource
Add the `options` resource
2 parents ed36372 + 90ed9de commit 67d94cc

65 files changed

Lines changed: 6065 additions & 455 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
down the call stack.
2727

2828
### Added
29+
- **Options resource** (`client.options()`) — all six endpoints, each in sync +
30+
async form: `lookup`, `expirations`, `strikes`, `quote` (single contract),
31+
`quotes` (multi-contract fan-out returning
32+
`Map<String, Response<OptionsQuotes>>`), and `chain`. Every endpoint takes a
33+
Builder-based per-endpoint request object (no `String` convenience overloads).
34+
The `chain` request models its mutually-exclusive expiration and strike groups
35+
as sealed types (`ExpirationFilter`, `StrikeFilter`) so the exclusivity is
36+
compiler-enforced. Covers the `rho` greek (decoded as an optional, nullable
37+
column — absent on some feeds), the `expiration=all` filter (the full chain
38+
vs. the default front-month), and the `countback` historical-window parameter
39+
(validated: positive, and mutually exclusive with `date`/`from`).
2940
- Project scaffold per ADRs 001–007: Gradle Kotlin DSL build, JDK 17 toolchain,
3041
`integrationTest` source set, Spotless + JaCoCo, Vanniktech Maven Publish.
3142
- `MarketDataClient` skeleton with two public constructors — a no-arg one

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,17 @@ demo-retry: ## Retry, Retry-After, preflight (needs mock-server)
8080
cd $(CONSUMER_DIR) && ./gradlew runRetry
8181

8282
.PHONY: demo-response
83-
demo-response: ## Response<T> surface features (needs mock-server)
83+
demo-response: ## MarketDataResponse surface features (needs mock-server)
8484
cd $(CONSUMER_DIR) && ./gradlew runResponse
8585

8686
.PHONY: demo-concurrency
8787
demo-concurrency: ## 50-permit semaphore (needs mock-server)
8888
cd $(CONSUMER_DIR) && ./gradlew runConcurrency
8989

90+
.PHONY: demo-options
91+
demo-options: ## Full options surface: every endpoint + all params, CSV facet, columns, Option A (needs mock-server)
92+
cd $(CONSUMER_DIR) && ./gradlew runOptions
93+
9094
.PHONY: demos-all
9195
demos-all: ## Run every mock-server-based demo back-to-back (needs mock-server)
92-
cd $(CONSUMER_DIR) && ./gradlew runDemoConfig runExceptions runRetry runResponse runConcurrency
96+
cd $(CONSUMER_DIR) && ./gradlew runDemoConfig runExceptions runRetry runResponse runConcurrency runOptions

README.md

Lines changed: 125 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Market Data Java SDK
22

3-
Java SDK for the [Market Data API](https://www.marketdata.app/). **Pre-release
4-
scaffold** — endpoints are not yet implemented; this iteration sets up the
5-
build, package layout, configuration cascade, exception taxonomy, and
6-
Kotlin-interop foundations.
3+
Java SDK for the [Market Data API](https://www.marketdata.app/). **Pre-release**
4+
— the `utilities` and `options` resources are implemented; `stocks`, `funds`,
5+
and `markets` are forthcoming. The build, package layout, configuration cascade,
6+
exception taxonomy, and Kotlin-interop foundations are in place.
77

88
## Requirements
99

@@ -32,18 +32,123 @@ common path is two lines:
3232

3333
```java
3434
try (var client = new MarketDataClient()) {
35-
// endpoint methods land in subsequent iterations
35+
var resp = client.options().expirations(OptionsExpirationsRequest.of("AAPL"));
36+
System.out.println(resp.values()); // values() is the typed payload (a List<ZonedDateTime>)
3637
}
3738
```
3839

3940
### Kotlin
4041

4142
```kotlin
4243
MarketDataClient().use { client ->
43-
// endpoint methods land in subsequent iterations
44+
val resp = client.options().expirations(OptionsExpirationsRequest.of("AAPL"))
45+
println(resp.values()) // List<ZonedDateTime>
4446
}
4547
```
4648

49+
Every response implements `MarketDataResponse<T>`: `values()` returns the typed payload
50+
(typed per endpoint — a `List`, a scalar `String`, …), and the same metadata accessors
51+
(`statusCode()`, `isNoData()`, `requestId()`, `json()`, `saveToFile(path)`) are available on
52+
every response, on every resource.
53+
54+
## Options
55+
56+
Reached via `client.options()`. Every endpoint has a synchronous method and an
57+
`…Async` variant returning `CompletableFuture`, and takes a Builder-based
58+
request object — there are no `String` convenience overloads, so the call shape
59+
is uniform across the SDK regardless of how many parameters an endpoint has.
60+
61+
| Method | Purpose |
62+
|--------|---------|
63+
| `lookup` | Resolve a human description (`"AAPL 1/16/2026 $200 Call"`) to an OCC symbol |
64+
| `expirations` | Expiration dates for an underlying |
65+
| `strikes` | Strike ladder per expiration |
66+
| `quote` | Quote for a single OCC option symbol |
67+
| `quotes` | Quotes for many symbols — fans out concurrently, returns a per-symbol map |
68+
| `chain` | Full option chain with the rich filter surface |
69+
70+
### Chain with filters
71+
72+
The `chain` request exposes the API's full filter set. Mutually-exclusive groups
73+
(expiration, strike) are modeled as sealed types, so the compiler lets you pick
74+
only one variant per group:
75+
76+
#### Java
77+
78+
```java
79+
try (var client = new MarketDataClient()) {
80+
var resp = client.options().chain(
81+
OptionsChainRequest.builder("AAPL")
82+
.expirationFilter(ExpirationFilter.all()) // every expiration, not just front-month
83+
.strikeFilter(StrikeFilter.range(150, 200)) // 150 <= strike <= 200
84+
.side(OptionSide.CALL)
85+
.strikeLimit(5)
86+
.build());
87+
88+
for (OptionQuote q : resp.values()) { // values() is a List<OptionQuote>
89+
System.out.printf("%s delta=%s rho=%s%n",
90+
q.optionSymbol(), q.delta(), q.rho()); // delta/rho are @Nullable Double
91+
}
92+
}
93+
```
94+
95+
#### Kotlin
96+
97+
```kotlin
98+
MarketDataClient().use { client ->
99+
val resp = client.options().chain(
100+
OptionsChainRequest.builder("AAPL")
101+
.expirationFilter(ExpirationFilter.all())
102+
.strikeFilter(StrikeFilter.range(150.0, 200.0))
103+
.side(OptionSide.CALL)
104+
.strikeLimit(5)
105+
.build()
106+
)
107+
resp.values().forEach { q ->
108+
println("${q.optionSymbol} delta=${q.delta} rho=${q.rho}") // delta/rho are nullable
109+
}
110+
}
111+
```
112+
113+
### Multiple quotes
114+
115+
`quotes` fans out one request per symbol concurrently and returns a
116+
`Map<String, OptionsQuotesResponse>` keyed by the input symbol, so per-symbol
117+
status and errors stay observable. `countback` caps the historical series to the
118+
N most recent rows before `to`:
119+
120+
```java
121+
Map<String, OptionsQuotesResponse> bySymbol = client.options().quotes(
122+
OptionsQuotesRequest.builder("AAPL250117C00150000", "AAPL250117P00150000")
123+
.to(LocalDate.now())
124+
.countback(5) // at most 5 EOD rows per symbol, before `to`
125+
.build());
126+
127+
bySymbol.forEach((sym, resp) -> System.out.println(sym + "" + resp.values().size() + " rows"));
128+
```
129+
130+
### Universal parameters & CSV
131+
132+
Universal parameters are set fluently on the resource (an immutable configured value, so you
133+
can "configure once, call many"); `columns` projects the response to a subset of fields, and
134+
`asCsv()` selects a CSV view of any endpoint:
135+
136+
```java
137+
// type-preserving universal params + column projection (typed):
138+
var chain = client.options()
139+
.dateFormat(DateFormat.TIMESTAMP).mode(Mode.DELAYED).limit(50)
140+
.columns("optionSymbol", "strike", "delta") // fields you don't request come back null
141+
.chain(OptionsChainRequest.of("AAPL"));
142+
143+
// CSV facet (adds human/headers, which only make sense for CSV):
144+
CsvResponse csv = client.options().asCsv().columns("optionSymbol", "strike").chain(req);
145+
csv.saveToFile(Path.of("aapl-chain.csv"));
146+
```
147+
148+
With `columns`, a field you didn't request decodes to `null` (no error); a **required** field
149+
you *did* request (or didn't project away) that the API omits raises a `ParseError` — so a
150+
`null` never silently hides a dropped field.
151+
47152
## Configuration
48153

49154
Values are resolved through this cascade (highest priority first):
@@ -70,9 +175,11 @@ Values are resolved through this cascade (highest priority first):
70175
| `MARKETDATA_USE_HUMAN_READABLE` | Human-readable field names | `false` |
71176
| `MARKETDATA_MODE` | Data mode (live/cached/delayed) | `live` |
72177

73-
Endpoint-shape variables (`OUTPUT_FORMAT`, `DATE_FORMAT`, `COLUMNS`,
74-
`ADD_HEADERS`, `USE_HUMAN_READABLE`, `MODE`) are reserved here and will be
75-
honored when the request layer lands.
178+
The corresponding per-call setters — `dateFormat`/`limit`/`offset`/`mode`/`columns` on the
179+
resource, plus `human`/`headers` and `asCsv()` on the CSV facet — are exposed on `options`
180+
today (and on every resource as it lands). Auto-applying these env-var values as request
181+
*defaults* (`DATE_FORMAT`, `COLUMNS`, `ADD_HEADERS`, `USE_HUMAN_READABLE`, `MODE`,
182+
`OUTPUT_FORMAT`) is still reserved.
76183

77184
### Demo mode
78185

@@ -136,9 +243,15 @@ isn't an exact match is rejected before any request is made.
136243
## Package layout
137244

138245
```
139-
com.marketdata.sdk # MarketDataClient + RateLimits (public);
140-
# Configuration, EnvVars, Tokens, Version
141-
# are package-private and not part of the API
246+
com.marketdata.sdk # MarketDataClient, RateLimits, the resource façades
247+
# (UtilitiesResource, OptionsResource, OptionsCsvResource),
248+
# and MarketDataResponse<T> + the named response types
249+
# (OptionsChainResponse, CsvResponse, …) — public;
250+
# Configuration, EnvVars, Tokens, Version are
251+
# package-private and not part of the API
252+
com.marketdata.sdk.options # Options request builders + row records
253+
# (OptionsChainRequest, OptionQuote, sealed
254+
# ExpirationFilter / StrikeFilter, Greek, …)
142255
com.marketdata.sdk.exception # Sealed MarketDataException hierarchy + ErrorContext
143256
```
144257

0 commit comments

Comments
 (0)