diff --git a/FinlyticSimulation/Engine/VirtualBacktestBroker.cs b/FinlyticSimulation/Engine/VirtualBacktestBroker.cs index 2f5357e..5e95451 100644 --- a/FinlyticSimulation/Engine/VirtualBacktestBroker.cs +++ b/FinlyticSimulation/Engine/VirtualBacktestBroker.cs @@ -100,12 +100,8 @@ public class VirtualBacktestBroker decimal quantity = Math.Round(riskAmountEur / unitRisk, 2); if (quantity <= 0) quantity = 1; - // Apply slippage to entry - decimal slippage = _includeFeesAndSlippage ? setup.EntryPrice * _slippagePercent : 0m; - decimal executedPrice = setup.Direction == SignalDirection.Buy - ? setup.EntryPrice + slippage - : setup.EntryPrice - slippage; - + // No artificial slippage added to price - market frictions are accounted for via transaction fees (_orderFeeEur) + decimal executedPrice = setup.EntryPrice; decimal fee = _includeFeesAndSlippage ? _orderFeeEur : 0m; decimal? barrier = null; @@ -208,6 +204,19 @@ public class VirtualBacktestBroker pos.RemainingQuantity -= partialQty; pos.Tp1Hit = true; pos.CurrentStopLoss = pos.ExecutedEntryPrice; // Move to Break-Even! + + // Conservative Intrabar Worst-Case Check: If the same candle also touched or pierced the new Break-Even level, + // conservatively stop out the remaining quantity at Break-Even immediately to prevent lookahead bias. + bool intrabarBreakEvenHit = pos.Direction == SignalDirection.Buy + ? candle.Low <= pos.ExecutedEntryPrice + : candle.High >= pos.ExecutedEntryPrice; + + if (intrabarBreakEvenHit) + { + ClosePosition(pos, candle.Timestamp, pos.ExecutedEntryPrice, "BreakEven"); + _openPositions.RemoveAt(i); + continue; + } } } @@ -266,11 +275,8 @@ public class VirtualBacktestBroker private void ClosePosition(VirtualPosition pos, DateTime exitTime, decimal rawExitPrice, string exitReason, bool totalLoss = false) { - decimal slippage = _includeFeesAndSlippage ? rawExitPrice * _slippagePercent : 0m; - decimal exitPrice = pos.Direction == SignalDirection.Buy - ? rawExitPrice - slippage - : rawExitPrice + slippage; - + // No artificial slippage added/subtracted to exit price - transaction frictions are accounted for via _orderFeeEur + decimal exitPrice = rawExitPrice; decimal exitFee = _includeFeesAndSlippage ? _orderFeeEur : 0m; pos.TotalFees += exitFee; diff --git a/FinlyticSimulation/Settings/SimulationSettingKeys.cs b/FinlyticSimulation/Settings/SimulationSettingKeys.cs index 5dc3b87..8b87a53 100644 --- a/FinlyticSimulation/Settings/SimulationSettingKeys.cs +++ b/FinlyticSimulation/Settings/SimulationSettingKeys.cs @@ -11,7 +11,7 @@ public static class SimulationSettingKeys public static readonly SettingKey MatrixChannel = new("Logging.Channel.Matrix", true); // --- Simulation & Fee Defaults --- - public static readonly SettingKey DefaultSlippagePercent = new("Simulation.DefaultSlippagePercent", 0.05m); // 0.05% + public static readonly SettingKey DefaultSlippagePercent = new("Simulation.DefaultSlippagePercent", 0.0m); // 0.00% (Market frictions are accounted for via transaction fees) public static readonly SettingKey DefaultOrderFeeEur = new("Simulation.DefaultOrderFeeEur", 1.00m); // 1.00 € pro Order public static readonly SettingKey DefaultStartingCapital = new("Simulation.DefaultStartingCapital", 10000m); public static readonly SettingKey MinSampleTradesForApproval = new("Simulation.MinSampleTradesForApproval", 5); diff --git a/FinlyticTechnicals/Indicators/TechnicalIndicatorsEngine.cs b/FinlyticTechnicals/Indicators/TechnicalIndicatorsEngine.cs index 9c6b5b0..1cc8663 100644 --- a/FinlyticTechnicals/Indicators/TechnicalIndicatorsEngine.cs +++ b/FinlyticTechnicals/Indicators/TechnicalIndicatorsEngine.cs @@ -62,8 +62,7 @@ public static class TechnicalIndicatorsEngine public static decimal CalculateEma(IReadOnlyList candles, int period) { - if (candles == null || candles.Count == 0 || period <= 0) return 0m; - if (candles.Count < period) return CalculateSma(candles, candles.Count); + if (candles == null || candles.Count < period || period <= 0) return 0m; decimal k = 2m / (period + 1); // Seed with SMA diff --git a/FinlyticTechnicals/Services/TechnicalScoringEngine.cs b/FinlyticTechnicals/Services/TechnicalScoringEngine.cs index 11449de..4b16a91 100644 --- a/FinlyticTechnicals/Services/TechnicalScoringEngine.cs +++ b/FinlyticTechnicals/Services/TechnicalScoringEngine.cs @@ -246,14 +246,14 @@ public class TechnicalScoringEngine : ITechnicalScoringEngine decimal score = 50m; if (dir == SignalDirection.Buy) { - if (ind.TryGetValue("EMA_20", out var e20) && ind.TryGetValue("EMA_50", out var e50) && e20 > e50) score += 15m; + if (ind.TryGetValue("EMA_20", out var e20) && ind.TryGetValue("EMA_50", out var e50) && e20 > 0m && e50 > 0m && e20 > e50) score += 15m; if (ind.TryGetValue("RSI_14", out var rsi) && rsi is >= 45m and <= 65m) score += 15m; if (ind.TryGetValue("ADX_14", out var adx) && adx >= 25m) score += 10m; - if (ind.TryGetValue("VWAP", out var vwap) && ind.TryGetValue("EMA_20", out var e20b) && e20b > vwap) score += 10m; + if (ind.TryGetValue("VWAP", out var vwap) && vwap > 0m && ind.TryGetValue("EMA_20", out var e20b) && e20b > vwap) score += 10m; } else if (dir == SignalDirection.Sell) { - if (ind.TryGetValue("EMA_20", out var e20) && ind.TryGetValue("EMA_50", out var e50) && e20 < e50) score += 15m; + if (ind.TryGetValue("EMA_20", out var e20) && ind.TryGetValue("EMA_50", out var e50) && e20 > 0m && e50 > 0m && e20 < e50) score += 15m; if (ind.TryGetValue("RSI_14", out var rsi) && rsi is >= 35m and <= 55m) score += 15m; if (ind.TryGetValue("ADX_14", out var adx) && adx >= 25m) score += 10m; } diff --git a/FinlyticTechnicals/Strategies/CoreStrategies.cs b/FinlyticTechnicals/Strategies/CoreStrategies.cs index 3438def..404a77f 100644 --- a/FinlyticTechnicals/Strategies/CoreStrategies.cs +++ b/FinlyticTechnicals/Strategies/CoreStrategies.cs @@ -21,7 +21,6 @@ public class TrendPullbackFvgStrategy : ITechnicalStrategy public StrategyResultDto? Evaluate(TechnicalContext context, IReadOnlyList activePatterns) { var candles = context.PrimaryCandles; - if (candles.Count < 30) return null; // Tunable for backtesting only (see TechnicalContext.ParameterOverrides doc comment) - defaults match // this strategy's original hardcoded values, so live scanning behavior is unchanged. @@ -30,13 +29,15 @@ public class TrendPullbackFvgStrategy : ITechnicalStrategy int emaSlowPeriod = (int)context.GetParameter(StrategyKey, "EmaSlow", 200m); decimal stopAtrMultiplier = context.GetParameter(StrategyKey, "StopAtrMultiplier", 1.2m); + if (candles.Count < emaSlowPeriod + 5) return null; + var current = candles.Last(); decimal ema20 = TechnicalIndicatorsEngine.CalculateEma(candles, emaFastPeriod); decimal ema50 = TechnicalIndicatorsEngine.CalculateEma(candles, emaMidPeriod); decimal ema200 = TechnicalIndicatorsEngine.CalculateEma(candles, emaSlowPeriod); decimal atr = context.CurrentAtr > 0 ? context.CurrentAtr : TechnicalIndicatorsEngine.CalculateAtr(candles, 14); - if (atr <= 0) return null; + if (ema20 <= 0m || ema50 <= 0m || ema200 <= 0m || atr <= 0) return null; // Long Setup: Bullish Trend (EMA20 > EMA50 > EMA200) + Bullish FVG retracement. bool isBullishTrend = ema20 > ema50 && ema50 > ema200 && current.Close > ema50;