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
@@ -65,6 +65,9 @@ namespace QuantEngine.Core.Domain
public static class FormulaEngine
{
private static bool IsValidNumber(double? value)
=> value.HasValue && !double.IsNaN(value.Value) && !double.IsInfinity(value.Value);
public static TimingDecisionResult ComputeTimingDecision(Dictionary<string, object> ctx)
{
var reasons = new List<string>();
@@ -98,9 +101,9 @@ namespace QuantEngine.Core.Domain
reasons.Add("entry_block");
}
if (leaderTotal.HasValue && !double.IsNaN(leaderTotal.Value) && !double.IsInfinity(leaderTotal.Value))
if (IsValidNumber(leaderTotal))
{
if (leaderTotal.Value >= 4)
if (leaderTotal!.Value >= 4)
{
entryScore += 20;
reasons.Add("leader_scan>=4");
@@ -116,9 +119,9 @@ namespace QuantEngine.Core.Domain
entryScore += 10;
}
if (flowCredit.HasValue && !double.IsNaN(flowCredit.Value) && !double.IsInfinity(flowCredit.Value))
if (IsValidNumber(flowCredit))
{
if (flowCredit.Value >= 0.7)
if (flowCredit!.Value >= 0.7)
{
entryScore += 20;
reasons.Add("flow_strong");
@@ -147,9 +150,9 @@ namespace QuantEngine.Core.Domain
reasons.Add("anti_climax_block");
}
if (ma20Slope.HasValue && !double.IsNaN(ma20Slope.Value) && !double.IsInfinity(ma20Slope.Value))
if (IsValidNumber(ma20Slope))
{
if (ma20Slope.Value > 0)
if (ma20Slope!.Value > 0)
{
entryScore += 8;
}
@@ -161,9 +164,9 @@ namespace QuantEngine.Core.Domain
}
}
if (disparity.HasValue && !double.IsNaN(disparity.Value) && !double.IsInfinity(disparity.Value))
if (IsValidNumber(disparity))
{
if (disparity.Value >= -5 && disparity.Value <= 4)
if (disparity!.Value >= -5 && disparity.Value <= 4)
{
entryScore += 10;
}
@@ -185,9 +188,9 @@ namespace QuantEngine.Core.Domain
}
}
if (rsi14.HasValue && !double.IsNaN(rsi14.Value) && !double.IsInfinity(rsi14.Value))
if (IsValidNumber(rsi14))
{
if (rsi14.Value >= 40 && rsi14.Value <= 65)
if (rsi14!.Value >= 40 && rsi14.Value <= 65)
{
entryScore += 10;
}
@@ -209,7 +212,7 @@ namespace QuantEngine.Core.Domain
}
}
if (avgTradeValue5D.HasValue && !double.IsNaN(avgTradeValue5D.Value) && !double.IsInfinity(avgTradeValue5D.Value) && avgTradeValue5D.Value >= 50 && (!spreadPct.HasValue || double.IsNaN(spreadPct.Value) || spreadPct.Value <= 0.8))
if (IsValidNumber(avgTradeValue5D) && avgTradeValue5D!.Value >= 50 && (!IsValidNumber(spreadPct) || spreadPct!.Value <= 0.8))
{
entryScore += 10;
}
@@ -219,9 +222,9 @@ namespace QuantEngine.Core.Domain
reasons.Add("liquidity_or_spread_fail");
}
if (rwPartial.HasValue && !double.IsNaN(rwPartial.Value) && !double.IsInfinity(rwPartial.Value))
if (IsValidNumber(rwPartial))
{
exitScore += Math.Min(100.0, Math.Max(0.0, (int)rwPartial.Value * 25.0));
exitScore += Math.Min(100.0, Math.Max(0.0, (int)rwPartial!.Value * 25.0));
}
if (!string.IsNullOrEmpty(exitSignal))
@@ -230,13 +233,13 @@ namespace QuantEngine.Core.Domain
exitScore += parts.Length * 10;
}
if (daysToTimeStop.HasValue && !double.IsNaN(daysToTimeStop.Value) && daysToTimeStop.Value >= 0 && daysToTimeStop.Value <= 7)
if (IsValidNumber(daysToTimeStop) && daysToTimeStop!.Value >= 0 && daysToTimeStop.Value <= 7)
{
exitScore += 20;
reasons.Add("time_stop_near");
}
if (profitPct.HasValue && !double.IsNaN(profitPct.Value) && profitPct.Value >= 10)
if (IsValidNumber(profitPct) && profitPct!.Value >= 10)
{
exitScore += 15;
reasons.Add("profit_protect_zone");
@@ -249,15 +252,15 @@ namespace QuantEngine.Core.Domain
double? atr20 = GetNullableDouble(ctx, "atr20");
string priceStatus = GetString(ctx, "priceStatus");
if (priceStatus != "PRICE_OK" || !atr20.HasValue || double.IsNaN(atr20.Value) || double.IsInfinity(atr20.Value))
if (priceStatus != "PRICE_OK" || !IsValidNumber(atr20))
{
action = "OBSERVE_DATA_MISSING";
}
else if (exitScore >= 75 || (rwPartial.HasValue && rwPartial.Value >= 4))
else if (exitScore >= 75 || (IsValidNumber(rwPartial) && rwPartial!.Value >= 4))
{
action = "STOP_OR_TIME_EXIT_READY";
}
else if (exitScore >= 50 || (rwPartial.HasValue && rwPartial.Value >= 3))
else if (exitScore >= 50 || (IsValidNumber(rwPartial) && rwPartial!.Value >= 3))
{
action = "EXIT_REVIEW";
}