Skip to content

Commit b3af209

Browse files
committed
feat: add exchanges() and instruments() discovery methods (0.3.0)
- QTSurfer#exchanges() -> List<Exchange> - QTSurfer#instruments(String exchangeId) -> List<InstrumentDetail> - Both wrap ExchangeApi from api-client v0.1.2 and surface failures as QTSError - ExchangesTest covers method signatures and null-guard
1 parent d192906 commit b3af209

5 files changed

Lines changed: 114 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66

77
## [Unreleased]
88

9+
## [0.3.0] — 2026-05-17
10+
11+
### Added
12+
13+
- **Exchange & instrument discovery:**
14+
- `QTSurfer#exchanges()``List<Exchange>` — list all exchanges available on the platform.
15+
- `QTSurfer#instruments(String exchangeId)``List<InstrumentDetail>` — list instruments for a given exchange, including `dataFrom`/`dataTo` availability windows, `lastPrice`, and `volume24h`.
16+
- Both methods wrap `net.qtsurfer.api.client.api.ExchangeApi` (already generated in `api-client v0.1.2`) and surface failures as `QTSError`.
17+
918
## [0.2.0] — 2026-05-01
1019

1120
### Added

README.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ Where `net.qtsurfer:api-client` gives you one method per endpoint, this package
3939
<dependency>
4040
<groupId>com.github.QTSurfer</groupId>
4141
<artifactId>sdk-java</artifactId>
42-
<version>v0.2.0</version>
42+
<version>v0.3.0</version>
4343
</dependency>
4444
```
4545

4646
The transitive `com.github.QTSurfer:api-client-java` and `dev.failsafe:failsafe` come along automatically.
4747

4848
### Maven Central (future)
4949

50-
Once published to Central, the coordinate will be `net.qtsurfer:sdk:0.2.0`.
50+
Once published to Central, the coordinate will be `net.qtsurfer:sdk:0.3.0`.
5151

5252
## Quick start
5353

@@ -149,6 +149,29 @@ try (var in = qts.klines("binance", "BTC", "USDT", "2026-01-15T10", DownloadForm
149149

150150
The caller closes the stream. HTTP errors surface as `QTSDownloadError` (subclass of `QTSError`).
151151

152+
## Exchange & instrument discovery
153+
154+
List available exchanges and the instruments (with data-availability windows) for a given exchange.
155+
156+
```java
157+
import net.qtsurfer.api.client.model.Exchange;
158+
import net.qtsurfer.api.client.model.InstrumentDetail;
159+
160+
// List exchanges
161+
List<Exchange> exchanges = qts.exchanges();
162+
exchanges.forEach(e -> System.out.println(e.getId() + "" + e.getName()));
163+
// → binance — Binance
164+
// → binancefutures — Binance Futures
165+
166+
// List instruments for an exchange
167+
List<InstrumentDetail> instruments = qts.instruments("binance");
168+
instruments.forEach(i -> System.out.printf(
169+
"%s data: %s → %s last: %.2f%n",
170+
i.getId(), i.getDataFrom(), i.getDataTo(), i.getLastPrice()));
171+
```
172+
173+
HTTP errors surface as `QTSError`. Responses reflect live platform state — no client-side cache.
174+
152175
## Error hierarchy
153176

154177
All SDK errors extend `QTSError` (a `RuntimeException`) and surface as the cause of the `CompletionException` wrapping them when the future fails.
@@ -227,10 +250,15 @@ JWT_API_TOKEN=... QTSURFER_API_URL=... QTSURFER_TEST_VERBOSE=1 mvn -B -Dtest='*I
227250
- [x] `Strategy` + `Backtest` handles with `id()`, `state()`, `progress()`, `await()`, `cancel()`
228251
- [x] Progress exposed as `Flow.Publisher<BacktestProgress>` (JDK reactive-streams)
229252
- [x] Hourly tickers/klines downloads (`qts.tickers(...)` / `qts.klines(...)`) with `DownloadFormat` (Lastra/Parquet)
230-
- [ ] TTL cache for `exchanges` / `instruments`
231253

232-
### v0.3 — Ecosystem
254+
### v0.3 — Exchange & instrument discovery ✅
255+
256+
- [x] `qts.exchanges()``List<Exchange>` (live, no cache)
257+
- [x] `qts.instruments(exchangeId)``List<InstrumentDetail>` with data-availability windows, last price, and 24 h volume
233258

259+
### v0.4 — Ecosystem
260+
261+
- [ ] TTL cache for `exchanges` / `instruments`
234262
- [ ] Loaders for `signalsUrl` Parquet into `duckdb-java` / `lastra-java`
235263
- [ ] Optional reactive adapters (Reactor / RxJava)
236264

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>net.qtsurfer</groupId>
88
<artifactId>sdk</artifactId>
9-
<version>0.2.0</version>
9+
<version>0.3.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>QTSurfer SDK</name>

src/main/java/net/qtsurfer/api/sdk/QTSurfer.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package net.qtsurfer.api.sdk;
22

33
import net.qtsurfer.api.client.api.BacktestingApi;
4+
import net.qtsurfer.api.client.api.ExchangeApi;
45
import net.qtsurfer.api.client.binary.ExchangeBinaryDownloads;
56
import net.qtsurfer.api.client.invoker.ApiClient;
67
import net.qtsurfer.api.client.invoker.ApiException;
8+
import net.qtsurfer.api.client.model.Exchange;
9+
import net.qtsurfer.api.client.model.InstrumentDetail;
710
import net.qtsurfer.api.client.model.ResultMap;
811
import net.qtsurfer.api.sdk.errors.QTSDownloadError;
12+
import net.qtsurfer.api.sdk.errors.QTSError;
913
import net.qtsurfer.api.sdk.internal.HttpStrategyCompileClient;
1014
import net.qtsurfer.api.sdk.workflows.BacktestWorkflow;
1115

1216
import java.io.InputStream;
17+
import java.util.List;
1318
import java.util.Objects;
1419
import java.util.concurrent.CompletableFuture;
1520
import java.util.concurrent.ExecutorService;
@@ -40,11 +45,14 @@ public final class QTSurfer {
4045
private final QTSurferOptions options;
4146
private final BacktestWorkflow backtestWorkflow;
4247
private final ExchangeBinaryDownloads downloads;
48+
private final ExchangeApi exchangeApi;
4349

44-
private QTSurfer(QTSurferOptions options, BacktestWorkflow backtestWorkflow, ExchangeBinaryDownloads downloads) {
50+
private QTSurfer(QTSurferOptions options, BacktestWorkflow backtestWorkflow,
51+
ExchangeBinaryDownloads downloads, ExchangeApi exchangeApi) {
4552
this.options = options;
4653
this.backtestWorkflow = backtestWorkflow;
4754
this.downloads = downloads;
55+
this.exchangeApi = exchangeApi;
4856
}
4957

5058
public QTSurferOptions options() { return options; }
@@ -84,6 +92,35 @@ public CompletableFuture<ResultMap> backtest(BacktestRequest request, BacktestOp
8492
return backtestWorkflow.runFull(request, options);
8593
}
8694

95+
/**
96+
* List available exchanges on the platform.
97+
*
98+
* @throws QTSError on HTTP 4xx/5xx or transport failure
99+
*/
100+
public List<Exchange> exchanges() {
101+
try {
102+
return exchangeApi.getExchanges();
103+
} catch (ApiException e) {
104+
throw new QTSError("exchanges call failed: " + describe(e), e);
105+
}
106+
}
107+
108+
/**
109+
* List instruments available on the given exchange, including data availability
110+
* and market info.
111+
*
112+
* @param exchangeId exchange identifier (e.g. {@code "binance"})
113+
* @throws QTSError on HTTP 4xx/5xx or transport failure
114+
*/
115+
public List<InstrumentDetail> instruments(String exchangeId) {
116+
Objects.requireNonNull(exchangeId, "exchangeId");
117+
try {
118+
return exchangeApi.getInstruments(exchangeId);
119+
} catch (ApiException e) {
120+
throw new QTSError("instruments call failed: " + describe(e), e);
121+
}
122+
}
123+
87124
/**
88125
* Download one hour of raw tickers for an instrument as a streaming
89126
* {@link InputStream}. Defaults to {@link DownloadFormat#LASTRA}; pass
@@ -159,7 +196,8 @@ public QTSurfer build() {
159196
ExecutorService exec = opts.executor() != null ? opts.executor() : ForkJoinPool.commonPool();
160197
BacktestWorkflow workflow = new BacktestWorkflow(
161198
new HttpStrategyCompileClient(apiClient), backtestingApi, exec);
162-
return new QTSurfer(opts, workflow, new ExchangeBinaryDownloads(apiClient));
199+
return new QTSurfer(opts, workflow, new ExchangeBinaryDownloads(apiClient),
200+
new ExchangeApi(apiClient));
163201
}
164202
}
165203
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package net.qtsurfer.api.sdk;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.List;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import static org.junit.jupiter.api.Assertions.assertThrows;
9+
10+
class ExchangesTest {
11+
12+
@Test
13+
void exchangesMethodReturnsListType() throws NoSuchMethodException {
14+
var method = QTSurfer.class.getMethod("exchanges");
15+
assertEquals(List.class, method.getReturnType());
16+
}
17+
18+
@Test
19+
void instrumentsMethodReturnsListType() throws NoSuchMethodException {
20+
var method = QTSurfer.class.getMethod("instruments", String.class);
21+
assertEquals(List.class, method.getReturnType());
22+
}
23+
24+
@Test
25+
void instrumentsRejectsNullExchangeId() {
26+
QTSurfer qts = QTSurfer.builder()
27+
.baseUrl("https://api.qtsurfer.net/v1")
28+
.token("test-token")
29+
.build();
30+
assertThrows(NullPointerException.class, () -> qts.instruments(null));
31+
}
32+
}

0 commit comments

Comments
 (0)