diff --git a/pom.xml b/pom.xml
index ef9ecd9..d4e2f78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,11 +15,11 @@
quant-trader-service
0.0.1-SNAPSHOT
quant-trader-service
- Clean P0 rebuild of the Trader-style strategy service.
+ Trader V4 P0 decision service: REPLAY_SIM and SHADOW only.
21
- 3.5.16
+ 0.8.13
@@ -35,15 +35,6 @@
org.springframework.boot
spring-boot-starter-webmvc
-
- org.springframework.boot
- spring-boot-starter-jdbc
-
-
- com.baomidou
- mybatis-plus-spring-boot4-starter
- ${mybatis-plus.version}
-
com.fasterxml.jackson.dataformat
jackson-dataformat-yaml
@@ -53,9 +44,8 @@
jackson-datatype-jsr310
- org.apache.commons
- commons-csv
- 1.10.0
+ org.springframework.boot
+ spring-boot-starter-jdbc
org.springframework.boot
@@ -99,6 +89,46 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+ check-line-coverage
+ test
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ LINE
+ COVEREDRATIO
+ 0.90
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/quantai/trader/QuantTraderServiceApplication.java b/src/main/java/com/quantai/trader/QuantTraderServiceApplication.java
index 437ef3e..e0d6aff 100644
--- a/src/main/java/com/quantai/trader/QuantTraderServiceApplication.java
+++ b/src/main/java/com/quantai/trader/QuantTraderServiceApplication.java
@@ -1,11 +1,9 @@
package com.quantai.trader;
-import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
-@MapperScan("com.quantai.trader.infrastructure.mapper")
@SpringBootApplication
@ConfigurationPropertiesScan
public class QuantTraderServiceApplication {
diff --git a/src/main/java/com/quantai/trader/artifact/TraderArtifactBundle.java b/src/main/java/com/quantai/trader/artifact/TraderArtifactBundle.java
new file mode 100644
index 0000000..5e15381
--- /dev/null
+++ b/src/main/java/com/quantai/trader/artifact/TraderArtifactBundle.java
@@ -0,0 +1,21 @@
+package com.quantai.trader.artifact;
+
+import com.quantai.trader.domain.TraderPmConfig;
+
+import java.util.Set;
+
+public record TraderArtifactBundle(
+ String modelBundleVersion,
+ String calibrationBundleVersion,
+ String pmConfigVersion,
+ String bundleHashSha256,
+ Set providedModels,
+ TraderPmConfig pmConfig
+) {
+ public TraderArtifactBundle {
+ if (providedModels == null || !providedModels.containsAll(Set.of("DIRECTION", "ENTRY", "CONTINUE", "EXIT", "RISK"))) {
+ throw new IllegalArgumentException("artifact bundle must provide all five V4 models");
+ }
+ pmConfig = java.util.Objects.requireNonNull(pmConfig, "pmConfig is required");
+ }
+}
diff --git a/src/main/java/com/quantai/trader/artifact/TraderArtifactLoader.java b/src/main/java/com/quantai/trader/artifact/TraderArtifactLoader.java
new file mode 100644
index 0000000..610e983
--- /dev/null
+++ b/src/main/java/com/quantai/trader/artifact/TraderArtifactLoader.java
@@ -0,0 +1,71 @@
+package com.quantai.trader.artifact;
+
+import com.quantai.trader.config.TraderProperties;
+import com.quantai.trader.domain.TraderException;
+import com.quantai.trader.domain.TraderPmConfig;
+import com.quantai.trader.enums.TraderErrorCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.Set;
+
+@Component
+public class TraderArtifactLoader {
+ private static final Logger log = LoggerFactory.getLogger(TraderArtifactLoader.class);
+
+ private final TraderProperties properties;
+
+ public TraderArtifactLoader(TraderProperties properties) {
+ this.properties = properties;
+ }
+
+ public TraderArtifactBundle loadActiveBundle() {
+ TraderProperties.Artifact artifact = properties.artifact();
+ if (artifact.modelBundleVersion().isBlank()
+ || artifact.calibrationBundleVersion().isBlank()
+ || artifact.pmConfigVersion().isBlank()) {
+ throw new TraderException(TraderErrorCode.TRADER_MODEL_ARTIFACT_MISSING,
+ "model/calibration/pm version is required");
+ }
+ TraderArtifactBundle bundle = deterministicP0Bundle(artifact);
+ log.info("event=trader.artifact.loaded modelBundleVersion={} calibrationBundleVersion={} pmConfigVersion={} providedModels={}",
+ bundle.modelBundleVersion(), bundle.calibrationBundleVersion(), bundle.pmConfigVersion(), bundle.providedModels());
+ return bundle;
+ }
+
+ private TraderArtifactBundle deterministicP0Bundle(TraderProperties.Artifact artifact) {
+ TraderPmConfig pmConfig = new TraderPmConfig(
+ artifact.pmConfigVersion(),
+ new TraderPmConfig.OpenRuleConfig(
+ new BigDecimal("0.58"), new BigDecimal("0.58"),
+ new BigDecimal("0.55"), new BigDecimal("0.55"),
+ new BigDecimal("0.45"), new BigDecimal("1.0"),
+ new BigDecimal("0.03"), new BigDecimal("0.10"), new BigDecimal("0.80")),
+ new TraderPmConfig.AddRuleConfig(
+ new BigDecimal("0.60"), new BigDecimal("0.60"),
+ new BigDecimal("0.58"), new BigDecimal("0.55"), new BigDecimal("0.45"),
+ new BigDecimal("0.45"), new BigDecimal("0.50"),
+ new BigDecimal("1.0"), BigDecimal.ZERO, new BigDecimal("0.10"),
+ new BigDecimal("500"), 3, 5),
+ new TraderPmConfig.ExitRuleConfig(
+ new BigDecimal("0.70"), new BigDecimal("0.70"), new BigDecimal("0.70"),
+ new BigDecimal("0.25"), new BigDecimal("0.62"),
+ new BigDecimal("0.35"), new BigDecimal("0.70"),
+ new BigDecimal("5.0"), new BigDecimal("80")),
+ new TraderPmConfig.SizingConfig(
+ new BigDecimal("0.80"), new BigDecimal("0.05"), BigDecimal.ONE,
+ new BigDecimal("0.02"), new BigDecimal("0.25"), BigDecimal.ONE,
+ new BigDecimal("1.0"), new BigDecimal("80"),
+ new BigDecimal("0.20"), new BigDecimal("0.50"), new BigDecimal("500"))
+ );
+ return new TraderArtifactBundle(
+ artifact.modelBundleVersion(),
+ artifact.calibrationBundleVersion(),
+ artifact.pmConfigVersion(),
+ "deterministic-p0-fixture",
+ Set.of("DIRECTION", "ENTRY", "CONTINUE", "EXIT", "RISK"),
+ pmConfig);
+ }
+}
diff --git a/src/main/java/com/quantai/trader/brain/LifecycleActionValueService.java b/src/main/java/com/quantai/trader/brain/LifecycleActionValueService.java
deleted file mode 100644
index 3df168d..0000000
--- a/src/main/java/com/quantai/trader/brain/LifecycleActionValueService.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.quantai.trader.brain;
-
-import com.quantai.trader.domain.ManagementDecision;
-import com.quantai.trader.domain.PlaybookCandidate;
-import com.quantai.trader.domain.TraderDecisionCycle;
-import com.quantai.trader.domain.TraderMarketSnapshot;
-import com.quantai.trader.domain.TraderPositionPath;
-import com.quantai.trader.enums.TraderActionType;
-import org.springframework.stereotype.Component;
-
-import java.util.Map;
-
-@Component
-public class LifecycleActionValueService {
-
- public ManagementDecision decide(
- TraderDecisionCycle cycle,
- PlaybookCandidate candidate,
- TraderPositionPath path,
- TraderMarketSnapshot snapshot
- ) {
- if (path != null && path.fullSize()) {
- return new ManagementDecision(TraderActionType.CLOSE, "P0_PROXY_CLOSE_AFTER_FULL_PATH", Map.of("proxyOnly", true));
- }
- return new ManagementDecision(TraderActionType.HOLD, "P0_PROXY_HOLD", Map.of("proxyOnly", true));
- }
-}
diff --git a/src/main/java/com/quantai/trader/brain/PlaybookCandidateEngine.java b/src/main/java/com/quantai/trader/brain/PlaybookCandidateEngine.java
deleted file mode 100644
index 0cce383..0000000
--- a/src/main/java/com/quantai/trader/brain/PlaybookCandidateEngine.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.quantai.trader.brain;
-
-import com.quantai.trader.domain.PlaybookCandidate;
-import com.quantai.trader.domain.TraderDecisionCycle;
-import com.quantai.trader.domain.TraderException;
-import com.quantai.trader.domain.TraderMarketSnapshot;
-import com.quantai.trader.domain.TraderPricePlan;
-import com.quantai.trader.enums.TraderErrorCode;
-import com.quantai.trader.enums.TraderSide;
-import com.quantai.trader.playbook.TraderPlaybookCatalog;
-import com.quantai.trader.playbook.TraderPlaybookDefinitionSnapshot;
-import com.quantai.trader.util.Ids;
-import org.springframework.stereotype.Component;
-
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.Map;
-
-@Component
-public class PlaybookCandidateEngine {
-
- private final TraderPlaybookCatalog catalog;
-
- public PlaybookCandidateEngine(TraderPlaybookCatalog catalog) {
- this.catalog = catalog;
- }
-
- public List generate(TraderMarketSnapshot snapshot, TraderDecisionCycle cycle) {
- if (!Boolean.TRUE.equals(snapshot.setupFeatures().get("setupPass"))) {
- return List.of();
- }
- TraderPlaybookDefinitionSnapshot playbook = catalog.require(cycle.playbookId(), cycle.playbookVersion());
- BigDecimal entry = requiredDecimal(snapshot.setupFeatures(), "entryPrice");
- TraderPricePlan pricePlan = new TraderPricePlan(
- entry,
- requiredDecimal(snapshot.setupFeatures(), "invalidPrice"),
- requiredDecimal(snapshot.setupFeatures(), "stopPrice"),
- requiredDecimal(snapshot.setupFeatures(), "targetPrice"),
- null,
- 300_000,
- 7_200_000
- );
- return List.of(new PlaybookCandidate(
- cycle.runId(),
- cycle.cycleId(),
- Ids.candidateId(cycle, playbook.playbookId()),
- playbook.playbookId(),
- playbook.playbookVersion(),
- requiredSide(snapshot.setupFeatures(), "side"),
- playbook.variant(),
- snapshot.snapshotTime(),
- pricePlan,
- playbook.definition().plannedEntryLegRule().maxPlannedEntryLegs(),
- snapshot.setupFeatures()
- ));
- }
-
- private TraderSide requiredSide(Map map, String key) {
- Object value = map.get(key);
- if (value instanceof TraderSide side) {
- return side;
- }
- if (value instanceof String text && !text.isBlank()) {
- return TraderSide.valueOf(text.trim().toUpperCase());
- }
- throw new TraderException(
- TraderErrorCode.TRADER_ENTRY_PLAN_INCOMPLETE,
- "setup feature is required when setupPass=true: " + key
- );
- }
-
- private BigDecimal requiredDecimal(Map map, String key) {
- Object value = map.get(key);
- if (value instanceof Number number) {
- return BigDecimal.valueOf(number.doubleValue());
- }
- if (value instanceof String text && !text.isBlank()) {
- return new BigDecimal(text);
- }
- throw new TraderException(
- TraderErrorCode.TRADER_ENTRY_PLAN_INCOMPLETE,
- "setup feature is required when setupPass=true: " + key
- );
- }
-}
diff --git a/src/main/java/com/quantai/trader/brain/TraderContextGate.java b/src/main/java/com/quantai/trader/brain/TraderContextGate.java
deleted file mode 100644
index c8d4a83..0000000
--- a/src/main/java/com/quantai/trader/brain/TraderContextGate.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.quantai.trader.brain;
-
-import com.quantai.trader.domain.StageDecision;
-import com.quantai.trader.domain.TraderMarketSnapshot;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-import java.util.Map;
-
-@Component
-public class TraderContextGate {
-
- public StageDecision evaluate(TraderMarketSnapshot snapshot) {
- Object missing = snapshot.dataQuality().get("missing_features");
- if (missing instanceof List> list && !list.isEmpty()) {
- return new StageDecision(false, "DATA_MISSING", "TRADER_DATA_QUALITY_FAILED", Map.of(
- "missingFeatures", list
- ));
- }
- Object pass = snapshot.contextFeatures().get("contextPass");
- if (Boolean.FALSE.equals(pass)) {
- return StageDecision.block("CONTEXT_BLOCKED", "TRADER_RISK_BLOCKED");
- }
- return StageDecision.pass("CONTEXT_PASS");
- }
-}
diff --git a/src/main/java/com/quantai/trader/brain/TraderCycleResult.java b/src/main/java/com/quantai/trader/brain/TraderCycleResult.java
deleted file mode 100644
index 3d274a3..0000000
--- a/src/main/java/com/quantai/trader/brain/TraderCycleResult.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.quantai.trader.brain;
-
-import com.quantai.trader.domain.TraderAction;
-import com.quantai.trader.domain.TraderDecisionCycle;
-import com.quantai.trader.domain.TraderPositionPath;
-import com.quantai.trader.domain.TraderTrainingSample;
-
-public record TraderCycleResult(
- TraderDecisionCycle cycle,
- TraderAction action,
- TraderPositionPath path,
- TraderTrainingSample sample
-) {
-}
diff --git a/src/main/java/com/quantai/trader/brain/TraderDecisionCycleRunner.java b/src/main/java/com/quantai/trader/brain/TraderDecisionCycleRunner.java
deleted file mode 100644
index bb5dad9..0000000
--- a/src/main/java/com/quantai/trader/brain/TraderDecisionCycleRunner.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package com.quantai.trader.brain;
-
-import com.quantai.trader.domain.ExecutionDecision;
-import com.quantai.trader.domain.ManagementDecision;
-import com.quantai.trader.domain.PlaybookCandidate;
-import com.quantai.trader.domain.RiskDecision;
-import com.quantai.trader.domain.StageDecision;
-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.TraderTrainingSample;
-import com.quantai.trader.evidence.EvidenceAppender;
-import com.quantai.trader.enums.TraderState;
-import com.quantai.trader.execution.ExecutionQualityGate;
-import com.quantai.trader.execution.TraderEntryPlanner;
-import com.quantai.trader.market.SnapshotBuilder;
-import com.quantai.trader.position.TraderPositionManager;
-import com.quantai.trader.replay.ReplayClockTick;
-import com.quantai.trader.risk.TraderRiskGate;
-import com.quantai.trader.sample.TrainingSampleExporter;
-import com.quantai.trader.state.TraderDecisionCycleFactory;
-import com.quantai.trader.state.TraderRuntimeState;
-import com.quantai.trader.state.TraderStateMachine;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-import java.util.Optional;
-
-@Component
-public class TraderDecisionCycleRunner {
-
- private final SnapshotBuilder snapshotBuilder;
- private final TraderContextGate contextGate;
- private final PlaybookCandidateEngine playbookCandidateEngine;
- private final TriggerMarkoutService triggerMarkoutService;
- private final TraderEntryPlanner entryPlanner;
- private final ExecutionQualityGate executionQualityGate;
- private final TraderRiskGate riskGate;
- private final TraderStateMachine stateMachine;
- private final TraderPositionManager positionManager;
- private final LifecycleActionValueService actionValueService;
- private final EvidenceAppender evidenceAppender;
- private final TrainingSampleExporter sampleExporter;
-
- public TraderDecisionCycleRunner(
- SnapshotBuilder snapshotBuilder,
- TraderContextGate contextGate,
- PlaybookCandidateEngine playbookCandidateEngine,
- TriggerMarkoutService triggerMarkoutService,
- TraderEntryPlanner entryPlanner,
- ExecutionQualityGate executionQualityGate,
- TraderRiskGate riskGate,
- TraderStateMachine stateMachine,
- TraderPositionManager positionManager,
- LifecycleActionValueService actionValueService,
- EvidenceAppender evidenceAppender,
- TrainingSampleExporter sampleExporter
- ) {
- this.snapshotBuilder = snapshotBuilder;
- this.contextGate = contextGate;
- this.playbookCandidateEngine = playbookCandidateEngine;
- this.triggerMarkoutService = triggerMarkoutService;
- this.entryPlanner = entryPlanner;
- this.executionQualityGate = executionQualityGate;
- this.riskGate = riskGate;
- this.stateMachine = stateMachine;
- this.positionManager = positionManager;
- this.actionValueService = actionValueService;
- this.evidenceAppender = evidenceAppender;
- this.sampleExporter = sampleExporter;
- }
-
- public TraderCycleResult runReplayTick(ReplayClockTick tick, TraderRuntimeState runtimeState) {
- TraderMarketSnapshot snapshot = snapshotBuilder.build(tick, runtimeState);
- TraderDecisionCycle cycle = TraderDecisionCycleFactory.create(snapshot, runtimeState);
-
- StageDecision context = contextGate.evaluate(snapshot);
- evidenceAppender.append(cycle, "CONTEXT_GATE", context);
- if (context.blocked()) {
- TraderTrainingSample sample = sampleExporter.export(cycle.withState(TraderState.BLOCKED, "BLOCKED", context.blocker()), snapshot, null, null, null);
- return new TraderCycleResult(cycle, null, null, sample);
- }
-
- List candidates = playbookCandidateEngine.generate(snapshot, cycle);
- if (candidates.isEmpty()) {
- evidenceAppender.append(cycle, "PLAYBOOK_CANDIDATE", StageDecision.block("NO_PLAYBOOK_CANDIDATE", "NO_PLAYBOOK_CANDIDATE"));
- TraderTrainingSample sample = sampleExporter.export(cycle, snapshot, null, null, null);
- return new TraderCycleResult(cycle, null, null, sample);
- }
-
- PlaybookCandidate selected = candidates.getFirst();
- var trigger = triggerMarkoutService.evaluate(snapshot, selected);
- evidenceAppender.append(cycle, "TRIGGER_MARKOUT", new StageDecision(trigger.pass(), trigger.reason(), trigger.blocker(), trigger.details()));
- if (trigger.blocked()) {
- TraderTrainingSample sample = sampleExporter.export(cycle.withState(TraderState.TRIGGER_WAIT, "WAIT", trigger.blocker()), snapshot, selected, null, null);
- return new TraderCycleResult(cycle, null, null, sample);
- }
-
- TraderDecisionCycle entryCycle = cycle.withState(TraderState.ENTRY_PLANNED, "RUNNING", null);
- TraderEntryPlan entryPlan = entryPlanner.planInitialEntry(entryCycle, selected, trigger);
- ExecutionDecision execution = executionQualityGate.evaluate(snapshot, entryPlan);
- evidenceAppender.append(entryCycle, "EXECUTION_QUALITY", new StageDecision(execution.pass(), execution.reason(), execution.blocker(), execution.details()));
- RiskDecision risk = riskGate.evaluate(entryCycle, entryPlan, execution);
- evidenceAppender.append(entryCycle, "RISK_GATE", new StageDecision(risk.allowAction(), risk.allowAction() ? "RISK_PASS" : "RISK_BLOCKED", risk.blocker(), risk.details()));
- if (execution.blocked() || risk.blocked()) {
- TraderTrainingSample sample = sampleExporter.export(entryCycle.withState(TraderState.BLOCKED, "BLOCKED", risk.blocker()), snapshot, selected, null, null);
- return new TraderCycleResult(entryCycle, null, null, sample);
- }
-
- TraderAction action = stateMachine.toInitialEntryAction(entryCycle, selected, entryPlan);
- TraderPositionPath path = positionManager.simulateOrUpdate(entryCycle, action, snapshot);
- evidenceAppender.append(entryCycle, "OPEN_INITIAL", StageDecision.pass(action.reason()));
- TraderLifecycleResult lifecycle = runPositionLifecycle(entryCycle, selected, action, path, snapshot);
- TraderTrainingSample sample = sampleExporter.export(
- lifecycle.finalCycle(),
- snapshot,
- selected,
- lifecycle.lastAction(),
- lifecycle.finalPath()
- );
- return new TraderCycleResult(lifecycle.finalCycle(), lifecycle.lastAction(), lifecycle.finalPath(), sample);
- }
-
- private TraderLifecycleResult runPositionLifecycle(
- TraderDecisionCycle initialCycle,
- PlaybookCandidate candidate,
- TraderAction initialAction,
- TraderPositionPath initialPath,
- TraderMarketSnapshot snapshot
- ) {
- TraderDecisionCycle cycle = initialCycle;
- TraderPositionPath path = initialPath;
- TraderAction lastAction = initialAction;
-
- if (!path.fullSize()) {
- cycle = cycle.withState(TraderState.PLANNED_LEG_WAIT, "RUNNING", null);
- Optional plannedLeg = entryPlanner.planNextDeclaredLeg(cycle, candidate, path);
- if (plannedLeg.isPresent()) {
- ExecutionDecision execution = executionQualityGate.evaluate(snapshot, plannedLeg.get());
- RiskDecision risk = riskGate.evaluate(cycle, plannedLeg.get(), execution);
- evidenceAppender.append(cycle, "PLANNED_LEG_EXECUTION", new StageDecision(execution.pass(), execution.reason(), execution.blocker(), execution.details()));
- evidenceAppender.append(cycle, "PLANNED_LEG_RISK", new StageDecision(risk.allowAction(), risk.allowAction() ? "RISK_PASS" : "RISK_BLOCKED", risk.blocker(), risk.details()));
- if (execution.pass() && risk.allowAction()) {
- TraderAction legAction = stateMachine.toPlannedLegAction(cycle, plannedLeg.get(), path);
- path = positionManager.simulateOrUpdate(cycle, legAction, snapshot);
- evidenceAppender.append(cycle, "OPEN_PLANNED_LEG", StageDecision.pass(legAction.reason()));
- lastAction = legAction;
- }
- }
- }
-
- cycle = cycle.withState(TraderState.MANAGING, "RUNNING", null);
- ManagementDecision management = actionValueService.decide(cycle, candidate, path, snapshot);
- evidenceAppender.append(cycle, "ACTION_VALUE", new StageDecision(true, management.reason(), null, management.details()));
- TraderAction managementAction = stateMachine.toManagementAction(cycle, path, management.actionType());
- riskGate.evaluateManagement(cycle, managementAction, path, management);
- path = positionManager.simulateOrUpdate(cycle, managementAction, snapshot);
- evidenceAppender.append(cycle, "MANAGEMENT_ACTION", StageDecision.pass(managementAction.reason()));
- lastAction = managementAction;
-
- return new TraderLifecycleResult(cycle.withState(TraderState.SAMPLE_EXPORTED, "COMPLETED", null), path, lastAction);
- }
-}
diff --git a/src/main/java/com/quantai/trader/brain/TraderLifecycleResult.java b/src/main/java/com/quantai/trader/brain/TraderLifecycleResult.java
deleted file mode 100644
index 6fca9c3..0000000
--- a/src/main/java/com/quantai/trader/brain/TraderLifecycleResult.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.quantai.trader.brain;
-
-import com.quantai.trader.domain.TraderAction;
-import com.quantai.trader.domain.TraderDecisionCycle;
-import com.quantai.trader.domain.TraderPositionPath;
-
-public record TraderLifecycleResult(
- TraderDecisionCycle finalCycle,
- TraderPositionPath finalPath,
- TraderAction lastAction
-) {
-}
diff --git a/src/main/java/com/quantai/trader/brain/TriggerMarkoutService.java b/src/main/java/com/quantai/trader/brain/TriggerMarkoutService.java
deleted file mode 100644
index a4d45a6..0000000
--- a/src/main/java/com/quantai/trader/brain/TriggerMarkoutService.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.quantai.trader.brain;
-
-import com.quantai.trader.domain.PlaybookCandidate;
-import com.quantai.trader.domain.TraderMarketSnapshot;
-import com.quantai.trader.domain.TriggerDecision;
-import org.springframework.stereotype.Component;
-
-import java.math.BigDecimal;
-import java.util.Map;
-
-@Component
-public class TriggerMarkoutService {
-
- public TriggerDecision evaluate(TraderMarketSnapshot snapshot, PlaybookCandidate candidate) {
- BigDecimal score = readScore(snapshot.triggerFeatures().get("triggerScore"));
- if (score == null) {
- return new TriggerDecision(false, BigDecimal.ZERO, "TRIGGER_SCORE_MISSING", "NO_TRIGGER_MARKOUT", Map.of());
- }
- if (score.compareTo(new BigDecimal("0.50")) < 0) {
- return new TriggerDecision(false, score, "TRIGGER_WAIT", "NO_TRIGGER_MARKOUT", Map.of(
- "triggerScore", score
- ));
- }
- return new TriggerDecision(true, score, "TRIGGER_ACCEPTED", null, Map.of(
- "triggerScore", score,
- "proxyOnly", true
- ));
- }
-
- private BigDecimal readScore(Object value) {
- if (value instanceof Number number) {
- return BigDecimal.valueOf(number.doubleValue());
- }
- if (value instanceof String text && !text.isBlank()) {
- return new BigDecimal(text);
- }
- return null;
- }
-}
diff --git a/src/main/java/com/quantai/trader/config/TraderProperties.java b/src/main/java/com/quantai/trader/config/TraderProperties.java
index 52e60d6..11c0462 100644
--- a/src/main/java/com/quantai/trader/config/TraderProperties.java
+++ b/src/main/java/com/quantai/trader/config/TraderProperties.java
@@ -1,299 +1,92 @@
package com.quantai.trader.config;
+import com.quantai.trader.enums.TraderExecutionMode;
import com.quantai.trader.enums.TraderRunMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.math.BigDecimal;
+import static com.quantai.trader.util.TraderNumbers.requiredText;
+
@ConfigurationProperties(prefix = "trader")
-public class TraderProperties {
-
- private String serviceName = "quant-trader-service";
- private TraderRunMode runMode = TraderRunMode.REPLAY;
- private String symbol = "BTCUSDT";
- private String featureVersion = "trader_feature_v0";
- private String labelVersion = "trader_label_v0";
- private Playbook playbook = new Playbook();
- private Replay replay = new Replay();
- private Integration integration = new Integration();
- private Risk risk = new Risk();
- private Sizing sizing = new Sizing();
- private DataSource dataSource = new DataSource();
-
- public String getServiceName() {
- return serviceName;
+public record TraderProperties(
+ String serviceName,
+ TraderRunMode runMode,
+ String symbol,
+ Artifact artifact,
+ Feedback feedback,
+ Execution execution,
+ Runtime runtime,
+ Outbox outbox,
+ Release release,
+ Risk risk,
+ PositionManager positionManager
+) {
+ public TraderProperties {
+ serviceName = defaultText(serviceName, "quant-trader-service");
+ runMode = runMode == null ? TraderRunMode.SHADOW : runMode;
+ symbol = defaultText(symbol, "BTC-USDT-PERP");
+ artifact = artifact == null ? new Artifact("trader-v4-btc-p0", "cal-v4-btc-p0", "pm-v4-btc-p0", ".") : artifact;
+ feedback = feedback == null ? new Feedback(false) : feedback;
+ execution = execution == null ? new Execution(TraderExecutionMode.SHADOW, 3, 1500) : execution;
+ runtime = runtime == null ? new Runtime("trader:v4", true, false) : runtime;
+ outbox = outbox == null ? new Outbox(true, 5) : outbox;
+ release = release == null ? new Release(true, true, true) : release;
+ risk = risk == null ? new Risk(new BigDecimal("200"), BigDecimal.ONE, new BigDecimal("500")) : risk;
+ positionManager = positionManager == null ? new PositionManager(BigDecimal.ONE, BigDecimal.ONE) : positionManager;
}
- public void setServiceName(String serviceName) {
- this.serviceName = serviceName;
+ private static String defaultText(String value, String defaultValue) {
+ return value == null || value.isBlank() ? defaultValue : value;
}
- public TraderRunMode getRunMode() {
- return runMode;
- }
-
- public void setRunMode(TraderRunMode runMode) {
- this.runMode = runMode;
- }
-
- public String getSymbol() {
- return symbol;
- }
-
- public void setSymbol(String symbol) {
- this.symbol = symbol;
- }
-
- public String getFeatureVersion() {
- return featureVersion;
- }
-
- public void setFeatureVersion(String featureVersion) {
- this.featureVersion = featureVersion;
- }
-
- public String getLabelVersion() {
- return labelVersion;
- }
-
- public void setLabelVersion(String labelVersion) {
- this.labelVersion = labelVersion;
- }
-
- public Playbook getPlaybook() {
- return playbook;
- }
-
- public void setPlaybook(Playbook playbook) {
- this.playbook = playbook;
- }
-
- public Replay getReplay() {
- return replay;
- }
-
- public void setReplay(Replay replay) {
- this.replay = replay;
- }
-
- public Integration getIntegration() {
- return integration;
- }
-
- public void setIntegration(Integration integration) {
- this.integration = integration;
- }
-
- public Risk getRisk() {
- return risk;
- }
-
- public void setRisk(Risk risk) {
- this.risk = risk;
- }
-
- public Sizing getSizing() {
- return sizing;
- }
-
- public void setSizing(Sizing sizing) {
- this.sizing = sizing;
- }
-
- public DataSource getDataSource() {
- return dataSource;
- }
-
- public void setDataSource(DataSource dataSource) {
- this.dataSource = dataSource;
- }
-
- public static class Playbook {
- private String locationPattern = "classpath:/playbooks/*.yml";
-
- public String getLocationPattern() {
- return locationPattern;
- }
-
- public void setLocationPattern(String locationPattern) {
- this.locationPattern = locationPattern;
+ public record Artifact(
+ String modelBundleVersion,
+ String calibrationBundleVersion,
+ String pmConfigVersion,
+ String artifactRoot
+ ) {
+ public Artifact {
+ modelBundleVersion = requiredText(modelBundleVersion, "artifact.modelBundleVersion");
+ calibrationBundleVersion = requiredText(calibrationBundleVersion, "artifact.calibrationBundleVersion");
+ pmConfigVersion = requiredText(pmConfigVersion, "artifact.pmConfigVersion");
+ artifactRoot = requiredText(artifactRoot, "artifact.artifactRoot");
}
}
- public static class Replay {
- private String outputDir = "/Users/zach/Desktop/app/trader/replay-output";
- private boolean failOnDataMissing = true;
+ public record Feedback(boolean httpEnabled) {
+ }
- public String getOutputDir() {
- return outputDir;
- }
-
- public void setOutputDir(String outputDir) {
- this.outputDir = outputDir;
- }
-
- public boolean isFailOnDataMissing() {
- return failOnDataMissing;
- }
-
- public void setFailOnDataMissing(boolean failOnDataMissing) {
- this.failOnDataMissing = failOnDataMissing;
+ public record Execution(TraderExecutionMode mode, int maxApiErrorCount, long maxExchangeLatencyMs) {
+ public Execution {
+ mode = mode == null ? TraderExecutionMode.SHADOW : mode;
}
}
- public static class Integration {
- private String appActionChannel = "JAR_FUTURE";
- private boolean httpFeedbackEnabled = false;
-
- public String getAppActionChannel() {
- return appActionChannel;
- }
-
- public void setAppActionChannel(String appActionChannel) {
- this.appActionChannel = appActionChannel;
- }
-
- public boolean isHttpFeedbackEnabled() {
- return httpFeedbackEnabled;
- }
-
- public void setHttpFeedbackEnabled(boolean httpFeedbackEnabled) {
- this.httpFeedbackEnabled = httpFeedbackEnabled;
+ public record Runtime(String redisKeyPrefix, boolean requireRedisForOpenAdd, boolean tradingEnabled) {
+ public Runtime {
+ redisKeyPrefix = defaultText(redisKeyPrefix, "trader:v4");
}
}
- public static class Risk {
- private BigDecimal leverageScreen = BigDecimal.TEN;
- private boolean requireOneXNotNegative = true;
- private int maxPlannedEntryLegs = 3;
- private boolean allowFreeScaleIn = false;
- private boolean allowReduceThenAdd = false;
- private boolean requireStop = true;
- private boolean requireTarget = true;
- private boolean requireInvalid = true;
+ public record Outbox(boolean enabled, int maxRetryCount) {
+ }
- public BigDecimal getLeverageScreen() {
- return leverageScreen;
- }
+ public record Release(boolean requireReviewForPaper, boolean requireReviewForLiveProbe, boolean activePointerCheckEnabled) {
+ }
- public void setLeverageScreen(BigDecimal leverageScreen) {
- this.leverageScreen = leverageScreen;
- }
-
- public boolean isRequireOneXNotNegative() {
- return requireOneXNotNegative;
- }
-
- public void setRequireOneXNotNegative(boolean requireOneXNotNegative) {
- this.requireOneXNotNegative = requireOneXNotNegative;
- }
-
- public int getMaxPlannedEntryLegs() {
- return maxPlannedEntryLegs;
- }
-
- public void setMaxPlannedEntryLegs(int maxPlannedEntryLegs) {
- this.maxPlannedEntryLegs = maxPlannedEntryLegs;
- }
-
- public boolean isAllowFreeScaleIn() {
- return allowFreeScaleIn;
- }
-
- public void setAllowFreeScaleIn(boolean allowFreeScaleIn) {
- this.allowFreeScaleIn = allowFreeScaleIn;
- }
-
- public boolean isAllowReduceThenAdd() {
- return allowReduceThenAdd;
- }
-
- public void setAllowReduceThenAdd(boolean allowReduceThenAdd) {
- this.allowReduceThenAdd = allowReduceThenAdd;
- }
-
- public boolean isRequireStop() {
- return requireStop;
- }
-
- public void setRequireStop(boolean requireStop) {
- this.requireStop = requireStop;
- }
-
- public boolean isRequireTarget() {
- return requireTarget;
- }
-
- public void setRequireTarget(boolean requireTarget) {
- this.requireTarget = requireTarget;
- }
-
- public boolean isRequireInvalid() {
- return requireInvalid;
- }
-
- public void setRequireInvalid(boolean requireInvalid) {
- this.requireInvalid = requireInvalid;
+ public record Risk(BigDecimal maxDailyLossBps, BigDecimal maxTotalExposureRatio, BigDecimal minLiquidationBufferBps) {
+ public Risk {
+ maxDailyLossBps = maxDailyLossBps == null ? new BigDecimal("200") : maxDailyLossBps;
+ maxTotalExposureRatio = maxTotalExposureRatio == null ? BigDecimal.ONE : maxTotalExposureRatio;
+ minLiquidationBufferBps = minLiquidationBufferBps == null ? new BigDecimal("500") : minLiquidationBufferBps;
}
}
- public static class Sizing {
- private String method = "SIGNAL_EXECUTION_RISK_DYNAMIC";
- private boolean allowFullInitialEntry = true;
- private int maxPlannedEntryLegs = 3;
- private BigDecimal maxTotalPositionRatio = BigDecimal.ONE;
- private BigDecimal maxSingleLegRatio = BigDecimal.ONE;
-
- public String getMethod() {
- return method;
- }
-
- public void setMethod(String method) {
- this.method = method;
- }
-
- public boolean isAllowFullInitialEntry() {
- return allowFullInitialEntry;
- }
-
- public void setAllowFullInitialEntry(boolean allowFullInitialEntry) {
- this.allowFullInitialEntry = allowFullInitialEntry;
- }
-
- public int getMaxPlannedEntryLegs() {
- return maxPlannedEntryLegs;
- }
-
- public void setMaxPlannedEntryLegs(int maxPlannedEntryLegs) {
- this.maxPlannedEntryLegs = maxPlannedEntryLegs;
- }
-
- public BigDecimal getMaxTotalPositionRatio() {
- return maxTotalPositionRatio;
- }
-
- public void setMaxTotalPositionRatio(BigDecimal maxTotalPositionRatio) {
- this.maxTotalPositionRatio = maxTotalPositionRatio;
- }
-
- public BigDecimal getMaxSingleLegRatio() {
- return maxSingleLegRatio;
- }
-
- public void setMaxSingleLegRatio(BigDecimal maxSingleLegRatio) {
- this.maxSingleLegRatio = maxSingleLegRatio;
- }
- }
-
- public static class DataSource {
- private String hashMode = "FULL_HASH_OR_SCHEMA_ROW_TIME_MISSING_SUMMARY";
-
- public String getHashMode() {
- return hashMode;
- }
-
- public void setHashMode(String hashMode) {
- this.hashMode = hashMode;
+ public record PositionManager(BigDecimal maxSingleLegRatio, BigDecimal maxTotalPositionRatio) {
+ public PositionManager {
+ maxSingleLegRatio = maxSingleLegRatio == null ? BigDecimal.ONE : maxSingleLegRatio;
+ maxTotalPositionRatio = maxTotalPositionRatio == null ? BigDecimal.ONE : maxTotalPositionRatio;
}
}
}
diff --git a/src/main/java/com/quantai/trader/controller/TraderApiError.java b/src/main/java/com/quantai/trader/controller/TraderApiError.java
index b778893..f1863d1 100644
--- a/src/main/java/com/quantai/trader/controller/TraderApiError.java
+++ b/src/main/java/com/quantai/trader/controller/TraderApiError.java
@@ -1,7 +1,6 @@
package com.quantai.trader.controller;
-public record TraderApiError(
- String code,
- String message
-) {
+import com.quantai.trader.enums.TraderErrorCode;
+
+public record TraderApiError(TraderErrorCode code, String message) {
}
diff --git a/src/main/java/com/quantai/trader/controller/TraderApiExceptionHandler.java b/src/main/java/com/quantai/trader/controller/TraderApiExceptionHandler.java
index 26425f8..89d3fe6 100644
--- a/src/main/java/com/quantai/trader/controller/TraderApiExceptionHandler.java
+++ b/src/main/java/com/quantai/trader/controller/TraderApiExceptionHandler.java
@@ -1,21 +1,19 @@
package com.quantai.trader.controller;
import com.quantai.trader.domain.TraderException;
-import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class TraderApiExceptionHandler {
-
@ExceptionHandler(TraderException.class)
- public ResponseEntity handleTraderException(TraderException ex) {
- return ResponseEntity.badRequest().body(new TraderApiError(ex.errorCode().name(), ex.getMessage()));
+ ResponseEntity traderException(TraderException exception) {
+ return ResponseEntity.badRequest().body(new TraderApiError(exception.code(), exception.getMessage()));
}
@ExceptionHandler(IllegalArgumentException.class)
- public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) {
- return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new TraderApiError("TRADER_NOT_FOUND", ex.getMessage()));
+ ResponseEntity illegalArgument(IllegalArgumentException exception) {
+ return ResponseEntity.badRequest().body(new TraderApiError(com.quantai.trader.enums.TraderErrorCode.TRADER_MODEL_OUTPUT_INVALID, exception.getMessage()));
}
}
diff --git a/src/main/java/com/quantai/trader/controller/TraderFeedbackController.java b/src/main/java/com/quantai/trader/controller/TraderFeedbackController.java
index a85163d..61f3adb 100644
--- a/src/main/java/com/quantai/trader/controller/TraderFeedbackController.java
+++ b/src/main/java/com/quantai/trader/controller/TraderFeedbackController.java
@@ -1,48 +1,37 @@
package com.quantai.trader.controller;
import com.quantai.trader.config.TraderProperties;
+import com.quantai.trader.domain.FeedbackValidator;
+import com.quantai.trader.domain.TraderAppFeedback;
+import com.quantai.trader.domain.TraderException;
import com.quantai.trader.enums.TraderErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
-@RequestMapping("/api/trader/feedback")
public class TraderFeedbackController {
-
private static final Logger log = LoggerFactory.getLogger(TraderFeedbackController.class);
private final TraderProperties properties;
+ private final FeedbackValidator feedbackValidator;
- public TraderFeedbackController(TraderProperties properties) {
+ public TraderFeedbackController(TraderProperties properties, FeedbackValidator feedbackValidator) {
this.properties = properties;
+ this.feedbackValidator = feedbackValidator;
}
- @PostMapping
- public ResponseEntity> feedback(@RequestBody TraderFeedbackRequest request) {
- if (!properties.getIntegration().isHttpFeedbackEnabled()) {
- log.info(
- "event=trader.feedback.rejected runId={} cycleId={} actionId={} reason={}",
- request.runId(),
- request.cycleId(),
- request.actionId(),
- TraderErrorCode.TRADER_FEEDBACK_DISABLED
- );
- return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new TraderApiError(
- TraderErrorCode.TRADER_FEEDBACK_DISABLED.name(),
- "P0 feedback endpoint is disabled; future App main channel is trader-core jar"
- ));
+ @PostMapping("/api/trader/feedback")
+ public Map feedback(@RequestBody TraderAppFeedback feedback) {
+ if (!properties.feedback().httpEnabled()) {
+ throw new TraderException(TraderErrorCode.TRADER_FEEDBACK_INVALID, "HTTP feedback is disabled in P0");
}
- return ResponseEntity.accepted().body(Map.of(
- "status", "ACCEPTED_CONTRACT_ONLY",
- "runId", request.runId(),
- "actionId", request.actionId()
- ));
+ feedbackValidator.validateP0(feedback);
+ log.info("event=trader.feedback.accepted runId={} cycleId={} actionId={} source={}",
+ feedback.runId(), feedback.cycleId(), feedback.actionId(), feedback.feedbackSource());
+ return Map.of("accepted", true, "feedbackId", feedback.feedbackId());
}
}
diff --git a/src/main/java/com/quantai/trader/controller/TraderFeedbackRequest.java b/src/main/java/com/quantai/trader/controller/TraderFeedbackRequest.java
deleted file mode 100644
index d8ad943..0000000
--- a/src/main/java/com/quantai/trader/controller/TraderFeedbackRequest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.quantai.trader.controller;
-
-import com.quantai.trader.enums.TraderFeedbackSource;
-import com.quantai.trader.enums.TraderFeedbackType;
-
-import java.math.BigDecimal;
-import java.util.Map;
-
-public record TraderFeedbackRequest(
- String runId,
- String cycleId,
- String actionId,
- TraderFeedbackType feedbackType,
- TraderFeedbackSource feedbackSource,
- boolean realFill,
- String proxyMethod,
- String simulatorVersion,
- String orderId,
- String positionId,
- BigDecimal filledPrice,
- BigDecimal filledQuantity,
- BigDecimal fee,
- BigDecimal slippageBps,
- Map rawFeedback
-) {
-}
diff --git a/src/main/java/com/quantai/trader/controller/TraderHealthController.java b/src/main/java/com/quantai/trader/controller/TraderHealthController.java
index a5afde1..fc999ed 100644
--- a/src/main/java/com/quantai/trader/controller/TraderHealthController.java
+++ b/src/main/java/com/quantai/trader/controller/TraderHealthController.java
@@ -1,33 +1,28 @@
package com.quantai.trader.controller;
import com.quantai.trader.config.TraderProperties;
-import com.quantai.trader.playbook.TraderPlaybookCatalog;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
-@RequestMapping("/api/trader")
public class TraderHealthController {
-
private final TraderProperties properties;
- private final TraderPlaybookCatalog catalog;
- public TraderHealthController(TraderProperties properties, TraderPlaybookCatalog catalog) {
+ public TraderHealthController(TraderProperties properties) {
this.properties = properties;
- this.catalog = catalog;
}
- @GetMapping("/health")
+ @GetMapping("/api/trader/health")
public Map health() {
return Map.of(
- "service", properties.getServiceName(),
- "runMode", properties.getRunMode(),
- "symbol", properties.getSymbol(),
- "playbookCount", catalog.list().size(),
- "httpFeedbackEnabled", properties.getIntegration().isHttpFeedbackEnabled()
- );
+ "status", "UP",
+ "runMode", properties.runMode(),
+ "executionMode", properties.execution().mode(),
+ "modelBundleVersion", properties.artifact().modelBundleVersion(),
+ "calibrationBundleVersion", properties.artifact().calibrationBundleVersion(),
+ "pmConfigVersion", properties.artifact().pmConfigVersion(),
+ "tradingEnabled", properties.runtime().tradingEnabled());
}
}
diff --git a/src/main/java/com/quantai/trader/controller/TraderPlaybookController.java b/src/main/java/com/quantai/trader/controller/TraderPlaybookController.java
deleted file mode 100644
index 12477e5..0000000
--- a/src/main/java/com/quantai/trader/controller/TraderPlaybookController.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.quantai.trader.controller;
-
-import com.quantai.trader.playbook.TraderPlaybookCatalog;
-import com.quantai.trader.playbook.TraderPlaybookDefinitionSnapshot;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/trader/playbooks")
-public class TraderPlaybookController {
-
- private final TraderPlaybookCatalog catalog;
-
- public TraderPlaybookController(TraderPlaybookCatalog catalog) {
- this.catalog = catalog;
- }
-
- @GetMapping
- public List list() {
- return catalog.list().stream().map(PlaybookResponse::from).toList();
- }
-
- @GetMapping("/{playbookId}")
- public PlaybookResponse get(@PathVariable String playbookId) {
- return PlaybookResponse.from(catalog.require(playbookId));
- }
-
- public record PlaybookResponse(
- String playbookId,
- String playbookVersion,
- String family,
- String variant,
- String definitionHashSha256,
- List outputActions
- ) {
-
- static PlaybookResponse from(TraderPlaybookDefinitionSnapshot snapshot) {
- return new PlaybookResponse(
- snapshot.playbookId(),
- snapshot.playbookVersion(),
- snapshot.family(),
- snapshot.variant(),
- snapshot.definitionHashSha256(),
- snapshot.definition().outputActions()
- );
- }
- }
-}
diff --git a/src/main/java/com/quantai/trader/controller/TraderReplayController.java b/src/main/java/com/quantai/trader/controller/TraderReplayController.java
index 76653ce..c33e344 100644
--- a/src/main/java/com/quantai/trader/controller/TraderReplayController.java
+++ b/src/main/java/com/quantai/trader/controller/TraderReplayController.java
@@ -1,52 +1,22 @@
package com.quantai.trader.controller;
-import com.quantai.trader.domain.TraderReplayReport;
-import com.quantai.trader.persistence.ReplayReportRepository;
-import com.quantai.trader.replay.ReplayRun;
-import com.quantai.trader.replay.ReplayRunConfig;
-import com.quantai.trader.replay.ReplayRunResponse;
-import com.quantai.trader.replay.ReplayRunService;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
+import com.quantai.trader.replay.ReplayMarketEvent;
+import com.quantai.trader.replay.TraderCycleResult;
+import com.quantai.trader.replay.TraderP0CycleRunner;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
-@RequestMapping("/api/trader/replay/runs")
public class TraderReplayController {
+ private final TraderP0CycleRunner runner;
- private final ReplayRunService replayRunService;
- private final ReplayReportRepository reportRepository;
-
- public TraderReplayController(ReplayRunService replayRunService, ReplayReportRepository reportRepository) {
- this.replayRunService = replayRunService;
- this.reportRepository = reportRepository;
+ public TraderReplayController(TraderP0CycleRunner runner) {
+ this.runner = runner;
}
- @PostMapping
- @ResponseStatus(HttpStatus.CREATED)
- public ReplayRunResponse create(@RequestBody ReplayRunConfig config) {
- return replayRunService.createRun(config);
- }
-
- @GetMapping("/{runId}")
- public ReplayRun get(@PathVariable String runId) {
- return replayRunService.find(runId)
- .orElseThrow(() -> new IllegalArgumentException("replay run not found: " + runId));
- }
-
- @PostMapping("/{runId}/cancel")
- public ReplayRun cancel(@PathVariable String runId) {
- return replayRunService.cancel(runId);
- }
-
- @GetMapping("/{runId}/report")
- public TraderReplayReport report(@PathVariable String runId) {
- return reportRepository.findByRunId(runId)
- .orElseThrow(() -> new IllegalArgumentException("replay report not found: " + runId));
+ @PostMapping("/api/trader/replay/cycles")
+ public TraderCycleResult runOneCycle(@RequestBody ReplayMarketEvent event) {
+ return runner.runFlatCycle(event);
}
}
diff --git a/src/main/java/com/quantai/trader/core/TraderCoreDecisionRequest.java b/src/main/java/com/quantai/trader/core/TraderCoreDecisionRequest.java
new file mode 100644
index 0000000..370dbca
--- /dev/null
+++ b/src/main/java/com/quantai/trader/core/TraderCoreDecisionRequest.java
@@ -0,0 +1,23 @@
+package com.quantai.trader.core;
+
+import com.quantai.trader.domain.*;
+
+import java.time.Instant;
+import java.util.Map;
+
+public record TraderCoreDecisionRequest(
+ String requestId,
+ String runId,
+ String cycleId,
+ String symbol,
+ Instant eventTime,
+ TraderMarketSnapshot snapshot,
+ TraderPositionState positionState,
+ TraderAccountState accountState,
+ TraderExecutionState executionState,
+ String modelBundleVersion,
+ String calibrationBundleVersion,
+ String pmConfigVersion,
+ Map requestContextJson
+) {
+}
diff --git a/src/main/java/com/quantai/trader/core/TraderCoreDecisionResponse.java b/src/main/java/com/quantai/trader/core/TraderCoreDecisionResponse.java
new file mode 100644
index 0000000..af647a2
--- /dev/null
+++ b/src/main/java/com/quantai/trader/core/TraderCoreDecisionResponse.java
@@ -0,0 +1,19 @@
+package com.quantai.trader.core;
+
+import com.quantai.trader.domain.TraderAction;
+
+import java.util.Map;
+
+public record TraderCoreDecisionResponse(
+ String responseId,
+ String requestId,
+ String cycleId,
+ String modelOutputId,
+ String pmDecisionId,
+ String riskDecisionId,
+ boolean actionAllowed,
+ TraderAction action,
+ String blocker,
+ Map responseContextJson
+) {
+}
diff --git a/src/main/java/com/quantai/trader/core/TraderCoreFeedbackEvent.java b/src/main/java/com/quantai/trader/core/TraderCoreFeedbackEvent.java
new file mode 100644
index 0000000..0b6753b
--- /dev/null
+++ b/src/main/java/com/quantai/trader/core/TraderCoreFeedbackEvent.java
@@ -0,0 +1,24 @@
+package com.quantai.trader.core;
+
+import com.quantai.trader.enums.FeedbackSource;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.util.Map;
+
+public record TraderCoreFeedbackEvent(
+ String feedbackId,
+ String actionId,
+ FeedbackSource feedbackSource,
+ boolean realFill,
+ String orderId,
+ String orderStatus,
+ BigDecimal filledPrice,
+ BigDecimal filledQuantity,
+ BigDecimal fee,
+ BigDecimal slippageBps,
+ String rejectReason,
+ Instant eventTime,
+ Map rawFeedbackJson
+) {
+}
diff --git a/src/main/java/com/quantai/trader/core/TraderCoreHealth.java b/src/main/java/com/quantai/trader/core/TraderCoreHealth.java
new file mode 100644
index 0000000..db486f7
--- /dev/null
+++ b/src/main/java/com/quantai/trader/core/TraderCoreHealth.java
@@ -0,0 +1,11 @@
+package com.quantai.trader.core;
+
+public record TraderCoreHealth(
+ boolean ready,
+ String runMode,
+ String modelBundleVersion,
+ String calibrationBundleVersion,
+ String pmConfigVersion,
+ String blocker
+) {
+}
diff --git a/src/main/java/com/quantai/trader/domain/ContinueOutput.java b/src/main/java/com/quantai/trader/domain/ContinueOutput.java
new file mode 100644
index 0000000..9dbf164
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/ContinueOutput.java
@@ -0,0 +1,28 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+public record ContinueOutput(
+ BigDecimal longContinueProb,
+ BigDecimal shortContinueProb,
+ BigDecimal trendPersistenceProb,
+ BigDecimal holdEdgeBps,
+ BigDecimal continueVsExitEdgeBps,
+ String modelVersion,
+ String calibrationVersion,
+ Map explanation
+) {
+ public ContinueOutput {
+ longContinueProb = probability(longContinueProb, "continue.longContinueProb");
+ shortContinueProb = probability(shortContinueProb, "continue.shortContinueProb");
+ trendPersistenceProb = probability(trendPersistenceProb, "continue.trendPersistenceProb");
+ holdEdgeBps = required(holdEdgeBps, "continue.holdEdgeBps");
+ continueVsExitEdgeBps = required(continueVsExitEdgeBps, "continue.continueVsExitEdgeBps");
+ modelVersion = requiredText(modelVersion, "continue.modelVersion");
+ calibrationVersion = requiredText(calibrationVersion, "continue.calibrationVersion");
+ explanation = Map.copyOf(explanation == null ? Map.of() : explanation);
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/DirectionOutput.java b/src/main/java/com/quantai/trader/domain/DirectionOutput.java
new file mode 100644
index 0000000..f98ad9c
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/DirectionOutput.java
@@ -0,0 +1,34 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+public record DirectionOutput(
+ BigDecimal longProb,
+ BigDecimal shortProb,
+ BigDecimal neutralProb,
+ BigDecimal directionConfidence,
+ BigDecimal directionMargin,
+ BigDecimal expectedReturnBps,
+ Integer horizonMinutes,
+ String modelVersion,
+ String calibrationVersion,
+ Map explanation
+) {
+ public DirectionOutput {
+ longProb = probability(longProb, "direction.longProb");
+ shortProb = probability(shortProb, "direction.shortProb");
+ neutralProb = probability(neutralProb, "direction.neutralProb");
+ directionConfidence = probability(directionConfidence, "direction.directionConfidence");
+ directionMargin = nonNegative(directionMargin, "direction.directionMargin");
+ expectedReturnBps = required(expectedReturnBps, "direction.expectedReturnBps");
+ if (horizonMinutes == null || horizonMinutes <= 0) {
+ throw new IllegalArgumentException("direction.horizonMinutes must be > 0");
+ }
+ modelVersion = requiredText(modelVersion, "direction.modelVersion");
+ calibrationVersion = requiredText(calibrationVersion, "direction.calibrationVersion");
+ explanation = Map.copyOf(explanation == null ? Map.of() : explanation);
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/EntryOutput.java b/src/main/java/com/quantai/trader/domain/EntryOutput.java
new file mode 100644
index 0000000..752933d
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/EntryOutput.java
@@ -0,0 +1,40 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+public record EntryOutput(
+ BigDecimal longEntryProb,
+ BigDecimal shortEntryProb,
+ BigDecimal entryQualityScore,
+ BigDecimal expectedEdgeBps,
+ String pricePlanId,
+ String pricePlanConfigHash,
+ BigDecimal stopDistanceBps,
+ BigDecimal targetDistanceBps,
+ Integer maxHoldMinutes,
+ BigDecimal costBps,
+ String modelVersion,
+ String calibrationVersion,
+ Map explanation
+) {
+ public EntryOutput {
+ longEntryProb = probability(longEntryProb, "entry.longEntryProb");
+ shortEntryProb = probability(shortEntryProb, "entry.shortEntryProb");
+ entryQualityScore = probability(entryQualityScore, "entry.entryQualityScore");
+ expectedEdgeBps = required(expectedEdgeBps, "entry.expectedEdgeBps");
+ pricePlanId = requiredText(pricePlanId, "entry.pricePlanId");
+ pricePlanConfigHash = requiredText(pricePlanConfigHash, "entry.pricePlanConfigHash");
+ stopDistanceBps = positive(stopDistanceBps, "entry.stopDistanceBps");
+ targetDistanceBps = positive(targetDistanceBps, "entry.targetDistanceBps");
+ if (maxHoldMinutes == null || maxHoldMinutes <= 0) {
+ throw new IllegalArgumentException("entry.maxHoldMinutes must be > 0");
+ }
+ costBps = nonNegative(costBps, "entry.costBps");
+ modelVersion = requiredText(modelVersion, "entry.modelVersion");
+ calibrationVersion = requiredText(calibrationVersion, "entry.calibrationVersion");
+ explanation = Map.copyOf(explanation == null ? Map.of() : explanation);
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/ExecutionDecision.java b/src/main/java/com/quantai/trader/domain/ExecutionDecision.java
deleted file mode 100644
index 976a876..0000000
--- a/src/main/java/com/quantai/trader/domain/ExecutionDecision.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.math.BigDecimal;
-import java.util.Map;
-
-public record ExecutionDecision(
- boolean pass,
- BigDecimal executionQualityScore,
- String reason,
- String blocker,
- Map details
-) {
-
- public ExecutionDecision {
- details = Maps.immutable(details);
- }
-
- public boolean blocked() {
- return !pass;
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/ExitOutput.java b/src/main/java/com/quantai/trader/domain/ExitOutput.java
new file mode 100644
index 0000000..0828b10
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/ExitOutput.java
@@ -0,0 +1,32 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+public record ExitOutput(
+ BigDecimal longExitProb,
+ BigDecimal shortExitProb,
+ BigDecimal profitGivebackProb,
+ BigDecimal reversalProb,
+ BigDecimal stopRiskProb,
+ BigDecimal stagnationProb,
+ BigDecimal expectedGivebackBps,
+ String modelVersion,
+ String calibrationVersion,
+ Map explanation
+) {
+ public ExitOutput {
+ longExitProb = probability(longExitProb, "exit.longExitProb");
+ shortExitProb = probability(shortExitProb, "exit.shortExitProb");
+ profitGivebackProb = probability(profitGivebackProb, "exit.profitGivebackProb");
+ reversalProb = probability(reversalProb, "exit.reversalProb");
+ stopRiskProb = probability(stopRiskProb, "exit.stopRiskProb");
+ stagnationProb = probability(stagnationProb, "exit.stagnationProb");
+ expectedGivebackBps = nonNegative(expectedGivebackBps, "exit.expectedGivebackBps");
+ modelVersion = requiredText(modelVersion, "exit.modelVersion");
+ calibrationVersion = requiredText(calibrationVersion, "exit.calibrationVersion");
+ explanation = Map.copyOf(explanation == null ? Map.of() : explanation);
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/FeedbackValidator.java b/src/main/java/com/quantai/trader/domain/FeedbackValidator.java
new file mode 100644
index 0000000..d7e0af1
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/FeedbackValidator.java
@@ -0,0 +1,14 @@
+package com.quantai.trader.domain;
+
+import com.quantai.trader.enums.TraderErrorCode;
+import org.springframework.stereotype.Component;
+
+@Component
+public class FeedbackValidator {
+ public void validateP0(TraderAppFeedback feedback) {
+ if (!feedback.feedbackSource().p0Allowed() || feedback.realFill()) {
+ throw new TraderException(TraderErrorCode.TRADER_FEEDBACK_INVALID,
+ "P0 rejects PAPER_APP/REAL_APP and any realFill feedback");
+ }
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/ManagementDecision.java b/src/main/java/com/quantai/trader/domain/ManagementDecision.java
deleted file mode 100644
index 3799530..0000000
--- a/src/main/java/com/quantai/trader/domain/ManagementDecision.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.quantai.trader.domain;
-
-import com.quantai.trader.enums.TraderActionType;
-
-import java.util.Map;
-
-public record ManagementDecision(
- TraderActionType actionType,
- String reason,
- Map details
-) {
-
- public ManagementDecision {
- details = Maps.immutable(details);
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/Maps.java b/src/main/java/com/quantai/trader/domain/Maps.java
deleted file mode 100644
index d1a79ab..0000000
--- a/src/main/java/com/quantai/trader/domain/Maps.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-final class Maps {
-
- private Maps() {
- }
-
- static Map immutable(Map value) {
- if (value == null || value.isEmpty()) {
- return Map.of();
- }
- return Map.copyOf(new LinkedHashMap<>(value));
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/OpenOrderState.java b/src/main/java/com/quantai/trader/domain/OpenOrderState.java
new file mode 100644
index 0000000..42b7ef5
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/OpenOrderState.java
@@ -0,0 +1,10 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.requiredText;
+
+public record OpenOrderState(String orderId, String status) {
+ public OpenOrderState {
+ orderId = requiredText(orderId, "orderId");
+ status = requiredText(status, "status");
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/PlaybookCandidate.java b/src/main/java/com/quantai/trader/domain/PlaybookCandidate.java
deleted file mode 100644
index 7e2f9a5..0000000
--- a/src/main/java/com/quantai/trader/domain/PlaybookCandidate.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.quantai.trader.domain;
-
-import com.quantai.trader.enums.TraderSide;
-
-import java.time.Instant;
-import java.util.Map;
-
-public record PlaybookCandidate(
- String runId,
- String cycleId,
- String candidateId,
- String playbookId,
- String playbookVersion,
- TraderSide side,
- String variant,
- Instant candidateTime,
- TraderPricePlan pricePlan,
- int maxPlannedEntryLegs,
- Map setupEvidence
-) {
-
- public PlaybookCandidate {
- setupEvidence = Maps.immutable(setupEvidence);
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/PositionManagerInput.java b/src/main/java/com/quantai/trader/domain/PositionManagerInput.java
new file mode 100644
index 0000000..29a1060
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/PositionManagerInput.java
@@ -0,0 +1,23 @@
+package com.quantai.trader.domain;
+
+import java.util.Objects;
+
+public record PositionManagerInput(
+ TraderDecisionCycle cycle,
+ TraderMarketSnapshot snapshot,
+ TraderModelOutput modelOutput,
+ TraderPositionState positionState,
+ TraderAccountState accountState,
+ TraderExecutionState executionState,
+ TraderPmConfig pmConfig
+) {
+ public PositionManagerInput {
+ cycle = Objects.requireNonNull(cycle, "cycle is required");
+ snapshot = Objects.requireNonNull(snapshot, "snapshot is required");
+ modelOutput = Objects.requireNonNull(modelOutput, "modelOutput is required");
+ positionState = Objects.requireNonNull(positionState, "positionState is required");
+ accountState = Objects.requireNonNull(accountState, "accountState is required");
+ executionState = Objects.requireNonNull(executionState, "executionState is required");
+ pmConfig = Objects.requireNonNull(pmConfig, "pmConfig is required");
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/PositionSizingPlan.java b/src/main/java/com/quantai/trader/domain/PositionSizingPlan.java
deleted file mode 100644
index 22e9e5f..0000000
--- a/src/main/java/com/quantai/trader/domain/PositionSizingPlan.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.math.BigDecimal;
-
-public record PositionSizingPlan(
- int plannedLegCount,
- BigDecimal initialLegRatio,
- BigDecimal nextLegRatio,
- String sizingMethod,
- BigDecimal signalStrengthScore,
- BigDecimal executionQualityScore,
- BigDecimal riskGateScore
-) {
-}
diff --git a/src/main/java/com/quantai/trader/domain/RiskDecision.java b/src/main/java/com/quantai/trader/domain/RiskDecision.java
deleted file mode 100644
index 5cc9746..0000000
--- a/src/main/java/com/quantai/trader/domain/RiskDecision.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.math.BigDecimal;
-import java.util.Map;
-
-public record RiskDecision(
- boolean allowAction,
- String blocker,
- BigDecimal riskGateScore,
- Map details
-) {
-
- public RiskDecision {
- details = Maps.immutable(details);
- }
-
- public boolean blocked() {
- return !allowAction;
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/RiskOutput.java b/src/main/java/com/quantai/trader/domain/RiskOutput.java
new file mode 100644
index 0000000..99b8828
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/RiskOutput.java
@@ -0,0 +1,38 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+public record RiskOutput(
+ BigDecimal marketRiskProb,
+ BigDecimal positionRiskProb,
+ BigDecimal marketRiskSeverityBps,
+ BigDecimal positionRiskSeverityBps,
+ BigDecimal drawdownProb,
+ BigDecimal expectedShortfallBps,
+ BigDecimal volatilityExpansionProb,
+ BigDecimal spikeProb,
+ BigDecimal liquidityRiskProb,
+ BigDecimal liquidityCapacityRatio,
+ String modelVersion,
+ String calibrationVersion,
+ Map explanation
+) {
+ public RiskOutput {
+ marketRiskProb = probability(marketRiskProb, "risk.marketRiskProb");
+ positionRiskProb = probability(positionRiskProb, "risk.positionRiskProb");
+ marketRiskSeverityBps = nonNegative(marketRiskSeverityBps, "risk.marketRiskSeverityBps");
+ positionRiskSeverityBps = nonNegative(positionRiskSeverityBps, "risk.positionRiskSeverityBps");
+ drawdownProb = probability(drawdownProb, "risk.drawdownProb");
+ expectedShortfallBps = nonNegative(expectedShortfallBps, "risk.expectedShortfallBps");
+ volatilityExpansionProb = probability(volatilityExpansionProb, "risk.volatilityExpansionProb");
+ spikeProb = probability(spikeProb, "risk.spikeProb");
+ liquidityRiskProb = probability(liquidityRiskProb, "risk.liquidityRiskProb");
+ liquidityCapacityRatio = nonNegative(liquidityCapacityRatio, "risk.liquidityCapacityRatio");
+ modelVersion = requiredText(modelVersion, "risk.modelVersion");
+ calibrationVersion = requiredText(calibrationVersion, "risk.calibrationVersion");
+ explanation = Map.copyOf(explanation == null ? Map.of() : explanation);
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/StageDecision.java b/src/main/java/com/quantai/trader/domain/StageDecision.java
deleted file mode 100644
index cfc6bc7..0000000
--- a/src/main/java/com/quantai/trader/domain/StageDecision.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.util.Map;
-
-public record StageDecision(
- boolean pass,
- String reason,
- String blocker,
- Map details
-) {
-
- public StageDecision {
- details = Maps.immutable(details);
- }
-
- public boolean blocked() {
- return !pass;
- }
-
- public static StageDecision pass(String reason) {
- return new StageDecision(true, reason, null, Map.of());
- }
-
- public static StageDecision block(String reason, String blocker) {
- return new StageDecision(false, reason, blocker, Map.of());
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderAccountState.java b/src/main/java/com/quantai/trader/domain/TraderAccountState.java
new file mode 100644
index 0000000..501e9cb
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/TraderAccountState.java
@@ -0,0 +1,27 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
+
+public record TraderAccountState(
+ String accountStateId,
+ String runId,
+ String cycleId,
+ BigDecimal dailyDrawdownBps,
+ BigDecimal portfolioExposureRatio,
+ BigDecimal remainingSymbolCapacityRatio,
+ int consecutiveLosses
+) {
+ public TraderAccountState {
+ accountStateId = requiredText(accountStateId, "accountStateId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ dailyDrawdownBps = nonNegative(dailyDrawdownBps, "dailyDrawdownBps");
+ portfolioExposureRatio = nonNegative(portfolioExposureRatio, "portfolioExposureRatio");
+ remainingSymbolCapacityRatio = nonNegative(remainingSymbolCapacityRatio, "remainingSymbolCapacityRatio");
+ if (consecutiveLosses < 0) {
+ throw new IllegalArgumentException("consecutiveLosses must be >= 0");
+ }
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/TraderAction.java b/src/main/java/com/quantai/trader/domain/TraderAction.java
index 82d7a2b..e2bf39d 100644
--- a/src/main/java/com/quantai/trader/domain/TraderAction.java
+++ b/src/main/java/com/quantai/trader/domain/TraderAction.java
@@ -1,30 +1,54 @@
package com.quantai.trader.domain;
-import com.quantai.trader.enums.TraderActionType;
-import com.quantai.trader.enums.TraderSide;
+import static com.quantai.trader.util.TraderNumbers.*;
+import com.quantai.trader.enums.PositionSide;
+import com.quantai.trader.enums.TraderActionType;
import java.math.BigDecimal;
-import java.time.Instant;
import java.util.Map;
+import java.util.Objects;
public record TraderAction(
+ String actionId,
String runId,
String cycleId,
- String actionId,
+ String modelOutputId,
+ String pmDecisionId,
+ String riskDecisionId,
TraderActionType actionType,
- String playbookId,
- String playbookVersion,
String symbol,
- TraderSide side,
- BigDecimal price,
+ PositionSide side,
+ String pricePlanId,
+ String pricePlanConfigHash,
+ BigDecimal positionRatio,
BigDecimal quantity,
- Instant actionTime,
+ BigDecimal stopPrice,
+ BigDecimal targetPrice,
+ boolean reduceOnly,
+ String idempotencyKey,
String reason,
- Map actionContext,
- String sendStatus
+ Map actionContextJson
) {
-
public TraderAction {
- actionContext = Maps.immutable(actionContext);
+ actionId = requiredText(actionId, "actionId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ modelOutputId = requiredText(modelOutputId, "modelOutputId");
+ pmDecisionId = requiredText(pmDecisionId, "pmDecisionId");
+ riskDecisionId = requiredText(riskDecisionId, "riskDecisionId");
+ actionType = Objects.requireNonNull(actionType, "actionType is required");
+ symbol = requiredText(symbol, "symbol");
+ side = Objects.requireNonNull(side, "side is required");
+ idempotencyKey = requiredText(idempotencyKey, "idempotencyKey");
+ reason = requiredText(reason, "reason");
+ actionContextJson = Map.copyOf(actionContextJson == null ? Map.of() : actionContextJson);
+ if (actionType.increasesExposure()) {
+ pricePlanId = requiredText(pricePlanId, "pricePlanId");
+ pricePlanConfigHash = requiredText(pricePlanConfigHash, "pricePlanConfigHash");
+ positionRatio = positive(positionRatio, "positionRatio");
+ }
+ if (actionType.reducesExposure() && !reduceOnly) {
+ throw new IllegalArgumentException("reduce/close action must be reduceOnly");
+ }
}
}
diff --git a/src/main/java/com/quantai/trader/domain/TraderActionFactory.java b/src/main/java/com/quantai/trader/domain/TraderActionFactory.java
new file mode 100644
index 0000000..c3a4cfe
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/TraderActionFactory.java
@@ -0,0 +1,44 @@
+package com.quantai.trader.domain;
+
+import com.quantai.trader.enums.PositionSide;
+import com.quantai.trader.enums.TraderActionType;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+@Component
+public class TraderActionFactory {
+ public TraderAction create(TraderPositionManagerDecision pmDecision, TraderRiskDecision riskDecision, String symbol) {
+ TraderActionType finalAction = riskDecision.finalAction();
+ PositionSide side = sideFor(finalAction, pmDecision.side());
+ return new TraderAction(
+ "action_" + pmDecision.cycleId(),
+ pmDecision.runId(),
+ pmDecision.cycleId(),
+ pmDecision.modelOutputId(),
+ pmDecision.pmDecisionId(),
+ riskDecision.riskDecisionId(),
+ finalAction,
+ symbol,
+ side,
+ finalAction.increasesExposure() ? pmDecision.pricePlanId() : null,
+ finalAction.increasesExposure() ? pmDecision.pricePlanConfigHash() : null,
+ finalAction == TraderActionType.OPEN_LONG || finalAction == TraderActionType.OPEN_SHORT ? pmDecision.targetPositionRatio() : pmDecision.addRatio(),
+ null,
+ pmDecision.stopPrice(),
+ pmDecision.targetPrice(),
+ finalAction.reducesExposure(),
+ "idem_" + pmDecision.runId() + "_" + pmDecision.cycleId() + "_" + finalAction,
+ riskDecision.allowAction() ? pmDecision.reason() : riskDecision.blocker(),
+ Map.of("riskAllowed", riskDecision.allowAction()));
+ }
+
+ private PositionSide sideFor(TraderActionType action, PositionSide pmSide) {
+ return switch (action) {
+ case OPEN_LONG, ADD_LONG, REDUCE_LONG, CLOSE_LONG -> PositionSide.LONG;
+ case OPEN_SHORT, ADD_SHORT, REDUCE_SHORT, CLOSE_SHORT -> PositionSide.SHORT;
+ case WAIT, CANCEL -> PositionSide.NONE;
+ case HOLD, MOVE_STOP -> pmSide;
+ };
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/TraderAppFeedback.java b/src/main/java/com/quantai/trader/domain/TraderAppFeedback.java
index cda5a4a..258315e 100644
--- a/src/main/java/com/quantai/trader/domain/TraderAppFeedback.java
+++ b/src/main/java/com/quantai/trader/domain/TraderAppFeedback.java
@@ -1,23 +1,21 @@
package com.quantai.trader.domain;
-import com.quantai.trader.enums.TraderFeedbackSource;
-import com.quantai.trader.enums.TraderFeedbackType;
+import static com.quantai.trader.util.TraderNumbers.*;
+import com.quantai.trader.enums.FeedbackSource;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Map;
+import java.util.Objects;
public record TraderAppFeedback(
+ String feedbackId,
String runId,
String cycleId,
String actionId,
- TraderFeedbackType feedbackType,
- TraderFeedbackSource feedbackSource,
- String proxyMethod,
- String simulatorVersion,
+ FeedbackSource feedbackSource,
boolean realFill,
String orderId,
- String positionId,
String orderStatus,
Instant appReceivedTime,
Instant exchangeAckTime,
@@ -26,19 +24,24 @@ public record TraderAppFeedback(
BigDecimal filledQuantity,
BigDecimal fee,
BigDecimal slippageBps,
- String closeReason,
- String closeSignalSource,
- String exchangeErrorCode,
- String platformErrorCode,
- Map rawFeedback
+ String rejectReason,
+ Map rawFeedbackJson
) {
-
public TraderAppFeedback {
- rawFeedback = Maps.immutable(rawFeedback);
- boolean sourceCanBeReal = feedbackSource == TraderFeedbackSource.PAPER_APP
- || feedbackSource == TraderFeedbackSource.REAL_APP;
- if (realFill != sourceCanBeReal) {
- throw new IllegalArgumentException("feedback_source and realFill are inconsistent");
+ feedbackId = requiredText(feedbackId, "feedbackId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ actionId = requiredText(actionId, "actionId");
+ feedbackSource = Objects.requireNonNull(feedbackSource, "feedbackSource is required");
+ if (realFill && !feedbackSource.canBeRealFill()) {
+ throw new IllegalArgumentException("realFill requires PAPER_APP or REAL_APP");
}
+ if (!realFill && feedbackSource.canBeRealFill()) {
+ throw new IllegalArgumentException("PAPER_APP/REAL_APP feedback must be realFill");
+ }
+ if (filledQuantity != null && filledQuantity.compareTo(ZERO) > 0) {
+ filledPrice = positive(filledPrice, "filledPrice");
+ }
+ rawFeedbackJson = Map.copyOf(rawFeedbackJson == null ? Map.of() : rawFeedbackJson);
}
}
diff --git a/src/main/java/com/quantai/trader/domain/TraderDataSourceManifest.java b/src/main/java/com/quantai/trader/domain/TraderDataSourceManifest.java
deleted file mode 100644
index 653114b..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderDataSourceManifest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.quantai.trader.domain;
-
-import com.quantai.trader.enums.TraderErrorCode;
-
-import java.time.Instant;
-import java.util.Map;
-
-public record TraderDataSourceManifest(
- String sourceId,
- String symbol,
- String sourceType,
- String exchange,
- String granularity,
- String sourcePath,
- String contentHashSha256,
- String schemaHashSha256,
- Instant dataFrom,
- Instant dataTo,
- Instant minEventTime,
- Instant maxEventTime,
- String timezone,
- Long rowCount,
- Map missingSummary,
- String qualityStatus
-) {
-
- public TraderDataSourceManifest {
- missingSummary = Maps.immutable(missingSummary);
- boolean hasFullHash = contentHashSha256 != null && !contentHashSha256.isBlank();
- boolean hasSchemaTrace = schemaHashSha256 != null
- && !schemaHashSha256.isBlank()
- && rowCount != null
- && minEventTime != null
- && maxEventTime != null;
- if (sourceId == null || sourceId.isBlank() || sourcePath == null || sourcePath.isBlank()) {
- throw new TraderException(TraderErrorCode.TRADER_DATA_SOURCE_MISSING, "data source id and path are required");
- }
- if (timezone == null || timezone.isBlank()) {
- throw new TraderException(TraderErrorCode.TRADER_DATA_SOURCE_MISSING, "data source timezone is required");
- }
- if (!hasFullHash && !hasSchemaTrace) {
- throw new TraderException(
- TraderErrorCode.TRADER_DATA_SOURCE_MISSING,
- "data source requires content hash or schema/row/time lineage"
- );
- }
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderDecisionCycle.java b/src/main/java/com/quantai/trader/domain/TraderDecisionCycle.java
index c7d808f..da7bd26 100644
--- a/src/main/java/com/quantai/trader/domain/TraderDecisionCycle.java
+++ b/src/main/java/com/quantai/trader/domain/TraderDecisionCycle.java
@@ -1,37 +1,29 @@
package com.quantai.trader.domain;
-import com.quantai.trader.enums.TraderRunMode;
-import com.quantai.trader.enums.TraderState;
+import static com.quantai.trader.util.TraderNumbers.requiredText;
+import com.quantai.trader.enums.TraderRunMode;
import java.time.Instant;
+import java.util.Objects;
public record TraderDecisionCycle(
String runId,
String cycleId,
- String snapshotId,
String symbol,
- String playbookId,
- String playbookVersion,
- TraderState state,
Instant cycleTime,
TraderRunMode runMode,
- String decisionStatus,
- String blocker
+ String modelBundleVersion,
+ String calibrationBundleVersion,
+ String pmConfigVersion
) {
-
- public TraderDecisionCycle withState(TraderState nextState, String nextStatus, String nextBlocker) {
- return new TraderDecisionCycle(
- runId,
- cycleId,
- snapshotId,
- symbol,
- playbookId,
- playbookVersion,
- nextState,
- cycleTime,
- runMode,
- nextStatus,
- nextBlocker
- );
+ public TraderDecisionCycle {
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ symbol = requiredText(symbol, "symbol");
+ cycleTime = Objects.requireNonNull(cycleTime, "cycleTime is required");
+ runMode = Objects.requireNonNull(runMode, "runMode is required");
+ modelBundleVersion = requiredText(modelBundleVersion, "modelBundleVersion");
+ calibrationBundleVersion = requiredText(calibrationBundleVersion, "calibrationBundleVersion");
+ pmConfigVersion = requiredText(pmConfigVersion, "pmConfigVersion");
}
}
diff --git a/src/main/java/com/quantai/trader/domain/TraderEntryPlan.java b/src/main/java/com/quantai/trader/domain/TraderEntryPlan.java
deleted file mode 100644
index aa5f11e..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderEntryPlan.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.quantai.trader.domain;
-
-import com.quantai.trader.enums.TraderActionType;
-
-import java.math.BigDecimal;
-
-public record TraderEntryPlan(
- String runId,
- String cycleId,
- String actionId,
- String entryLegId,
- String candidateId,
- TraderActionType entryAction,
- int plannedLegIndex,
- int plannedLegCount,
- BigDecimal plannedLegRatio,
- String sizingMethod,
- BigDecimal signalStrengthScore,
- BigDecimal executionQualityScore,
- BigDecimal riskGateScore,
- BigDecimal entryPrice,
- BigDecimal invalidPrice,
- BigDecimal stopPrice,
- BigDecimal targetPrice,
- BigDecimal partialTakeProfitPrice,
- long maxEntryWaitMs,
- long maxHoldMs,
- String reason
-) {
-
- public boolean completeForEntry() {
- return entryPrice != null
- && invalidPrice != null
- && stopPrice != null
- && targetPrice != null
- && maxEntryWaitMs > 0
- && maxHoldMs > 0;
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderEvidence.java b/src/main/java/com/quantai/trader/domain/TraderEvidence.java
index cf5bd2f..514ed09 100644
--- a/src/main/java/com/quantai/trader/domain/TraderEvidence.java
+++ b/src/main/java/com/quantai/trader/domain/TraderEvidence.java
@@ -1,21 +1,29 @@
package com.quantai.trader.domain;
+import static com.quantai.trader.util.TraderNumbers.requiredText;
+
import java.time.Instant;
import java.util.Map;
+import java.util.Objects;
public record TraderEvidence(
+ String evidenceId,
String runId,
String cycleId,
- String evidenceId,
String stage,
boolean pass,
String reason,
String blocker,
Instant evidenceTime,
- Map details
+ Map detailsJson
) {
-
public TraderEvidence {
- details = Maps.immutable(details);
+ evidenceId = requiredText(evidenceId, "evidenceId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ stage = requiredText(stage, "stage");
+ reason = requiredText(reason, "reason");
+ evidenceTime = Objects.requireNonNull(evidenceTime, "evidenceTime is required");
+ detailsJson = Map.copyOf(detailsJson == null ? Map.of() : detailsJson);
}
}
diff --git a/src/main/java/com/quantai/trader/domain/TraderException.java b/src/main/java/com/quantai/trader/domain/TraderException.java
index 2a97bf2..2635030 100644
--- a/src/main/java/com/quantai/trader/domain/TraderException.java
+++ b/src/main/java/com/quantai/trader/domain/TraderException.java
@@ -3,15 +3,14 @@ package com.quantai.trader.domain;
import com.quantai.trader.enums.TraderErrorCode;
public class TraderException extends RuntimeException {
+ private final TraderErrorCode code;
- private final TraderErrorCode errorCode;
-
- public TraderException(TraderErrorCode errorCode, String message) {
+ public TraderException(TraderErrorCode code, String message) {
super(message);
- this.errorCode = errorCode;
+ this.code = code;
}
- public TraderErrorCode errorCode() {
- return errorCode;
+ public TraderErrorCode code() {
+ return code;
}
}
diff --git a/src/main/java/com/quantai/trader/domain/TraderExecutionState.java b/src/main/java/com/quantai/trader/domain/TraderExecutionState.java
new file mode 100644
index 0000000..8eb4fd0
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/TraderExecutionState.java
@@ -0,0 +1,46 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+public record TraderExecutionState(
+ String executionStateId,
+ String runId,
+ String cycleId,
+ String symbol,
+ List openOrders,
+ BigDecimal expectedSlippageBps,
+ long exchangeLatencyMs,
+ int apiErrorCount,
+ BigDecimal makerFeeBps,
+ BigDecimal takerFeeBps,
+ BigDecimal minNotional,
+ BigDecimal priceTickSize,
+ BigDecimal lotSizeStepSize,
+ BigDecimal marketLotSizeStepSize,
+ BigDecimal liquidityCapacityRatio
+) {
+ public TraderExecutionState {
+ executionStateId = requiredText(executionStateId, "executionStateId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ symbol = requiredText(symbol, "symbol");
+ openOrders = List.copyOf(openOrders == null ? List.of() : openOrders);
+ expectedSlippageBps = nonNegative(expectedSlippageBps, "expectedSlippageBps");
+ if (exchangeLatencyMs < 0) {
+ throw new IllegalArgumentException("exchangeLatencyMs must be >= 0");
+ }
+ if (apiErrorCount < 0) {
+ throw new IllegalArgumentException("apiErrorCount must be >= 0");
+ }
+ makerFeeBps = nonNegative(makerFeeBps, "makerFeeBps");
+ takerFeeBps = nonNegative(takerFeeBps, "takerFeeBps");
+ minNotional = positive(minNotional, "minNotional");
+ priceTickSize = positive(priceTickSize, "priceTickSize");
+ lotSizeStepSize = positive(lotSizeStepSize, "lotSizeStepSize");
+ marketLotSizeStepSize = positive(marketLotSizeStepSize, "marketLotSizeStepSize");
+ liquidityCapacityRatio = nonNegative(liquidityCapacityRatio, "liquidityCapacityRatio");
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/TraderManagementAction.java b/src/main/java/com/quantai/trader/domain/TraderManagementAction.java
deleted file mode 100644
index 2e94ce6..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderManagementAction.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.quantai.trader.domain;
-
-import com.quantai.trader.enums.TraderActionType;
-
-import java.math.BigDecimal;
-import java.time.Instant;
-import java.util.Map;
-
-public record TraderManagementAction(
- String runId,
- String cycleId,
- String actionId,
- String managementActionId,
- String positionId,
- TraderActionType managementActionType,
- Instant actionTime,
- BigDecimal beforeRiskBps,
- BigDecimal afterRiskBps,
- String reason,
- Map details
-) {
-
- public TraderManagementAction {
- details = Maps.immutable(details);
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderMarketEvent.java b/src/main/java/com/quantai/trader/domain/TraderMarketEvent.java
deleted file mode 100644
index 7f19e2b..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderMarketEvent.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.time.Instant;
-import java.util.Map;
-
-public record TraderMarketEvent(
- String runId,
- String eventId,
- String symbol,
- Instant eventTime,
- String source,
- String sourcePath,
- Map payload
-) {
-
- public TraderMarketEvent {
- payload = Maps.immutable(payload);
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderMarketSnapshot.java b/src/main/java/com/quantai/trader/domain/TraderMarketSnapshot.java
index 294b935..cba33e1 100644
--- a/src/main/java/com/quantai/trader/domain/TraderMarketSnapshot.java
+++ b/src/main/java/com/quantai/trader/domain/TraderMarketSnapshot.java
@@ -1,29 +1,44 @@
package com.quantai.trader.domain;
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
import java.time.Instant;
import java.util.Map;
public record TraderMarketSnapshot(
+ String snapshotId,
String runId,
String cycleId,
- String snapshotId,
String symbol,
Instant snapshotTime,
String featureVersion,
- Map contextFeatures,
- Map setupFeatures,
- Map triggerFeatures,
- Map executionFeatures,
- Map dataQuality,
- Map labelInputs
+ BigDecimal markPrice,
+ BigDecimal indexPrice,
+ BigDecimal spreadBps,
+ BigDecimal fundingRateBps,
+ BigDecimal depthNotional5Bps,
+ BigDecimal depthNotional10Bps,
+ BigDecimal depthNotional25Bps,
+ boolean dataReady,
+ Map featureJson,
+ Map dataQualityJson
) {
-
public TraderMarketSnapshot {
- contextFeatures = Maps.immutable(contextFeatures);
- setupFeatures = Maps.immutable(setupFeatures);
- triggerFeatures = Maps.immutable(triggerFeatures);
- executionFeatures = Maps.immutable(executionFeatures);
- dataQuality = Maps.immutable(dataQuality);
- labelInputs = Maps.immutable(labelInputs);
+ snapshotId = requiredText(snapshotId, "snapshotId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ symbol = requiredText(symbol, "symbol");
+ snapshotTime = java.util.Objects.requireNonNull(snapshotTime, "snapshotTime is required");
+ featureVersion = requiredText(featureVersion, "featureVersion");
+ markPrice = positive(markPrice, "markPrice");
+ indexPrice = positive(indexPrice, "indexPrice");
+ spreadBps = nonNegative(spreadBps, "spreadBps");
+ fundingRateBps = required(fundingRateBps, "fundingRateBps");
+ depthNotional5Bps = nonNegative(depthNotional5Bps, "depthNotional5Bps");
+ depthNotional10Bps = nonNegative(depthNotional10Bps, "depthNotional10Bps");
+ depthNotional25Bps = nonNegative(depthNotional25Bps, "depthNotional25Bps");
+ featureJson = Map.copyOf(featureJson == null ? Map.of() : featureJson);
+ dataQualityJson = Map.copyOf(dataQualityJson == null ? Map.of() : dataQualityJson);
}
}
diff --git a/src/main/java/com/quantai/trader/domain/TraderModelManifest.java b/src/main/java/com/quantai/trader/domain/TraderModelManifest.java
deleted file mode 100644
index 2a464b2..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderModelManifest.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.time.Instant;
-import java.util.Map;
-
-public record TraderModelManifest(
- String modelName,
- String modelVersion,
- String featureVersion,
- String labelVersion,
- String artifactPath,
- Instant trainedAt,
- Map metrics,
- String status
-) {
-
- public TraderModelManifest {
- metrics = Maps.immutable(metrics);
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderModelOutput.java b/src/main/java/com/quantai/trader/domain/TraderModelOutput.java
index 00eeabb..61800e3 100644
--- a/src/main/java/com/quantai/trader/domain/TraderModelOutput.java
+++ b/src/main/java/com/quantai/trader/domain/TraderModelOutput.java
@@ -1,20 +1,39 @@
package com.quantai.trader.domain;
+import static com.quantai.trader.util.TraderNumbers.*;
+
import java.math.BigDecimal;
-import java.time.Instant;
import java.util.Map;
+import java.util.Objects;
public record TraderModelOutput(
- String modelName,
- String modelVersion,
- BigDecimal score,
+ String modelOutputId,
+ String runId,
+ String cycleId,
+ String modelBundleVersion,
+ String calibrationBundleVersion,
+ DirectionOutput direction,
+ EntryOutput entry,
+ ContinueOutput continuation,
+ ExitOutput exit,
+ RiskOutput risk,
BigDecimal uncertainty,
BigDecimal oodScore,
- Instant predictedAt,
- Map details
+ Map explanation
) {
-
public TraderModelOutput {
- details = Maps.immutable(details);
+ modelOutputId = requiredText(modelOutputId, "modelOutputId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ modelBundleVersion = requiredText(modelBundleVersion, "modelBundleVersion");
+ calibrationBundleVersion = requiredText(calibrationBundleVersion, "calibrationBundleVersion");
+ direction = Objects.requireNonNull(direction, "direction is required");
+ entry = Objects.requireNonNull(entry, "entry is required");
+ continuation = Objects.requireNonNull(continuation, "continuation is required");
+ exit = Objects.requireNonNull(exit, "exit is required");
+ risk = Objects.requireNonNull(risk, "risk is required");
+ uncertainty = probability(uncertainty, "uncertainty");
+ oodScore = probability(oodScore, "oodScore");
+ explanation = Map.copyOf(explanation == null ? Map.of() : explanation);
}
}
diff --git a/src/main/java/com/quantai/trader/domain/TraderPmConfig.java b/src/main/java/com/quantai/trader/domain/TraderPmConfig.java
new file mode 100644
index 0000000..6a71b87
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/TraderPmConfig.java
@@ -0,0 +1,130 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import java.math.BigDecimal;
+
+public record TraderPmConfig(
+ String pmConfigVersion,
+ OpenRuleConfig open,
+ AddRuleConfig add,
+ ExitRuleConfig exit,
+ SizingConfig sizing
+) {
+ public TraderPmConfig {
+ pmConfigVersion = requiredText(pmConfigVersion, "pmConfigVersion");
+ open = java.util.Objects.requireNonNull(open, "open config is required");
+ add = java.util.Objects.requireNonNull(add, "add config is required");
+ exit = java.util.Objects.requireNonNull(exit, "exit config is required");
+ sizing = java.util.Objects.requireNonNull(sizing, "sizing config is required");
+ }
+
+ public record OpenRuleConfig(
+ BigDecimal longOpenProb,
+ BigDecimal shortOpenProb,
+ BigDecimal minLongEntryProb,
+ BigDecimal minShortEntryProb,
+ BigDecimal maxMarketRiskProb,
+ BigDecimal minExpectedEdgeBps,
+ BigDecimal minDirectionMargin,
+ BigDecimal minLiquidityCapacityRatio,
+ BigDecimal maxOodScore
+ ) {
+ public OpenRuleConfig {
+ longOpenProb = probability(longOpenProb, "open.longOpenProb");
+ shortOpenProb = probability(shortOpenProb, "open.shortOpenProb");
+ minLongEntryProb = probability(minLongEntryProb, "open.minLongEntryProb");
+ minShortEntryProb = probability(minShortEntryProb, "open.minShortEntryProb");
+ maxMarketRiskProb = probability(maxMarketRiskProb, "open.maxMarketRiskProb");
+ minExpectedEdgeBps = required(minExpectedEdgeBps, "open.minExpectedEdgeBps");
+ minDirectionMargin = nonNegative(minDirectionMargin, "open.minDirectionMargin");
+ minLiquidityCapacityRatio = nonNegative(minLiquidityCapacityRatio, "open.minLiquidityCapacityRatio");
+ maxOodScore = probability(maxOodScore, "open.maxOodScore");
+ }
+ }
+
+ public record AddRuleConfig(
+ BigDecimal minLongProb,
+ BigDecimal minShortProb,
+ BigDecimal minContinueProb,
+ BigDecimal minEntryProb,
+ BigDecimal maxExitProb,
+ BigDecimal maxMarketRiskProb,
+ BigDecimal maxPositionRiskProb,
+ BigDecimal minExpectedEdgeBps,
+ BigDecimal minContinueVsExitEdgeBps,
+ BigDecimal minLiquidityCapacityRatio,
+ BigDecimal minPostTradeLiquidationBufferBps,
+ int maxAddCount,
+ long cooldownMinutes
+ ) {
+ public AddRuleConfig {
+ minLongProb = probability(minLongProb, "add.minLongProb");
+ minShortProb = probability(minShortProb, "add.minShortProb");
+ minContinueProb = probability(minContinueProb, "add.minContinueProb");
+ minEntryProb = probability(minEntryProb, "add.minEntryProb");
+ maxExitProb = probability(maxExitProb, "add.maxExitProb");
+ maxMarketRiskProb = probability(maxMarketRiskProb, "add.maxMarketRiskProb");
+ maxPositionRiskProb = probability(maxPositionRiskProb, "add.maxPositionRiskProb");
+ minExpectedEdgeBps = required(minExpectedEdgeBps, "add.minExpectedEdgeBps");
+ minContinueVsExitEdgeBps = required(minContinueVsExitEdgeBps, "add.minContinueVsExitEdgeBps");
+ minLiquidityCapacityRatio = nonNegative(minLiquidityCapacityRatio, "add.minLiquidityCapacityRatio");
+ minPostTradeLiquidationBufferBps = nonNegative(minPostTradeLiquidationBufferBps, "add.minPostTradeLiquidationBufferBps");
+ if (maxAddCount < 0 || cooldownMinutes < 0) {
+ throw new IllegalArgumentException("add count and cooldown must be >= 0");
+ }
+ }
+ }
+
+ public record ExitRuleConfig(
+ BigDecimal closeExitProb,
+ BigDecimal closePositionRiskProb,
+ BigDecimal closeMarketRiskProb,
+ BigDecimal closeContinueMax,
+ BigDecimal reduceGivebackProb,
+ BigDecimal reduceContinueMin,
+ BigDecimal reduceContinueMax,
+ BigDecimal minProfitForReduceBps,
+ BigDecimal maxExpectedShortfallBps
+ ) {
+ public ExitRuleConfig {
+ closeExitProb = probability(closeExitProb, "exit.closeExitProb");
+ closePositionRiskProb = probability(closePositionRiskProb, "exit.closePositionRiskProb");
+ closeMarketRiskProb = probability(closeMarketRiskProb, "exit.closeMarketRiskProb");
+ closeContinueMax = probability(closeContinueMax, "exit.closeContinueMax");
+ reduceGivebackProb = probability(reduceGivebackProb, "exit.reduceGivebackProb");
+ reduceContinueMin = probability(reduceContinueMin, "exit.reduceContinueMin");
+ reduceContinueMax = probability(reduceContinueMax, "exit.reduceContinueMax");
+ minProfitForReduceBps = nonNegative(minProfitForReduceBps, "exit.minProfitForReduceBps");
+ maxExpectedShortfallBps = nonNegative(maxExpectedShortfallBps, "exit.maxExpectedShortfallBps");
+ }
+ }
+
+ public record SizingConfig(
+ BigDecimal baseRatio,
+ BigDecimal minInitialRatio,
+ BigDecimal maxSingleLegRatio,
+ BigDecimal minAddRatio,
+ BigDecimal maxAddRatio,
+ BigDecimal maxTotalPositionRatio,
+ BigDecimal minEdgeBps,
+ BigDecimal maxLossPerTradeBps,
+ BigDecimal maxLiquidityUsageRatio,
+ BigDecimal uncertaintyPenaltyMultiplier,
+ BigDecimal minPostTradeLiquidationBufferBps
+ ) {
+ public SizingConfig {
+ baseRatio = positive(baseRatio, "sizing.baseRatio");
+ minInitialRatio = nonNegative(minInitialRatio, "sizing.minInitialRatio");
+ maxSingleLegRatio = positive(maxSingleLegRatio, "sizing.maxSingleLegRatio");
+ minAddRatio = nonNegative(minAddRatio, "sizing.minAddRatio");
+ maxAddRatio = nonNegative(maxAddRatio, "sizing.maxAddRatio");
+ maxTotalPositionRatio = positive(maxTotalPositionRatio, "sizing.maxTotalPositionRatio");
+ minEdgeBps = required(minEdgeBps, "sizing.minEdgeBps");
+ maxLossPerTradeBps = positive(maxLossPerTradeBps, "sizing.maxLossPerTradeBps");
+ maxLiquidityUsageRatio = positive(maxLiquidityUsageRatio, "sizing.maxLiquidityUsageRatio");
+ uncertaintyPenaltyMultiplier = nonNegative(uncertaintyPenaltyMultiplier, "sizing.uncertaintyPenaltyMultiplier");
+ minPostTradeLiquidationBufferBps = nonNegative(minPostTradeLiquidationBufferBps, "sizing.minPostTradeLiquidationBufferBps");
+ }
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/TraderPositionLeg.java b/src/main/java/com/quantai/trader/domain/TraderPositionLeg.java
deleted file mode 100644
index cde2c9f..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderPositionLeg.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.quantai.trader.domain;
-
-import com.quantai.trader.enums.TraderActionType;
-
-import java.math.BigDecimal;
-import java.time.Instant;
-
-public record TraderPositionLeg(
- String runId,
- String cycleId,
- String positionId,
- String legId,
- TraderActionType actionType,
- BigDecimal quantity,
- BigDecimal price,
- BigDecimal legRatio,
- BigDecimal riskDeltaBps,
- Instant actionTime,
- String reason
-) {
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderPositionManagerDecision.java b/src/main/java/com/quantai/trader/domain/TraderPositionManagerDecision.java
new file mode 100644
index 0000000..e5729c0
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/TraderPositionManagerDecision.java
@@ -0,0 +1,48 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import com.quantai.trader.enums.PositionSide;
+import com.quantai.trader.enums.TraderActionType;
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.Objects;
+
+public record TraderPositionManagerDecision(
+ String pmDecisionId,
+ String runId,
+ String cycleId,
+ String modelOutputId,
+ String positionStateId,
+ String accountStateId,
+ String executionStateId,
+ TraderActionType candidateAction,
+ PositionSide side,
+ String pricePlanId,
+ String pricePlanConfigHash,
+ BigDecimal targetPositionRatio,
+ BigDecimal addRatio,
+ BigDecimal reduceRatio,
+ BigDecimal stopPrice,
+ BigDecimal targetPrice,
+ String reason,
+ Map decisionJson
+) {
+ public TraderPositionManagerDecision {
+ pmDecisionId = requiredText(pmDecisionId, "pmDecisionId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ modelOutputId = requiredText(modelOutputId, "modelOutputId");
+ positionStateId = requiredText(positionStateId, "positionStateId");
+ accountStateId = requiredText(accountStateId, "accountStateId");
+ executionStateId = requiredText(executionStateId, "executionStateId");
+ candidateAction = Objects.requireNonNull(candidateAction, "candidateAction is required");
+ side = Objects.requireNonNull(side, "side is required");
+ reason = requiredText(reason, "reason");
+ decisionJson = Map.copyOf(decisionJson == null ? Map.of() : decisionJson);
+ if (candidateAction.increasesExposure()) {
+ pricePlanId = requiredText(pricePlanId, "pricePlanId");
+ pricePlanConfigHash = requiredText(pricePlanConfigHash, "pricePlanConfigHash");
+ }
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/TraderPositionPath.java b/src/main/java/com/quantai/trader/domain/TraderPositionPath.java
deleted file mode 100644
index afbf053..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderPositionPath.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.quantai.trader.domain;
-
-import com.quantai.trader.enums.TraderSide;
-
-import java.math.BigDecimal;
-import java.time.Instant;
-import java.util.Map;
-
-public record TraderPositionPath(
- String runId,
- String cycleId,
- String actionId,
- String positionId,
- TraderSide side,
- Instant entryTime,
- Instant lastEventTime,
- BigDecimal entryPrice,
- BigDecimal currentPrice,
- BigDecimal mfeBps,
- BigDecimal maeBps,
- Long timeToTargetMs,
- Long timeToInvalidMs,
- boolean targetBeforeStop,
- boolean stagnationTimeoutHit,
- boolean proxyOnly,
- boolean reduceSeen,
- int filledLegCount,
- BigDecimal totalPositionRatio,
- Map pathSummary
-) {
-
- public TraderPositionPath {
- pathSummary = Maps.immutable(pathSummary);
- }
-
- public boolean opened() {
- return positionId != null && filledLegCount > 0;
- }
-
- public boolean fullSize() {
- return totalPositionRatio != null && totalPositionRatio.compareTo(BigDecimal.ONE) >= 0;
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderPositionState.java b/src/main/java/com/quantai/trader/domain/TraderPositionState.java
new file mode 100644
index 0000000..5d11145
--- /dev/null
+++ b/src/main/java/com/quantai/trader/domain/TraderPositionState.java
@@ -0,0 +1,47 @@
+package com.quantai.trader.domain;
+
+import static com.quantai.trader.util.TraderNumbers.*;
+
+import com.quantai.trader.enums.PositionSide;
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.util.Objects;
+
+public record TraderPositionState(
+ String positionStateId,
+ String runId,
+ String cycleId,
+ String symbol,
+ PositionSide side,
+ BigDecimal positionRatio,
+ BigDecimal averageEntryPrice,
+ BigDecimal currentPrice,
+ BigDecimal unrealizedPnlBps,
+ BigDecimal liquidationBufferBps,
+ int addCount,
+ BigDecimal remainingAddCapacity,
+ Instant lastAddTime
+) {
+ public TraderPositionState {
+ positionStateId = requiredText(positionStateId, "positionStateId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ symbol = requiredText(symbol, "symbol");
+ side = Objects.requireNonNull(side, "side is required");
+ positionRatio = nonNegative(positionRatio, "positionRatio");
+ currentPrice = positive(currentPrice, "currentPrice");
+ if (side == PositionSide.NONE && positionRatio.compareTo(ZERO) != 0) {
+ throw new IllegalArgumentException("flat position must have zero ratio");
+ }
+ if (side != PositionSide.NONE) {
+ averageEntryPrice = positive(averageEntryPrice, "averageEntryPrice");
+ }
+ unrealizedPnlBps = required(unrealizedPnlBps, "unrealizedPnlBps");
+ liquidationBufferBps = nonNegative(liquidationBufferBps, "liquidationBufferBps");
+ remainingAddCapacity = nonNegative(remainingAddCapacity, "remainingAddCapacity");
+ }
+
+ public boolean isFlat() {
+ return side == PositionSide.NONE;
+ }
+}
diff --git a/src/main/java/com/quantai/trader/domain/TraderPricePlan.java b/src/main/java/com/quantai/trader/domain/TraderPricePlan.java
deleted file mode 100644
index 1210cf0..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderPricePlan.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.math.BigDecimal;
-
-public record TraderPricePlan(
- BigDecimal entryPrice,
- BigDecimal invalidPrice,
- BigDecimal stopPrice,
- BigDecimal targetPrice,
- BigDecimal partialTakeProfitPrice,
- long maxEntryWaitMs,
- long maxHoldMs
-) {
-
- public boolean completeForEntry() {
- return entryPrice != null
- && invalidPrice != null
- && stopPrice != null
- && targetPrice != null
- && maxEntryWaitMs > 0
- && maxHoldMs > 0;
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderReplayReport.java b/src/main/java/com/quantai/trader/domain/TraderReplayReport.java
deleted file mode 100644
index 120bca8..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderReplayReport.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.math.BigDecimal;
-import java.time.Instant;
-import java.util.List;
-import java.util.Map;
-
-public record TraderReplayReport(
- String runId,
- String reportId,
- String symbol,
- String playbookId,
- String playbookVersion,
- int candidateEvents,
- int monthsCovered,
- BigDecimal baseNetReturnBps1x,
- BigDecimal leveragedNetReturnBps10x,
- BigDecimal holdoutReturnBps10x,
- Map strictVsLoose,
- List failureRisks,
- String conclusion,
- String reportPath,
- Instant createdAt
-) {
-
- public TraderReplayReport {
- strictVsLoose = Maps.immutable(strictVsLoose);
- failureRisks = failureRisks == null ? List.of() : List.copyOf(failureRisks);
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TraderRiskDecision.java b/src/main/java/com/quantai/trader/domain/TraderRiskDecision.java
index 503fdb2..66bda46 100644
--- a/src/main/java/com/quantai/trader/domain/TraderRiskDecision.java
+++ b/src/main/java/com/quantai/trader/domain/TraderRiskDecision.java
@@ -1,32 +1,32 @@
package com.quantai.trader.domain;
-import com.quantai.trader.enums.TraderActionType;
+import static com.quantai.trader.util.TraderNumbers.requiredText;
-import java.math.BigDecimal;
-import java.time.Instant;
+import com.quantai.trader.enums.TraderActionType;
import java.util.Map;
+import java.util.Objects;
public record TraderRiskDecision(
+ String riskDecisionId,
String runId,
String cycleId,
- String actionId,
- String accountStateId,
- TraderActionType actionType,
- BigDecimal leverageScreen,
- BigDecimal plannedTotalPositionRatio,
- BigDecimal maxLossBps,
- BigDecimal liquidationBufferBps,
- BigDecimal expectedValueBps1x,
- BigDecimal expectedValueBps10x,
- BigDecimal uncertainty,
- BigDecimal oodScore,
+ String pmDecisionId,
boolean allowAction,
+ TraderActionType originalAction,
+ TraderActionType finalAction,
String blocker,
- Map decision,
- Instant createdAt
+ Map decisionJson
) {
-
public TraderRiskDecision {
- decision = Maps.immutable(decision);
+ riskDecisionId = requiredText(riskDecisionId, "riskDecisionId");
+ runId = requiredText(runId, "runId");
+ cycleId = requiredText(cycleId, "cycleId");
+ pmDecisionId = requiredText(pmDecisionId, "pmDecisionId");
+ originalAction = Objects.requireNonNull(originalAction, "originalAction is required");
+ finalAction = Objects.requireNonNull(finalAction, "finalAction is required");
+ if (!allowAction) {
+ blocker = requiredText(blocker, "blocker");
+ }
+ decisionJson = Map.copyOf(decisionJson == null ? Map.of() : decisionJson);
}
}
diff --git a/src/main/java/com/quantai/trader/domain/TraderTrainingSample.java b/src/main/java/com/quantai/trader/domain/TraderTrainingSample.java
deleted file mode 100644
index fe5ea90..0000000
--- a/src/main/java/com/quantai/trader/domain/TraderTrainingSample.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.math.BigDecimal;
-import java.time.Instant;
-import java.util.Map;
-
-public record TraderTrainingSample(
- String runId,
- String cycleId,
- String sampleId,
- String actionId,
- String positionId,
- String featureVersion,
- String labelVersion,
- Instant sampleTime,
- Map features,
- Map labels,
- BigDecimal netReturnBps1x,
- BigDecimal netReturnBps10x,
- boolean proxyOnly
-) {
-
- public TraderTrainingSample {
- features = Maps.immutable(features);
- labels = Maps.immutable(labels);
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TriggerDecision.java b/src/main/java/com/quantai/trader/domain/TriggerDecision.java
deleted file mode 100644
index ee12444..0000000
--- a/src/main/java/com/quantai/trader/domain/TriggerDecision.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.math.BigDecimal;
-import java.util.Map;
-
-public record TriggerDecision(
- boolean pass,
- BigDecimal signalStrengthScore,
- String reason,
- String blocker,
- Map details
-) {
-
- public TriggerDecision {
- details = Maps.immutable(details);
- }
-
- public boolean blocked() {
- return !pass;
- }
-}
diff --git a/src/main/java/com/quantai/trader/domain/TriggerEvent.java b/src/main/java/com/quantai/trader/domain/TriggerEvent.java
deleted file mode 100644
index f9a546e..0000000
--- a/src/main/java/com/quantai/trader/domain/TriggerEvent.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.quantai.trader.domain;
-
-import java.time.Instant;
-import java.util.Map;
-
-public record TriggerEvent(
- String runId,
- String cycleId,
- String candidateId,
- String triggerId,
- Instant triggerTime,
- String triggerTimeframe,
- String featureVersion,
- Map triggerEvidence,
- Map markout
-) {
-
- public TriggerEvent {
- triggerEvidence = Maps.immutable(triggerEvidence);
- markout = Maps.immutable(markout);
- }
-}
diff --git a/src/main/java/com/quantai/trader/enums/FeedbackSource.java b/src/main/java/com/quantai/trader/enums/FeedbackSource.java
new file mode 100644
index 0000000..fdc5ceb
--- /dev/null
+++ b/src/main/java/com/quantai/trader/enums/FeedbackSource.java
@@ -0,0 +1,16 @@
+package com.quantai.trader.enums;
+
+public enum FeedbackSource {
+ REPLAY_SIMULATOR,
+ SHADOW_APP,
+ PAPER_APP,
+ REAL_APP;
+
+ public boolean p0Allowed() {
+ return this == REPLAY_SIMULATOR || this == SHADOW_APP;
+ }
+
+ public boolean canBeRealFill() {
+ return this == PAPER_APP || this == REAL_APP;
+ }
+}
diff --git a/src/main/java/com/quantai/trader/enums/PositionSide.java b/src/main/java/com/quantai/trader/enums/PositionSide.java
new file mode 100644
index 0000000..cf7a7b6
--- /dev/null
+++ b/src/main/java/com/quantai/trader/enums/PositionSide.java
@@ -0,0 +1,15 @@
+package com.quantai.trader.enums;
+
+public enum PositionSide {
+ NONE,
+ LONG,
+ SHORT;
+
+ public boolean isLong() {
+ return this == LONG;
+ }
+
+ public boolean isShort() {
+ return this == SHORT;
+ }
+}
diff --git a/src/main/java/com/quantai/trader/enums/ReplayRunStatus.java b/src/main/java/com/quantai/trader/enums/ReplayRunStatus.java
deleted file mode 100644
index 9eef8b5..0000000
--- a/src/main/java/com/quantai/trader/enums/ReplayRunStatus.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.quantai.trader.enums;
-
-public enum ReplayRunStatus {
- CREATED,
- RUNNING,
- CANCEL_REQUESTED,
- CANCELLED,
- COMPLETED,
- FAILED
-}
diff --git a/src/main/java/com/quantai/trader/enums/TraderActionType.java b/src/main/java/com/quantai/trader/enums/TraderActionType.java
index fa18293..6e9a59e 100644
--- a/src/main/java/com/quantai/trader/enums/TraderActionType.java
+++ b/src/main/java/com/quantai/trader/enums/TraderActionType.java
@@ -2,12 +2,23 @@ package com.quantai.trader.enums;
public enum TraderActionType {
WAIT,
- OPEN_INITIAL,
- OPEN_PLANNED_LEG,
+ OPEN_LONG,
+ OPEN_SHORT,
+ ADD_LONG,
+ ADD_SHORT,
HOLD,
- REDUCE,
+ REDUCE_LONG,
+ REDUCE_SHORT,
MOVE_STOP,
- CLOSE,
- CANCEL,
- REQUOTE
+ CLOSE_LONG,
+ CLOSE_SHORT,
+ CANCEL;
+
+ public boolean increasesExposure() {
+ return this == OPEN_LONG || this == OPEN_SHORT || this == ADD_LONG || this == ADD_SHORT;
+ }
+
+ public boolean reducesExposure() {
+ return this == REDUCE_LONG || this == REDUCE_SHORT || this == CLOSE_LONG || this == CLOSE_SHORT;
+ }
}
diff --git a/src/main/java/com/quantai/trader/enums/TraderErrorCode.java b/src/main/java/com/quantai/trader/enums/TraderErrorCode.java
index 14709ac..1028013 100644
--- a/src/main/java/com/quantai/trader/enums/TraderErrorCode.java
+++ b/src/main/java/com/quantai/trader/enums/TraderErrorCode.java
@@ -1,13 +1,15 @@
package com.quantai.trader.enums;
public enum TraderErrorCode {
- TRADER_PLAYBOOK_VERSION_CONFLICT,
- TRADER_DATA_SOURCE_MISSING,
- TRADER_DATA_QUALITY_FAILED,
- TRADER_ENTRY_PLAN_INCOMPLETE,
- TRADER_ILLEGAL_ACTION_TRANSITION,
- TRADER_PLANNED_LEG_AFTER_REDUCE,
+ TRADER_DATA_NOT_READY,
+ TRADER_MODEL_ARTIFACT_MISSING,
+ TRADER_CALIBRATION_MISMATCH,
+ TRADER_PM_CONFIG_MISMATCH,
+ TRADER_MODEL_OUTPUT_INVALID,
TRADER_RISK_BLOCKED,
- TRADER_FEEDBACK_DISABLED,
- TRADER_SAMPLE_EXPORT_FAILED
+ TRADER_EXECUTION_BLOCKED,
+ TRADER_FEEDBACK_INVALID,
+ TRADER_P0_MODE_BLOCKED,
+ TRADER_KILL_SWITCH_ACTIVE,
+ TRADER_ACTIVE_POINTER_MISMATCH
}
diff --git a/src/main/java/com/quantai/trader/enums/TraderExecutionMode.java b/src/main/java/com/quantai/trader/enums/TraderExecutionMode.java
new file mode 100644
index 0000000..98001da
--- /dev/null
+++ b/src/main/java/com/quantai/trader/enums/TraderExecutionMode.java
@@ -0,0 +1,12 @@
+package com.quantai.trader.enums;
+
+public enum TraderExecutionMode {
+ REPLAY_SIM,
+ SHADOW,
+ PAPER,
+ REAL;
+
+ public boolean p0Allowed() {
+ return this == REPLAY_SIM || this == SHADOW;
+ }
+}
diff --git a/src/main/java/com/quantai/trader/enums/TraderFeedbackSource.java b/src/main/java/com/quantai/trader/enums/TraderFeedbackSource.java
deleted file mode 100644
index b1a2c2f..0000000
--- a/src/main/java/com/quantai/trader/enums/TraderFeedbackSource.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.quantai.trader.enums;
-
-public enum TraderFeedbackSource {
- MARKET_PROXY,
- SHADOW_APP,
- PAPER_APP,
- REAL_APP
-}
diff --git a/src/main/java/com/quantai/trader/enums/TraderFeedbackType.java b/src/main/java/com/quantai/trader/enums/TraderFeedbackType.java
deleted file mode 100644
index bf9edbe..0000000
--- a/src/main/java/com/quantai/trader/enums/TraderFeedbackType.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.quantai.trader.enums;
-
-public enum TraderFeedbackType {
- FILL_EVENT,
- CANCEL_EVENT,
- CLOSE_EVENT,
- REJECT_EVENT
-}
diff --git a/src/main/java/com/quantai/trader/enums/TraderPlaybookId.java b/src/main/java/com/quantai/trader/enums/TraderPlaybookId.java
deleted file mode 100644
index 23d127c..0000000
--- a/src/main/java/com/quantai/trader/enums/TraderPlaybookId.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.quantai.trader.enums;
-
-public enum TraderPlaybookId {
- BREAKOUT_RETEST_CONTINUATION,
- SUPPORT_PULLBACK_CONTINUATION,
- FALSE_BREAK_RECLAIM
-}
diff --git a/src/main/java/com/quantai/trader/enums/TraderRunMode.java b/src/main/java/com/quantai/trader/enums/TraderRunMode.java
index 31226c8..c9f122c 100644
--- a/src/main/java/com/quantai/trader/enums/TraderRunMode.java
+++ b/src/main/java/com/quantai/trader/enums/TraderRunMode.java
@@ -1,7 +1,12 @@
package com.quantai.trader.enums;
public enum TraderRunMode {
- REPLAY,
+ REPLAY_SIM,
SHADOW,
- PAPER
+ PAPER,
+ REAL;
+
+ public boolean p0Allowed() {
+ return this == REPLAY_SIM || this == SHADOW;
+ }
}
diff --git a/src/main/java/com/quantai/trader/enums/TraderSide.java b/src/main/java/com/quantai/trader/enums/TraderSide.java
deleted file mode 100644
index c7caa6a..0000000
--- a/src/main/java/com/quantai/trader/enums/TraderSide.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.quantai.trader.enums;
-
-public enum TraderSide {
- LONG,
- SHORT
-}
diff --git a/src/main/java/com/quantai/trader/enums/TraderState.java b/src/main/java/com/quantai/trader/enums/TraderState.java
deleted file mode 100644
index c8d75dc..0000000
--- a/src/main/java/com/quantai/trader/enums/TraderState.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.quantai.trader.enums;
-
-public enum TraderState {
- CONTEXT_CHECK,
- SETUP_ARMED,
- TRIGGER_WAIT,
- ENTRY_PLANNED,
- ENTRY_SENT_SHADOW,
- ENTRY_FILLED_PROXY,
- ENTRY_MISSED_PROXY,
- PLANNED_LEG_WAIT,
- PLANNED_LEG_SENT_SHADOW,
- PLANNED_LEG_FILLED_PROXY,
- PLANNED_LEG_MISSED_PROXY,
- MANAGING,
- SAMPLE_EXPORTED,
- BLOCKED
-}
diff --git a/src/main/java/com/quantai/trader/evidence/EvidenceAppender.java b/src/main/java/com/quantai/trader/evidence/EvidenceAppender.java
index 15daca4..398c21c 100644
--- a/src/main/java/com/quantai/trader/evidence/EvidenceAppender.java
+++ b/src/main/java/com/quantai/trader/evidence/EvidenceAppender.java
@@ -1,50 +1,31 @@
package com.quantai.trader.evidence;
-import com.quantai.trader.domain.StageDecision;
-import com.quantai.trader.domain.TraderDecisionCycle;
import com.quantai.trader.domain.TraderEvidence;
-import com.quantai.trader.persistence.TraderEvidenceRepository;
-import com.quantai.trader.util.Ids;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
@Component
public class EvidenceAppender {
-
private static final Logger log = LoggerFactory.getLogger(EvidenceAppender.class);
- private final TraderEvidenceRepository repository;
+ private final CopyOnWriteArrayList evidence = new CopyOnWriteArrayList<>();
- public EvidenceAppender(TraderEvidenceRepository repository) {
- this.repository = repository;
+ public TraderEvidence append(String runId, String cycleId, String stage, boolean pass, String reason, String blocker, Map details) {
+ TraderEvidence item = new TraderEvidence("evidence_" + cycleId + "_" + evidence.size(), runId, cycleId,
+ stage, pass, reason, blocker, Instant.now(), details);
+ evidence.add(item);
+ log.info("event=trader.evidence.appended runId={} cycleId={} stage={} pass={} reason={} blocker={}",
+ runId, cycleId, stage, pass, reason, blocker);
+ return item;
}
- public TraderEvidence append(TraderDecisionCycle cycle, String stage, StageDecision decision) {
- TraderEvidence evidence = new TraderEvidence(
- cycle.runId(),
- cycle.cycleId(),
- Ids.evidenceId(cycle, stage),
- stage,
- decision.pass(),
- decision.reason(),
- decision.blocker(),
- cycle.cycleTime(),
- decision.details()
- );
- repository.insert(evidence);
- log.info(
- "event=trader.evidence runId={} cycleId={} symbol={} playbookId={} playbookVersion={} state={} stage={} pass={} reason={} blocker={}",
- cycle.runId(),
- cycle.cycleId(),
- cycle.symbol(),
- cycle.playbookId(),
- cycle.playbookVersion(),
- cycle.state(),
- stage,
- decision.pass(),
- decision.reason(),
- decision.blocker()
- );
- return evidence;
+ public List all() {
+ return new ArrayList<>(evidence);
}
}
diff --git a/src/main/java/com/quantai/trader/execution/ExecutionQualityGate.java b/src/main/java/com/quantai/trader/execution/ExecutionQualityGate.java
deleted file mode 100644
index df316bb..0000000
--- a/src/main/java/com/quantai/trader/execution/ExecutionQualityGate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.quantai.trader.execution;
-
-import com.quantai.trader.domain.ExecutionDecision;
-import com.quantai.trader.domain.TraderEntryPlan;
-import com.quantai.trader.domain.TraderMarketSnapshot;
-import org.springframework.stereotype.Component;
-
-import java.math.BigDecimal;
-import java.util.Map;
-
-@Component
-public class ExecutionQualityGate {
-
- public ExecutionDecision evaluate(TraderMarketSnapshot snapshot, TraderEntryPlan entryPlan) {
- BigDecimal score = entryPlan.executionQualityScore() == null
- ? new BigDecimal("0.50")
- : entryPlan.executionQualityScore();
- if (!entryPlan.completeForEntry()) {
- return new ExecutionDecision(false, score, "ENTRY_PLAN_INCOMPLETE", "TRADER_ENTRY_PLAN_INCOMPLETE", Map.of());
- }
- if (score.compareTo(new BigDecimal("0.20")) < 0) {
- return new ExecutionDecision(false, score, "EXECUTION_QUALITY_TOO_LOW", "TRADER_RISK_BLOCKED", Map.of(
- "executionQualityScore", score
- ));
- }
- return new ExecutionDecision(true, score, "EXECUTION_PROXY_PASS", null, Map.of(
- "executionQualityScore", score,
- "proxyOnly", true
- ));
- }
-}
diff --git a/src/main/java/com/quantai/trader/execution/TraderEntryPlanner.java b/src/main/java/com/quantai/trader/execution/TraderEntryPlanner.java
deleted file mode 100644
index f3df074..0000000
--- a/src/main/java/com/quantai/trader/execution/TraderEntryPlanner.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package com.quantai.trader.execution;
-
-import com.quantai.trader.domain.PlaybookCandidate;
-import com.quantai.trader.domain.PositionSizingPlan;
-import com.quantai.trader.domain.TraderDecisionCycle;
-import com.quantai.trader.domain.TraderEntryPlan;
-import com.quantai.trader.domain.TraderException;
-import com.quantai.trader.domain.TraderPositionPath;
-import com.quantai.trader.domain.TraderPricePlan;
-import com.quantai.trader.domain.TriggerDecision;
-import com.quantai.trader.enums.TraderActionType;
-import com.quantai.trader.enums.TraderErrorCode;
-import com.quantai.trader.risk.TraderPositionSizer;
-import com.quantai.trader.util.Ids;
-import org.springframework.stereotype.Component;
-
-import java.math.BigDecimal;
-import java.util.Optional;
-
-@Component
-public class TraderEntryPlanner {
-
- private final TraderPositionSizer positionSizer;
-
- public TraderEntryPlanner(TraderPositionSizer positionSizer) {
- this.positionSizer = positionSizer;
- }
-
- public TraderEntryPlan planInitialEntry(
- TraderDecisionCycle cycle,
- PlaybookCandidate candidate,
- TriggerDecision trigger
- ) {
- TraderPricePlan pricePlan = candidate.pricePlan();
- validatePricePlan(pricePlan);
- PositionSizingPlan sizingPlan = positionSizer.sizeInitialPlan(cycle, candidate, trigger, pricePlan);
- return new TraderEntryPlan(
- cycle.runId(),
- cycle.cycleId(),
- Ids.actionId(cycle, 1),
- Ids.entryLegId(cycle, 0),
- candidate.candidateId(),
- TraderActionType.OPEN_INITIAL,
- 0,
- sizingPlan.plannedLegCount(),
- sizingPlan.initialLegRatio(),
- sizingPlan.sizingMethod(),
- sizingPlan.signalStrengthScore(),
- sizingPlan.executionQualityScore(),
- sizingPlan.riskGateScore(),
- pricePlan.entryPrice(),
- pricePlan.invalidPrice(),
- pricePlan.stopPrice(),
- pricePlan.targetPrice(),
- pricePlan.partialTakeProfitPrice(),
- pricePlan.maxEntryWaitMs(),
- pricePlan.maxHoldMs(),
- "INITIAL_ENTRY_FROM_PLAYBOOK_TRIGGER"
- );
- }
-
- public Optional planNextDeclaredLeg(
- TraderDecisionCycle cycle,
- PlaybookCandidate candidate,
- TraderPositionPath path
- ) {
- if (path == null || !path.opened() || path.reduceSeen() || path.fullSize()) {
- return Optional.empty();
- }
- int nextIndex = path.filledLegCount();
- if (nextIndex >= candidate.maxPlannedEntryLegs()) {
- return Optional.empty();
- }
- TraderPricePlan pricePlan = candidate.pricePlan();
- validatePricePlan(pricePlan);
- PositionSizingPlan sizingPlan = positionSizer.sizeNextPlannedLeg(cycle, candidate, path, pricePlan, nextIndex);
- if (sizingPlan.nextLegRatio().compareTo(BigDecimal.ZERO) <= 0) {
- return Optional.empty();
- }
- return Optional.of(new TraderEntryPlan(
- cycle.runId(),
- cycle.cycleId(),
- Ids.actionId(cycle, nextIndex + 1),
- Ids.entryLegId(cycle, nextIndex),
- candidate.candidateId(),
- TraderActionType.OPEN_PLANNED_LEG,
- nextIndex,
- sizingPlan.plannedLegCount(),
- sizingPlan.nextLegRatio(),
- sizingPlan.sizingMethod(),
- sizingPlan.signalStrengthScore(),
- sizingPlan.executionQualityScore(),
- sizingPlan.riskGateScore(),
- pricePlan.entryPrice(),
- pricePlan.invalidPrice(),
- pricePlan.stopPrice(),
- pricePlan.targetPrice(),
- pricePlan.partialTakeProfitPrice(),
- pricePlan.maxEntryWaitMs(),
- pricePlan.maxHoldMs(),
- "DECLARED_PLANNED_ENTRY_LEG"
- ));
- }
-
- private void validatePricePlan(TraderPricePlan pricePlan) {
- if (pricePlan == null || !pricePlan.completeForEntry()) {
- throw new TraderException(
- TraderErrorCode.TRADER_ENTRY_PLAN_INCOMPLETE,
- "entry/invalid/stop/target/maxHold are required before an entry action"
- );
- }
- }
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/entity/TraderEvidenceEntity.java b/src/main/java/com/quantai/trader/infrastructure/entity/TraderEvidenceEntity.java
deleted file mode 100644
index c6502ec..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/entity/TraderEvidenceEntity.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.quantai.trader.infrastructure.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-@Data
-@TableName("trader_evidence")
-public class TraderEvidenceEntity {
- @TableId(type = IdType.AUTO)
- private Long id;
- private String runId;
- private String cycleId;
- private String evidenceId;
- private String stage;
- private Boolean pass;
- private String reason;
- private String blocker;
- private LocalDateTime evidenceTime;
- private String detailsJson;
- private LocalDateTime createdAt;
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/entity/TraderPlaybookDefinitionEntity.java b/src/main/java/com/quantai/trader/infrastructure/entity/TraderPlaybookDefinitionEntity.java
deleted file mode 100644
index 8ab81df..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/entity/TraderPlaybookDefinitionEntity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.quantai.trader.infrastructure.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-@Data
-@TableName("trader_playbook_definition")
-public class TraderPlaybookDefinitionEntity {
- @TableId(type = IdType.AUTO)
- private Long id;
- private String playbookId;
- private String playbookVersion;
- private String family;
- private String variant;
- private String sideMode;
- private String sourcePath;
- private String definitionHashSha256;
- private String definitionJson;
- private LocalDateTime loadedAt;
- private String status;
- private LocalDateTime createdAt;
- private LocalDateTime updatedAt;
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/entity/TraderReplayReportEntity.java b/src/main/java/com/quantai/trader/infrastructure/entity/TraderReplayReportEntity.java
deleted file mode 100644
index 08c78c5..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/entity/TraderReplayReportEntity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.quantai.trader.infrastructure.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-@Data
-@TableName("trader_replay_report")
-public class TraderReplayReportEntity {
- @TableId(type = IdType.AUTO)
- private Long id;
- private String runId;
- private String reportId;
- private String symbol;
- private String playbookId;
- private String playbookVersion;
- private Integer candidateEvents;
- private Integer monthsCovered;
- @TableField("base_net_return_bps_1x")
- private BigDecimal baseNetReturnBps1x;
- @TableField("leveraged_net_return_bps_10x")
- private BigDecimal leveragedNetReturnBps10x;
- @TableField("holdout_return_bps_10x")
- private BigDecimal holdoutReturnBps10x;
- private String strictVsLooseJson;
- private String failureRisksJson;
- private String conclusion;
- private String reportPath;
- private LocalDateTime createdAt;
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/entity/TraderReplayRunEntity.java b/src/main/java/com/quantai/trader/infrastructure/entity/TraderReplayRunEntity.java
deleted file mode 100644
index c785450..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/entity/TraderReplayRunEntity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.quantai.trader.infrastructure.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-@Data
-@TableName("trader_replay_run")
-public class TraderReplayRunEntity {
- @TableId(type = IdType.AUTO)
- private Long id;
- private String runId;
- private String runMode;
- private String symbol;
- private String playbookId;
- private String playbookVersion;
- private String playbookDefinitionHash;
- private LocalDateTime dataFrom;
- private LocalDateTime dataTo;
- private String featureVersion;
- private String labelVersion;
- private String dataSourceManifestJson;
- private String status;
- private String configJson;
- private LocalDateTime startedAt;
- private LocalDateTime finishedAt;
- private String failureReason;
- private LocalDateTime createdAt;
- private LocalDateTime updatedAt;
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/entity/TraderRiskDecisionEntity.java b/src/main/java/com/quantai/trader/infrastructure/entity/TraderRiskDecisionEntity.java
deleted file mode 100644
index ab05247..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/entity/TraderRiskDecisionEntity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.quantai.trader.infrastructure.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-@Data
-@TableName("trader_risk_decision")
-public class TraderRiskDecisionEntity {
- @TableId(type = IdType.AUTO)
- private Long id;
- private String runId;
- private String cycleId;
- private String actionId;
- private String accountStateId;
- private String actionType;
- private BigDecimal leverageScreen;
- private BigDecimal plannedTotalPositionRatio;
- private BigDecimal maxLossBps;
- private BigDecimal liquidationBufferBps;
- @TableField("expected_value_bps_1x")
- private BigDecimal expectedValueBps1x;
- @TableField("expected_value_bps_10x")
- private BigDecimal expectedValueBps10x;
- private BigDecimal uncertainty;
- private BigDecimal oodScore;
- private Boolean allowAction;
- private String blocker;
- private String decisionJson;
- private LocalDateTime createdAt;
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/entity/TraderTrainingSampleEntity.java b/src/main/java/com/quantai/trader/infrastructure/entity/TraderTrainingSampleEntity.java
deleted file mode 100644
index 3f22c96..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/entity/TraderTrainingSampleEntity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.quantai.trader.infrastructure.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-@Data
-@TableName("trader_training_sample")
-public class TraderTrainingSampleEntity {
- @TableId(type = IdType.AUTO)
- private Long id;
- private String runId;
- private String cycleId;
- private String sampleId;
- private String actionId;
- private String positionId;
- private String featureVersion;
- private String labelVersion;
- private LocalDateTime sampleTime;
- private String featuresJson;
- private String labelsJson;
- @TableField("net_return_bps_1x")
- private BigDecimal netReturnBps1x;
- @TableField("net_return_bps_10x")
- private BigDecimal netReturnBps10x;
- private Boolean proxyOnly;
- private LocalDateTime createdAt;
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderEvidenceMapper.java b/src/main/java/com/quantai/trader/infrastructure/mapper/TraderEvidenceMapper.java
deleted file mode 100644
index 6f7958b..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderEvidenceMapper.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.quantai.trader.infrastructure.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.quantai.trader.infrastructure.entity.TraderEvidenceEntity;
-
-public interface TraderEvidenceMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderPlaybookDefinitionMapper.java b/src/main/java/com/quantai/trader/infrastructure/mapper/TraderPlaybookDefinitionMapper.java
deleted file mode 100644
index f5d2b14..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderPlaybookDefinitionMapper.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.quantai.trader.infrastructure.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.quantai.trader.infrastructure.entity.TraderPlaybookDefinitionEntity;
-
-public interface TraderPlaybookDefinitionMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderReplayReportMapper.java b/src/main/java/com/quantai/trader/infrastructure/mapper/TraderReplayReportMapper.java
deleted file mode 100644
index 17c259d..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderReplayReportMapper.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.quantai.trader.infrastructure.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.quantai.trader.infrastructure.entity.TraderReplayReportEntity;
-
-public interface TraderReplayReportMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderReplayRunMapper.java b/src/main/java/com/quantai/trader/infrastructure/mapper/TraderReplayRunMapper.java
deleted file mode 100644
index dc97339..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderReplayRunMapper.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.quantai.trader.infrastructure.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.quantai.trader.infrastructure.entity.TraderReplayRunEntity;
-
-public interface TraderReplayRunMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderRiskDecisionMapper.java b/src/main/java/com/quantai/trader/infrastructure/mapper/TraderRiskDecisionMapper.java
deleted file mode 100644
index a725ace..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderRiskDecisionMapper.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.quantai.trader.infrastructure.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.quantai.trader.infrastructure.entity.TraderRiskDecisionEntity;
-
-public interface TraderRiskDecisionMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderTrainingSampleMapper.java b/src/main/java/com/quantai/trader/infrastructure/mapper/TraderTrainingSampleMapper.java
deleted file mode 100644
index 5f57a7b..0000000
--- a/src/main/java/com/quantai/trader/infrastructure/mapper/TraderTrainingSampleMapper.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.quantai.trader.infrastructure.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.quantai.trader.infrastructure.entity.TraderTrainingSampleEntity;
-
-public interface TraderTrainingSampleMapper extends BaseMapper {
-}
diff --git a/src/main/java/com/quantai/trader/market/SnapshotBuilder.java b/src/main/java/com/quantai/trader/market/SnapshotBuilder.java
deleted file mode 100644
index 22343cb..0000000
--- a/src/main/java/com/quantai/trader/market/SnapshotBuilder.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.quantai.trader.market;
-
-import com.quantai.trader.config.TraderProperties;
-import com.quantai.trader.domain.TraderMarketSnapshot;
-import com.quantai.trader.replay.ReplayClockTick;
-import com.quantai.trader.state.TraderRuntimeState;
-import com.quantai.trader.util.Ids;
-import org.springframework.stereotype.Component;
-
-import java.util.Objects;
-
-@Component
-public class SnapshotBuilder {
-
- private final TraderProperties properties;
-
- public SnapshotBuilder(TraderProperties properties) {
- this.properties = properties;
- }
-
- public TraderMarketSnapshot build(ReplayClockTick tick, TraderRuntimeState runtimeState) {
- String cycleId = Ids.cycleId(runtimeState.runId(), tick.symbol(), tick.eventTime());
- return new TraderMarketSnapshot(
- runtimeState.runId(),
- cycleId,
- Ids.snapshotId(cycleId),
- tick.symbol(),
- tick.eventTime(),
- properties.getFeatureVersion(),
- Objects.requireNonNull(tick.contextFeatures(), "contextFeatures is required"),
- Objects.requireNonNull(tick.setupFeatures(), "setupFeatures is required"),
- Objects.requireNonNull(tick.triggerFeatures(), "triggerFeatures is required"),
- Objects.requireNonNull(tick.executionFeatures(), "executionFeatures is required"),
- Objects.requireNonNull(tick.dataQuality(), "dataQuality is required"),
- Objects.requireNonNull(tick.labelInputs(), "labelInputs is required")
- );
- }
-
- public TraderMarketSnapshot buildNextManagementSnapshot(
- TraderMarketSnapshot previous,
- ReplayClockTick tick,
- TraderRuntimeState runtimeState
- ) {
- return build(tick, runtimeState);
- }
-}
diff --git a/src/main/java/com/quantai/trader/model/DeterministicTraderModelService.java b/src/main/java/com/quantai/trader/model/DeterministicTraderModelService.java
new file mode 100644
index 0000000..a6f9bef
--- /dev/null
+++ b/src/main/java/com/quantai/trader/model/DeterministicTraderModelService.java
@@ -0,0 +1,50 @@
+package com.quantai.trader.model;
+
+import com.quantai.trader.artifact.TraderArtifactBundle;
+import com.quantai.trader.domain.*;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+@Service
+public class DeterministicTraderModelService implements TraderModelService {
+ @Override
+ public TraderModelOutput evaluate(TraderMarketSnapshot snapshot, TraderArtifactBundle bundle) {
+ BigDecimal directionalBias = snapshot.markPrice().compareTo(snapshot.indexPrice()) >= 0
+ ? new BigDecimal("0.62")
+ : new BigDecimal("0.32");
+ BigDecimal shortBias = BigDecimal.ONE.subtract(directionalBias).subtract(new BigDecimal("0.10"));
+ if (shortBias.compareTo(BigDecimal.ZERO) < 0) {
+ shortBias = BigDecimal.ZERO;
+ }
+ BigDecimal neutral = BigDecimal.ONE.subtract(directionalBias).subtract(shortBias);
+ EntryOutput entry = new EntryOutput(
+ new BigDecimal("0.63"), new BigDecimal("0.42"), new BigDecimal("0.64"),
+ new BigDecimal("12.0"), "p0-plan-atr-2r", "p0-price-plan-hash",
+ new BigDecimal("35"), new BigDecimal("70"), 45, new BigDecimal("4.0"),
+ "entry-p0", bundle.calibrationBundleVersion(), Map.of("source", "deterministic_fixture"));
+ return new TraderModelOutput(
+ "model_output_" + snapshot.cycleId(),
+ snapshot.runId(),
+ snapshot.cycleId(),
+ bundle.modelBundleVersion(),
+ bundle.calibrationBundleVersion(),
+ new DirectionOutput(directionalBias, shortBias, neutral, directionalBias, directionalBias.subtract(shortBias).abs(),
+ new BigDecimal("8.0"), 45, "direction-p0", bundle.calibrationBundleVersion(), Map.of()),
+ entry,
+ new ContinueOutput(new BigDecimal("0.61"), new BigDecimal("0.39"), new BigDecimal("0.58"),
+ new BigDecimal("5.0"), new BigDecimal("3.0"), "continue-p0", bundle.calibrationBundleVersion(), Map.of()),
+ new ExitOutput(new BigDecimal("0.24"), new BigDecimal("0.48"), new BigDecimal("0.20"),
+ new BigDecimal("0.25"), new BigDecimal("0.22"), new BigDecimal("0.20"),
+ new BigDecimal("10"), "exit-p0", bundle.calibrationBundleVersion(), Map.of()),
+ new RiskOutput(new BigDecimal("0.20"), new BigDecimal("0.18"),
+ new BigDecimal("20"), new BigDecimal("18"), new BigDecimal("0.15"),
+ new BigDecimal("20"), new BigDecimal("0.20"), new BigDecimal("0.10"),
+ new BigDecimal("0.12"), snapshot.depthNotional5Bps().compareTo(BigDecimal.ZERO) > 0 ? new BigDecimal("1.0") : BigDecimal.ZERO,
+ "risk-p0", bundle.calibrationBundleVersion(), Map.of()),
+ new BigDecimal("0.10"),
+ new BigDecimal("0.05"),
+ Map.of("source", "deterministic_p0_fixture"));
+ }
+}
diff --git a/src/main/java/com/quantai/trader/model/TraderModel.java b/src/main/java/com/quantai/trader/model/TraderModel.java
deleted file mode 100644
index adb9942..0000000
--- a/src/main/java/com/quantai/trader/model/TraderModel.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.quantai.trader.model;
-
-import com.quantai.trader.domain.TraderModelManifest;
-import com.quantai.trader.domain.TraderModelOutput;
-
-public interface TraderModel {
-
- TraderModelOutput predict(TInput input, TraderModelManifest manifest);
-}
diff --git a/src/main/java/com/quantai/trader/model/TraderModelService.java b/src/main/java/com/quantai/trader/model/TraderModelService.java
new file mode 100644
index 0000000..3e722b8
--- /dev/null
+++ b/src/main/java/com/quantai/trader/model/TraderModelService.java
@@ -0,0 +1,9 @@
+package com.quantai.trader.model;
+
+import com.quantai.trader.artifact.TraderArtifactBundle;
+import com.quantai.trader.domain.TraderMarketSnapshot;
+import com.quantai.trader.domain.TraderModelOutput;
+
+public interface TraderModelService {
+ TraderModelOutput evaluate(TraderMarketSnapshot snapshot, TraderArtifactBundle bundle);
+}
diff --git a/src/main/java/com/quantai/trader/outbox/InMemoryOutboxRepository.java b/src/main/java/com/quantai/trader/outbox/InMemoryOutboxRepository.java
new file mode 100644
index 0000000..32ea3b7
--- /dev/null
+++ b/src/main/java/com/quantai/trader/outbox/InMemoryOutboxRepository.java
@@ -0,0 +1,30 @@
+package com.quantai.trader.outbox;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Repository;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+@Repository
+public class InMemoryOutboxRepository {
+ private static final Logger log = LoggerFactory.getLogger(InMemoryOutboxRepository.class);
+ private final CopyOnWriteArrayList events = new CopyOnWriteArrayList<>();
+
+ public void insert(TraderOutboxEvent event) {
+ boolean duplicate = events.stream().anyMatch(existing -> existing.destination().equals(event.destination())
+ && existing.idempotencyKey().equals(event.idempotencyKey()));
+ if (duplicate) {
+ throw new IllegalArgumentException("duplicate outbox idempotency key: " + event.idempotencyKey());
+ }
+ events.add(event);
+ log.info("event=trader.outbox.inserted runId={} cycleId={} destination={} aggregateType={} aggregateId={} status={}",
+ event.runId(), event.cycleId(), event.destination(), event.aggregateType(), event.aggregateId(), event.status());
+ }
+
+ public List all() {
+ return new ArrayList<>(events);
+ }
+}
diff --git a/src/main/java/com/quantai/trader/outbox/TraderOutboxEvent.java b/src/main/java/com/quantai/trader/outbox/TraderOutboxEvent.java
new file mode 100644
index 0000000..38a50bd
--- /dev/null
+++ b/src/main/java/com/quantai/trader/outbox/TraderOutboxEvent.java
@@ -0,0 +1,34 @@
+package com.quantai.trader.outbox;
+
+import static com.quantai.trader.util.TraderNumbers.requiredText;
+
+import java.time.Instant;
+import java.util.Map;
+import java.util.Objects;
+
+public record TraderOutboxEvent(
+ String outboxId,
+ String runId,
+ String cycleId,
+ String aggregateType,
+ String aggregateId,
+ String eventType,
+ String destination,
+ Map payloadJson,
+ String idempotencyKey,
+ String status,
+ Instant createdAt
+) {
+ public TraderOutboxEvent {
+ outboxId = requiredText(outboxId, "outboxId");
+ runId = requiredText(runId, "runId");
+ aggregateType = requiredText(aggregateType, "aggregateType");
+ aggregateId = requiredText(aggregateId, "aggregateId");
+ eventType = requiredText(eventType, "eventType");
+ destination = requiredText(destination, "destination");
+ idempotencyKey = requiredText(idempotencyKey, "idempotencyKey");
+ status = requiredText(status, "status");
+ createdAt = Objects.requireNonNull(createdAt, "createdAt is required");
+ payloadJson = Map.copyOf(payloadJson == null ? Map.of() : payloadJson);
+ }
+}
diff --git a/src/main/java/com/quantai/trader/persistence/MybatisReplayReportRepository.java b/src/main/java/com/quantai/trader/persistence/MybatisReplayReportRepository.java
deleted file mode 100644
index 0f48489..0000000
--- a/src/main/java/com/quantai/trader/persistence/MybatisReplayReportRepository.java
+++ /dev/null
@@ -1,75 +0,0 @@
-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 findByRunId(String runId) {
- TraderReplayReportEntity entity = mapper.selectOne(
- Wrappers.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())
- );
- }
-}
diff --git a/src/main/java/com/quantai/trader/persistence/MybatisReplayRunRepository.java b/src/main/java/com/quantai/trader/persistence/MybatisReplayRunRepository.java
deleted file mode 100644
index 7927033..0000000
--- a/src/main/java/com/quantai/trader/persistence/MybatisReplayRunRepository.java
+++ /dev/null
@@ -1,88 +0,0 @@
-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.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 findByRunId(String runId) {
- TraderReplayRunEntity entity = mapper.selectOne(
- Wrappers.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()
- );
- }
-}
diff --git a/src/main/java/com/quantai/trader/persistence/MybatisTraderEvidenceRepository.java b/src/main/java/com/quantai/trader/persistence/MybatisTraderEvidenceRepository.java
deleted file mode 100644
index fd99334..0000000
--- a/src/main/java/com/quantai/trader/persistence/MybatisTraderEvidenceRepository.java
+++ /dev/null
@@ -1,61 +0,0 @@
-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 findByCycleId(String runId, String cycleId) {
- return mapper.selectList(
- Wrappers.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())
- );
- }
-}
diff --git a/src/main/java/com/quantai/trader/persistence/MybatisTraderPlaybookDefinitionRepository.java b/src/main/java/com/quantai/trader/persistence/MybatisTraderPlaybookDefinitionRepository.java
deleted file mode 100644
index 4632704..0000000
--- a/src/main/java/com/quantai/trader/persistence/MybatisTraderPlaybookDefinitionRepository.java
+++ /dev/null
@@ -1,83 +0,0 @@
-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 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 findPlaybookDefinition(String playbookId, String playbookVersion) {
- TraderPlaybookDefinitionEntity entity = mapper.selectOne(
- Wrappers.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)
- );
- }
-}
diff --git a/src/main/java/com/quantai/trader/persistence/MybatisTraderRiskDecisionRepository.java b/src/main/java/com/quantai/trader/persistence/MybatisTraderRiskDecisionRepository.java
deleted file mode 100644
index b8be1f2..0000000
--- a/src/main/java/com/quantai/trader/persistence/MybatisTraderRiskDecisionRepository.java
+++ /dev/null
@@ -1,76 +0,0 @@
-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 findByCycleId(String runId, String cycleId) {
- return mapper.selectList(
- Wrappers.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())
- );
- }
-}
diff --git a/src/main/java/com/quantai/trader/persistence/MybatisTraderSampleRepository.java b/src/main/java/com/quantai/trader/persistence/MybatisTraderSampleRepository.java
deleted file mode 100644
index 458e913..0000000
--- a/src/main/java/com/quantai/trader/persistence/MybatisTraderSampleRepository.java
+++ /dev/null
@@ -1,68 +0,0 @@
-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 findByRunId(String runId) {
- return mapper.selectList(
- Wrappers.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())
- );
- }
-}
diff --git a/src/main/java/com/quantai/trader/persistence/ReplayReportRepository.java b/src/main/java/com/quantai/trader/persistence/ReplayReportRepository.java
deleted file mode 100644
index c8788e1..0000000
--- a/src/main/java/com/quantai/trader/persistence/ReplayReportRepository.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.quantai.trader.persistence;
-
-import com.quantai.trader.domain.TraderReplayReport;
-
-import java.util.Optional;
-
-public interface ReplayReportRepository {
-
- void insert(TraderReplayReport report);
-
- Optional findByRunId(String runId);
-}
diff --git a/src/main/java/com/quantai/trader/persistence/ReplayRunRepository.java b/src/main/java/com/quantai/trader/persistence/ReplayRunRepository.java
deleted file mode 100644
index 261a22a..0000000
--- a/src/main/java/com/quantai/trader/persistence/ReplayRunRepository.java
+++ /dev/null
@@ -1,14 +0,0 @@
-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 findByRunId(String runId);
-}
diff --git a/src/main/java/com/quantai/trader/persistence/TraderEvidenceRepository.java b/src/main/java/com/quantai/trader/persistence/TraderEvidenceRepository.java
deleted file mode 100644
index f092047..0000000
--- a/src/main/java/com/quantai/trader/persistence/TraderEvidenceRepository.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.quantai.trader.persistence;
-
-import com.quantai.trader.domain.TraderEvidence;
-
-import java.util.List;
-
-public interface TraderEvidenceRepository {
-
- void insert(TraderEvidence evidence);
-
- List findByCycleId(String runId, String cycleId);
-}
diff --git a/src/main/java/com/quantai/trader/persistence/TraderPersistenceCodec.java b/src/main/java/com/quantai/trader/persistence/TraderPersistenceCodec.java
deleted file mode 100644
index d6154ff..0000000
--- a/src/main/java/com/quantai/trader/persistence/TraderPersistenceCodec.java
+++ /dev/null
@@ -1,86 +0,0 @@
-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