|
1 | 1 | package com.qtsurfer.api.sdk.integration; |
2 | 2 |
|
| 3 | +import com.qtsurfer.api.client.model.CoverageWindow; |
| 4 | +import com.qtsurfer.api.client.model.InstrumentCoverage; |
| 5 | +import com.qtsurfer.api.client.model.InstrumentDetail; |
3 | 6 | import com.qtsurfer.api.client.model.ResultMap; |
4 | 7 | import com.qtsurfer.api.sdk.BacktestOptions; |
5 | 8 | import com.qtsurfer.api.sdk.BacktestRequest; |
|
14 | 17 | import java.io.InputStream; |
15 | 18 | import java.nio.charset.StandardCharsets; |
16 | 19 | import java.time.Duration; |
17 | | -import java.time.Instant; |
18 | | -import java.time.temporal.ChronoUnit; |
| 20 | +import java.time.OffsetDateTime; |
19 | 21 | import java.util.Objects; |
20 | 22 | import java.util.Set; |
21 | 23 | import java.util.concurrent.ConcurrentSkipListSet; |
@@ -55,12 +57,26 @@ void completesCompilePrepareExecuteAgainstBinanceBtcUsdt() throws Exception { |
55 | 57 | .token(token) |
56 | 58 | .build(); |
57 | 59 |
|
| 60 | + // Honor the instrument's real data availability from the instruments API |
| 61 | + // instead of presupposing a fixed calendar day — the backend may hold only |
| 62 | + // a short recent window. Prefer ticker coverage (this is a TICKER backtest), |
| 63 | + // fall back to klines, and cap the span at 24h. |
| 64 | + CoverageWindow window = availableWindow(qts, "binance", "BTC/USDT"); |
| 65 | + OffsetDateTime to = window.getTo(); |
| 66 | + OffsetDateTime from = window.getFrom(); |
| 67 | + if (Duration.between(from, to).compareTo(Duration.ofHours(24)) > 0) { |
| 68 | + from = to.minus(Duration.ofHours(24)); |
| 69 | + } |
| 70 | + if (VERBOSE) { |
| 71 | + log.info("Backtest window (instrument coverage, capped at 24h): {} → {}", from, to); |
| 72 | + } |
| 73 | + |
58 | 74 | BacktestRequest req = BacktestRequest.builder() |
59 | 75 | .strategy(loadFixture("fixtures/ForcedTradeStrategy.java")) |
60 | 76 | .exchangeId("binance") |
61 | 77 | .instrument("BTC/USDT") |
62 | | - .from(dayStartIso(1)) |
63 | | - .to(dayStartIso(0)) |
| 78 | + .from(from.toInstant().toString()) |
| 79 | + .to(to.toInstant().toString()) |
64 | 80 | .build(); |
65 | 81 |
|
66 | 82 | Set<BacktestStage> stages = new ConcurrentSkipListSet<>(); |
@@ -96,11 +112,22 @@ void completesCompilePrepareExecuteAgainstBinanceBtcUsdt() throws Exception { |
96 | 112 | assertTrue(stages.contains(BacktestStage.EXECUTING), "executing stage fired"); |
97 | 113 | } |
98 | 114 |
|
99 | | - private static String dayStartIso(int offsetDays) { |
100 | | - return Instant.now() |
101 | | - .truncatedTo(ChronoUnit.DAYS) |
102 | | - .minus(offsetDays, ChronoUnit.DAYS) |
103 | | - .toString(); |
| 115 | + /** |
| 116 | + * Resolves the instrument's currently-available data window from the |
| 117 | + * instruments API, preferring ticker coverage and falling back to klines. |
| 118 | + */ |
| 119 | + private static CoverageWindow availableWindow(QTSurfer qts, String exchangeId, String instrumentId) { |
| 120 | + InstrumentDetail instrument = qts.instruments(exchangeId).stream() |
| 121 | + .filter(i -> instrumentId.equals(i.getId())) |
| 122 | + .findFirst() |
| 123 | + .orElseThrow(() -> new AssertionError(instrumentId + " not listed on " + exchangeId)); |
| 124 | + InstrumentCoverage coverage = instrument.getCoverage(); |
| 125 | + assertNotNull(coverage, "coverage for " + instrumentId); |
| 126 | + CoverageWindow window = coverage.getTickers() != null ? coverage.getTickers() : coverage.getKlines(); |
| 127 | + assertNotNull(window, "ticker/kline coverage window for " + instrumentId); |
| 128 | + assertNotNull(window.getFrom(), "coverage window from"); |
| 129 | + assertNotNull(window.getTo(), "coverage window to"); |
| 130 | + return window; |
104 | 131 | } |
105 | 132 |
|
106 | 133 | private static String loadFixture(String path) throws IOException { |
|
0 commit comments