Ver en GitHub

Kloss MQL/8186 Estrategia

La Estrategia Kloss MQL/8186 es una conversión directa del MetaTrader4 asesor experto Kloss.mq4. Combina un índice de canal de productos básicos (CCI), un oscilador Stochastic y un filtro de precios típico desplazado para cronometrar las reversiones de una sola posición. La versión StockSharp mantiene los umbrales de entrada originales, las distancias de stop-loss y take-profit y la lógica de volumen (tamaño de lote fijo o tamaño basado en porcentaje) mientras utiliza la suscripción de vela de alto nivel API.

Lógica de trading

  • Datos: Velas completadas del período de tiempo configurado (predeterminado 5 minutos). Los indicadores se calculan sobre la misma serie.
  • Indicadores:
    • CCI con período 10. El valor absoluto se compara con ±CciThreshold (predeterminado 120).
    • Stochastic oscilador con %K=5, %D=3, suavizando =3. La línea principal %K se compara con las bandas de sobreventa/sobrecompra.
    • Precio típico ((Máximo + Mínimo + Cierre) / 3) retrasado por cinco velas completas para replicar la LWMA desplazada del asesor experto.
  • Entrada Larga:
    • CCI <= -CciThreshold.
    • Stochastic %K < StochasticOversold (predeterminado 30).
    • Apertura de vela anterior > precio típico de hace cinco velas.
    • No existe ninguna posición larga (Position <= 0). Cualquier apertura corta se cierra y se revierte a una posición larga en una única orden de mercado.
  • Entrada corta:
    • CCI >= CciThreshold.
    • Stochastic %K > StochasticOverbought (predeterminado 70).
    • Cierre de vela anterior <precio típico de hace cinco velas.
    • No existe ninguna posición corta existente (Position >= 0). Cualquier apertura larga se cierra y se revierte a una posición corta con una orden de mercado.
  • Gestión de posición: StockSharp's StartProtection emite órdenes de stop-loss y take-profit automáticamente utilizando las distancias de puntos especificadas. Por lo demás, la estrategia mantiene una única posición en todo momento (plana, larga o corta).

Dimensionamiento de posiciones

  • Volumen fijo: si es FixedVolume > 0, la estrategia siempre negocia ese volumen exacto (después de alinearse con los VolumeStep y MinVolume del instrumento).
  • Porcentaje de riesgo: cuando FixedVolume = 0, la estrategia asigna RiskPercent (predeterminado 0,2) del valor de la cuenta dividido por el último cierre para estimar el tamaño del pedido. El volumen se fija mediante MaxVolume (predeterminado 5) y se redondea al paso del instrumento.
  • Salvaguardias: el método recurre al volumen mínimo negociable si falta información de la cuenta o el valor calculado no es positivo.

Parámetros

Nombre Descripción Predeterminado
CciPeriod Número de velas utilizadas para calcular el índice del canal de productos básicos. 10
CciThreshold Nivel absoluto CCI que activa las entradas. 120
StochasticKPeriod Periodo %K del oscilador Stochastic. 5
StochasticDPeriod %D período de suavizado. 3
StochasticSmooth Suavizado adicional aplicado a %K antes de la señal. 3
StochasticOversold Umbral %K para confirmar entradas largas. 30
StochasticOverbought Umbral %K para confirmar entradas cortas. 70
StopLossPoints Distancia en puntos de precio para la parada de protección. 48
TakeProfitPoints Distancia en puntos de precio para el objetivo de ganancias. 152
FixedVolume El valor positivo obliga a un volumen comercial fijo. 0
RiskPercent Fracción de cartera convertida a volumen cuando FixedVolume es cero. 0,2
MaxVolume Volumen comercial máximo permitido. 5
CandleType Tipo de vela/plazo de tiempo para los cálculos del indicador. marco de tiempo de 5 minutos

Notas de ejecución

  • Posición única: Solo se mantiene abierta una posición. Las reversiones cierran la posición existente y abren la nueva con una orden de mercado única.
  • Sincronización del indicador: El cambio de precio utiliza las últimas cinco velas completadas; Se deben procesar al menos seis velas antes de que pueda aparecer la primera operación.
  • Paradas/Objetivos: StartProtection convierte distancias basadas en puntos en compensaciones de precios absolutos utilizando el PriceStep del instrumento. Si se desconoce PriceStep, se aplica el valor de puntos sin procesar.
  • Requisitos de datos: Funciona con cualquier instrumento que proporcione OHLC velas; la alineación del volumen respeta MinVolume y VolumeStep cuando estén disponibles.
  • Diferencias frente a MT4: los cálculos de margen de MetaTrader se aproximan a través del capital de la cuenta (Portfolio.CurrentValue). Cuando los datos sobre acciones no están disponibles, la estrategia vuelve al volumen mínimo negociable.

