Skip to content
Open
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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ mvn -pl library package # build only the library module
mvn -pl examples package # build only the examples module
```

There are no tests in this project. CI runs `mvn clean package` on PRs targeting `master`.
The library's JUnit 5 tests use JSON fixtures and Mockito mocks of `KrakenRestRequester`, without network access or API keys. Run them with `mvn -pl library test`. CI runs `mvn clean package` on PRs targeting `master`.

Java 25 with Temurin is required (configured via `maven-compiler-plugin` with `<release>25</release>`).

## Layout

`KrakenAPI` is the entry point: typed methods for implemented endpoints, generic `query()` methods taking a `Public`/`Private` enum value and returning a `JsonNode`, and raw `queryPublic()`/`queryPrivate()` taking a path string.

Every endpoint extends `Endpoint<T>`, either `PublicEndpoint<T>` (GET on `/0/public/{path}`, parameters from `QueryParams`) or `PrivateEndpoint<T>` (POST on `/0/private/{path}`, parameters from `PostParams`, signed with a nonce-based HMAC). Concrete endpoints live in a domain package under `endpoint/` — `market/` for public market data, `account/` for private account data, `subaccount/` for subaccount management, `transparency/` for public pre- and post-trade data, `earn/` for earn strategies and allocations — and follow `{Name}Endpoint`, `params/{Name}Params`, `response/{ResponseType}`.
Every endpoint extends `Endpoint<T>`, either `PublicEndpoint<T>` (GET on `/0/public/{path}`, parameters from `QueryParams`) or `PrivateEndpoint<T>` (POST on `/0/private/{path}`, parameters from `PostParams`, signed with a nonce-based HMAC). Concrete endpoints live in a domain package under `endpoint/` — `market/` for market data, including authenticated Level3, `account/` for private account data, `subaccount/` for subaccount management, `transparency/` for public pre- and post-trade data, `earn/` for earn strategies and allocations — and follow `{Name}Endpoint`, `params/{Name}Params`, `response/{ResponseType}`.

`KrakenRestRequester` performs the HTTP calls and can be swapped for another HTTP client. Responses are unwrapped from the Kraken `{error, result}` envelope by `KrakenResponse<T>`; ZIP responses (report exports) go through `Endpoint.processZipResponse()`.

Expand Down
71 changes: 63 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,64 @@ JsonNode trades = api.queryPublic("Trades", Map.of("pair", "XBTUSD", "count", "1
// {"XXBTZUSD":[["68515.60000","0.00029628",1.7100231295628998E9,"s","m","",68007835]], …
```

### Market Data

All 12 Market Data endpoints in Kraken's current Spot REST specification have typed methods. In addition to `serverTime`, `systemStatus`, `assetInfo`, `assetPairs`, and `ticker`:

| Endpoint | Typed method | Response |
|---|---|---|
| `OHLC` | `ohlc(pair)` / `ohlc(params)` | `OhlcData`: candles by pair and a `last` cursor |
| `Depth` | `orderBook(pair)` / `orderBook(params)` | `Map<String, OrderBook>` |
| `GroupedBook` | `groupedOrderBook(pair)` / `groupedOrderBook(params)` | `GroupedOrderBook` |
| `Trades` | `recentTrades(pair)` / `recentTrades(params)` | `RecentTrades`: trades by pair and a `last` cursor |
| `Spread` | `recentSpreads(pair)` / `recentSpreads(params)` | `RecentSpreads`: spreads by pair and a `last` cursor |
| `Level3` (private) | `level3OrderBook(pair)` / `level3OrderBook(params)` | `Level3OrderBook` |
| `MaintenanceSchedule` | `maintenanceSchedule()` | `MaintenanceSchedule` |

Use parameter builders to set optional fields; omitted fields retain Kraken's defaults:

