Add crypto lake replay labels
This commit is contained in:
@@ -5,6 +5,7 @@ import com.quantai.trader.domain.PlaybookCandidate;
|
||||
import com.quantai.trader.domain.TraderAction;
|
||||
import com.quantai.trader.domain.TraderDecisionCycle;
|
||||
import com.quantai.trader.domain.TraderEntryPlan;
|
||||
import com.quantai.trader.domain.TraderMarketSnapshot;
|
||||
import com.quantai.trader.domain.TraderPositionPath;
|
||||
import com.quantai.trader.domain.TraderPricePlan;
|
||||
import com.quantai.trader.domain.TriggerDecision;
|
||||
@@ -15,6 +16,7 @@ import com.quantai.trader.enums.TraderState;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class TestFixtures {
|
||||
@@ -72,6 +74,35 @@ public final class TestFixtures {
|
||||
);
|
||||
}
|
||||
|
||||
public static TraderMarketSnapshot labeledSnapshot() {
|
||||
return new TraderMarketSnapshot(
|
||||
"trader_run_test",
|
||||
"trader_cycle_test",
|
||||
"trader_snapshot_test",
|
||||
"BTCUSDT",
|
||||
NOW,
|
||||
"trader_feature_v0",
|
||||
Map.of("contextPass", true),
|
||||
Map.of("setupPass", true, "side", "LONG"),
|
||||
Map.of("triggerScore", "0.95"),
|
||||
Map.of("lastPrice", "65010"),
|
||||
Map.of("missing_features", List.of()),
|
||||
Map.ofEntries(
|
||||
Map.entry("labelSource", "CRYPTO_LAKE_1M_REPLAY"),
|
||||
Map.entry("labelStatus", "REPLAY_MARKOUT_LABELED"),
|
||||
Map.entry("side", "LONG"),
|
||||
Map.entry("entryPrice", "65000"),
|
||||
Map.entry("markoutBps1m", "5"),
|
||||
Map.entry("markoutBps5m", "12"),
|
||||
Map.entry("markoutBps15m", "24"),
|
||||
Map.entry("mfeBps15m", "30"),
|
||||
Map.entry("maeBps15m", "6"),
|
||||
Map.entry("targetBeforeStop15m", true),
|
||||
Map.entry("expectedSlippageBps", "1")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static TriggerDecision strongTrigger() {
|
||||
return new TriggerDecision(true, new BigDecimal("0.95"), "TRIGGER_ACCEPTED", null, Map.of());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
@@ -24,13 +25,15 @@ class TraderDecisionCycleRunnerTest {
|
||||
|
||||
@Test
|
||||
void exportsSampleForHappyPathReplayTick() {
|
||||
String runId = "trader_run_runner_" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
|
||||
ReplayClockTick tick = new ReplayClockTick(
|
||||
"trader_run_runner",
|
||||
runId,
|
||||
"BTCUSDT",
|
||||
Instant.parse("2026-06-23T12:00:00Z"),
|
||||
Map.of("contextPass", true),
|
||||
Map.of(
|
||||
"setupPass", true,
|
||||
"side", "LONG",
|
||||
"entryPrice", new BigDecimal("65000"),
|
||||
"invalidPrice", new BigDecimal("64800"),
|
||||
"stopPrice", new BigDecimal("64920"),
|
||||
@@ -39,13 +42,14 @@ class TraderDecisionCycleRunnerTest {
|
||||
),
|
||||
Map.of("triggerScore", "0.95"),
|
||||
Map.of("lastPrice", "65010"),
|
||||
Map.of(),
|
||||
Map.of()
|
||||
);
|
||||
|
||||
TraderCycleResult result = runner.runReplayTick(
|
||||
tick,
|
||||
new TraderRuntimeState(
|
||||
"trader_run_runner",
|
||||
runId,
|
||||
TraderRunMode.REPLAY,
|
||||
"BREAKOUT_RETEST_CONTINUATION",
|
||||
"2026-06-22.p0"
|
||||
@@ -64,9 +68,10 @@ class TraderDecisionCycleRunnerTest {
|
||||
"BTCUSDT",
|
||||
Instant.parse("2026-06-23T12:00:00Z"),
|
||||
Map.of("contextPass", true),
|
||||
Map.of("setupPass", true),
|
||||
Map.of("setupPass", true, "side", "LONG"),
|
||||
Map.of("triggerScore", "0.95"),
|
||||
Map.of("lastPrice", "65010"),
|
||||
Map.of(),
|
||||
Map.of()
|
||||
);
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ class TraderControllerTest {
|
||||
mockMvc.perform(get("/api/trader/replay/runs/{runId}/report", runId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.candidateEvents").value(1))
|
||||
.andExpect(jsonPath("$.strictVsLoose.p0ReplayEngine").value("jsonl_fixture"));
|
||||
.andExpect(jsonPath("$.strictVsLoose.replayEngine").value("jsonl_fixture"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -47,7 +47,7 @@ class MybatisReplayPersistenceTest {
|
||||
assertThat(run.status()).isEqualTo(ReplayRunStatus.COMPLETED);
|
||||
assertThat(reportRepository.findByRunId(run.runId())).isPresent()
|
||||
.get()
|
||||
.extracting(report -> report.strictVsLoose().get("p0ReplayEngine"))
|
||||
.extracting(report -> report.strictVsLoose().get("replayEngine"))
|
||||
.isEqualTo("jsonl_fixture");
|
||||
|
||||
var samples = sampleRepository.findByRunId(run.runId());
|
||||
|
||||
@@ -56,6 +56,7 @@ class TraderPositionManagerTest {
|
||||
Map.of(),
|
||||
Map.of(),
|
||||
Map.of("lastPrice", "65010"),
|
||||
Map.of(),
|
||||
Map.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.quantai.trader.replay;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CryptoLakeReplayCsvMarketEventReaderTest {
|
||||
|
||||
private final CryptoLakeReplayCsvMarketEventReader reader = new CryptoLakeReplayCsvMarketEventReader();
|
||||
|
||||
@Test
|
||||
void readsCandidateTicksAndRecomputesReplayMarkoutLabels() throws Exception {
|
||||
ReplayRunConfig config = config();
|
||||
|
||||
var ticks = reader.readTicks(config);
|
||||
|
||||
assertThat(ticks).hasSize(2);
|
||||
assertThat(ticks.getFirst().setupFeatures())
|
||||
.containsEntry("setupPass", true)
|
||||
.containsEntry("side", "LONG")
|
||||
.containsEntry("entryPrice", "100");
|
||||
assertThat(ticks.getFirst().labelInputs())
|
||||
.containsEntry("labelStatus", "REPLAY_MARKOUT_LABELED")
|
||||
.containsEntry("side", "LONG")
|
||||
.containsEntry("markoutBps15m", "200");
|
||||
assertThat(ticks.get(1).setupFeatures())
|
||||
.containsEntry("side", "SHORT");
|
||||
assertThat(ticks.get(1).labelInputs())
|
||||
.containsEntry("labelStatus", "REPLAY_MARKOUT_LABELED")
|
||||
.containsEntry("side", "SHORT");
|
||||
}
|
||||
|
||||
private ReplayRunConfig config() throws Exception {
|
||||
Path replay = Path.of(new ClassPathResource("replay-fixtures/crypto-lake-replay-mini.csv").getFile().toURI());
|
||||
Path candidates = Path.of(new ClassPathResource("replay-fixtures/crypto-lake-candidate-events-mini.csv").getFile().toURI());
|
||||
return new ReplayRunConfig(
|
||||
"trader_run_csv_test",
|
||||
"BTCUSDT",
|
||||
"BREAKOUT_RETEST_CONTINUATION",
|
||||
"2026-06-22.p0",
|
||||
Instant.parse("2026-01-01T00:00:00Z"),
|
||||
Instant.parse("2026-01-01T00:05:00Z"),
|
||||
"trader_feature_v0",
|
||||
"trader_label_v0",
|
||||
Map.of(
|
||||
"cryptoLakeReplay1m", new DataSourceSpec(
|
||||
"crypto-lake-mini",
|
||||
replay.toString(),
|
||||
"fixture-hash-not-used",
|
||||
null,
|
||||
18L,
|
||||
null,
|
||||
null,
|
||||
"UTC",
|
||||
Map.of()
|
||||
),
|
||||
"candidateEvents", new DataSourceSpec(
|
||||
"candidate-events-mini",
|
||||
candidates.toString(),
|
||||
"fixture-hash-not-used",
|
||||
null,
|
||||
2L,
|
||||
null,
|
||||
null,
|
||||
"UTC",
|
||||
Map.of()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -44,15 +44,18 @@ class TraderReplayFixtureAcceptanceTest {
|
||||
assertThat(run.status()).as(fixture.fileName()).isEqualTo(ReplayRunStatus.COMPLETED);
|
||||
TraderReplayReport report = reportRepository.findByRunId(run.runId()).orElseThrow();
|
||||
assertThat(report.strictVsLoose())
|
||||
.containsEntry("p0ReplayEngine", "jsonl_fixture")
|
||||
.containsEntry("replayEngine", "jsonl_fixture")
|
||||
.containsEntry("tickCount", 1)
|
||||
.containsEntry("sampleCount", 1)
|
||||
.containsEntry("actionCount", fixture.expectedActionCount());
|
||||
.containsEntry("actionCount", fixture.expectedActionCount())
|
||||
.containsEntry("labeledSampleCount", 0)
|
||||
.containsEntry("proxyOnlySampleCount", 1);
|
||||
assertThat(report.candidateEvents()).isEqualTo(fixture.expectedActionCount());
|
||||
|
||||
var samples = sampleRepository.findByRunId(run.runId());
|
||||
assertThat(samples).hasSize(1);
|
||||
assertThat(samples.getFirst().features()).containsEntry("actionType", fixture.expectedSampleActionType());
|
||||
assertThat(samples.getFirst().labels()).containsEntry("label_status", "PROXY_ONLY_NO_REPLAY_LABEL");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,20 +13,23 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class TrainingSampleExporterTest {
|
||||
|
||||
@Test
|
||||
void exportsProxyOnlySampleWithFeatureAndLabelVersions() {
|
||||
void exportsReplayMarkoutSampleWithFeatureAndLabelVersions() {
|
||||
CapturingSampleRepository repository = new CapturingSampleRepository();
|
||||
TrainingSampleExporter exporter = new TrainingSampleExporter(TestFixtures.properties(), repository);
|
||||
TrainingSampleExporter exporter = new TrainingSampleExporter(TestFixtures.properties(), repository, new TriggerMarkoutLabeler());
|
||||
|
||||
var sample = exporter.export(
|
||||
TestFixtures.cycle(com.quantai.trader.enums.TraderState.SAMPLE_EXPORTED),
|
||||
TestFixtures.labeledSnapshot(),
|
||||
TestFixtures.candidate(),
|
||||
TestFixtures.action(),
|
||||
TestFixtures.openedPath(false)
|
||||
);
|
||||
|
||||
assertThat(sample.proxyOnly()).isTrue();
|
||||
assertThat(sample.proxyOnly()).isFalse();
|
||||
assertThat(sample.featureVersion()).isEqualTo("trader_feature_v0");
|
||||
assertThat(sample.labelVersion()).isEqualTo("trader_label_v0");
|
||||
assertThat(sample.netReturnBps1x()).isEqualByComparingTo("14.00000000");
|
||||
assertThat(sample.labels()).containsEntry("label_status", "REPLAY_MARKOUT_LABELED");
|
||||
assertThat(repository.findByRunId("trader_run_test")).hasSize(1);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user