Load trader V4 artifacts from manifests

This commit is contained in:
Codex
2026-06-26 22:07:43 +08:00
parent 6bbedda97d
commit 4e5f49d6fe
11 changed files with 598 additions and 151 deletions
@@ -1,5 +1,7 @@
package com.quantai.trader;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.quantai.trader.config.TraderProperties;
import com.quantai.trader.domain.*;
import com.quantai.trader.enums.PositionSide;
@@ -9,6 +11,9 @@ import com.quantai.trader.enums.TraderRunMode;
import com.quantai.trader.risk.RiskLimits;
import java.math.BigDecimal;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -27,6 +32,26 @@ public final class TestFixtures {
return properties(TraderRunMode.SHADOW, TraderExecutionMode.SHADOW, false, false);
}
public static ObjectMapper objectMapper() {
return JsonMapper.builder().findAndAddModules().build();
}
public static TraderProperties propertiesWithArtifactRoot(Path artifactRoot) {
TraderProperties base = properties();
return new TraderProperties(
base.serviceName(),
base.runMode(),
base.symbol(),
new TraderProperties.Artifact("trader-v4-btc-p0", "cal-v4-btc-p0", "pm-v4-btc-p0", artifactRoot.toString()),
base.feedback(),
base.execution(),
base.runtime(),
base.outbox(),
base.release(),
base.risk(),
base.positionManager());
}
public static TraderProperties properties(TraderRunMode runMode, TraderExecutionMode executionMode,
boolean tradingEnabled, boolean feedbackHttpEnabled) {
return new TraderProperties(
@@ -190,4 +215,145 @@ public final class TestFixtures {
public static RiskLimits riskLimits() {
return new RiskLimits(bd("200"), BigDecimal.ONE, bd("500"), 3, 1500, false, false);
}
public static void writeArtifactBundle(Path artifactRoot) throws IOException {
Files.createDirectories(artifactRoot.resolve("manifests"));
Files.writeString(artifactRoot.resolve("manifests/model_bundle_manifest.json"), """
{
"model_bundle_version": "trader-v4-btc-p0",
"calibration_bundle_version": "cal-v4-btc-p0",
"feature_version": "feature-v4-p0",
"label_version": "label-v4-p0",
"split_version": "split-v4-p0",
"required_models_json": ["DIRECTION", "ENTRY", "CONTINUE", "EXIT", "RISK"],
"provided_models_json": ["DIRECTION", "ENTRY", "CONTINUE", "EXIT", "RISK"],
"missing_models_json": [],
"bundle_hash_sha256": "00000000000000000000000000000000000000000000000000000000000000a1",
"complete": true,
"status": "ACTIVE"
}
""");
Files.writeString(artifactRoot.resolve("manifests/position_manager_manifest.json"), """
{
"pm_config_version": "pm-v4-btc-p0",
"model_bundle_version": "trader-v4-btc-p0",
"calibration_bundle_version": "cal-v4-btc-p0",
"allowed_run_modes_json": ["REPLAY_SIM", "SHADOW"],
"config_hash_sha256": "00000000000000000000000000000000000000000000000000000000000000b1",
"status": "ACTIVE",
"config_json": {
"pmConfigVersion": "pm-v4-btc-p0",
"open": {
"longOpenProb": 0.58,
"shortOpenProb": 0.58,
"minLongEntryProb": 0.55,
"minShortEntryProb": 0.55,
"maxMarketRiskProb": 0.45,
"minExpectedEdgeBps": 1.0,
"minDirectionMargin": 0.03,
"minLiquidityCapacityRatio": 0.10,
"maxOodScore": 0.80
},
"add": {
"minLongProb": 0.60,
"minShortProb": 0.60,
"minContinueProb": 0.58,
"minEntryProb": 0.55,
"maxExitProb": 0.45,
"maxMarketRiskProb": 0.45,
"maxPositionRiskProb": 0.50,
"minExpectedEdgeBps": 1.0,
"minContinueVsExitEdgeBps": 0,
"minLiquidityCapacityRatio": 0.10,
"minPostTradeLiquidationBufferBps": 500,
"maxAddCount": 3,
"cooldownMinutes": 5
},
"exit": {
"closeExitProb": 0.70,
"closePositionRiskProb": 0.70,
"closeMarketRiskProb": 0.70,
"closeContinueMax": 0.25,
"reduceGivebackProb": 0.62,
"reduceContinueMin": 0.35,
"reduceContinueMax": 0.70,
"minProfitForReduceBps": 5.0,
"maxExpectedShortfallBps": 80
},
"sizing": {
"baseRatio": 0.80,
"minInitialRatio": 0.05,
"maxSingleLegRatio": 1.0,
"minAddRatio": 0.02,
"maxAddRatio": 0.25,
"maxTotalPositionRatio": 1.0,
"minEdgeBps": 1.0,
"maxLossPerTradeBps": 80,
"maxLiquidityUsageRatio": 0.20,
"uncertaintyPenaltyMultiplier": 0.50,
"minPostTradeLiquidationBufferBps": 500
}
}
}
""");
Files.writeString(artifactRoot.resolve("model_output_policy.json"), """
{
"direction": {
"longProbWhenMarkGteIndex": 0.62,
"longProbWhenMarkLtIndex": 0.32,
"neutralProb": 0.10,
"expectedReturnBps": 8.0,
"horizonMinutes": 45,
"modelVersion": "direction-p0"
},
"entry": {
"longEntryProb": 0.63,
"shortEntryProb": 0.42,
"entryQualityScore": 0.64,
"expectedEdgeBps": 12.0,
"pricePlanId": "p0-plan-atr-2r",
"pricePlanConfigHash": "p0-price-plan-hash",
"stopDistanceBps": 35,
"targetDistanceBps": 70,
"maxHoldMinutes": 45,
"costBps": 4.0,
"modelVersion": "entry-p0"
},
"continuation": {
"longContinueProb": 0.61,
"shortContinueProb": 0.39,
"trendPersistenceProb": 0.58,
"holdEdgeBps": 5.0,
"continueVsExitEdgeBps": 3.0,
"modelVersion": "continue-p0"
},
"exit": {
"longExitProb": 0.24,
"shortExitProb": 0.48,
"profitGivebackProb": 0.20,
"reversalProb": 0.25,
"stopRiskProb": 0.22,
"stagnationProb": 0.20,
"expectedGivebackBps": 10,
"modelVersion": "exit-p0"
},
"risk": {
"marketRiskProb": 0.20,
"positionRiskProb": 0.18,
"marketRiskSeverityBps": 20,
"positionRiskSeverityBps": 18,
"drawdownProb": 0.15,
"expectedShortfallBps": 20,
"volatilityExpansionProb": 0.20,
"spikeProb": 0.10,
"liquidityRiskProb": 0.12,
"liquidityCapacityRatioWhenReady": 1.0,
"liquidityCapacityRatioWhenNotReady": 0,
"modelVersion": "risk-p0"
},
"uncertainty": 0.10,
"oodScore": 0.05
}
""");
}
}
@@ -2,20 +2,29 @@ package com.quantai.trader.artifact;
import com.quantai.trader.config.TraderProperties;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Set;
import static com.quantai.trader.TestFixtures.properties;
import static com.quantai.trader.TestFixtures.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class TraderArtifactLoaderTest {
@TempDir
Path artifactRoot;
@Test
void deterministicP0BundleProvidesAllFiveModelFamilies() {
TraderArtifactBundle bundle = new TraderArtifactLoader(properties()).loadActiveBundle();
void activeArtifactBundleProvidesAllFiveModelFamilies() throws IOException {
writeArtifactBundle(artifactRoot);
TraderArtifactBundle bundle = new TraderArtifactLoader(propertiesWithArtifactRoot(artifactRoot), objectMapper()).loadActiveBundle();
assertThat(bundle.providedModels()).containsExactlyInAnyOrder("DIRECTION", "ENTRY", "CONTINUE", "EXIT", "RISK");
assertThat(bundle.pmConfig().pmConfigVersion()).isEqualTo("pm-v4-btc-p0");
assertThat(bundle.modelPolicy().entry().pricePlanConfigHash()).isEqualTo("p0-price-plan-hash");
}
@Test
@@ -26,13 +35,21 @@ class TraderArtifactLoaderTest {
}
@Test
void bundleContractRejectsMissingModelFamily() {
TraderArtifactBundle bundle = new TraderArtifactLoader(properties()).loadActiveBundle();
void bundleContractRejectsMissingModelFamily() throws IOException {
writeArtifactBundle(artifactRoot);
TraderArtifactBundle bundle = new TraderArtifactLoader(propertiesWithArtifactRoot(artifactRoot), objectMapper()).loadActiveBundle();
assertThatThrownBy(() -> new TraderArtifactBundle(
bundle.modelBundleVersion(), bundle.calibrationBundleVersion(), bundle.pmConfigVersion(),
bundle.bundleHashSha256(), Set.of("DIRECTION", "ENTRY", "CONTINUE", "RISK"), bundle.pmConfig()))
bundle.bundleHashSha256(), Set.of("DIRECTION", "ENTRY", "CONTINUE", "RISK"), bundle.pmConfig(), bundle.modelPolicy()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("all five");
}
@Test
void rejectsMissingArtifactFiles() {
assertThatThrownBy(() -> new TraderArtifactLoader(propertiesWithArtifactRoot(artifactRoot), objectMapper()).loadActiveBundle())
.isInstanceOf(com.quantai.trader.domain.TraderException.class)
.hasMessageContaining("artifact file is missing");
}
}
@@ -5,33 +5,39 @@ import com.quantai.trader.domain.*;
import com.quantai.trader.enums.TraderActionType;
import com.quantai.trader.evidence.EvidenceAppender;
import com.quantai.trader.evidence.TraderEvidenceRepository;
import com.quantai.trader.model.DeterministicTraderModelService;
import com.quantai.trader.model.ArtifactTraderModelService;
import com.quantai.trader.outbox.TraderOutboxEvent;
import com.quantai.trader.outbox.TraderOutboxRepository;
import com.quantai.trader.persistence.TraderDecisionTraceWriter;
import com.quantai.trader.position.TraderPositionManager;
import com.quantai.trader.risk.TraderRiskGate;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Path;
import static com.quantai.trader.TestFixtures.T0;
import static com.quantai.trader.TestFixtures.properties;
import static com.quantai.trader.TestFixtures.*;
import static org.assertj.core.api.Assertions.assertThat;
class TraderP0CycleRunnerTest {
@TempDir
Path artifactRoot;
@Test
void runsReplayShadowCycleThroughModelPmRiskActionOutboxAndEvidence() {
void runsReplayShadowCycleThroughModelPmRiskActionOutboxAndEvidence() throws IOException {
writeArtifactBundle(artifactRoot);
RecordingEvidenceRepository evidenceRepository = new RecordingEvidenceRepository();
EvidenceAppender evidenceAppender = new EvidenceAppender(evidenceRepository);
RecordingTraceWriter traceWriter = new RecordingTraceWriter();
RecordingOutboxRepository outboxRepository = new RecordingOutboxRepository();
TraderP0CycleRunner runner = new TraderP0CycleRunner(
properties(),
new TraderArtifactLoader(properties()),
new DeterministicTraderModelService(),
propertiesWithArtifactRoot(artifactRoot),
new TraderArtifactLoader(propertiesWithArtifactRoot(artifactRoot), objectMapper()),
new ArtifactTraderModelService(),
new TraderPositionManager(),
new TraderRiskGate(),
new TraderActionFactory(),
@@ -53,15 +59,16 @@ class TraderP0CycleRunnerTest {
}
@Test
void recordsWaitActionWhenReplaySnapshotHasNoLiquidity() {
void recordsWaitActionWhenReplaySnapshotHasNoLiquidity() throws IOException {
writeArtifactBundle(artifactRoot);
RecordingEvidenceRepository evidenceRepository = new RecordingEvidenceRepository();
EvidenceAppender evidenceAppender = new EvidenceAppender(evidenceRepository);
RecordingTraceWriter traceWriter = new RecordingTraceWriter();
RecordingOutboxRepository outboxRepository = new RecordingOutboxRepository();
TraderP0CycleRunner runner = new TraderP0CycleRunner(
properties(),
new TraderArtifactLoader(properties()),
new DeterministicTraderModelService(),
propertiesWithArtifactRoot(artifactRoot),
new TraderArtifactLoader(propertiesWithArtifactRoot(artifactRoot), objectMapper()),
new ArtifactTraderModelService(),
new TraderPositionManager(),
new TraderRiskGate(),
new TraderActionFactory(),