```java
OhlcData candles = api.ohlc(OhlcParams.builder()
.pair("BTC/USD").interval(60).assetVersion(1).build());
List<OhlcData.Candle> hourly = candles.candles().get("BTC/USD");

RecentTrades trades = api.recentTrades(RecentTradesParams.builder()
.pair("BTC/USD").count(10).build());
RecentTrades nextBatch = api.recentTrades(RecentTradesParams.builder()
.pair("BTC/USD").since(trades.last()).count(10).build());
```

`OHLC`, `Depth`, `Trades`, and `Spread` accept `assetVersion(1)` for display pair keys such as `BTC/USD`; without it, Kraken returns internal keys such as `XXBTZUSD`. Their `assetClass("tokenized_asset")` option supports xStocks. Response maps preserve the keys Kraken returns.

The required `pair` field selects one asset pair. Optional numeric fields accept the following values; omit them to use Kraken's defaults:

| Endpoint | Option | Values | Default |
|---|---|---|---|
| `OHLC` | `interval` | 1, 5, 15, 30, 60, 240, 1440, 10080, 21600 minutes | 1 |
| `Depth` | `count` | 1–500 entries per side | 100 |
| `Trades` | `count` | 1–1000 trades | 1000 |
| `GroupedBook` | `depth` | 10, 25, 100, 250, 1000 levels per side | 10 |
| `GroupedBook` | `grouping` | 1, 5, 10, 25, 50, 100, 250, 500, 1000 ticks per level | 1 |
| `Level3` | `depth` | 0 (full book), 10, 25, 100, 250, 1000 levels per side | 100 |

OHLC candle times, L2 level times and spread times use `Instant`; the `since` fields for OHLC and spreads remain Unix seconds. Grouped books round asks up and bids down to the nearest grouped price level. `MaintenanceSchedule` returns scheduled events for the next seven days, ordered by expected start time; its times use `Instant`, and `cancelBefore` can be absent.

OHLC includes a final candle that is still forming and retains at most 720 entries. Reuse its `last()` cursor as `since` to poll for committed updates. Trade cursors are opaque strings: pass `last()` unchanged. Prices and quantities use `BigDecimal`; trade and Level3 times use `Instant`, retaining nanosecond precision. Level3 decodes Kraken's integer epoch nanoseconds explicitly.

Level3 requires credentials with **Orders and trades – Query open orders & trades** permission:

```java
KrakenAPI authenticated = new KrakenAPI("my key", "my secret");
Level3OrderBook book = authenticated.level3OrderBook(Level3OrderBookParams.builder()
.pair("YFI/EUR").depth(10).build());
```

Run the public examples without credentials (after `mvn clean install`):

```sh
mvn -pl examples exec:java -Dexec.mainClass=dev.andstuff.kraken.example.MarketDataExample
```

### Private endpoints