Consejos de uso

  1. Ajuste CandleType a la sesión de mercado utilizada en MetaTrader (M5 en la plantilla original).
  2. Revisar las distancias de parada en relación con el tamaño de las marcas; La conversión de punto a precio se produce automáticamente, pero es posible que sea necesario ajustar los valores para instrumentos que no sean Forex.
  3. Para tamaños de contrato fijos, establezca FixedVolume en el lote deseado y RiskPercent en cero.
  4. Habilite la optimización de los umbrales de los indicadores al calibrar la estrategia en nuevos símbolos.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy converted from the MetaTrader 4 "Kloss" expert advisor (MQL/8186).
/// Implements the original combination of CCI and Stochastic oscillators with a price shift filter.
/// </summary>
public class KlossMql8186Strategy : Strategy
{
        private readonly StrategyParam<int> _cciPeriod;
        private readonly StrategyParam<decimal> _cciThreshold;
        private readonly StrategyParam<int> _stochasticKPeriod;
        private readonly StrategyParam<int> _stochasticDPeriod;
        private readonly StrategyParam<int> _stochasticSmooth;
        private readonly StrategyParam<decimal> _stochasticOversold;
        private readonly StrategyParam<decimal> _stochasticOverbought;
        private readonly StrategyParam<decimal> _stopLossPoints;
        private readonly StrategyParam<decimal> _takeProfitPoints;
        private readonly StrategyParam<decimal> _fixedVolume;
        private readonly StrategyParam<decimal> _riskPercent;
        private readonly StrategyParam<decimal> _maxVolume;
        private readonly StrategyParam<DataType> _candleType;

        private CommodityChannelIndex _cci = null!;
        private StochasticOscillator _stochastic = null!;

        private decimal? _previousOpen;
        private decimal? _previousClose;
        private readonly decimal?[] _typicalHistory = new decimal?[5];

        /// <summary>
        /// Initializes a new instance of the <see cref="KlossMql8186Strategy"/> class.
        /// </summary>
        public KlossMql8186Strategy()
        {
                _cciPeriod = Param(nameof(CciPeriod), 10)
                        .SetGreaterThanZero()
                        .SetDisplay("CCI Period", "Number of candles for the CCI calculation", "Indicators")
                        
                        .SetOptimize(5, 40, 5);

                _cciThreshold = Param(nameof(CciThreshold), 150m)
                        .SetGreaterThanZero()
                        .SetDisplay("CCI Threshold", "Absolute CCI level that triggers entries", "Indicators")
                        
                        .SetOptimize(80m, 200m, 10m);

                _stochasticKPeriod = Param(nameof(StochasticKPeriod), 5)
                        .SetGreaterThanZero()
                        .SetDisplay("Stochastic %K", "Period of the %K line", "Indicators")
                        
                        .SetOptimize(3, 15, 1);

                _stochasticDPeriod = Param(nameof(StochasticDPeriod), 3)
                        .SetGreaterThanZero()
                        .SetDisplay("Stochastic %D", "SMA length of the %D line", "Indicators")

                        .SetOptimize(1, 10, 1);

                _stochasticSmooth = Param(nameof(StochasticSmooth), 3)
                        .SetGreaterThanZero()
                        .SetDisplay("Stochastic Smoothing", "Smoothing applied to the %K calculation", "Indicators")
                        
                        .SetOptimize(1, 10, 1);

                _stochasticOversold = Param(nameof(StochasticOversold), 45m)
                        .SetNotNegative()
                        .SetDisplay("Stochastic Oversold", "Threshold under which %K confirms a long signal", "Signals")
                        
                        .SetOptimize(10m, 40m, 5m);

                _stochasticOverbought = Param(nameof(StochasticOverbought), 55m)
                        .SetNotNegative()
                        .SetDisplay("Stochastic Overbought", "Threshold above which %K confirms a short signal", "Signals")
                        
                        .SetOptimize(60m, 90m, 5m);

                _stopLossPoints = Param(nameof(StopLossPoints), 48m)
                        .SetNotNegative()
                        .SetDisplay("Stop Loss (pts)", "Stop loss distance expressed in price points", "Risk");

                _takeProfitPoints = Param(nameof(TakeProfitPoints), 152m)
                        .SetNotNegative()
                        .SetDisplay("Take Profit (pts)", "Take profit distance expressed in price points", "Risk");

                _fixedVolume = Param(nameof(FixedVolume), 0m)
                        .SetNotNegative()
                        .SetDisplay("Fixed Volume", "If greater than zero this volume will be used for orders", "Trading");

                _riskPercent = Param(nameof(RiskPercent), 0.2m)
                        .SetNotNegative()
                        .SetDisplay("Risk Percent", "Fraction of portfolio value used when Fixed Volume is zero", "Trading");

                _maxVolume = Param(nameof(MaxVolume), 5m)
                        .SetGreaterThanZero()
                        .SetDisplay("Max Volume", "Upper limit for the trade volume", "Trading");

                _candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
                        .SetDisplay("Candle Type", "Timeframe used for indicator calculations", "General");
        }

