Load trader V4 artifacts from manifests
This commit is contained in:
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user