Add crypto lake replay labels

This commit is contained in:
Codex
2026-06-23 22:21:56 +08:00
parent 7ff786f658
commit 2fe4077164
28 changed files with 977 additions and 64 deletions
@@ -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);
}