refactor(dotnet): centralize domain numeric guards
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 15s
Validators (Pushes and Pull Requests) / validate-core (push) Successful in 2m4s

This commit is contained in:
2026-07-13 00:48:21 +09:00
parent bccefed35e
commit aa61465ce0
2 changed files with 43 additions and 30 deletions
@@ -28,6 +28,9 @@ namespace QuantEngine.Core.Domain
public static class ExitDecisions
{
private static bool IsValidNumber(double? value)
=> value.HasValue && !double.IsNaN(value.Value) && !double.IsInfinity(value.Value);
public static StopPriceResult ComputeStopPriceCore(
double? entryPrice,
double? atr20,
@@ -36,7 +39,7 @@ namespace QuantEngine.Core.Domain
{
var result = new StopPriceResult();
if (!entryPrice.HasValue)
if (!IsValidNumber(entryPrice))
{
result.StopPrice = null;
result.StopPriceStatus = "NO_STOP_PRICE";
@@ -44,38 +47,45 @@ namespace QuantEngine.Core.Domain
return result;
}
if (!atr20.HasValue && !atrMultiplier.HasValue)
if (!IsValidNumber(atr20) && !IsValidNumber(atrMultiplier))
{
result.StopPrice = entryPrice.Value * 0.92;
result.StopPrice = entryPrice.GetValueOrDefault() * 0.92;
result.StopPriceStatus = "DATA_MISSING — 하네스 업데이트 필요";
result.DataMissing.Add("atr20");
return result;
}
if (!atrMultiplier.HasValue && (!currentPrice.HasValue || currentPrice.Value == 0))
var hasCurrentPrice = IsValidNumber(currentPrice) && currentPrice!.Value != 0;
if (!IsValidNumber(atrMultiplier) && !hasCurrentPrice)
{
result.StopPrice = entryPrice.Value * 0.92;
result.StopPrice = entryPrice.GetValueOrDefault() * 0.92;
result.StopPriceStatus = "DATA_MISSING — 하네스 업데이트 필요";
if (!atr20.HasValue) result.DataMissing.Add("atr20");
if (!currentPrice.HasValue || currentPrice.Value == 0) result.DataMissing.Add("current_price");
if (!IsValidNumber(atr20)) result.DataMissing.Add("atr20");
if (!hasCurrentPrice) result.DataMissing.Add("current_price");
return result;
}
if (!atrMultiplier.HasValue)
if (!IsValidNumber(atrMultiplier))
{
double atr20Pct = (atr20!.Value / currentPrice!.Value) * 100;
var atr20Value = atr20.GetValueOrDefault();
var currentPriceValue = currentPrice.GetValueOrDefault();
double atr20Pct = (atr20Value / currentPriceValue) * 100;
atrMultiplier = atr20Pct >= 8 ? 2.0 : 1.5;
result.Atr20Pct = atr20Pct;
}
else
{
result.Atr20Pct = (currentPrice.HasValue && currentPrice.Value != 0)
? (atr20!.Value / currentPrice.Value) * 100
result.Atr20Pct = hasCurrentPrice
? (atr20.GetValueOrDefault() / currentPrice.GetValueOrDefault()) * 100
: (double?)null;
}
var entryPriceValue = entryPrice.GetValueOrDefault();
var atr20FinalValue = atr20.GetValueOrDefault();
var atrMultiplierValue = atrMultiplier.GetValueOrDefault();
result.AtrMultiplier = atrMultiplier;
result.StopPrice = Math.Max(entryPrice.Value * 0.92, entryPrice.Value - atr20!.Value * atrMultiplier.Value);
result.StopPrice = Math.Max(entryPriceValue * 0.92, entryPriceValue - atr20FinalValue * atrMultiplierValue);
result.StopPriceStatus = "PASS";
return result;