Track replay position state across cycles

This commit is contained in:
Codex
2026-06-26 22:17:48 +08:00
parent 4e5f49d6fe
commit dad6b831b4
9 changed files with 295 additions and 30 deletions
@@ -0,0 +1,25 @@
package com.quantai.trader.domain;
import com.quantai.trader.enums.PositionSide;
import com.quantai.trader.enums.TraderActionType;
import org.junit.jupiter.api.Test;
import static com.quantai.trader.TestFixtures.*;
import static org.assertj.core.api.Assertions.assertThat;
class TraderActionFactoryTest {
private final TraderActionFactory factory = new TraderActionFactory();
@Test
void mapsReduceDecisionRatioOntoActionPositionRatio() {
TraderRiskDecision riskDecision = new TraderRiskDecision(
"risk-1", "run-1", "cycle-1", "pm-cycle-1",
true, TraderActionType.REDUCE_LONG, TraderActionType.REDUCE_LONG, null, java.util.Map.of());
TraderAction action = factory.create(pmDecision(TraderActionType.REDUCE_LONG, PositionSide.LONG),
riskDecision, "BTC-USDT-PERP");
assertThat(action.positionRatio()).isEqualByComparingTo("0.50");
assertThat(action.reduceOnly()).isTrue();
}
}
@@ -10,6 +10,7 @@ import com.quantai.trader.outbox.TraderOutboxEvent;
import com.quantai.trader.outbox.TraderOutboxRepository;
import com.quantai.trader.persistence.TraderDecisionTraceWriter;
import com.quantai.trader.position.TraderPositionManager;
import com.quantai.trader.replay.state.P0ReplayStateStore;
import com.quantai.trader.risk.TraderRiskGate;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -34,18 +35,20 @@ class TraderP0CycleRunnerTest {
EvidenceAppender evidenceAppender = new EvidenceAppender(evidenceRepository);
RecordingTraceWriter traceWriter = new RecordingTraceWriter();
RecordingOutboxRepository outboxRepository = new RecordingOutboxRepository();
var properties = propertiesWithArtifactRoot(artifactRoot);
TraderP0CycleRunner runner = new TraderP0CycleRunner(
propertiesWithArtifactRoot(artifactRoot),
new TraderArtifactLoader(propertiesWithArtifactRoot(artifactRoot), objectMapper()),
properties,
new TraderArtifactLoader(properties, objectMapper()),
new ArtifactTraderModelService(),
new TraderPositionManager(),
new TraderRiskGate(),
new TraderActionFactory(),
evidenceAppender,
traceWriter,
outboxRepository);
outboxRepository,
new P0ReplayStateStore(properties));
TraderCycleResult result = runner.runFlatCycle(new ReplayMarketEvent(
TraderCycleResult result = runner.runCycle(new ReplayMarketEvent(
"run-1", "BTC-USDT-PERP", T0, new BigDecimal("100"), new BigDecimal("99.5"),
new BigDecimal("1.2"), new BigDecimal("1000")));
@@ -65,18 +68,20 @@ class TraderP0CycleRunnerTest {
EvidenceAppender evidenceAppender = new EvidenceAppender(evidenceRepository);
RecordingTraceWriter traceWriter = new RecordingTraceWriter();
RecordingOutboxRepository outboxRepository = new RecordingOutboxRepository();
var properties = propertiesWithArtifactRoot(artifactRoot);
TraderP0CycleRunner runner = new TraderP0CycleRunner(
propertiesWithArtifactRoot(artifactRoot),
new TraderArtifactLoader(propertiesWithArtifactRoot(artifactRoot), objectMapper()),
properties,
new TraderArtifactLoader(properties, objectMapper()),
new ArtifactTraderModelService(),
new TraderPositionManager(),
new TraderRiskGate(),
new TraderActionFactory(),
evidenceAppender,
traceWriter,
outboxRepository);
outboxRepository,
new P0ReplayStateStore(properties));
TraderCycleResult result = runner.runFlatCycle(new ReplayMarketEvent(
TraderCycleResult result = runner.runCycle(new ReplayMarketEvent(
"run-1", "BTC-USDT-PERP", T0.plusSeconds(60), new BigDecimal("100"), new BigDecimal("99.5"),
new BigDecimal("1.2"), BigDecimal.ZERO));
@@ -87,6 +92,40 @@ class TraderP0CycleRunnerTest {
assertThat(evidenceRepository.items()).hasSize(4);
}
@Test
void laterCycleUsesPositionStateFromEarlierOpen() throws IOException {
writeArtifactBundle(artifactRoot);
RecordingEvidenceRepository evidenceRepository = new RecordingEvidenceRepository();
EvidenceAppender evidenceAppender = new EvidenceAppender(evidenceRepository);
RecordingTraceWriter traceWriter = new RecordingTraceWriter();
RecordingOutboxRepository outboxRepository = new RecordingOutboxRepository();
var properties = propertiesWithArtifactRoot(artifactRoot);
TraderP0CycleRunner runner = new TraderP0CycleRunner(
properties,
new TraderArtifactLoader(properties, objectMapper()),
new ArtifactTraderModelService(),
new TraderPositionManager(),
new TraderRiskGate(),
new TraderActionFactory(),
evidenceAppender,
traceWriter,
outboxRepository,
new P0ReplayStateStore(properties));
TraderCycleResult first = runner.runCycle(new ReplayMarketEvent(
"run-state-1", "BTC-USDT-PERP", T0, new BigDecimal("100"), new BigDecimal("99.5"),
new BigDecimal("1.2"), new BigDecimal("1000")));
TraderCycleResult second = runner.runCycle(new ReplayMarketEvent(
"run-state-1", "BTC-USDT-PERP", T0.plusSeconds(60), new BigDecimal("101"), new BigDecimal("100.5"),
new BigDecimal("1.2"), new BigDecimal("1000")));
assertThat(first.action().actionType()).isEqualTo(TraderActionType.OPEN_LONG);
assertThat(second.action().actionType()).isEqualTo(TraderActionType.ADD_LONG);
assertThat(traceWriter.positionStates()).extracting("side").containsExactly(
com.quantai.trader.enums.PositionSide.NONE,
com.quantai.trader.enums.PositionSide.LONG);
}
private static final class RecordingEvidenceRepository implements TraderEvidenceRepository {
private final List<TraderEvidence> items = new ArrayList<>();
@@ -115,16 +154,22 @@ class TraderP0CycleRunnerTest {
private static final class RecordingTraceWriter implements TraderDecisionTraceWriter {
private final List<TraderAction> actions = new ArrayList<>();
private final List<TraderPositionState> positionStates = new ArrayList<>();
@Override
public void persistCycleTrace(TraderDecisionCycle cycle, TraderMarketSnapshot snapshot, TraderModelOutput modelOutput,
TraderPositionState positionState, TraderPositionManagerDecision pmDecision,
TraderRiskDecision riskDecision, TraderAction action) {
actions.add(action);
positionStates.add(positionState);
}
List<TraderAction> actions() {
return actions;
}
List<TraderPositionState> positionStates() {
return positionStates;
}
}
}