Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa61465ce0 |
@@ -28,6 +28,9 @@ namespace QuantEngine.Core.Domain
|
|||||||
|
|
||||||
public static class ExitDecisions
|
public static class ExitDecisions
|
||||||
{
|
{
|
||||||
|
private static bool IsValidNumber(double? value)
|
||||||
|
=> value.HasValue && !double.IsNaN(value.Value) && !double.IsInfinity(value.Value);
|
||||||
|
|
||||||
public static StopPriceResult ComputeStopPriceCore(
|
public static StopPriceResult ComputeStopPriceCore(
|
||||||
double? entryPrice,
|
double? entryPrice,
|
||||||
double? atr20,
|
double? atr20,
|
||||||
@@ -36,7 +39,7 @@ namespace QuantEngine.Core.Domain
|
|||||||
{
|
{
|
||||||
var result = new StopPriceResult();
|
var result = new StopPriceResult();
|
||||||
|
|
||||||
if (!entryPrice.HasValue)
|
if (!IsValidNumber(entryPrice))
|
||||||
{
|
{
|
||||||
result.StopPrice = null;
|
result.StopPrice = null;
|
||||||
result.StopPriceStatus = "NO_STOP_PRICE";
|
result.StopPriceStatus = "NO_STOP_PRICE";
|
||||||
@@ -44,38 +47,45 @@ namespace QuantEngine.Core.Domain
|
|||||||
return result;
|
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.StopPriceStatus = "DATA_MISSING — 하네스 업데이트 필요";
|
||||||
result.DataMissing.Add("atr20");
|
result.DataMissing.Add("atr20");
|
||||||
return result;
|
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 — 하네스 업데이트 필요";
|
result.StopPriceStatus = "DATA_MISSING — 하네스 업데이트 필요";
|
||||||
if (!atr20.HasValue) result.DataMissing.Add("atr20");
|
if (!IsValidNumber(atr20)) result.DataMissing.Add("atr20");
|
||||||
if (!currentPrice.HasValue || currentPrice.Value == 0) result.DataMissing.Add("current_price");
|
if (!hasCurrentPrice) result.DataMissing.Add("current_price");
|
||||||
return result;
|
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;
|
atrMultiplier = atr20Pct >= 8 ? 2.0 : 1.5;
|
||||||
result.Atr20Pct = atr20Pct;
|
result.Atr20Pct = atr20Pct;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result.Atr20Pct = (currentPrice.HasValue && currentPrice.Value != 0)
|
result.Atr20Pct = hasCurrentPrice
|
||||||
? (atr20!.Value / currentPrice.Value) * 100
|
? (atr20.GetValueOrDefault() / currentPrice.GetValueOrDefault()) * 100
|
||||||
: (double?)null;
|
: (double?)null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var entryPriceValue = entryPrice.GetValueOrDefault();
|
||||||
|
var atr20FinalValue = atr20.GetValueOrDefault();
|
||||||
|
var atrMultiplierValue = atrMultiplier.GetValueOrDefault();
|
||||||
result.AtrMultiplier = atrMultiplier;
|
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";
|
result.StopPriceStatus = "PASS";
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ namespace QuantEngine.Core.Domain
|
|||||||
|
|
||||||
public static class FormulaEngine
|
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)
|
public static TimingDecisionResult ComputeTimingDecision(Dictionary<string, object> ctx)
|
||||||
{
|
{
|
||||||
var reasons = new List<string>();
|
var reasons = new List<string>();
|
||||||
@@ -98,9 +101,9 @@ namespace QuantEngine.Core.Domain
|
|||||||
reasons.Add("entry_block");
|
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;
|
entryScore += 20;
|
||||||
reasons.Add("leader_scan>=4");
|
reasons.Add("leader_scan>=4");
|
||||||
@@ -116,9 +119,9 @@ namespace QuantEngine.Core.Domain
|
|||||||
entryScore += 10;
|
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;
|
entryScore += 20;
|
||||||
reasons.Add("flow_strong");
|
reasons.Add("flow_strong");
|
||||||
@@ -147,9 +150,9 @@ namespace QuantEngine.Core.Domain
|
|||||||
reasons.Add("anti_climax_block");
|
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;
|
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;
|
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;
|
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;
|
entryScore += 10;
|
||||||
}
|
}
|
||||||
@@ -219,9 +222,9 @@ namespace QuantEngine.Core.Domain
|
|||||||
reasons.Add("liquidity_or_spread_fail");
|
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))
|
if (!string.IsNullOrEmpty(exitSignal))
|
||||||
@@ -230,13 +233,13 @@ namespace QuantEngine.Core.Domain
|
|||||||
exitScore += parts.Length * 10;
|
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;
|
exitScore += 20;
|
||||||
reasons.Add("time_stop_near");
|
reasons.Add("time_stop_near");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profitPct.HasValue && !double.IsNaN(profitPct.Value) && profitPct.Value >= 10)
|
if (IsValidNumber(profitPct) && profitPct!.Value >= 10)
|
||||||
{
|
{
|
||||||
exitScore += 15;
|
exitScore += 15;
|
||||||
reasons.Add("profit_protect_zone");
|
reasons.Add("profit_protect_zone");
|
||||||
@@ -249,15 +252,15 @@ namespace QuantEngine.Core.Domain
|
|||||||
double? atr20 = GetNullableDouble(ctx, "atr20");
|
double? atr20 = GetNullableDouble(ctx, "atr20");
|
||||||
string priceStatus = GetString(ctx, "priceStatus");
|
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";
|
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";
|
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";
|
action = "EXIT_REVIEW";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user