Initial quant trader service baseline

This commit is contained in:
Codex
2026-06-23 22:09:06 +08:00
commit 7ff786f658
137 changed files with 6664 additions and 0 deletions
@@ -0,0 +1,75 @@
package com.quantai.trader.persistence;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.quantai.trader.domain.TraderReplayReport;
import com.quantai.trader.infrastructure.entity.TraderReplayReportEntity;
import com.quantai.trader.infrastructure.mapper.TraderReplayReportMapper;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public class MybatisReplayReportRepository implements ReplayReportRepository {
private final TraderReplayReportMapper mapper;
public MybatisReplayReportRepository(TraderReplayReportMapper mapper) {
this.mapper = mapper;
}
@Override
public void insert(TraderReplayReport report) {
mapper.insert(toEntity(report));
}
@Override
public Optional<TraderReplayReport> findByRunId(String runId) {
TraderReplayReportEntity entity = mapper.selectOne(
Wrappers.<TraderReplayReportEntity>lambdaQuery()
.eq(TraderReplayReportEntity::getRunId, runId)
.orderByDesc(TraderReplayReportEntity::getCreatedAt)
.last("limit 1")
);
return Optional.ofNullable(entity).map(this::toDomain);
}
private TraderReplayReportEntity toEntity(TraderReplayReport report) {
TraderReplayReportEntity entity = new TraderReplayReportEntity();
entity.setRunId(report.runId());
entity.setReportId(report.reportId());
entity.setSymbol(report.symbol());
entity.setPlaybookId(report.playbookId());
entity.setPlaybookVersion(report.playbookVersion());
entity.setCandidateEvents(report.candidateEvents());
entity.setMonthsCovered(report.monthsCovered());
entity.setBaseNetReturnBps1x(report.baseNetReturnBps1x());
entity.setLeveragedNetReturnBps10x(report.leveragedNetReturnBps10x());
entity.setHoldoutReturnBps10x(report.holdoutReturnBps10x());
entity.setStrictVsLooseJson(TraderPersistenceCodec.json(report.strictVsLoose()));
entity.setFailureRisksJson(TraderPersistenceCodec.json(report.failureRisks()));
entity.setConclusion(report.conclusion());
entity.setReportPath(report.reportPath());
entity.setCreatedAt(TraderPersistenceCodec.requiredLocal("report.createdAt", report.createdAt()));
return entity;
}
private TraderReplayReport toDomain(TraderReplayReportEntity entity) {
return new TraderReplayReport(
entity.getRunId(),
entity.getReportId(),
entity.getSymbol(),
entity.getPlaybookId(),
entity.getPlaybookVersion(),
entity.getCandidateEvents(),
entity.getMonthsCovered(),
entity.getBaseNetReturnBps1x(),
entity.getLeveragedNetReturnBps10x(),
entity.getHoldoutReturnBps10x(),
TraderPersistenceCodec.map(entity.getStrictVsLooseJson()),
TraderPersistenceCodec.stringList(entity.getFailureRisksJson()),
entity.getConclusion(),
entity.getReportPath(),
TraderPersistenceCodec.instant(entity.getCreatedAt())
);
}
}
@@ -0,0 +1,88 @@
package com.quantai.trader.persistence;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.quantai.trader.enums.ReplayRunStatus;
import com.quantai.trader.infrastructure.entity.TraderReplayRunEntity;
import com.quantai.trader.infrastructure.mapper.TraderReplayRunMapper;
import com.quantai.trader.replay.ReplayRun;
import com.quantai.trader.replay.ReplayRunConfig;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public class MybatisReplayRunRepository implements ReplayRunRepository {
private final TraderReplayRunMapper mapper;
public MybatisReplayRunRepository(TraderReplayRunMapper mapper) {
this.mapper = mapper;
}
@Override
public void insert(ReplayRun run) {
mapper.insert(toEntity(run));
}
@Override
public void update(ReplayRun run) {
int updated = mapper.update(
null,
Wrappers.<TraderReplayRunEntity>lambdaUpdate()
.set(TraderReplayRunEntity::getStatus, run.status().name())
.set(TraderReplayRunEntity::getConfigJson, TraderPersistenceCodec.json(run.config()))
.set(TraderReplayRunEntity::getStartedAt, TraderPersistenceCodec.local(run.startedAt()))
.set(TraderReplayRunEntity::getFinishedAt, TraderPersistenceCodec.local(run.finishedAt()))
.set(TraderReplayRunEntity::getFailureReason, run.failureReason())
.eq(TraderReplayRunEntity::getRunId, run.runId())
);
if (updated == 0) {
throw new IllegalStateException("replay run not found for update: " + run.runId());
}
}
@Override
public Optional<ReplayRun> findByRunId(String runId) {
TraderReplayRunEntity entity = mapper.selectOne(
Wrappers.<TraderReplayRunEntity>lambdaQuery()
.eq(TraderReplayRunEntity::getRunId, runId)
);
return Optional.ofNullable(entity).map(this::toDomain);
}
private TraderReplayRunEntity toEntity(ReplayRun run) {
ReplayRunConfig config = run.config();
TraderReplayRunEntity entity = new TraderReplayRunEntity();
entity.setRunId(run.runId());
entity.setRunMode("REPLAY");
entity.setSymbol(config.symbol());
entity.setPlaybookId(config.playbookId());
entity.setPlaybookVersion(config.playbookVersion());
entity.setPlaybookDefinitionHash(run.playbookDefinitionHash());
entity.setDataFrom(TraderPersistenceCodec.local(config.from()));
entity.setDataTo(TraderPersistenceCodec.local(config.to()));
entity.setFeatureVersion(config.featureVersion());
entity.setLabelVersion(config.labelVersion());
entity.setDataSourceManifestJson(TraderPersistenceCodec.json(config.dataSources()));
entity.setStatus(run.status().name());
entity.setConfigJson(TraderPersistenceCodec.json(config));
entity.setStartedAt(TraderPersistenceCodec.local(run.startedAt()));
entity.setFinishedAt(TraderPersistenceCodec.local(run.finishedAt()));
entity.setFailureReason(run.failureReason());
entity.setCreatedAt(TraderPersistenceCodec.requiredLocal("run.createdAt", run.createdAt()));
return entity;
}
private ReplayRun toDomain(TraderReplayRunEntity entity) {
return new ReplayRun(
entity.getRunId(),
ReplayRunStatus.valueOf(entity.getStatus()),
TraderPersistenceCodec.replayRunConfig(entity.getConfigJson()),
entity.getPlaybookDefinitionHash(),
TraderPersistenceCodec.instant(entity.getCreatedAt()),
TraderPersistenceCodec.instant(entity.getStartedAt()),
TraderPersistenceCodec.instant(entity.getFinishedAt()),
entity.getFailureReason()
);
}
}
@@ -0,0 +1,61 @@
package com.quantai.trader.persistence;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.quantai.trader.domain.TraderEvidence;
import com.quantai.trader.infrastructure.entity.TraderEvidenceEntity;
import com.quantai.trader.infrastructure.mapper.TraderEvidenceMapper;
import org.springframework.stereotype.Repository;
import java.time.Instant;
import java.util.List;
@Repository
public class MybatisTraderEvidenceRepository implements TraderEvidenceRepository {
private final TraderEvidenceMapper mapper;
public MybatisTraderEvidenceRepository(TraderEvidenceMapper mapper) {
this.mapper = mapper;
}
@Override
public void insert(TraderEvidence evidence) {
TraderEvidenceEntity entity = new TraderEvidenceEntity();
entity.setRunId(evidence.runId());
entity.setCycleId(evidence.cycleId());
entity.setEvidenceId(evidence.evidenceId());
entity.setStage(evidence.stage());
entity.setPass(evidence.pass());
entity.setReason(evidence.reason());
entity.setBlocker(evidence.blocker());
entity.setEvidenceTime(TraderPersistenceCodec.local(evidence.evidenceTime()));
entity.setDetailsJson(TraderPersistenceCodec.json(evidence.details()));
entity.setCreatedAt(TraderPersistenceCodec.local(Instant.now()));
mapper.insert(entity);
}
@Override
public List<TraderEvidence> findByCycleId(String runId, String cycleId) {
return mapper.selectList(
Wrappers.<TraderEvidenceEntity>lambdaQuery()
.eq(TraderEvidenceEntity::getRunId, runId)
.eq(TraderEvidenceEntity::getCycleId, cycleId)
.orderByAsc(TraderEvidenceEntity::getEvidenceTime)
.orderByAsc(TraderEvidenceEntity::getId)
).stream().map(this::toDomain).toList();
}
private TraderEvidence toDomain(TraderEvidenceEntity entity) {
return new TraderEvidence(
entity.getRunId(),
entity.getCycleId(),
entity.getEvidenceId(),
entity.getStage(),
Boolean.TRUE.equals(entity.getPass()),
entity.getReason(),
entity.getBlocker(),
TraderPersistenceCodec.instant(entity.getEvidenceTime()),
TraderPersistenceCodec.map(entity.getDetailsJson())
);
}
}
@@ -0,0 +1,83 @@
package com.quantai.trader.persistence;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.quantai.trader.domain.TraderException;
import com.quantai.trader.enums.TraderErrorCode;
import com.quantai.trader.infrastructure.entity.TraderPlaybookDefinitionEntity;
import com.quantai.trader.infrastructure.mapper.TraderPlaybookDefinitionMapper;
import com.quantai.trader.playbook.TraderPlaybookDefinitionSnapshot;
import org.springframework.stereotype.Repository;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
@Repository
public class MybatisTraderPlaybookDefinitionRepository implements TraderPlaybookDefinitionRepository {
private final TraderPlaybookDefinitionMapper mapper;
public MybatisTraderPlaybookDefinitionRepository(TraderPlaybookDefinitionMapper mapper) {
this.mapper = mapper;
}
@Override
public void insertPlaybookDefinitionIfAbsent(TraderPlaybookDefinitionSnapshot definition) {
Optional<TraderPlaybookDefinitionSnapshot> existing = findPlaybookDefinition(
definition.playbookId(),
definition.playbookVersion()
);
if (existing.isPresent()) {
if (!existing.get().definitionHashSha256().equals(definition.definitionHashSha256())) {
throw new TraderException(
TraderErrorCode.TRADER_PLAYBOOK_VERSION_CONFLICT,
"playbook version already exists with another definition hash"
);
}
return;
}
mapper.insert(toEntity(definition));
}
@Override
public Optional<TraderPlaybookDefinitionSnapshot> findPlaybookDefinition(String playbookId, String playbookVersion) {
TraderPlaybookDefinitionEntity entity = mapper.selectOne(
Wrappers.<TraderPlaybookDefinitionEntity>lambdaQuery()
.eq(TraderPlaybookDefinitionEntity::getPlaybookId, playbookId)
.eq(TraderPlaybookDefinitionEntity::getPlaybookVersion, playbookVersion)
);
return Optional.ofNullable(entity).map(this::toDomain);
}
private TraderPlaybookDefinitionEntity toEntity(TraderPlaybookDefinitionSnapshot definition) {
TraderPlaybookDefinitionEntity entity = new TraderPlaybookDefinitionEntity();
entity.setPlaybookId(definition.playbookId());
entity.setPlaybookVersion(definition.playbookVersion());
entity.setFamily(definition.family());
entity.setVariant(definition.variant());
entity.setSideMode(Objects.requireNonNull(definition.definition(), "playbook definition is required").sideMode());
entity.setSourcePath(definition.sourcePath());
entity.setDefinitionHashSha256(definition.definitionHashSha256());
entity.setDefinitionJson(definition.definitionJson());
entity.setLoadedAt(TraderPersistenceCodec.local(definition.loadedAt()));
entity.setStatus(definition.status());
entity.setCreatedAt(TraderPersistenceCodec.local(Instant.now()));
return entity;
}
private TraderPlaybookDefinitionSnapshot toDomain(TraderPlaybookDefinitionEntity entity) {
String definitionJson = entity.getDefinitionJson();
return new TraderPlaybookDefinitionSnapshot(
entity.getPlaybookId(),
entity.getPlaybookVersion(),
entity.getFamily(),
entity.getVariant(),
entity.getSourcePath(),
entity.getDefinitionHashSha256(),
definitionJson,
TraderPersistenceCodec.instant(entity.getLoadedAt()),
entity.getStatus(),
TraderPersistenceCodec.playbookDefinition(definitionJson)
);
}
}
@@ -0,0 +1,76 @@
package com.quantai.trader.persistence;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.quantai.trader.domain.TraderRiskDecision;
import com.quantai.trader.enums.TraderActionType;
import com.quantai.trader.infrastructure.entity.TraderRiskDecisionEntity;
import com.quantai.trader.infrastructure.mapper.TraderRiskDecisionMapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class MybatisTraderRiskDecisionRepository implements TraderRiskDecisionRepository {
private final TraderRiskDecisionMapper mapper;
public MybatisTraderRiskDecisionRepository(TraderRiskDecisionMapper mapper) {
this.mapper = mapper;
}
@Override
public void insert(TraderRiskDecision decision) {
TraderRiskDecisionEntity entity = new TraderRiskDecisionEntity();
entity.setRunId(decision.runId());
entity.setCycleId(decision.cycleId());
entity.setActionId(decision.actionId());
entity.setAccountStateId(decision.accountStateId());
entity.setActionType(decision.actionType().name());
entity.setLeverageScreen(decision.leverageScreen());
entity.setPlannedTotalPositionRatio(decision.plannedTotalPositionRatio());
entity.setMaxLossBps(decision.maxLossBps());
entity.setLiquidationBufferBps(decision.liquidationBufferBps());
entity.setExpectedValueBps1x(decision.expectedValueBps1x());
entity.setExpectedValueBps10x(decision.expectedValueBps10x());
entity.setUncertainty(decision.uncertainty());
entity.setOodScore(decision.oodScore());
entity.setAllowAction(decision.allowAction());
entity.setBlocker(decision.blocker());
entity.setDecisionJson(TraderPersistenceCodec.json(decision.decision()));
entity.setCreatedAt(TraderPersistenceCodec.requiredLocal("decision.createdAt", decision.createdAt()));
mapper.insert(entity);
}
@Override
public List<TraderRiskDecision> findByCycleId(String runId, String cycleId) {
return mapper.selectList(
Wrappers.<TraderRiskDecisionEntity>lambdaQuery()
.eq(TraderRiskDecisionEntity::getRunId, runId)
.eq(TraderRiskDecisionEntity::getCycleId, cycleId)
.orderByAsc(TraderRiskDecisionEntity::getCreatedAt)
.orderByAsc(TraderRiskDecisionEntity::getId)
).stream().map(this::toDomain).toList();
}
private TraderRiskDecision toDomain(TraderRiskDecisionEntity entity) {
return new TraderRiskDecision(
entity.getRunId(),
entity.getCycleId(),
entity.getActionId(),
entity.getAccountStateId(),
TraderActionType.valueOf(entity.getActionType()),
entity.getLeverageScreen(),
entity.getPlannedTotalPositionRatio(),
entity.getMaxLossBps(),
entity.getLiquidationBufferBps(),
entity.getExpectedValueBps1x(),
entity.getExpectedValueBps10x(),
entity.getUncertainty(),
entity.getOodScore(),
Boolean.TRUE.equals(entity.getAllowAction()),
entity.getBlocker(),
TraderPersistenceCodec.map(entity.getDecisionJson()),
TraderPersistenceCodec.instant(entity.getCreatedAt())
);
}
}
@@ -0,0 +1,68 @@
package com.quantai.trader.persistence;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.quantai.trader.domain.TraderTrainingSample;
import com.quantai.trader.infrastructure.entity.TraderTrainingSampleEntity;
import com.quantai.trader.infrastructure.mapper.TraderTrainingSampleMapper;
import org.springframework.stereotype.Repository;
import java.time.Instant;
import java.util.List;
@Repository
public class MybatisTraderSampleRepository implements TraderSampleRepository {
private final TraderTrainingSampleMapper mapper;
public MybatisTraderSampleRepository(TraderTrainingSampleMapper mapper) {
this.mapper = mapper;
}
@Override
public void insert(TraderTrainingSample sample) {
TraderTrainingSampleEntity entity = new TraderTrainingSampleEntity();
entity.setRunId(sample.runId());
entity.setCycleId(sample.cycleId());
entity.setSampleId(sample.sampleId());
entity.setActionId(sample.actionId());
entity.setPositionId(sample.positionId());
entity.setFeatureVersion(sample.featureVersion());
entity.setLabelVersion(sample.labelVersion());
entity.setSampleTime(TraderPersistenceCodec.local(sample.sampleTime()));
entity.setFeaturesJson(TraderPersistenceCodec.json(sample.features()));
entity.setLabelsJson(TraderPersistenceCodec.json(sample.labels()));
entity.setNetReturnBps1x(sample.netReturnBps1x());
entity.setNetReturnBps10x(sample.netReturnBps10x());
entity.setProxyOnly(sample.proxyOnly());
entity.setCreatedAt(TraderPersistenceCodec.local(Instant.now()));
mapper.insert(entity);
}
@Override
public List<TraderTrainingSample> findByRunId(String runId) {
return mapper.selectList(
Wrappers.<TraderTrainingSampleEntity>lambdaQuery()
.eq(TraderTrainingSampleEntity::getRunId, runId)
.orderByAsc(TraderTrainingSampleEntity::getSampleTime)
.orderByAsc(TraderTrainingSampleEntity::getId)
).stream().map(this::toDomain).toList();
}
private TraderTrainingSample toDomain(TraderTrainingSampleEntity entity) {
return new TraderTrainingSample(
entity.getRunId(),
entity.getCycleId(),
entity.getSampleId(),
entity.getActionId(),
entity.getPositionId(),
entity.getFeatureVersion(),
entity.getLabelVersion(),
TraderPersistenceCodec.instant(entity.getSampleTime()),
TraderPersistenceCodec.map(entity.getFeaturesJson()),
TraderPersistenceCodec.map(entity.getLabelsJson()),
entity.getNetReturnBps1x(),
entity.getNetReturnBps10x(),
Boolean.TRUE.equals(entity.getProxyOnly())
);
}
}
@@ -0,0 +1,12 @@
package com.quantai.trader.persistence;
import com.quantai.trader.domain.TraderReplayReport;
import java.util.Optional;
public interface ReplayReportRepository {
void insert(TraderReplayReport report);
Optional<TraderReplayReport> findByRunId(String runId);
}
@@ -0,0 +1,14 @@
package com.quantai.trader.persistence;
import com.quantai.trader.replay.ReplayRun;
import java.util.Optional;
public interface ReplayRunRepository {
void insert(ReplayRun run);
void update(ReplayRun run);
Optional<ReplayRun> findByRunId(String runId);
}
@@ -0,0 +1,12 @@
package com.quantai.trader.persistence;
import com.quantai.trader.domain.TraderEvidence;
import java.util.List;
public interface TraderEvidenceRepository {
void insert(TraderEvidence evidence);
List<TraderEvidence> findByCycleId(String runId, String cycleId);
}
@@ -0,0 +1,86 @@
package com.quantai.trader.persistence;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.quantai.trader.playbook.TraderPlaybookDefinition;
import com.quantai.trader.replay.ReplayRunConfig;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Map;
import java.util.Objects;
final class TraderPersistenceCodec {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().findAndRegisterModules();
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
};
private static final TypeReference<List<String>> STRING_LIST_TYPE = new TypeReference<>() {
};
private TraderPersistenceCodec() {
}
static String json(Object value) {
try {
return OBJECT_MAPPER.writeValueAsString(Objects.requireNonNull(value, "json value is required"));
} catch (Exception ex) {
throw new IllegalStateException("failed to serialize trader persistence json", ex);
}
}
static Map<String, Object> map(String json) {
try {
return OBJECT_MAPPER.readValue(requiredJson(json), MAP_TYPE);
} catch (Exception ex) {
throw new IllegalStateException("failed to deserialize trader persistence json map", ex);
}
}
static List<String> stringList(String json) {
try {
return OBJECT_MAPPER.readValue(requiredJson(json), STRING_LIST_TYPE);
} catch (Exception ex) {
throw new IllegalStateException("failed to deserialize trader persistence json list", ex);
}
}
static ReplayRunConfig replayRunConfig(String json) {
try {
return OBJECT_MAPPER.readValue(requiredJson(json), ReplayRunConfig.class);
} catch (Exception ex) {
throw new IllegalStateException("failed to deserialize replay run config", ex);
}
}
static TraderPlaybookDefinition playbookDefinition(String json) {
try {
return OBJECT_MAPPER.readValue(requiredJson(json), TraderPlaybookDefinition.class);
} catch (Exception ex) {
throw new IllegalStateException("failed to deserialize playbook definition", ex);
}
}
static LocalDateTime local(Instant value) {
return value == null ? null : LocalDateTime.ofInstant(value, ZoneOffset.UTC);
}
static Instant instant(LocalDateTime value) {
return value == null ? null : value.toInstant(ZoneOffset.UTC);
}
static LocalDateTime requiredLocal(String fieldName, Instant value) {
return LocalDateTime.ofInstant(Objects.requireNonNull(value, fieldName + " is required"), ZoneOffset.UTC);
}
private static String requiredJson(String json) {
Objects.requireNonNull(json, "json text is required");
String trimmed = json.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("json text is blank");
}
return trimmed;
}
}
@@ -0,0 +1,12 @@
package com.quantai.trader.persistence;
import com.quantai.trader.playbook.TraderPlaybookDefinitionSnapshot;
import java.util.Optional;
public interface TraderPlaybookDefinitionRepository {
void insertPlaybookDefinitionIfAbsent(TraderPlaybookDefinitionSnapshot definition);
Optional<TraderPlaybookDefinitionSnapshot> findPlaybookDefinition(String playbookId, String playbookVersion);
}
@@ -0,0 +1,12 @@
package com.quantai.trader.persistence;
import com.quantai.trader.domain.TraderRiskDecision;
import java.util.List;
public interface TraderRiskDecisionRepository {
void insert(TraderRiskDecision decision);
List<TraderRiskDecision> findByCycleId(String runId, String cycleId);
}
@@ -0,0 +1,12 @@
package com.quantai.trader.persistence;
import com.quantai.trader.domain.TraderTrainingSample;
import java.util.List;
public interface TraderSampleRepository {
void insert(TraderTrainingSample sample);
List<TraderTrainingSample> findByRunId(String runId);
}