Implement Trader V4 training artifact pipeline

This commit is contained in:
Codex
2026-06-27 16:15:23 +08:00
parent dad6b831b4
commit e58e4a5572
113 changed files with 7959 additions and 477 deletions
@@ -2,37 +2,68 @@ package com.quantai.trader.domain;
import static com.quantai.trader.util.TraderNumbers.*;
import com.quantai.trader.enums.PositionSide;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Set;
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<String, Object> explanation
BigDecimal longPositionRiskProb,
BigDecimal shortPositionRiskProb,
BigDecimal marketPathRiskBps,
BigDecimal longPositionPathRiskBps,
BigDecimal shortPositionPathRiskBps,
Map<String, BigDecimal> riskReasonScores
) {
private static final Set<String> REQUIRED_REASON_KEYS = Set.of(
"market_drawdown_prob", "volatility_expansion_prob", "spike_prob",
"liquidity_deterioration_prob", "position_drawdown_prob");
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);
longPositionRiskProb = probability(longPositionRiskProb, "risk.longPositionRiskProb");
shortPositionRiskProb = probability(shortPositionRiskProb, "risk.shortPositionRiskProb");
marketPathRiskBps = nonNegative(marketPathRiskBps, "risk.marketPathRiskBps");
longPositionPathRiskBps = nonNegative(longPositionPathRiskBps, "risk.longPositionPathRiskBps");
shortPositionPathRiskBps = nonNegative(shortPositionPathRiskBps, "risk.shortPositionPathRiskBps");
riskReasonScores = checkedProbabilities(riskReasonScores, "risk.riskReasonScores");
}
public BigDecimal reasonScore(String key) {
return riskReasonScores.get(requiredText(key, "risk reason key"));
}
public BigDecimal sideRiskProbFor(PositionSide side) {
if (side == PositionSide.LONG) {
return longPositionRiskProb;
}
if (side == PositionSide.SHORT) {
return shortPositionRiskProb;
}
throw new IllegalArgumentException("position risk requires LONG or SHORT side");
}
public BigDecimal positionPathRiskBpsFor(PositionSide side) {
if (side == PositionSide.LONG) {
return longPositionPathRiskBps;
}
if (side == PositionSide.SHORT) {
return shortPositionPathRiskBps;
}
throw new IllegalArgumentException("position path risk requires LONG or SHORT side");
}
private static Map<String, BigDecimal> checkedProbabilities(Map<String, BigDecimal> scores, String field) {
Map<String, BigDecimal> source = scores == null ? Map.of() : scores;
if (!source.keySet().containsAll(REQUIRED_REASON_KEYS)) {
throw new IllegalArgumentException(field + " must contain " + REQUIRED_REASON_KEYS);
}
source.forEach((key, value) -> {
requiredText(key, field + ".key");
probability(value, field + "." + key);
});
return Map.copyOf(source);
}
}