Private endpoints can be queried in the same way as the public ones, but an API key and secret must be provided to the `KrakenAPI` instance:
Expand All @@ -105,22 +163,19 @@ JsonNode order = api.query(KrakenAPI.Private.ADD_ORDER, Map.of(

### Custom endpoints

An endpoint the library doesn't implement can also be given a proper type, instead of falling back to `JsonNode`. Extend `PublicEndpoint<T>`, or `PrivateEndpoint<T>` for a private one, and pass your endpoint to `query`:
You can also define typed endpoints outside the library. The following example demonstrates the same mechanism used by the built-in order book endpoint. Extend `PublicEndpoint<T>`, or `PrivateEndpoint<T>` for a private one, and pass your endpoint to `query`:

```java
public class TradesEndpoint extends PublicEndpoint<Map<String, List<Trade>>> {
public class MyOrderBookEndpoint extends PublicEndpoint<Map<String, OrderBook>> {

public TradesEndpoint(String pair) {
super("Trades", () -> Map.of("pair", pair), new TypeReference<>() {});
public MyOrderBookEndpoint(String pair) {
super("Depth", () -> Map.of("pair", pair), new TypeReference<>() {});
}
}

record Trade(BigDecimal price, BigDecimal volume) {}

KrakenAPI api = new KrakenAPI();

Map<String, List<Trade>> trades = api.query(new TradesEndpoint("XBTUSD"));
// {XXBTZUSD=[Trade[price=68515.60000, volume=0.00029628]], …
Map<String, OrderBook> books = api.query(new MyOrderBookEndpoint("XBTUSD"));
```

The endpoint is run through the same `KrakenRestRequester` as the built-in ones, and a `PrivateEndpoint` is signed with the credentials and nonce generator the `KrakenAPI` instance was built with. Querying one on an instance built without credentials throws an `IllegalStateException`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.andstuff.kraken.example;

import dev.andstuff.kraken.api.KrakenAPI;
import dev.andstuff.kraken.api.endpoint.market.params.GroupedOrderBookParams;
import dev.andstuff.kraken.api.endpoint.market.params.OhlcParams;
import dev.andstuff.kraken.api.endpoint.market.params.OrderBookParams;
import dev.andstuff.kraken.api.endpoint.market.params.RecentTradesParams;
import dev.andstuff.kraken.api.endpoint.market.response.OhlcData;
import dev.andstuff.kraken.api.endpoint.market.response.RecentTrades;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MarketDataExample {

static void main() {
KrakenAPI api = new KrakenAPI();
String pair = "BTC/USD";

OhlcData candles = api.ohlc(OhlcParams.builder().pair(pair).interval(60).assetVersion(1).build());
log.info("Hourly candles: {}", candles.candles().get(pair));

log.info("L2 order book: {}", api.orderBook(OrderBookParams.builder().pair(pair).count(10).build()));
log.info("Grouped book: {}", api.groupedOrderBook(GroupedOrderBookParams.builder()
.pair(pair).depth(10).grouping(1000).build()));

RecentTrades trades = api.recentTrades(RecentTradesParams.builder().pair(pair).count(2).build());
log.info("Recent trades: {}", trades.trades());
log.info("Next trade cursor: {}", trades.last());

log.info("Recent spreads: {}", api.recentSpreads(pair));
log.info("Upcoming maintenance: {}", api.maintenanceSchedule());
}
}
18 changes: 18 additions & 0 deletions library/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,22 @@
<artifactId>kraken-api</artifactId>
<name>Java Kraken API Client</name>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
165 changes: 165 additions & 0 deletions library/src/main/java/dev/andstuff/kraken/api/KrakenAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,32 @@
import dev.andstuff.kraken.api.endpoint.earn.response.EarnStrategies;
import dev.andstuff.kraken.api.endpoint.market.AssetInfoEndpoint;
import dev.andstuff.kraken.api.endpoint.market.AssetPairEndpoint;
import dev.andstuff.kraken.api.endpoint.market.GroupedOrderBookEndpoint;
import dev.andstuff.kraken.api.endpoint.market.Level3OrderBookEndpoint;
import dev.andstuff.kraken.api.endpoint.market.MaintenanceScheduleEndpoint;
import dev.andstuff.kraken.api.endpoint.market.OhlcEndpoint;
import dev.andstuff.kraken.api.endpoint.market.OrderBookEndpoint;
import dev.andstuff.kraken.api.endpoint.market.RecentSpreadsEndpoint;
import dev.andstuff.kraken.api.endpoint.market.RecentTradesEndpoint;
import dev.andstuff.kraken.api.endpoint.market.ServerTimeEndpoint;
import dev.andstuff.kraken.api.endpoint.market.SystemStatusEndpoint;
import dev.andstuff.kraken.api.endpoint.market.TickerEndpoint;
import dev.andstuff.kraken.api.endpoint.market.params.AssetPairParams;
import dev.andstuff.kraken.api.endpoint.market.params.GroupedOrderBookParams;
import dev.andstuff.kraken.api.endpoint.market.params.Level3OrderBookParams;
import dev.andstuff.kraken.api.endpoint.market.params.OhlcParams;
import dev.andstuff.kraken.api.endpoint.market.params.OrderBookParams;
import dev.andstuff.kraken.api.endpoint.market.params.RecentSpreadsParams;
import dev.andstuff.kraken.api.endpoint.market.params.RecentTradesParams;
import dev.andstuff.kraken.api.endpoint.market.response.AssetInfo;
import dev.andstuff.kraken.api.endpoint.market.response.AssetPairs;
import dev.andstuff.kraken.api.endpoint.market.response.GroupedOrderBook;
import dev.andstuff.kraken.api.endpoint.market.response.Level3OrderBook;
import dev.andstuff.kraken.api.endpoint.market.response.MaintenanceSchedule;
import dev.andstuff.kraken.api.endpoint.market.response.OhlcData;
import dev.andstuff.kraken.api.endpoint.market.response.OrderBook;
import dev.andstuff.kraken.api.endpoint.market.response.RecentSpreads;
import dev.andstuff.kraken.api.endpoint.market.response.RecentTrades;
import dev.andstuff.kraken.api.endpoint.market.response.ServerTime;
import dev.andstuff.kraken.api.endpoint.market.response.SystemStatus;
import dev.andstuff.kraken.api.endpoint.market.response.Ticker;
Expand Down Expand Up @@ -242,6 +262,126 @@ public Map<String, Ticker> ticker(List<String> pairs) {
return query(new TickerEndpoint(pairs));
}

/**
* Queries the {@code OHLC} endpoint using Kraken's default options.
*
* @param pair the asset pair to query, e.g. {@code BTC/USD}
* @return the candles by returned pair name and the cursor for committed updates
* @throws KrakenException if Kraken returns an error
*/
public OhlcData ohlc(String pair) {
return query(new OhlcEndpoint(pair));
}

/**
* Queries the {@code OHLC} endpoint.
*
* @param params the request parameters
* @return the candles by returned pair name and the cursor for committed updates
* @throws KrakenException if Kraken returns an error
*/
public OhlcData ohlc(OhlcParams params) {
return query(new OhlcEndpoint(params));
}

/**
* Queries the {@code Depth} endpoint using Kraken's default options.
*
* @param pair the asset pair to query, e.g. {@code BTC/USD}
* @return the L2 order books by returned pair name
* @throws KrakenException if Kraken returns an error
*/
public Map<String, OrderBook> orderBook(String pair) {
return query(new OrderBookEndpoint(pair));
}

/**
* Queries the {@code Depth} endpoint.
*
* @param params the request parameters
* @return the L2 order books by returned pair name
* @throws KrakenException if Kraken returns an error
*/
public Map<String, OrderBook> orderBook(OrderBookParams params) {
return query(new OrderBookEndpoint(params));
}

/**
* Queries the {@code Trades} endpoint using Kraken's default options.
*
* @param pair the asset pair to query, e.g. {@code BTC/USD}
* @return the trades by returned pair name and the next polling cursor
* @throws KrakenException if Kraken returns an error
*/
public RecentTrades recentTrades(String pair) {
return query(new RecentTradesEndpoint(pair));
}

/**
* Queries the {@code Trades} endpoint.
*
* @param params the request parameters
* @return the trades by returned pair name and the next polling cursor
* @throws KrakenException if Kraken returns an error
*/
public RecentTrades recentTrades(RecentTradesParams params) {
return query(new RecentTradesEndpoint(params));
}

/**
* Queries the {@code Spread} endpoint using Kraken's default options.
*
* @param pair the asset pair to query, e.g. {@code BTC/USD}
* @return the spreads by returned pair name and the next polling cursor
* @throws KrakenException if Kraken returns an error
*/
public RecentSpreads recentSpreads(String pair) {
return query(new RecentSpreadsEndpoint(pair));
}

/**
* Queries the {@code Spread} endpoint.
*
* @param params the request parameters
* @return the spreads by returned pair name and the next polling cursor
* @throws KrakenException if Kraken returns an error
*/
public RecentSpreads recentSpreads(RecentSpreadsParams params) {
return query(new RecentSpreadsEndpoint(params));
}

/**
* Queries the {@code GroupedBook} endpoint using Kraken's default options.
*
* @param pair the asset pair to query, e.g. {@code BTC/USD}
* @return the grouped bids and asks, pair and grouping value
* @throws KrakenException if Kraken returns an error
*/
public GroupedOrderBook groupedOrderBook(String pair) {
return query(new GroupedOrderBookEndpoint(pair));
}

/**
* Queries the {@code GroupedBook} endpoint.
*
* @param params the request parameters
* @return the grouped bids and asks, pair and grouping value
* @throws KrakenException if Kraken returns an error
*/
public GroupedOrderBook groupedOrderBook(GroupedOrderBookParams params) {
return query(new GroupedOrderBookEndpoint(params));
}

/**
* Queries the {@code MaintenanceSchedule} endpoint for scheduled events in the next seven days.
*
* @return the maintenance schedule
* @throws KrakenException if Kraken returns an error
*/
public MaintenanceSchedule maintenanceSchedule() {
return query(new MaintenanceScheduleEndpoint());
}

/**
* Queries the {@code PreTrade} endpoint, returning the aggregated order book of a currency pair, with at most ten price levels on each side.
*
Expand Down Expand Up @@ -277,6 +417,30 @@ public PostTrade postTrade(PostTradeParams params) {

/* Implemented private endpoints */

/**
* Queries the {@code Level3} endpoint using Kraken's default options. Requires the Orders and trades - Query open orders &amp; trades API key permission.
*
* @param pair the asset pair to query, e.g. {@code BTC/USD}
* @return the individual bid and ask orders with IDs and nanosecond timestamps
* @throws KrakenException if Kraken returns an error
* @throws IllegalStateException if credentials are missing
*/
public Level3OrderBook level3OrderBook(String pair) {
return query(new Level3OrderBookEndpoint(pair));
}

/**
* Queries the {@code Level3} endpoint. Requires the Orders and trades - Query open orders &amp; trades API key permission.
*
* @param params the request parameters
* @return the individual bid and ask orders with IDs and nanosecond timestamps
* @throws KrakenException if Kraken returns an error
* @throws IllegalStateException if credentials are missing
*/
public Level3OrderBook level3OrderBook(Level3OrderBookParams params) {
return query(new Level3OrderBookEndpoint(params));
}

/**
* Queries the private {@code Ledgers} endpoint, returning at most 50 ledger entries per call.
*
Expand Down Expand Up @@ -597,6 +761,7 @@ public enum Public {
ASSET_PAIRS("AssetPairs"),
DEPTH("Depth"),
GROUPED_BOOK("GroupedBook"),
MAINTENANCE_SCHEDULE("MaintenanceSchedule"),
OHLC("OHLC"),
POST_TRADE("PostTrade"),
PRE_TRADE("PreTrade"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dev.andstuff.kraken.api.endpoint.market;

import com.fasterxml.jackson.core.type.TypeReference;

import dev.andstuff.kraken.api.endpoint.market.params.GroupedOrderBookParams;
import dev.andstuff.kraken.api.endpoint.market.response.GroupedOrderBook;
import dev.andstuff.kraken.api.endpoint.pub.PublicEndpoint;

/**
* The public {@code GroupedBook} endpoint, returning bid and ask quantities aggregated over grouped price levels.
*/
public class GroupedOrderBookEndpoint extends PublicEndpoint<GroupedOrderBook> {

/**
* Creates the {@code GroupedBook} endpoint using Kraken's default options.
*
* @param pair the asset pair to query
*/
public GroupedOrderBookEndpoint(String pair) {
this(GroupedOrderBookParams.builder().pair(pair).build());
}

/**
* Creates the {@code GroupedBook} endpoint.
*
* @param params the request parameters
*/
public GroupedOrderBookEndpoint(GroupedOrderBookParams params) {
super("GroupedBook", params, new TypeReference<>() {});
}
}
Loading