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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
event_id,bar_time,signal_type,direction,source_service,symbol,old_fusion_score
|
||||
mini-long-1,1767225600000,BREAKOUT_RETEST_CONTINUATION,LONG,TEST_CANDIDATE_EVENT,BTCUSDT,0.95
|
||||
mini-short-1,1767225720000,BREAKOUT_RETEST_CONTINUATION,SHORT,TEST_CANDIDATE_EVENT,BTCUSDT,0.90
|
||||
|
@@ -0,0 +1,19 @@
|
||||
symbol,timeframe,open_time,open,high,low,close,volume,taker_buy_volume,funding_bps,open_interest,best_bid_price,best_ask_price,observed_spread_bps,observed_slippage_bps,expected_slippage_bps,p95_latency_ms,source_coverage
|
||||
BTCUSDT,1m,2026-01-01T00:00:00Z,100.0,100.4,99.8,100.0,10,5,0.1,1000,99.99,100.01,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:01:00Z,100.0,100.7,99.9,100.4,11,6,0.1,1001,100.39,100.41,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:02:00Z,100.4,100.6,100.0,100.2,12,7,0.1,1002,100.19,100.21,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:03:00Z,100.2,100.8,100.1,100.5,13,8,0.1,1003,100.49,100.51,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:04:00Z,100.5,101.0,100.2,100.8,14,9,0.1,1004,100.79,100.81,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:05:00Z,100.8,101.3,100.5,101.0,15,10,0.1,1005,100.99,101.01,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:06:00Z,101.0,101.6,100.7,101.2,16,11,0.1,1006,101.19,101.21,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:07:00Z,101.2,101.7,100.8,101.3,17,12,0.1,1007,101.29,101.31,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:08:00Z,101.3,101.8,100.9,101.4,18,13,0.1,1008,101.39,101.41,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:09:00Z,101.4,101.9,101.0,101.5,19,14,0.1,1009,101.49,101.51,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:10:00Z,101.5,102.0,101.1,101.6,20,15,0.1,1010,101.59,101.61,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:11:00Z,101.6,102.1,101.2,101.7,21,16,0.1,1011,101.69,101.71,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:12:00Z,101.7,102.2,101.3,101.8,22,17,0.1,1012,101.79,101.81,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:13:00Z,101.8,102.3,101.4,101.9,23,18,0.1,1013,101.89,101.91,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:14:00Z,101.9,102.4,101.5,102.0,24,19,0.1,1014,101.99,102.01,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:15:00Z,102.0,102.5,101.6,102.0,25,20,0.1,1015,101.99,102.01,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:16:00Z,102.0,102.1,99.5,99.8,26,6,0.1,1016,99.79,99.81,2,1,1,30,1
|
||||
BTCUSDT,1m,2026-01-01T00:17:00Z,99.8,100.0,98.8,99.0,27,5,0.1,1017,98.99,99.01,2,1,1,30,1
|
||||
|
@@ -1 +1 @@
|
||||
{"eventTime":"2026-01-01T03:00:00Z","contextFeatures":{"contextPass":true,"trendRegime":"UP","marketStructure":"weak_breakout_retest","timeframeAlignment":"1h_up_4h_flat"},"setupFeatures":{"setupPass":true,"setupName":"breakout_retest_continuation","entryPrice":"65120","invalidPrice":"64980","stopPrice":"65040","targetPrice":"65450","executionQualityScore":"0.76","volumeImpulse":"0.88","retestHold":true},"triggerFeatures":{"triggerScore":"0.32","triggerName":"false_breakout_probe","breakoutFollowThrough":false},"executionFeatures":{"lastPrice":"65105","spreadBps":"1.4","bookImbalance":"0.49"},"dataQuality":{"missing_features":[]}}
|
||||
{"eventTime":"2026-01-01T03:00:00Z","contextFeatures":{"contextPass":true,"trendRegime":"UP","marketStructure":"weak_breakout_retest","timeframeAlignment":"1h_up_4h_flat"},"setupFeatures":{"setupPass":true,"setupName":"breakout_retest_continuation","side":"LONG","entryPrice":"65120","invalidPrice":"64980","stopPrice":"65040","targetPrice":"65450","executionQualityScore":"0.76","volumeImpulse":"0.88","retestHold":true},"triggerFeatures":{"triggerScore":"0.32","triggerName":"false_breakout_probe","breakoutFollowThrough":false},"executionFeatures":{"lastPrice":"65105","spreadBps":"1.4","bookImbalance":"0.49"},"dataQuality":{"missing_features":[]}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"eventTime":"2026-01-01T05:00:00Z","contextFeatures":{"contextPass":true,"trendRegime":"UP","marketStructure":"breakout_retest"},"setupFeatures":{"setupPass":true,"setupName":"breakout_retest_continuation","executionQualityScore":"0.90"},"triggerFeatures":{"triggerScore":"0.95","triggerName":"micro_continuation"},"executionFeatures":{"lastPrice":"65300"},"dataQuality":{"missing_features":[]}}
|
||||
{"eventTime":"2026-01-01T05:00:00Z","contextFeatures":{"contextPass":true,"trendRegime":"UP","marketStructure":"breakout_retest"},"setupFeatures":{"setupPass":true,"setupName":"breakout_retest_continuation","side":"LONG","executionQualityScore":"0.90"},"triggerFeatures":{"triggerScore":"0.95","triggerName":"micro_continuation"},"executionFeatures":{"lastPrice":"65300"},"dataQuality":{"missing_features":[]}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"eventTime":"2026-01-01T04:00:00Z","contextFeatures":{"contextPass":true,"trendRegime":"UP","marketStructure":"breakout_retest"},"setupFeatures":{"setupPass":true,"setupName":"breakout_retest_continuation","entryPrice":"65200","invalidPrice":"65020","stopPrice":"65100","targetPrice":"65580","executionQualityScore":"0.84"},"triggerFeatures":{"triggerScore":"0.91","triggerName":"micro_continuation"},"executionFeatures":{"lastPrice":"65210"},"dataQuality":{"missing_features":["level_1.best_bid","level_1.best_ask"]}}
|
||||
{"eventTime":"2026-01-01T04:00:00Z","contextFeatures":{"contextPass":true,"trendRegime":"UP","marketStructure":"breakout_retest"},"setupFeatures":{"setupPass":true,"setupName":"breakout_retest_continuation","side":"LONG","entryPrice":"65200","invalidPrice":"65020","stopPrice":"65100","targetPrice":"65580","executionQualityScore":"0.84"},"triggerFeatures":{"triggerScore":"0.91","triggerName":"micro_continuation"},"executionFeatures":{"lastPrice":"65210"},"dataQuality":{"missing_features":["level_1.best_bid","level_1.best_ask"]}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"eventTime":"2026-01-01T00:00:00Z","contextFeatures":{"contextPass":true,"trendRegime":"UP","marketStructure":"breakout_retest","timeframeAlignment":"1h_4h_up"},"setupFeatures":{"setupPass":true,"setupName":"breakout_retest_continuation","entryPrice":"65000","invalidPrice":"64800","stopPrice":"64920","targetPrice":"65350","executionQualityScore":"0.92","volumeImpulse":"1.48","retestHold":true},"triggerFeatures":{"triggerScore":"0.95","triggerName":"micro_continuation","breakoutFollowThrough":true},"executionFeatures":{"lastPrice":"65010","spreadBps":"0.8","bookImbalance":"0.57"},"dataQuality":{"missing_features":[]}}
|
||||
{"eventTime":"2026-01-01T00:00:00Z","contextFeatures":{"contextPass":true,"trendRegime":"UP","marketStructure":"breakout_retest","timeframeAlignment":"1h_4h_up"},"setupFeatures":{"setupPass":true,"setupName":"breakout_retest_continuation","side":"LONG","entryPrice":"65000","invalidPrice":"64800","stopPrice":"64920","targetPrice":"65350","executionQualityScore":"0.92","volumeImpulse":"1.48","retestHold":true},"triggerFeatures":{"triggerScore":"0.95","triggerName":"micro_continuation","breakoutFollowThrough":true},"executionFeatures":{"lastPrice":"65010","spreadBps":"0.8","bookImbalance":"0.57"},"dataQuality":{"missing_features":[]}}
|
||||
|
||||
Reference in New Issue
Block a user