test(dotnet): add 32 xUnit tests for domain calculators (WBS-10.2)
Deploy to Production / Build Release Package (push) Failing after 17s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 5s
Deploy to Production / Deploy to Production Server (push) Has been skipped
Deploy to Production / Post-Deployment Checks (push) Has been skipped
Snapshot Admin Deployment / build-and-deploy (push) Failing after 38s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 2m19s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build Release Package (push) Failing after 17s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 5s
Deploy to Production / Deploy to Production Server (push) Has been skipped
Deploy to Production / Post-Deployment Checks (push) Has been skipped
Snapshot Admin Deployment / build-and-deploy (push) Failing after 38s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 2m19s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
using QuantEngine.Core.Domain;
|
||||
|
||||
namespace QuantEngine.Core.Tests
|
||||
{
|
||||
public class ExitDecisionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ComputeStopPriceCore_AtrBased_ReturnsCorrectPrice()
|
||||
{
|
||||
// ATR 2.0배 기반 계산 검증
|
||||
var res = ExitDecisions.ComputeStopPriceCore(
|
||||
entryPrice: 100000,
|
||||
atr20: 3000,
|
||||
currentPrice: 100000,
|
||||
atrMultiplier: 2.0
|
||||
);
|
||||
|
||||
Assert.Equal("PASS", res.StopPriceStatus);
|
||||
Assert.Equal(94000, res.StopPrice); // 100000 - 3000 * 2.0 = 94000
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeStopPriceCore_FallbackPrice_Returns8PercentDown()
|
||||
{
|
||||
// 결측인 경우 8% 하락 폴백 가격으로 설정 검증
|
||||
var res = ExitDecisions.ComputeStopPriceCore(
|
||||
entryPrice: 100000,
|
||||
atr20: null,
|
||||
currentPrice: null,
|
||||
atrMultiplier: null
|
||||
);
|
||||
|
||||
Assert.Contains("DATA_MISSING", res.StopPriceStatus);
|
||||
Assert.Equal(92000, res.StopPrice); // 100000 * 0.92 = 92000
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeStopPriceCore_AtrPercentBased_SetsCorrectMultiplier()
|
||||
{
|
||||
// ATR 비율에 따른 동적 승수 선택 검증 (atr20=10000, current=100000 -> atr20Pct = 10% >= 8% -> multiplier = 2.0)
|
||||
var res = ExitDecisions.ComputeStopPriceCore(
|
||||
entryPrice: 100000,
|
||||
atr20: 10000,
|
||||
currentPrice: 100000,
|
||||
atrMultiplier: null
|
||||
);
|
||||
|
||||
Assert.Equal("PASS", res.StopPriceStatus);
|
||||
Assert.Equal(2.0, res.AtrMultiplier);
|
||||
Assert.Equal(92000, res.StopPrice); // Max(92000, 100000 - 10000 * 2.0) = Max(92000, 80000) = 92000
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("STOP_OR_TIME_EXIT_READY", 4, "EXIT_100", "STOP_OR_TIME_EXIT_READY")]
|
||||
[InlineData("NORMAL_TRADING", 4, "EXIT_100", "RW_EXIT_STRONG")]
|
||||
[InlineData("NORMAL_TRADING", 1, "REGIME_TRIM_50", "REGIME_RISK_OFF")] // REGIME_PRELIM="RISK_OFF"
|
||||
[InlineData("NORMAL_TRADING", 1, "TRIM_70", "TIMING_EXIT_SCORE")] // timingExitScore = 75
|
||||
[InlineData("NORMAL_TRADING", 1, "TRIM_50", "TRAILING_STOP_BREACH")] // trailingStopBreach = true
|
||||
[InlineData("NORMAL_TRADING", 0, "TIME_EXIT_100", "TIME_STOP_EXPIRED")] // daysToTimeStop = 0
|
||||
public void ComputeStopActionLadder_Scenarios_ReturnExpectedAction(
|
||||
string timingAction,
|
||||
int rwPartial,
|
||||
string expectedAction,
|
||||
string expectedReason)
|
||||
{
|
||||
var ctx = new Dictionary<string, object>
|
||||
{
|
||||
{ "timingAction", timingAction },
|
||||
{ "rw_partial", rwPartial },
|
||||
{ "REGIME_PRELIM", expectedReason == "REGIME_RISK_OFF" ? "RISK_OFF" : "RISK_ON" },
|
||||
{ "timingExitScore", expectedReason == "TIMING_EXIT_SCORE" ? 75.0 : 0.0 },
|
||||
{ "trailingStopBreach", expectedReason == "TRAILING_STOP_BREACH" },
|
||||
{ "daysToTimeStop", expectedReason == "TIME_STOP_EXPIRED" ? 0 : 9999 }
|
||||
};
|
||||
|
||||
var res = ExitDecisions.ComputeStopActionLadder(ctx);
|
||||
|
||||
Assert.Equal(expectedAction, res.Action);
|
||||
Assert.Equal(expectedReason, res.Reason);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("EVENT_SHOCK", 5.0, 3.5)]
|
||||
[InlineData("RISK_OFF", 7.0, 5.0)]
|
||||
[InlineData("SECULAR_LEADER_RISK_ON", 13.0, 9.0)]
|
||||
[InlineData("RISK_ON", 12.0, 8.5)]
|
||||
[InlineData("NEUTRAL", 10.0, 7.0)]
|
||||
public void ComputeDynamicHeatThresholds_Regimes_ReturnCorrectThresholds(
|
||||
string regime,
|
||||
double expectedHard,
|
||||
double expectedHalve)
|
||||
{
|
||||
var res = ExitDecisions.ComputeDynamicHeatThresholds(regime);
|
||||
Assert.Equal(expectedHard, res.HardBlock);
|
||||
Assert.Equal(expectedHalve, res.Halve);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user