        /// <summary>
        /// CCI lookback period.
        /// </summary>
        public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }

        /// <summary>
        /// Absolute CCI level that triggers entries.
        /// </summary>
        public decimal CciThreshold { get => _cciThreshold.Value; set => _cciThreshold.Value = value; }

        /// <summary>
        /// Stochastic %K period.
        /// </summary>
        public int StochasticKPeriod { get => _stochasticKPeriod.Value; set => _stochasticKPeriod.Value = value; }

        /// <summary>
        /// Stochastic %D period.
        /// </summary>
        public int StochasticDPeriod { get => _stochasticDPeriod.Value; set => _stochasticDPeriod.Value = value; }

        /// <summary>
        /// Stochastic smoothing factor.
        /// </summary>
        public int StochasticSmooth { get => _stochasticSmooth.Value; set => _stochasticSmooth.Value = value; }

        /// <summary>
        /// Stochastic oversold threshold.
        /// </summary>
        public decimal StochasticOversold { get => _stochasticOversold.Value; set => _stochasticOversold.Value = value; }

        /// <summary>
        /// Stochastic overbought threshold.
        /// </summary>
        public decimal StochasticOverbought { get => _stochasticOverbought.Value; set => _stochasticOverbought.Value = value; }

        /// <summary>
        /// Stop loss distance in points.
        /// </summary>
        public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }

        /// <summary>
        /// Take profit distance in points.
        /// </summary>
        public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

        /// <summary>
        /// Fixed trading volume.
        /// </summary>
        public decimal FixedVolume { get => _fixedVolume.Value; set => _fixedVolume.Value = value; }

        /// <summary>
        /// Fraction of the portfolio value converted into volume when <see cref="FixedVolume"/> is zero.
        /// </summary>
        public decimal RiskPercent { get => _riskPercent.Value; set => _riskPercent.Value = value; }

        /// <summary>
        /// Maximum volume allowed per trade.
        /// </summary>
        public decimal MaxVolume { get => _maxVolume.Value; set => _maxVolume.Value = value; }

        /// <summary>
        /// Candle type used for indicator calculations.
        /// </summary>
        public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

        /// <inheritdoc />
        public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
        {
                return [(Security, CandleType)];
        }

        /// <inheritdoc />
        protected override void OnReseted()
        {
                base.OnReseted();

                _previousOpen = null;
                _previousClose = null;
                Array.Clear(_typicalHistory, 0, _typicalHistory.Length);
        }

        /// <inheritdoc />
        protected override void OnStarted2(DateTime time)
        {
                base.OnStarted2(time);

                _cci = new CommodityChannelIndex { Length = CciPeriod };
                _stochastic = new StochasticOscillator();
                _stochastic.K.Length = StochasticKPeriod;
                _stochastic.D.Length = StochasticDPeriod;

                var subscription = SubscribeCandles(CandleType);

                subscription
                        .Bind(ProcessCandle)
                        .Start();

                // Configure automatic position protection for stop loss and take profit.
                StartProtection(
                        takeProfit: CreatePriceUnit(TakeProfitPoints),
                        stopLoss: CreatePriceUnit(StopLossPoints));
        }

        private void ProcessCandle(ICandleMessage candle)
        {
                if (candle.State != CandleStates.Finished)
                        return;

                var cciResult = _cci.Process(candle).ToNullableDecimal();
                var stochResult = _stochastic.Process(candle);

                UpdateHistory(candle);

                if (cciResult is null)
                        return;

                if (!_stochastic.IsFormed)
                        return;

                var stochValue = (StochasticOscillatorValue)stochResult;
                if (stochValue.K is not decimal stochMain)
                        return;

                if (!IsFormedAndOnlineAndAllowTrading())
                        return;

                var cci = cciResult.Value;

                if (_previousOpen is decimal prevOpen &&
                        _previousClose is decimal prevClose &&
                        _typicalHistory[4] is decimal shiftedTypical)
                {
                        var buySignal = cci <= -CciThreshold && stochMain < StochasticOversold && prevOpen > shiftedTypical;
                        var sellSignal = cci >= CciThreshold && stochMain > StochasticOverbought && prevClose < shiftedTypical;

                        if (buySignal && Position <= 0)
                                BuyMarket();
                        else if (sellSignal && Position >= 0)
                                SellMarket();
                }
        }

        private void UpdateHistory(ICandleMessage candle)
        {
                // Shift stored typical prices to keep five previous values available.
                for (var i = _typicalHistory.Length - 1; i > 0; i--)
                        _typicalHistory[i] = _typicalHistory[i - 1];

                _typicalHistory[0] = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;

                // Store previous candle prices for the next iteration.
                _previousOpen = candle.OpenPrice;
                _previousClose = candle.ClosePrice;
        }

        private Unit CreatePriceUnit(decimal points)
        {
                if (points <= 0)
                        return new Unit(0m, UnitTypes.Absolute);

                if (Security?.PriceStep is decimal priceStep && priceStep > 0)
                        return new Unit(points * priceStep, UnitTypes.Absolute);

                return new Unit(points, UnitTypes.Absolute);
        }
}