// ============================================================================ // AG_QUANT_PROB_V4.cs // AG QUANT PROB V4 - Motor cuantitativo institucional para NinjaTrader 8 // ---------------------------------------------------------------------------- // MATEMATICA CORREGIDA Y ELEVADA respecto a V1: // // 1. Half-life de Ornstein-Uhlenbeck REAL: // Regresion dy_t = a + b * y_{t-1} sobre LOG-precios. // HL = -ln(2) / ln(1 + b). (V1 usaba AR(1) sobre precios crudos, // phi ~ 0.999 => quality = 0 siempre => reversion nunca operaba) // // 2. Pendiente estandarizada con t-statistic OLS CORRECTO: // t = b / SE(b), SE(b) = sqrt( s^2_residual / Sxx ) // (V1 usaba slope*sqrt(N)/sigma_ret, ~10x demasiado pequeno => // momentum nunca superaba su umbral) // Ahora el umbral por defecto es t >= 2.0: solo tendencias // estadisticamente significativas al ~95%. // // 3. Variance Ratio de Lo-MacKinlay VR(q) + exponente de Hurst aproximado: // VR < 1 => serie anti-persistente (favorece reversion) // VR > 1 => serie persistente (favorece momentum) // H ~ 0.5 + ln(VR) / (2 ln q) // // 4. Volatilidad EWMA estilo RiskMetrics (lambda = 0.94) para el z-score: // mas reactiva y estable que la desviacion muestral rodante. // // 5. Z-score de LOG-volumen (el volumen es aprox. lognormal; // el z-score sobre volumen crudo esta sesgado por colas). // // 6. Esperanza matematica NETA DE COSTOS: // EV_R = p*RR - (1-p) - (comision + slippage) / riesgo$ // // 7. Sizing por riesgo + Kelly fraccionado con tope, y piso opcional // de 1 contrato (V1 devolvia qty=0 en NQ con $100 de riesgo => // CERO trades aunque hubiera senal). // // 8. CONTADOR DE BLOQUEOS: cada senal rechazada queda registrada con // su causa (probabilidad, EV, RR, sizing, volatilidad, cooldown...) // y se imprime resumen al cierre de sesion. Nunca mas a ciegas. // // Filosofia de gates: los filtros duros solo definen DIRECCION y regimen; // la CALIDAD la decide un unico gate probabilistico (p, EV, RR). // Sin doble castigo redundante como en V1. // // Uso educativo. Validar en Strategy Analyzer + Market Replay + walk-forward // (ventanas mensuales independientes) antes de cuentas de evaluacion o reales. // Calculate.OnBarClose para evitar artefactos de fill intrabar. // ============================================================================ #region Using declarations using System; using System.ComponentModel.DataAnnotations; using System.Windows.Media; using NinjaTrader.Cbi; using NinjaTrader.Data; using NinjaTrader.Gui.Tools; using NinjaTrader.NinjaScript; using NinjaTrader.NinjaScript.DrawingTools; using NinjaTrader.NinjaScript.Indicators; #endregion namespace NinjaTrader.NinjaScript.Strategies { public enum AGQ4EngineMode { Auto, MeanReversionOnly, MomentumOnly } public enum AGQ4SizingMode { FixedContracts, VolatilityRisk } public enum AGQ4SignalModel { None, MeanReversion, Momentum } public class AG_QUANT_PROB_V4 : Strategy { private ATR atr; // --- Estado de sesion ------------------------------------------------- private DateTime sessionDate; private int lastTradeCount; private int tradesToday; private int lastLossBar; private int lastExitBar; private int sessionStartBar; private int consecutiveLosses; private double realizedPnLToday; private double lastTradePnL; private bool haltedToday; // --- Estado de posicion activa --------------------------------------- private AGQ4SignalModel activeModel; private string activeSignal; private int entryBar; private double activeEntryPrice; private double activeInitialRisk; private double activeStopPrice; private double activeTargetPrice; private bool breakevenMoved; // --- Estado cuantitativo (features) ----------------------------------- private double ewmaMean; private double ewmaVar; private bool ewmaInitialized; private double lastZScore; private double lastPreviousZScore; private double lastSlopeT; // t-statistic OLS de la pendiente log-precio private double lastAccelT; // cambio del t-stat (aceleracion de tendencia) private double lastTrendEfficiency; // Kaufman Efficiency Ratio private double lastVarianceRatio; // Lo-MacKinlay VR(q) private double lastHurst; // exponente de Hurst aproximado private double lastHalfLife; // half-life OU en barras private double lastReversionQuality; private double lastVolumeZ; // z-score de log-volumen private double lastAtrPercent; private double lastAtrExpansion; private double lastMRLongP; private double lastMRShortP; private double lastMOMLongP; private double lastMOMShortP; private string lastRegimeText; private string lastBlockReason; // --- Contadores de bloqueo (diagnostico institucional) ---------------- private int blkNoSetup; private int blkProbability; private int blkExpectedValue; private int blkRewardRisk; private int blkSizing; private int blkVolatility; private int blkCooldown; private int blkSessionLimits; private int blkCostRatio; protected override void OnStateChange() { if (State == State.SetDefaults) { Description = "AG QUANT PROB V4 - motor cuantitativo institucional: OU half-life, t-stat OLS, Variance Ratio, Hurst, EWMA vol, EV neto de costos, Kelly fraccionado y diagnostico de bloqueos."; Name = "AG QUANT PROB V4"; Calculate = Calculate.OnBarClose; // leccion validada: sin artefactos intrabar EntriesPerDirection = 1; EntryHandling = EntryHandling.AllEntries; IsExitOnSessionCloseStrategy = true; ExitOnSessionCloseSeconds = 30; BarsRequiredToTrade = 120; IsUnmanaged = false; TraceOrders = false; RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose; // 01. Motor EngineMode = AGQ4EngineMode.Auto; EnableLongs = true; EnableShorts = true; // 02. Lookbacks MeanLookback = 80; VolatilityLookback = 60; MomentumLookback = 40; AccelerationLookback = 8; RegimeLookback = 30; VolumeLookback = 40; ATRPeriod = 14; ATRMeanLookback = 40; EwmaLambda = 0.94; // RiskMetrics VarianceRatioQ = 8; // 03. Gate probabilistico unico ProbabilityThreshold = 0.52; ExitProbabilityThreshold = 0.30; LogisticSlope = 1.00; ExpectedValueThreshold = 0.05; // en unidades R, NETO de costos MinimumRewardRisk = 0.70; MaxCostPercentOfRisk = 0.12; // gate de costos: bloquea trades donde // comision+slippage > 12% del riesgo. // Mata el ruido del 1-min estructuralmente. // 04. Reversion a la media EnableMeanReversion = true; MeanReversionEntryZ = 1.30; MeanReversionExitZ = 0.20; MeanReversionMaxZ = 3.50; MaxHalfLifeBars = 80; MinReversionQuality = 0.05; MeanReversionStopSigma = 1.30; MaxTrendEfficiencyForMR = 0.50; MaxAbsSlopeTForMR = 3.00; MaxVarianceRatioForMR = 1.15; // 05. Momentum EnableMomentum = true; MomentumEntryT = 1.50; // ~87% significancia; t=2.0 era demasiado raro intradia MomentumExitT = 0.30; MinTrendEfficiencyForMOM = 0.25; MinVarianceRatioForMOM = 0.90; MomentumStopATR = 1.80; MomentumRewardRisk = 1.80; MaxAbsZForMomentumEntry = 3.00; UseMomentumBreakoutConfirmation = false; MomentumBreakoutLookback = 12; // 06. Volumen y volatilidad UseVolumeAnomalyFilter = false; MinimumVolumeZ = -1.00; VolumeBoostWeight = 0.10; UseVolatilityFilter = true; MinATRPercentOfPrice = 0.010; MaxATRPercentOfPrice = 2.50; MaxATRExpansionRatio = 4.00; // 07. Costos (para EV neto) CommissionPerRoundTurn = 5.00; // $ por contrato ida+vuelta SlippageTicksPerSide = 1; // 08. Riesgo y sizing SizingMode = AGQ4SizingMode.VolatilityRisk; FixedContracts = 1; RiskPerTradeDollars = 250; MaxContracts = 3; MinimumStopTicks = 8; MaxStopTicks = 200; UseKellySizing = true; KellyFraction = 0.50; // half-Kelly AllowMinimumOneContract = true; // evita qty=0 (bug V1 en NQ) // 09. Gestion del trade UseBreakeven = true; BreakevenAtR = 1.20; BreakevenOffsetTicks = 1; UseVolatilityTrailing = true; TrailStartsAtR = 1.80; TrailATRMultiplier = 2.00; TimeStopBars = 40; UseProbabilityExit = false; // OFF: el prob-exit cortaba ganadores temprano // (RR realizado 1.36 vs target 1.8 en backtest) // 10. Riesgo de sesion UseTimeFilter = true; StartTime = 1800; // ventana overnight validada (18:00-23:00 ET) EndTime = 2300; UnlimitedTrades = false; // ON: elimina max trades/dia, min bars y cooldown. // Solo mandan las matematicas + guardas de riesgo. MaxTradesPerDay = 10; MaxDailyLossDollars = 500; DailyProfitGoalDollars = 1000; CooldownBarsAfterLoss = 3; EscalateCooldown = true; // cooldown x N tras N perdidas consecutivas: // evita cascadas tipo 18-31 losers seguidos MinBarsBetweenTrades = 2; MaxConsecutiveLosses = 3; MinBarsAfterSessionOpen = 2; // 11. Visual ShowDiagnosticPanel = true; DrawSignals = true; PanelTextSize = 12; PrintBlockDiagnostics = true; } else if (State == State.DataLoaded) { atr = ATR(ATRPeriod); ewmaInitialized = false; ResetSessionState(); } } protected override void OnBarUpdate() { if (CurrentBar < 1) return; UpdateEwma(); // el EWMA se actualiza cada barra desde el inicio if (CurrentBar < MinimumBarsNeeded()) return; if (Bars.IsFirstBarOfSession || sessionDate == DateTime.MinValue) { PrintSessionSummary(); ResetSessionState(); sessionDate = Time[0].Date; } UpdateClosedTradeStats(); ComputeQuantState(); ManageOpenPosition(); ApplyDailyRiskGuards(); DrawDiagnostics(); if (haltedToday) { FlattenOpenPosition("DailyHalt"); return; } if (UseTimeFilter && !InTradingWindow()) { FlattenOpenPosition("TimeExit"); return; } if (Position.MarketPosition != MarketPosition.Flat) return; if (!CanTradeNow()) return; EvaluateNewEntry(); } // ====================================================================== // NUCLEO CUANTITATIVO // ====================================================================== private void UpdateEwma() { double price = Close[0]; if (!ewmaInitialized) { ewmaMean = price; ewmaVar = 0; ewmaInitialized = true; return; } double lambda = Clamp(EwmaLambda, 0.80, 0.999); double deviation = price - ewmaMean; ewmaMean = lambda * ewmaMean + (1 - lambda) * price; ewmaVar = lambda * ewmaVar + (1 - lambda) * deviation * deviation; } private void ComputeQuantState() { // --- Z-score hibrido: media rodante + sigma EWMA (RiskMetrics) --- double mean = MeanClose(MeanLookback, 0); double sigmaEwma = Math.Sqrt(Math.Max(ewmaVar, 0)); double sigmaSample = StdDevClose(MeanLookback, 0); double sigma = 0.5 * sigmaEwma + 0.5 * sigmaSample; // blend: reactividad + estabilidad double previousMean = MeanClose(MeanLookback, 1); lastZScore = SafeDivide(Close[0] - mean, sigma); lastPreviousZScore = SafeDivide(Close[1] - previousMean, sigma); // --- Pendiente OLS con t-statistic correcto ----------------------- double tNow = SlopeTStat(MomentumLookback, 0); double tPast = SlopeTStat(MomentumLookback, AccelerationLookback); lastSlopeT = tNow; lastAccelT = tNow - tPast; // --- Regimen: eficiencia, Variance Ratio, Hurst ------------------- lastTrendEfficiency = EfficiencyRatio(RegimeLookback); lastVarianceRatio = VarianceRatio(VarianceRatioQ, VolatilityLookback); lastHurst = HurstFromVR(lastVarianceRatio, VarianceRatioQ); // --- Half-life de Ornstein-Uhlenbeck sobre log-precio ------------- lastHalfLife = OrnsteinUhlenbeckHalfLife(MeanLookback); lastReversionQuality = (lastHalfLife <= 0 || double.IsInfinity(lastHalfLife)) ? 0.0 : Clamp(1.0 - lastHalfLife / Math.Max(1.0, MaxHalfLifeBars), 0.0, 1.0); // --- Volumen y volatilidad ---------------------------------------- lastVolumeZ = LogVolumeZScore(VolumeLookback); lastAtrPercent = Close[0] <= 0 ? 0 : 100.0 * atr[0] / Close[0]; lastAtrExpansion = SafeDivide(atr[0], AverageATR(ATRMeanLookback)); // --- Probabilidades calibradas ------------------------------------ double volumeBoost = Clamp(lastVolumeZ / 2.0, -1.0, 1.0) * VolumeBoostWeight; bool longRecovery = CloseRecovery(true); bool shortRecovery = CloseRecovery(false); bool zImprovingLong = lastZScore > lastPreviousZScore; bool zImprovingShort = lastZScore < lastPreviousZScore; // Reversion: la evidencia crece con el exceso de |z| sobre el umbral, // la calidad OU, la anti-persistencia (VR < 1) y la confirmacion de vela. double mrLongScore = -0.60 + 0.90 * Math.Max(0.0, -lastZScore - MeanReversionEntryZ) + 0.80 * lastReversionQuality + 1.00 * Math.Max(0.0, 1.0 - lastVarianceRatio) + 0.45 * (longRecovery ? 1.0 : 0.0) + 0.45 * (zImprovingLong ? 1.0 : 0.0) - 0.50 * Math.Max(0.0, lastTrendEfficiency - MaxTrendEfficiencyForMR) - 0.30 * Math.Max(0.0, Math.Abs(lastSlopeT) - MaxAbsSlopeTForMR) + volumeBoost; double mrShortScore = -0.60 + 0.90 * Math.Max(0.0, lastZScore - MeanReversionEntryZ) + 0.80 * lastReversionQuality + 1.00 * Math.Max(0.0, 1.0 - lastVarianceRatio) + 0.45 * (shortRecovery ? 1.0 : 0.0) + 0.45 * (zImprovingShort ? 1.0 : 0.0) - 0.50 * Math.Max(0.0, lastTrendEfficiency - MaxTrendEfficiencyForMR) - 0.30 * Math.Max(0.0, Math.Abs(lastSlopeT) - MaxAbsSlopeTForMR) + volumeBoost; // Momentum: evidencia = exceso del t-stat sobre 2.0 (significancia), // persistencia (VR > 1), eficiencia y aceleracion. double momLongScore = -0.60 + 0.55 * Math.Max(0.0, lastSlopeT - MomentumEntryT) + 1.50 * Math.Max(0.0, lastTrendEfficiency - MinTrendEfficiencyForMOM) + 0.80 * Clamp(lastVarianceRatio - 1.0, 0.0, 1.0) + 0.30 * Clamp(lastAccelT, 0.0, 2.0) + 0.40 * (MomentumBreakoutPasses(true) ? 1.0 : 0.0) - 0.25 * Math.Max(0.0, Math.Abs(lastZScore) - MaxAbsZForMomentumEntry) + volumeBoost; double momShortScore = -0.60 + 0.55 * Math.Max(0.0, -lastSlopeT - MomentumEntryT) + 1.50 * Math.Max(0.0, lastTrendEfficiency - MinTrendEfficiencyForMOM) + 0.80 * Clamp(lastVarianceRatio - 1.0, 0.0, 1.0) + 0.30 * Clamp(-lastAccelT, 0.0, 2.0) + 0.40 * (MomentumBreakoutPasses(false) ? 1.0 : 0.0) - 0.25 * Math.Max(0.0, Math.Abs(lastZScore) - MaxAbsZForMomentumEntry) + volumeBoost; lastMRLongP = Logistic(mrLongScore * LogisticSlope); lastMRShortP = Logistic(mrShortScore * LogisticSlope); lastMOMLongP = Logistic(momLongScore * LogisticSlope); lastMOMShortP = Logistic(momShortScore * LogisticSlope); if (lastVarianceRatio > 1.05 && lastTrendEfficiency >= MinTrendEfficiencyForMOM) lastRegimeText = "Momentum (VR " + lastVarianceRatio.ToString("0.00") + ")"; else if (lastVarianceRatio < 0.95 && lastTrendEfficiency <= MaxTrendEfficiencyForMR) lastRegimeText = "Reversion (VR " + lastVarianceRatio.ToString("0.00") + ")"; else lastRegimeText = "Mixto (VR " + lastVarianceRatio.ToString("0.00") + ")"; } // ====================================================================== // ENTRADAS // ====================================================================== private void EvaluateNewEntry() { QuantCandidate best = new QuantCandidate(); if (EnableMeanReversion && EngineMode != AGQ4EngineMode.MomentumOnly) { if (EnableLongs && MeanReversionLongSetup()) best = Better(best, BuildMeanReversionCandidate(true)); if (EnableShorts && MeanReversionShortSetup()) best = Better(best, BuildMeanReversionCandidate(false)); } if (EnableMomentum && EngineMode != AGQ4EngineMode.MeanReversionOnly) { if (EnableLongs && MomentumLongSetup()) best = Better(best, BuildMomentumCandidate(true)); if (EnableShorts && MomentumShortSetup()) best = Better(best, BuildMomentumCandidate(false)); } if (!best.IsValid) { blkNoSetup++; return; } if (best.RewardRisk < MinimumRewardRisk) { blkRewardRisk++; lastBlockReason = "RR " + best.RewardRisk.ToString("0.00") + " < " + MinimumRewardRisk.ToString("0.00"); return; } if (best.Probability < ProbabilityThreshold) { blkProbability++; lastBlockReason = "p " + best.Probability.ToString("0.00") + " < " + ProbabilityThreshold.ToString("0.00"); return; } int qty = CalculateQuantity(best.StopDistance, best.Probability, best.RewardRisk); if (qty < 1) { blkSizing++; lastBlockReason = "Sizing qty=0 (riesgo/contrato > presupuesto)"; return; } // GATE DE COSTOS: si comision + slippage superan el X% del riesgo, // el trade es estructuralmente inviable (tipico del ruido en 1-min). double costRatio = CostPercentOfRisk(best.StopDistance); if (MaxCostPercentOfRisk > 0 && costRatio > MaxCostPercentOfRisk) { blkCostRatio++; lastBlockReason = "Costo " + (costRatio * 100).ToString("0.0") + "% del riesgo > " + (MaxCostPercentOfRisk * 100).ToString("0") + "%"; return; } double netEV = NetExpectedValueR(best.Probability, best.RewardRisk, best.StopDistance); if (netEV < ExpectedValueThreshold) { blkExpectedValue++; lastBlockReason = "EV neto " + netEV.ToString("0.00") + "R < " + ExpectedValueThreshold.ToString("0.00") + "R"; return; } lastBlockReason = string.Empty; EnterQuantTrade(best, qty, netEV); } // Gates duros = solo direccion + regimen. La calidad la decide p/EV/RR. private bool MeanReversionLongSetup() { return lastZScore <= -MeanReversionEntryZ && Math.Abs(lastZScore) <= MeanReversionMaxZ && lastTrendEfficiency <= MaxTrendEfficiencyForMR && lastVarianceRatio <= MaxVarianceRatioForMR && lastReversionQuality >= MinReversionQuality && VolumeFilterPasses(); } private bool MeanReversionShortSetup() { return lastZScore >= MeanReversionEntryZ && Math.Abs(lastZScore) <= MeanReversionMaxZ && lastTrendEfficiency <= MaxTrendEfficiencyForMR && lastVarianceRatio <= MaxVarianceRatioForMR && lastReversionQuality >= MinReversionQuality && VolumeFilterPasses(); } private bool MomentumLongSetup() { return lastSlopeT >= MomentumEntryT && lastTrendEfficiency >= MinTrendEfficiencyForMOM && lastVarianceRatio >= MinVarianceRatioForMOM && Math.Abs(lastZScore) <= MaxAbsZForMomentumEntry && MomentumBreakoutPasses(true) && VolumeFilterPasses(); } private bool MomentumShortSetup() { return lastSlopeT <= -MomentumEntryT && lastTrendEfficiency >= MinTrendEfficiencyForMOM && lastVarianceRatio >= MinVarianceRatioForMOM && Math.Abs(lastZScore) <= MaxAbsZForMomentumEntry && MomentumBreakoutPasses(false) && VolumeFilterPasses(); } private QuantCandidate BuildMeanReversionCandidate(bool isLong) { double mean = MeanClose(MeanLookback, 0); double sigma = 0.5 * Math.Sqrt(Math.Max(ewmaVar, 0)) + 0.5 * StdDevClose(MeanLookback, 0); double entry = Close[0]; // Stop: sigma fija mas alla de la entrada (no en la banda MaxZ, que // en V1 destruia el RR). Target: la media menos la zona de salida. double stopDistance = ClampStopDistance(MeanReversionStopSigma * sigma); double target = isLong ? mean - MeanReversionExitZ * sigma : mean + MeanReversionExitZ * sigma; if (isLong && target <= entry + TickSize) target = entry + stopDistance; if (!isLong && target >= entry - TickSize) target = entry - stopDistance; double reward = Math.Abs(target - entry); double rr = SafeDivide(reward, stopDistance); return new QuantCandidate { IsValid = true, IsLong = isLong, Model = AGQ4SignalModel.MeanReversion, Probability = isLong ? lastMRLongP : lastMRShortP, EntryPrice = entry, StopDistance = stopDistance, TargetPrice = RoundToTick(target), RewardRisk = rr }; } private QuantCandidate BuildMomentumCandidate(bool isLong) { double entry = Close[0]; double stopDistance = ClampStopDistance(Math.Max(atr[0] * MomentumStopATR, MinimumStopTicks * TickSize)); double target = isLong ? entry + stopDistance * MomentumRewardRisk : entry - stopDistance * MomentumRewardRisk; return new QuantCandidate { IsValid = true, IsLong = isLong, Model = AGQ4SignalModel.Momentum, Probability = isLong ? lastMOMLongP : lastMOMShortP, EntryPrice = entry, StopDistance = stopDistance, TargetPrice = RoundToTick(target), RewardRisk = MomentumRewardRisk }; } private QuantCandidate Better(QuantCandidate current, QuantCandidate candidate) { if (!candidate.IsValid) return current; if (!current.IsValid) return candidate; double currentEV = candidate.IsValid ? GrossEV(current.Probability, current.RewardRisk) : double.MinValue; double candidateEV = GrossEV(candidate.Probability, candidate.RewardRisk); return candidateEV > currentEV ? candidate : current; } private void EnterQuantTrade(QuantCandidate candidate, int qty, double netEV) { activeModel = candidate.Model; activeSignal = candidate.IsLong ? "AGQ4_L" : "AGQ4_S"; entryBar = CurrentBar; activeEntryPrice = candidate.EntryPrice; activeInitialRisk = candidate.StopDistance; activeTargetPrice = candidate.TargetPrice; breakevenMoved = false; if (candidate.IsLong) { activeStopPrice = RoundToTick(candidate.EntryPrice - candidate.StopDistance); SetStopLoss(activeSignal, CalculationMode.Price, activeStopPrice, false); SetProfitTarget(activeSignal, CalculationMode.Price, activeTargetPrice); EnterLong(qty, activeSignal); } else { activeStopPrice = RoundToTick(candidate.EntryPrice + candidate.StopDistance); SetStopLoss(activeSignal, CalculationMode.Price, activeStopPrice, false); SetProfitTarget(activeSignal, CalculationMode.Price, activeTargetPrice); EnterShort(qty, activeSignal); } tradesToday++; if (DrawSignals) DrawEntry(candidate, qty); Print(Time[0] + " | AGQ4 | " + candidate.Model + " " + (candidate.IsLong ? "LONG" : "SHORT") + " qty=" + qty + " p=" + candidate.Probability.ToString("0.00") + " RR=" + candidate.RewardRisk.ToString("0.00") + " EVnet=" + netEV.ToString("0.00") + "R" + " | z=" + lastZScore.ToString("0.00") + " t=" + lastSlopeT.ToString("0.00") + " VR=" + lastVarianceRatio.ToString("0.00") + " HL=" + lastHalfLife.ToString("0")); } // ====================================================================== // GESTION DE POSICION // ====================================================================== private void ManageOpenPosition() { if (Position.MarketPosition == MarketPosition.Flat) { activeModel = AGQ4SignalModel.None; activeSignal = string.Empty; return; } if (entryBar >= 0 && TimeStopBars > 0 && CurrentBar - entryBar >= TimeStopBars) { FlattenOpenPosition("TimeStop"); return; } if (activeModel == AGQ4SignalModel.MeanReversion) { if (Position.MarketPosition == MarketPosition.Long && lastZScore >= -MeanReversionExitZ) { ExitLong("MR_ZExit", activeSignal); return; } if (Position.MarketPosition == MarketPosition.Short && lastZScore <= MeanReversionExitZ) { ExitShort("MR_ZExit", activeSignal); return; } } if (activeModel == AGQ4SignalModel.Momentum) { bool probExitLong = UseProbabilityExit && lastMOMLongP < ExitProbabilityThreshold; bool probExitShort = UseProbabilityExit && lastMOMShortP < ExitProbabilityThreshold; if (Position.MarketPosition == MarketPosition.Long && (lastSlopeT <= MomentumExitT || probExitLong)) { ExitLong("MOM_TExit", activeSignal); return; } if (Position.MarketPosition == MarketPosition.Short && (lastSlopeT >= -MomentumExitT || probExitShort)) { ExitShort("MOM_TExit", activeSignal); return; } } ManageProtectionStops(); } private void ManageProtectionStops() { if (activeInitialRisk <= 0 || string.IsNullOrEmpty(activeSignal)) return; if (Position.MarketPosition == MarketPosition.Long) { double openProfitDistance = Close[0] - activeEntryPrice; if (UseBreakeven && !breakevenMoved && openProfitDistance >= BreakevenAtR * activeInitialRisk) { double be = RoundToTick(activeEntryPrice + BreakevenOffsetTicks * TickSize); if (be > activeStopPrice) { activeStopPrice = be; SetStopLoss(activeSignal, CalculationMode.Price, activeStopPrice, false); breakevenMoved = true; } } if (UseVolatilityTrailing && openProfitDistance >= TrailStartsAtR * activeInitialRisk) { double trail = RoundToTick(Close[0] - atr[0] * TrailATRMultiplier); if (trail > activeStopPrice && trail < Close[0] - TickSize) { activeStopPrice = trail; SetStopLoss(activeSignal, CalculationMode.Price, activeStopPrice, false); } } } else if (Position.MarketPosition == MarketPosition.Short) { double openProfitDistance = activeEntryPrice - Close[0]; if (UseBreakeven && !breakevenMoved && openProfitDistance >= BreakevenAtR * activeInitialRisk) { double be = RoundToTick(activeEntryPrice - BreakevenOffsetTicks * TickSize); if (be < activeStopPrice) { activeStopPrice = be; SetStopLoss(activeSignal, CalculationMode.Price, activeStopPrice, false); breakevenMoved = true; } } if (UseVolatilityTrailing && openProfitDistance >= TrailStartsAtR * activeInitialRisk) { double trail = RoundToTick(Close[0] + atr[0] * TrailATRMultiplier); if (trail < activeStopPrice && trail > Close[0] + TickSize) { activeStopPrice = trail; SetStopLoss(activeSignal, CalculationMode.Price, activeStopPrice, false); } } } } // ====================================================================== // RIESGO DE SESION // ====================================================================== private void ApplyDailyRiskGuards() { double equity = realizedPnLToday + OpenPnL(); if (MaxDailyLossDollars > 0 && equity <= -Math.Abs(MaxDailyLossDollars)) { haltedToday = true; FlattenOpenPosition("DailyLoss"); } // En modo ilimitado NO se capa la ganancia: el profit goal es un tope // artificial de upside, no un guarda de riesgo. if (!UnlimitedTrades && DailyProfitGoalDollars > 0 && equity >= DailyProfitGoalDollars) { haltedToday = true; FlattenOpenPosition("DailyGoal"); } } private bool CanTradeNow() { // MODO ILIMITADO: sin limites artificiales de frecuencia. // Las condiciones matematicas (setup + probabilidad + EV neto + RR + costo) // siguen siendo el unico gate de entrada. Los guardas de RIESGO // (perdida diaria, racha de perdidas) se mantienen activos por separado. if (UnlimitedTrades) { if (MaxConsecutiveLosses > 0 && consecutiveLosses >= MaxConsecutiveLosses) { blkSessionLimits++; lastBlockReason = "Max perdidas consecutivas"; return false; } if (!VolatilityFilterPasses()) { blkVolatility++; lastBlockReason = "Filtro de volatilidad"; return false; } return true; } if (MaxTradesPerDay > 0 && tradesToday >= MaxTradesPerDay) { blkSessionLimits++; lastBlockReason = "Max trades/dia"; return false; } if (MaxConsecutiveLosses > 0 && consecutiveLosses >= MaxConsecutiveLosses) { blkSessionLimits++; lastBlockReason = "Max perdidas consecutivas"; return false; } if (CurrentBar - sessionStartBar < MinBarsAfterSessionOpen) { blkCooldown++; return false; } if (CurrentBar - lastExitBar < MinBarsBetweenTrades) { blkCooldown++; return false; } int effectiveCooldown = CooldownBarsAfterLoss; if (EscalateCooldown && consecutiveLosses > 1) effectiveCooldown = CooldownBarsAfterLoss * consecutiveLosses; // 3, 6, 9... if (CurrentBar - lastLossBar < effectiveCooldown) { blkCooldown++; lastBlockReason = "Cooldown post-perdida (" + effectiveCooldown + " barras)"; return false; } if (!VolatilityFilterPasses()) { blkVolatility++; lastBlockReason = "Filtro de volatilidad"; return false; } return true; } private bool VolumeFilterPasses() { if (!UseVolumeAnomalyFilter) return true; return lastVolumeZ >= MinimumVolumeZ; } private bool VolatilityFilterPasses() { if (!UseVolatilityFilter) return true; if (atr == null || atr[0] <= 0 || Close[0] <= 0) return false; if (lastAtrPercent < MinATRPercentOfPrice || lastAtrPercent > MaxATRPercentOfPrice) return false; if (MaxATRExpansionRatio > 0 && lastAtrExpansion > MaxATRExpansionRatio) return false; return true; } private bool MomentumBreakoutPasses(bool isLong) { if (!UseMomentumBreakoutConfirmation) return true; if (CurrentBar < MomentumBreakoutLookback + 2) return false; return isLong ? Close[0] > HighestHigh(MomentumBreakoutLookback, 1) : Close[0] < LowestLow(MomentumBreakoutLookback, 1); } private bool CloseRecovery(bool isLong) { double range = High[0] - Low[0]; if (range <= TickSize) return false; if (isLong) return (Close[0] - Low[0]) / range >= 0.50; return (High[0] - Close[0]) / range >= 0.50; } // ====================================================================== // SIZING: riesgo + Kelly fraccionado + piso opcional // ====================================================================== private int CalculateQuantity(double stopDistance, double probability, double rewardRisk) { if (SizingMode == AGQ4SizingMode.FixedContracts) return Math.Max(1, Math.Min(FixedContracts, MaxContracts)); double riskPerContract = stopDistance * Instrument.MasterInstrument.PointValue; if (riskPerContract <= 0 || double.IsNaN(riskPerContract)) return 0; double budget = RiskPerTradeDollars; if (UseKellySizing && rewardRisk > 0) { // Kelly: f* = p - (1-p)/RR, escalado por KellyFraction, acotado [0.25, 1.0] double kelly = probability - (1.0 - probability) / rewardRisk; double scale = Clamp(kelly * 2.0 * KellyFraction, 0.25, 1.0); budget *= scale; } int qty = (int)Math.Floor(budget / riskPerContract); if (qty < 1 && AllowMinimumOneContract && riskPerContract <= RiskPerTradeDollars * 2.0) qty = 1; // piso de 1 contrato si el riesgo no excede 2x el presupuesto return Math.Max(0, Math.Min(qty, MaxContracts)); } private double ClampStopDistance(double stopDistance) { double min = Math.Max(1, MinimumStopTicks) * TickSize; double max = Math.Max(MinimumStopTicks + 1, MaxStopTicks) * TickSize; return Math.Max(min, Math.Min(stopDistance, max)); } // EV bruto en unidades R (sin costos), para comparar candidatos private double GrossEV(double probability, double rewardRisk) { return probability * rewardRisk - (1.0 - probability); } // Costo total (comision RT + slippage 2 lados) como fraccion del riesgo del trade private double CostPercentOfRisk(double stopDistance) { double riskDollars = stopDistance * Instrument.MasterInstrument.PointValue; if (riskDollars <= 0) return 1.0; double slippageDollars = 2.0 * SlippageTicksPerSide * TickSize * Instrument.MasterInstrument.PointValue; return (CommissionPerRoundTurn + slippageDollars) / riskDollars; } // EV NETO de costos en unidades R: (comision RT + slippage 2 lados) / riesgo$ private double NetExpectedValueR(double probability, double rewardRisk, double stopDistance) { double riskDollars = stopDistance * Instrument.MasterInstrument.PointValue; if (riskDollars <= 0) return double.MinValue; double slippageDollars = 2.0 * SlippageTicksPerSide * TickSize * Instrument.MasterInstrument.PointValue; double costR = (CommissionPerRoundTurn + slippageDollars) / riskDollars; return GrossEV(probability, rewardRisk) - costR; } // ====================================================================== // UTILIDADES DE SESION Y DIBUJO // ====================================================================== private void FlattenOpenPosition(string reason) { if (Position.MarketPosition == MarketPosition.Long) ExitLong("X_" + reason, activeSignal); else if (Position.MarketPosition == MarketPosition.Short) ExitShort("X_" + reason, activeSignal); } private bool InTradingWindow() { int now = ToTime(Time[0]) / 100; if (StartTime == EndTime) return true; if (StartTime < EndTime) return now >= StartTime && now < EndTime; return now >= StartTime || now < EndTime; // ventana que cruza medianoche } private void ResetSessionState() { tradesToday = 0; realizedPnLToday = 0; lastTradePnL = 0; lastLossBar = -100000; lastExitBar = -100000; sessionStartBar = CurrentBar; consecutiveLosses = 0; haltedToday = false; lastTradeCount = SystemPerformance.AllTrades.Count; activeModel = AGQ4SignalModel.None; activeSignal = string.Empty; entryBar = -1; activeEntryPrice = 0; activeInitialRisk = 0; activeStopPrice = 0; activeTargetPrice = 0; breakevenMoved = false; lastRegimeText = "Inicializando"; lastBlockReason = string.Empty; blkNoSetup = 0; blkProbability = 0; blkExpectedValue = 0; blkRewardRisk = 0; blkSizing = 0; blkVolatility = 0; blkCooldown = 0; blkSessionLimits = 0; blkCostRatio = 0; } private void PrintSessionSummary() { if (!PrintBlockDiagnostics || sessionDate == DateTime.MinValue) return; Print("AGQ4 RESUMEN " + sessionDate.ToShortDateString() + " | Trades: " + tradesToday + " PnL: " + realizedPnLToday.ToString("C0") + " | Bloqueos -> SinSetup:" + blkNoSetup + " Prob:" + blkProbability + " EV:" + blkExpectedValue + " RR:" + blkRewardRisk + " Sizing:" + blkSizing + " Volatilidad:" + blkVolatility + " Cooldown:" + blkCooldown + " CostoRuido:" + blkCostRatio + " LimitesSesion:" + blkSessionLimits); } private void UpdateClosedTradeStats() { int count = SystemPerformance.AllTrades.Count; for (int i = lastTradeCount; i < count; i++) { double pnl = SystemPerformance.AllTrades[i].ProfitCurrency; realizedPnLToday += pnl; lastTradePnL = pnl; lastExitBar = CurrentBar; if (pnl < 0) { lastLossBar = CurrentBar; consecutiveLosses++; } else if (pnl > 0) consecutiveLosses = 0; } lastTradeCount = count; } private void DrawEntry(QuantCandidate candidate, int qty) { string label = candidate.Model == AGQ4SignalModel.MeanReversion ? "MR" : "MOM"; string text = label + " " + (candidate.IsLong ? "L" : "S") + " p=" + candidate.Probability.ToString("0.00") + " x" + qty; if (candidate.IsLong) { Draw.ArrowUp(this, "AGQ4_L_" + CurrentBar, false, 0, Low[0] - 2 * TickSize, Brushes.DeepSkyBlue); Draw.Text(this, "AGQ4_LT_" + CurrentBar, false, text, 0, Low[0] - 6 * TickSize, 0, Brushes.DeepSkyBlue, new SimpleFont("Arial", 10), System.Windows.TextAlignment.Center, Brushes.Transparent, Brushes.Transparent, 0); } else { Draw.ArrowDown(this, "AGQ4_S_" + CurrentBar, false, 0, High[0] + 2 * TickSize, Brushes.HotPink); Draw.Text(this, "AGQ4_ST_" + CurrentBar, false, text, 0, High[0] + 6 * TickSize, 0, Brushes.HotPink, new SimpleFont("Arial", 10), System.Windows.TextAlignment.Center, Brushes.Transparent, Brushes.Transparent, 0); } } private void DrawDiagnostics() { if (!ShowDiagnosticPanel) return; string block = string.IsNullOrEmpty(lastBlockReason) ? "-" : lastBlockReason; string text = "AG QUANT PROB V4" + "\nRegimen: " + lastRegimeText + " | H: " + lastHurst.ToString("0.00") + " | HL: " + (double.IsInfinity(lastHalfLife) ? "inf" : lastHalfLife.ToString("0")) + "\nZ: " + lastZScore.ToString("0.00") + " | t: " + lastSlopeT.ToString("0.00") + " | AccT: " + lastAccelT.ToString("0.00") + " | Eff: " + lastTrendEfficiency.ToString("0.00") + " | VolZ: " + lastVolumeZ.ToString("0.00") + "\nMR L/S: " + lastMRLongP.ToString("0.00") + "/" + lastMRShortP.ToString("0.00") + " | MOM L/S: " + lastMOMLongP.ToString("0.00") + "/" + lastMOMShortP.ToString("0.00") + "\nHoy: " + (realizedPnLToday + OpenPnL()).ToString("C0") + " | Trades: " + tradesToday + " | Ultimo bloqueo: " + block; Brush bg = realizedPnLToday + OpenPnL() >= 0 ? Brushes.DarkGreen : Brushes.DarkRed; Draw.TextFixed(this, "AGQ4_PANEL", text, TextPosition.TopLeft, Brushes.White, new SimpleFont("Consolas", PanelTextSize), Brushes.Transparent, bg, 45); } // ====================================================================== // MATEMATICA INSTITUCIONAL // ====================================================================== private int MinimumBarsNeeded() { int need = BarsRequiredToTrade; need = Math.Max(need, MeanLookback + 5); need = Math.Max(need, VolatilityLookback + VarianceRatioQ + 5); need = Math.Max(need, MomentumLookback + AccelerationLookback + 5); need = Math.Max(need, RegimeLookback + 5); need = Math.Max(need, VolumeLookback + 5); need = Math.Max(need, ATRPeriod + 5); need = Math.Max(need, ATRMeanLookback + 5); need = Math.Max(need, MomentumBreakoutLookback + 5); return need; } // t-statistic OLS de la pendiente sobre log-precio. // b = Sxy/Sxx ; SE(b) = sqrt( s2/Sxx ), s2 = SSR/(n-2) ; t = b/SE(b) private double SlopeTStat(int period, int barsAgo) { if (period < 5) return 0; double n = period; double sumX = 0, sumY = 0, sumXY = 0, sumXX = 0; for (int j = 0; j < period; j++) { double x = j; double y = Math.Log(Math.Max(TickSize, Close[barsAgo + period - 1 - j])); sumX += x; sumY += y; sumXY += x * y; sumXX += x * x; } double xBar = sumX / n; double yBar = sumY / n; double sxx = sumXX - n * xBar * xBar; if (sxx <= 0) return 0; double sxy = sumXY - n * xBar * yBar; double b = sxy / sxx; double a = yBar - b * xBar; double ssr = 0; for (int j = 0; j < period; j++) { double x = j; double y = Math.Log(Math.Max(TickSize, Close[barsAgo + period - 1 - j])); double resid = y - (a + b * x); ssr += resid * resid; } double s2 = ssr / Math.Max(1.0, n - 2.0); if (s2 <= 0) return b > 0 ? 50 : (b < 0 ? -50 : 0); double se = Math.Sqrt(s2 / sxx); if (se <= 0) return 0; return Clamp(b / se, -50, 50); } // Half-life de Ornstein-Uhlenbeck: dy_t = a + b*y_{t-1} + e, sobre log-precio. // HL = -ln(2)/ln(1+b). Si b >= 0 no hay reversion (HL infinita). private double OrnsteinUhlenbeckHalfLife(int period) { if (period < 10) return double.PositiveInfinity; double n = period - 1; double sumX = 0, sumY = 0, sumXY = 0, sumXX = 0; for (int i = 0; i < period - 1; i++) { double yLag = Math.Log(Math.Max(TickSize, Close[i + 1])); // y_{t-1} double dy = Math.Log(Math.Max(TickSize, Close[i])) - yLag; // y_t - y_{t-1} sumX += yLag; sumY += dy; sumXY += yLag * dy; sumXX += yLag * yLag; } double denom = n * sumXX - sumX * sumX; if (Math.Abs(denom) < 1e-12) return double.PositiveInfinity; double b = (n * sumXY - sumX * sumY) / denom; if (b >= 0) return double.PositiveInfinity; double onePlusB = 1.0 + b; if (onePlusB <= 0 || onePlusB >= 1) return double.PositiveInfinity; return -Math.Log(2.0) / Math.Log(onePlusB); } // Variance Ratio de Lo-MacKinlay con retornos solapados: // VR(q) = Var(r_q) / (q * Var(r_1)). VR<1 anti-persistente, VR>1 persistente. private double VarianceRatio(int q, int window) { if (q < 2 || window < q + 5) return 1.0; int n = window; // Varianza de retornos de 1 periodo double mean1 = 0; for (int i = 0; i < n; i++) mean1 += LogReturn(i); mean1 /= n; double var1 = 0; for (int i = 0; i < n; i++) { double d = LogReturn(i) - mean1; var1 += d * d; } var1 /= Math.Max(1, n - 1); if (var1 <= 0) return 1.0; // Varianza de retornos de q periodos (solapados) int m = n - q + 1; double meanQ = mean1 * q; double varQ = 0; for (int i = 0; i < m; i++) { double rq = Math.Log(Math.Max(TickSize, Close[i])) - Math.Log(Math.Max(TickSize, Close[i + q])); double d = rq - meanQ; varQ += d * d; } varQ /= Math.Max(1, m - 1); return Clamp(varQ / (q * var1), 0.05, 5.0); } // Hurst aproximado a partir del VR: H ~ 0.5 + ln(VR)/(2 ln q) private double HurstFromVR(double vr, int q) { if (vr <= 0 || q < 2) return 0.5; return Clamp(0.5 + Math.Log(vr) / (2.0 * Math.Log(q)), 0.0, 1.0); } // Z-score de log-volumen (el volumen es aprox. lognormal) private double LogVolumeZScore(int period) { double mean = 0; for (int i = 0; i < period; i++) mean += Math.Log(Math.Max(1.0, Convert.ToDouble(Volume[i]))); mean /= period; double sumSq = 0; for (int i = 0; i < period; i++) { double d = Math.Log(Math.Max(1.0, Convert.ToDouble(Volume[i]))) - mean; sumSq += d * d; } double std = Math.Sqrt(sumSq / Math.Max(1, period - 1)); return SafeDivide(Math.Log(Math.Max(1.0, Convert.ToDouble(Volume[0]))) - mean, std); } private double EfficiencyRatio(int period) { double direction = Math.Abs(Close[0] - Close[period]); double path = 0; for (int i = 0; i < period; i++) path += Math.Abs(Close[i] - Close[i + 1]); return path <= 0 ? 0 : Clamp(direction / path, 0, 1); } private double HighestHigh(int period, int barsAgo) { double value = double.MinValue; for (int i = barsAgo; i < barsAgo + period; i++) value = Math.Max(value, High[i]); return value; } private double LowestLow(int period, int barsAgo) { double value = double.MaxValue; for (int i = barsAgo; i < barsAgo + period; i++) value = Math.Min(value, Low[i]); return value; } private double AverageATR(int period) { if (atr == null) return 0; int safePeriod = Math.Min(period, CurrentBar - 1); if (safePeriod <= 1) return atr[0]; double sum = 0; for (int i = 0; i < safePeriod; i++) sum += atr[i]; return sum / safePeriod; } private double MeanClose(int period, int barsAgo) { double sum = 0; for (int i = barsAgo; i < barsAgo + period; i++) sum += Close[i]; return sum / period; } private double StdDevClose(int period, int barsAgo) { double mean = MeanClose(period, barsAgo); double sumSq = 0; for (int i = barsAgo; i < barsAgo + period; i++) { double d = Close[i] - mean; sumSq += d * d; } return Math.Sqrt(sumSq / Math.Max(1, period - 1)); } private double LogReturn(int barsAgo) { if (Close[barsAgo] <= 0 || Close[barsAgo + 1] <= 0) return 0; return Math.Log(Close[barsAgo] / Close[barsAgo + 1]); } private double Logistic(double x) { if (x > 50) return 1; if (x < -50) return 0; return 1.0 / (1.0 + Math.Exp(-x)); } private double SafeDivide(double numerator, double denominator) { if (Math.Abs(denominator) < 1e-9 || double.IsNaN(denominator)) return 0; return numerator / denominator; } private double Clamp(double value, double min, double max) { return Math.Max(min, Math.Min(max, value)); } private double RoundToTick(double price) { return Instrument.MasterInstrument.RoundToTickSize(price); } private double OpenPnL() { return Position.MarketPosition == MarketPosition.Flat ? 0.0 : Position.GetUnrealizedProfitLoss(PerformanceUnit.Currency, Close[0]); } private struct QuantCandidate { public bool IsValid; public bool IsLong; public AGQ4SignalModel Model; public double Probability; public double EntryPrice; public double StopDistance; public double TargetPrice; public double RewardRisk; } // ====================================================================== // PROPIEDADES // ====================================================================== #region Properties [NinjaScriptProperty] [Display(Name = "Engine Mode", Order = 1, GroupName = "01. Motor")] public AGQ4EngineMode EngineMode { get; set; } [NinjaScriptProperty] [Display(Name = "Enable Longs", Order = 2, GroupName = "01. Motor")] public bool EnableLongs { get; set; } [NinjaScriptProperty] [Display(Name = "Enable Shorts", Order = 3, GroupName = "01. Motor")] public bool EnableShorts { get; set; } [NinjaScriptProperty, Range(20, 500)] [Display(Name = "Mean Lookback", Order = 1, GroupName = "02. Lookbacks")] public int MeanLookback { get; set; } [NinjaScriptProperty, Range(20, 500)] [Display(Name = "Volatility Lookback", Order = 2, GroupName = "02. Lookbacks")] public int VolatilityLookback { get; set; } [NinjaScriptProperty, Range(10, 300)] [Display(Name = "Momentum Lookback", Order = 3, GroupName = "02. Lookbacks")] public int MomentumLookback { get; set; } [NinjaScriptProperty, Range(2, 100)] [Display(Name = "Acceleration Lookback", Order = 4, GroupName = "02. Lookbacks")] public int AccelerationLookback { get; set; } [NinjaScriptProperty, Range(10, 300)] [Display(Name = "Regime Lookback", Order = 5, GroupName = "02. Lookbacks")] public int RegimeLookback { get; set; } [NinjaScriptProperty, Range(10, 300)] [Display(Name = "Volume Lookback", Order = 6, GroupName = "02. Lookbacks")] public int VolumeLookback { get; set; } [NinjaScriptProperty, Range(2, 100)] [Display(Name = "ATR Period", Order = 7, GroupName = "02. Lookbacks")] public int ATRPeriod { get; set; } [NinjaScriptProperty, Range(2, 300)] [Display(Name = "ATR Mean Lookback", Order = 8, GroupName = "02. Lookbacks")] public int ATRMeanLookback { get; set; } [NinjaScriptProperty, Range(0.80, 0.999)] [Display(Name = "EWMA Lambda (RiskMetrics)", Order = 9, GroupName = "02. Lookbacks")] public double EwmaLambda { get; set; } [NinjaScriptProperty, Range(2, 30)] [Display(Name = "Variance Ratio Q", Order = 10, GroupName = "02. Lookbacks")] public int VarianceRatioQ { get; set; } [NinjaScriptProperty, Range(0.50, 0.95)] [Display(Name = "Probability Threshold", Order = 1, GroupName = "03. Gate Probabilistico")] public double ProbabilityThreshold { get; set; } [NinjaScriptProperty, Range(0.05, 0.60)] [Display(Name = "Exit Probability Threshold", Order = 2, GroupName = "03. Gate Probabilistico")] public double ExitProbabilityThreshold { get; set; } [NinjaScriptProperty, Range(0.10, 5.00)] [Display(Name = "Logistic Slope", Order = 3, GroupName = "03. Gate Probabilistico")] public double LogisticSlope { get; set; } [NinjaScriptProperty, Range(-1.00, 3.00)] [Display(Name = "Expected Value Threshold (R, neto)", Order = 4, GroupName = "03. Gate Probabilistico")] public double ExpectedValueThreshold { get; set; } [NinjaScriptProperty, Range(0.10, 10.00)] [Display(Name = "Minimum Reward Risk", Order = 5, GroupName = "03. Gate Probabilistico")] public double MinimumRewardRisk { get; set; } [NinjaScriptProperty, Range(0.00, 1.00)] [Display(Name = "Max Cost Percent Of Risk", Order = 6, GroupName = "03. Gate Probabilistico")] public double MaxCostPercentOfRisk { get; set; } [NinjaScriptProperty] [Display(Name = "Enable Mean Reversion", Order = 1, GroupName = "04. Mean Reversion")] public bool EnableMeanReversion { get; set; } [NinjaScriptProperty, Range(0.50, 5.00)] [Display(Name = "Entry Z", Order = 2, GroupName = "04. Mean Reversion")] public double MeanReversionEntryZ { get; set; } [NinjaScriptProperty, Range(0.00, 2.00)] [Display(Name = "Exit Z", Order = 3, GroupName = "04. Mean Reversion")] public double MeanReversionExitZ { get; set; } [NinjaScriptProperty, Range(1.00, 10.00)] [Display(Name = "Max Z", Order = 4, GroupName = "04. Mean Reversion")] public double MeanReversionMaxZ { get; set; } [NinjaScriptProperty, Range(5, 1000)] [Display(Name = "Max Half-Life Bars (OU)", Order = 5, GroupName = "04. Mean Reversion")] public int MaxHalfLifeBars { get; set; } [NinjaScriptProperty, Range(0.00, 1.00)] [Display(Name = "Min Reversion Quality", Order = 6, GroupName = "04. Mean Reversion")] public double MinReversionQuality { get; set; } [NinjaScriptProperty, Range(0.30, 5.00)] [Display(Name = "Stop Sigma", Order = 7, GroupName = "04. Mean Reversion")] public double MeanReversionStopSigma { get; set; } [NinjaScriptProperty, Range(0.05, 1.00)] [Display(Name = "Max Trend Efficiency", Order = 8, GroupName = "04. Mean Reversion")] public double MaxTrendEfficiencyForMR { get; set; } [NinjaScriptProperty, Range(0.00, 10.00)] [Display(Name = "Max Abs Slope T", Order = 9, GroupName = "04. Mean Reversion")] public double MaxAbsSlopeTForMR { get; set; } [NinjaScriptProperty, Range(0.50, 3.00)] [Display(Name = "Max Variance Ratio", Order = 10, GroupName = "04. Mean Reversion")] public double MaxVarianceRatioForMR { get; set; } [NinjaScriptProperty] [Display(Name = "Enable Momentum", Order = 1, GroupName = "05. Momentum")] public bool EnableMomentum { get; set; } [NinjaScriptProperty, Range(0.50, 10.00)] [Display(Name = "Entry T-Stat", Order = 2, GroupName = "05. Momentum")] public double MomentumEntryT { get; set; } [NinjaScriptProperty, Range(0.00, 5.00)] [Display(Name = "Exit T-Stat", Order = 3, GroupName = "05. Momentum")] public double MomentumExitT { get; set; } [NinjaScriptProperty, Range(0.00, 1.00)] [Display(Name = "Min Trend Efficiency", Order = 4, GroupName = "05. Momentum")] public double MinTrendEfficiencyForMOM { get; set; } [NinjaScriptProperty, Range(0.50, 3.00)] [Display(Name = "Min Variance Ratio", Order = 5, GroupName = "05. Momentum")] public double MinVarianceRatioForMOM { get; set; } [NinjaScriptProperty, Range(0.10, 10.00)] [Display(Name = "Stop ATR Mult", Order = 6, GroupName = "05. Momentum")] public double MomentumStopATR { get; set; } [NinjaScriptProperty, Range(0.10, 10.00)] [Display(Name = "Reward Risk", Order = 7, GroupName = "05. Momentum")] public double MomentumRewardRisk { get; set; } [NinjaScriptProperty, Range(0.50, 10.00)] [Display(Name = "Max Abs Z Entry", Order = 8, GroupName = "05. Momentum")] public double MaxAbsZForMomentumEntry { get; set; } [NinjaScriptProperty] [Display(Name = "Use Breakout Confirmation", Order = 9, GroupName = "05. Momentum")] public bool UseMomentumBreakoutConfirmation { get; set; } [NinjaScriptProperty, Range(2, 200)] [Display(Name = "Breakout Lookback", Order = 10, GroupName = "05. Momentum")] public int MomentumBreakoutLookback { get; set; } [NinjaScriptProperty] [Display(Name = "Use Volume Anomaly Filter", Order = 1, GroupName = "06. Volumen y Volatilidad")] public bool UseVolumeAnomalyFilter { get; set; } [NinjaScriptProperty, Range(-5.00, 5.00)] [Display(Name = "Minimum Volume Z", Order = 2, GroupName = "06. Volumen y Volatilidad")] public double MinimumVolumeZ { get; set; } [NinjaScriptProperty, Range(-2.00, 2.00)] [Display(Name = "Volume Boost Weight", Order = 3, GroupName = "06. Volumen y Volatilidad")] public double VolumeBoostWeight { get; set; } [NinjaScriptProperty] [Display(Name = "Use Volatility Filter", Order = 4, GroupName = "06. Volumen y Volatilidad")] public bool UseVolatilityFilter { get; set; } [NinjaScriptProperty, Range(0.00, 10.00)] [Display(Name = "Min ATR Percent Of Price", Order = 5, GroupName = "06. Volumen y Volatilidad")] public double MinATRPercentOfPrice { get; set; } [NinjaScriptProperty, Range(0.01, 20.00)] [Display(Name = "Max ATR Percent Of Price", Order = 6, GroupName = "06. Volumen y Volatilidad")] public double MaxATRPercentOfPrice { get; set; } [NinjaScriptProperty, Range(0.00, 20.00)] [Display(Name = "Max ATR Expansion Ratio", Order = 7, GroupName = "06. Volumen y Volatilidad")] public double MaxATRExpansionRatio { get; set; } [NinjaScriptProperty, Range(0.00, 100.00)] [Display(Name = "Commission Per Round Turn ($)", Order = 1, GroupName = "07. Costos")] public double CommissionPerRoundTurn { get; set; } [NinjaScriptProperty, Range(0, 20)] [Display(Name = "Slippage Ticks Per Side", Order = 2, GroupName = "07. Costos")] public int SlippageTicksPerSide { get; set; } [NinjaScriptProperty] [Display(Name = "Sizing Mode", Order = 1, GroupName = "08. Riesgo y Sizing")] public AGQ4SizingMode SizingMode { get; set; } [NinjaScriptProperty, Range(1, 100)] [Display(Name = "Fixed Contracts", Order = 2, GroupName = "08. Riesgo y Sizing")] public int FixedContracts { get; set; } [NinjaScriptProperty, Range(1.0, 100000.0)] [Display(Name = "Risk Per Trade ($)", Order = 3, GroupName = "08. Riesgo y Sizing")] public double RiskPerTradeDollars { get; set; } [NinjaScriptProperty, Range(1, 100)] [Display(Name = "Max Contracts", Order = 4, GroupName = "08. Riesgo y Sizing")] public int MaxContracts { get; set; } [NinjaScriptProperty, Range(1, 10000)] [Display(Name = "Minimum Stop Ticks", Order = 5, GroupName = "08. Riesgo y Sizing")] public int MinimumStopTicks { get; set; } [NinjaScriptProperty, Range(2, 10000)] [Display(Name = "Max Stop Ticks", Order = 6, GroupName = "08. Riesgo y Sizing")] public int MaxStopTicks { get; set; } [NinjaScriptProperty] [Display(Name = "Use Kelly Sizing", Order = 7, GroupName = "08. Riesgo y Sizing")] public bool UseKellySizing { get; set; } [NinjaScriptProperty, Range(0.10, 1.00)] [Display(Name = "Kelly Fraction", Order = 8, GroupName = "08. Riesgo y Sizing")] public double KellyFraction { get; set; } [NinjaScriptProperty] [Display(Name = "Allow Minimum One Contract", Order = 9, GroupName = "08. Riesgo y Sizing")] public bool AllowMinimumOneContract { get; set; } [NinjaScriptProperty] [Display(Name = "Use Breakeven", Order = 1, GroupName = "09. Gestion del Trade")] public bool UseBreakeven { get; set; } [NinjaScriptProperty, Range(0.10, 10.00)] [Display(Name = "Breakeven At R", Order = 2, GroupName = "09. Gestion del Trade")] public double BreakevenAtR { get; set; } [NinjaScriptProperty, Range(-100, 100)] [Display(Name = "Breakeven Offset Ticks", Order = 3, GroupName = "09. Gestion del Trade")] public int BreakevenOffsetTicks { get; set; } [NinjaScriptProperty] [Display(Name = "Use Volatility Trailing", Order = 4, GroupName = "09. Gestion del Trade")] public bool UseVolatilityTrailing { get; set; } [NinjaScriptProperty, Range(0.10, 10.00)] [Display(Name = "Trail Starts At R", Order = 5, GroupName = "09. Gestion del Trade")] public double TrailStartsAtR { get; set; } [NinjaScriptProperty, Range(0.10, 10.00)] [Display(Name = "Trail ATR Multiplier", Order = 6, GroupName = "09. Gestion del Trade")] public double TrailATRMultiplier { get; set; } [NinjaScriptProperty, Range(0, 10000)] [Display(Name = "Time Stop Bars", Order = 7, GroupName = "09. Gestion del Trade")] public int TimeStopBars { get; set; } [NinjaScriptProperty] [Display(Name = "Use Probability Exit", Order = 8, GroupName = "09. Gestion del Trade")] public bool UseProbabilityExit { get; set; } [NinjaScriptProperty] [Display(Name = "Use Time Filter", Order = 1, GroupName = "10. Riesgo de Sesion")] public bool UseTimeFilter { get; set; } [NinjaScriptProperty] [Display(Name = "UNLIMITED TRADES (solo matematicas)", Order = 0, GroupName = "10. Riesgo de Sesion")] public bool UnlimitedTrades { get; set; } [NinjaScriptProperty, Range(0, 2359)] [Display(Name = "Start Time HHMM", Order = 2, GroupName = "10. Riesgo de Sesion")] public int StartTime { get; set; } [NinjaScriptProperty, Range(0, 2359)] [Display(Name = "End Time HHMM", Order = 3, GroupName = "10. Riesgo de Sesion")] public int EndTime { get; set; } [NinjaScriptProperty, Range(0, 1000)] [Display(Name = "Max Trades Per Day", Order = 4, GroupName = "10. Riesgo de Sesion")] public int MaxTradesPerDay { get; set; } [NinjaScriptProperty, Range(0.0, 1000000.0)] [Display(Name = "Max Daily Loss ($)", Order = 5, GroupName = "10. Riesgo de Sesion")] public double MaxDailyLossDollars { get; set; } [NinjaScriptProperty, Range(0.0, 1000000.0)] [Display(Name = "Daily Profit Goal ($)", Order = 6, GroupName = "10. Riesgo de Sesion")] public double DailyProfitGoalDollars { get; set; } [NinjaScriptProperty, Range(0, 1000)] [Display(Name = "Cooldown Bars After Loss", Order = 7, GroupName = "10. Riesgo de Sesion")] public int CooldownBarsAfterLoss { get; set; } [NinjaScriptProperty] [Display(Name = "Escalate Cooldown", Order = 11, GroupName = "10. Riesgo de Sesion")] public bool EscalateCooldown { get; set; } [NinjaScriptProperty, Range(0, 1000)] [Display(Name = "Min Bars Between Trades", Order = 8, GroupName = "10. Riesgo de Sesion")] public int MinBarsBetweenTrades { get; set; } [NinjaScriptProperty, Range(0, 100)] [Display(Name = "Max Consecutive Losses", Order = 9, GroupName = "10. Riesgo de Sesion")] public int MaxConsecutiveLosses { get; set; } [NinjaScriptProperty, Range(0, 1000)] [Display(Name = "Min Bars After Session Open", Order = 10, GroupName = "10. Riesgo de Sesion")] public int MinBarsAfterSessionOpen { get; set; } [NinjaScriptProperty] [Display(Name = "Show Diagnostic Panel", Order = 1, GroupName = "11. Visual")] public bool ShowDiagnosticPanel { get; set; } [NinjaScriptProperty] [Display(Name = "Draw Signals", Order = 2, GroupName = "11. Visual")] public bool DrawSignals { get; set; } [NinjaScriptProperty, Range(8, 30)] [Display(Name = "Panel Text Size", Order = 3, GroupName = "11. Visual")] public int PanelTextSize { get; set; } [NinjaScriptProperty] [Display(Name = "Print Block Diagnostics", Order = 4, GroupName = "11. Visual")] public bool PrintBlockDiagnostics { get; set; } #endregion } }