Ver en GitHub

Estrategia Blau Ergodic MDI Time

Descripción General

La Estrategia Blau Ergodic MDI Time es una conversión directa del experto MetaTrader Exp_BlauErgodicMDI_Tm.mq5 a StockSharp. Opera sobre velas de marco temporal superior y reproduce los tres modos de señal del algoritmo original: Breakdown, Twist y CloudTwist. La estrategia se basa en un proceso de suavizado de media móvil exponencial (EMA) de múltiples etapas aplicado a un precio de vela seleccionado. Todos los cálculos se realizan dentro de la estrategia sin indicadores adicionales para que la lógica coincida con el experto MetaTrader mientras permanece compatible con la API de alto nivel de StockSharp.

El pipeline de suavizado sigue la lógica del oscilador Blau Ergodic MDI:

  1. Suavizar el precio elegido con una EMA (longitud BaseLength).
  2. Restar el valor suavizado del precio bruto para obtener una serie de diferencias.
  3. Aplicar tres EMA consecutivas a la diferencia (longitudes FirstSmoothingLength, SecondSmoothingLength, ThirdSmoothingLength).
  4. Escalar las salidas intermedia (histogram) y final (signal) por el paso de precio del instrumento. Estos valores impulsan las señales de trading.

Modos de Señal

Breakdown

  • Usa el histograma dos barras atrás (controlado por SignalBar).
  • Cuando el valor anterior del histograma es positivo y la barra seleccionada se mueve a territorio no positivo, la estrategia prepara una entrada en largo y opcionalmente cierra posiciones cortas.
  • Cuando el valor anterior del histograma es negativo y la barra seleccionada sube a territorio no negativo, la estrategia prepara una entrada corta y opcionalmente cierra posiciones largas.

Twist

  • Compara la pendiente del histograma sobre dos barras históricas.
  • Si el histograma acelera hacia arriba (barra SignalBar + 1 < barra SignalBar + 2) y la barra seleccionada más reciente está por encima de la anterior, se genera una señal de entrada en largo. Las posiciones cortas pueden cerrarse en el mismo bloque.
  • Si el histograma acelera hacia abajo (barra SignalBar + 1 > barra SignalBar + 2) y la barra seleccionada más reciente está por debajo de la anterior, la estrategia prepara una entrada corta y puede cerrar posiciones largas.

CloudTwist

  • Usa tanto el histograma como la línea suavizada adicional.
  • Cuando el histograma anterior permanece por encima de la línea de señal pero la barra seleccionada cae por debajo, se prepara una entrada en largo y pueden cerrarse posiciones cortas.
  • Cuando el histograma anterior está por debajo de la línea de señal pero la barra seleccionada cruza por encima, la estrategia prepara una entrada corta y puede salir de posiciones largas.

Filtro de Ventana de Tiempo

El experto original restringe el trading a una sesión configurable. La versión StockSharp replica las mismas reglas mediante los parámetros UseTimeFilter, StartHour, StartMinute, EndHour y EndMinute. La lógica de sesión soporta ventanas que cruzan la medianoche, idéntica a la implementación MetaTrader:

  • Si la hora de inicio es anterior a la hora de fin, la sesión se mantiene dentro de un día.
  • Si la hora de inicio es igual a la hora de fin, los minutos definen un intervalo más corto durante esa hora.
  • Si la hora de inicio es posterior a la hora de fin, la sesión se extiende sobre la medianoche.

Cuando el trading está deshabilitado por el filtro de sesión, la estrategia cierra a mercado cualquier posición abierta y bloquea nuevas entradas hasta que la sesión se vuelva a abrir.

Gestión de Riesgo

Los parámetros StopLossPoints y TakeProfitPoints reflejan las distancias de stop-loss y take-profit del experto. Las distancias se expresan en pasos de precio. La estrategia recalcula los precios protectores cada vez que se abre una nueva posición. Cada vela finalizada comprueba si el rango de la barra tocó algún nivel protector y cierra inmediatamente la posición si se activa.

Entradas de Precio

El parámetro PriceMode expone la misma lista de fuentes de precio que el indicador original:

Modo Descripción
Close Precio de cierre.
Open Precio de apertura.
High Precio máximo.
Low Precio mínimo.
Median (High + Low) / 2.
Typical (High + Low + Close) / 3.
Weighted (High + Low + 2 × Close) / 4.
Simple (Open + Close) / 2.
Quarter (Open + High + Low + Close) / 4.
TrendFollow0 High en velas alcistas, Low en bajistas, Close en neutras.
TrendFollow1 Promedio de Close con el extremo de la vela en la dirección de la tendencia.
Demark Precio Demark (ponderado por la dirección de la vela).

Parámetros

Parámetro Predeterminado Descripción
Mode Twist Selecciona la evaluación de señal Breakdown, Twist o CloudTwist.
PriceMode Close Fuente de precio usada para el oscilador.
BaseLength 20 Longitud de EMA aplicada al precio bruto.
FirstSmoothingLength 5 Longitud de EMA del primer suavizado de diferencias.
SecondSmoothingLength 3 Longitud de EMA del segundo suavizado de diferencias.
ThirdSmoothingLength 8 Longitud de EMA del tercer suavizado de diferencias.
SignalBar 1 Número de barras completadas atrás usadas para verificaciones de señal (1 coincide con el predeterminado de MetaTrader).
AllowLongEntry / AllowShortEntry true Habilitar o deshabilitar entradas en largo/corto.
AllowLongExit / AllowShortExit true Habilitar o deshabilitar salidas para el lado correspondiente.
UseTimeFilter true Activa el filtro de sesión de trading.
StartHour, StartMinute, EndHour, EndMinute 0/0/23/59 Límites de la sesión.
StopLossPoints 1000 Distancia de stop-loss en pasos de precio (0 deshabilita).
TakeProfitPoints 2000 Distancia de take-profit en pasos de precio (0 deshabilita).
CandleType Marco temporal 4h Suscripción de velas usada para cálculos.
Volume 0.1 Volumen de la orden, coincidiendo con el input MM del experto.

Resumen de Reglas de Trading

  1. Suscribirse a las velas del marco temporal configurado.
  2. En cada vela finalizada, actualizar el pipeline EMA de cuatro etapas y almacenar los valores del histograma y la señal en buffers deslizantes.
  3. Esperar hasta que se alcance la profundidad mínima de historial (coincidiendo con el cálculo original de min_rates_total).
  4. Evaluar el modo seleccionado usando la barra SignalBar y valores más antiguos para establecer indicadores de apertura/cierre.
  5. Cerrar posiciones primero si se activa el indicador de salida correspondiente o si el filtro de tiempo bloquea el trading.
  6. Abrir nuevas operaciones en largo o corto solo cuando se establezca el indicador respectivo, el filtro de tiempo permita el trading y la posición actual no apunte ya en la misma dirección. Al revertir, la estrategia dimensiona automáticamente la orden para cubrir la exposición existente más el volumen configurado.
  7. Mantener stops y objetivos protectores usando extremos de vela para detectar activaciones.

Notas de Uso

  • La estrategia usa tabulaciones para indentación, consistente con las directrices del proyecto.
  • Llama a StartProtection() una vez durante el inicio para mantener las funciones de seguridad de StockSharp alineadas con los cambios de posición.
  • Los valores del indicador se almacenan solo para el número mínimo de barras requeridas por las señales. No se crean grandes colecciones, siguiendo las instrucciones del repositorio.
  • Para experimentar con otros métodos de suavizado de la versión MetaTrader, ajustar las longitudes de EMA en consecuencia. El pipeline basado en EMA proporciona la aproximación más cercana soportada por la API de alto nivel de StockSharp.

Ejecución de la Estrategia

  1. Agregar la clase de estrategia a su solución StockSharp y compilar el proyecto.
  2. Configurar los parámetros (instrumento, marco temporal de velas, modo, sesión y configuración de riesgo).
  3. Adjuntar la estrategia a un conector que proporcione los datos de mercado requeridos.
  4. Iniciar la estrategia; se suscribirá automáticamente a las velas configuradas y gestionará las órdenes según las reglas anteriores.
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>
/// Blau Ergodic MDI strategy converted from MetaTrader version 21013.
/// </summary>
public class BlauErgodicMdiTimeStrategy : Strategy
{
	/// <summary>
	/// Trading strategy based on the Blau Ergodic MDI oscillator with three signal modes.
	/// The strategy replicates the behaviour of the original MetaTrader expert advisor
	/// by evaluating the oscillator on a higher timeframe and optionally restricting
	/// trading to a custom time window.
	/// </summary>
	public enum BlauErgodicMdiModes
	{
		/// <summary>
		/// Generates entries when the histogram crosses the zero line.
		/// </summary>
		Breakdown,

		/// <summary>
		/// Generates entries when the histogram twists and changes slope.
		/// </summary>
		Twist,

		/// <summary>
		/// Generates entries when the histogram and its smoothed copy cross.
		/// </summary>
		CloudTwist
	}

	/// <summary>
	/// Price source used to feed the Blau Ergodic MDI calculation.
	/// </summary>
	public enum PriceInputModes
	{
		/// <summary>
		/// Candle close price.
		/// </summary>
		Close,

		/// <summary>
		/// Candle open price.
		/// </summary>
		Open,

		/// <summary>
		/// Candle high price.
		/// </summary>
		High,

		/// <summary>
		/// Candle low price.
		/// </summary>
		Low,

		/// <summary>
		/// Median price (high + low) / 2.
		/// </summary>
		Median,

		/// <summary>
		/// Typical price (high + low + close) / 3.
		/// </summary>
		Typical,

		/// <summary>
		/// Weighted close price (high + low + 2 * close) / 4.
		/// </summary>
		Weighted,

		/// <summary>
		/// Simplified price (open + close) / 2.
		/// </summary>
		Simple,

		/// <summary>
		/// Quarter price (open + high + low + close) / 4.
		/// </summary>
		Quarter,

		/// <summary>
		/// Trend follow price - picks the high on bullish candles and the low on bearish candles.
		/// </summary>
		TrendFollow0,

		/// <summary>
		/// Half trend follow price - averages close with the extreme price of the candle.
		/// </summary>
		TrendFollow1,

		/// <summary>
		/// Demark price calculation.
		/// </summary>
		Demark
	}

	private readonly StrategyParam<BlauErgodicMdiModes> _mode;
	private readonly StrategyParam<PriceInputModes> _priceMode;
	private readonly StrategyParam<int> _baseLength;
	private readonly StrategyParam<int> _firstSmoothingLength;
	private readonly StrategyParam<int> _secondSmoothingLength;
	private readonly StrategyParam<int> _thirdSmoothingLength;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<bool> _allowLongEntry;
	private readonly StrategyParam<bool> _allowShortEntry;
	private readonly StrategyParam<bool> _allowLongExit;
	private readonly StrategyParam<bool> _allowShortExit;
	private readonly StrategyParam<bool> _useTimeFilter;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _startMinute;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<int> _endMinute;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _priceEma;
	private decimal? _diffEma1;
	private decimal? _diffEma2;
	private decimal? _diffEma3;

	private decimal[] _histBuffer = Array.Empty<decimal>();
	private decimal[] _signalBuffer = Array.Empty<decimal>();
	private DateTimeOffset[] _timeBuffer = Array.Empty<DateTimeOffset>();
	private int _bufferCount;

	private decimal? _entryPrice;
	private decimal? _longStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortStopPrice;
	private decimal? _shortTakePrice;

	private TimeSpan _candleSpan;
	private int _barsProcessed;

	/// <summary>
	/// Selected signal mode.
	/// </summary>
	public BlauErgodicMdiModes Mode
	{
		get => _mode.Value;
		set => _mode.Value = value;
	}

	/// <summary>
	/// Selected price source.
	/// </summary>
	public PriceInputModes PriceMode
	{
		get => _priceMode.Value;
		set => _priceMode.Value = value;
	}

	/// <summary>
	/// Base smoothing length applied to the price series.
	/// </summary>
	public int BaseLength
	{
		get => _baseLength.Value;
		set => _baseLength.Value = value;
	}

	/// <summary>
	/// Length of the first smoothing of the price difference.
	/// </summary>
	public int FirstSmoothingLength
	{
		get => _firstSmoothingLength.Value;
		set => _firstSmoothingLength.Value = value;
	}

	/// <summary>
	/// Length of the second smoothing stage.
	/// </summary>
	public int SecondSmoothingLength
	{
		get => _secondSmoothingLength.Value;
		set => _secondSmoothingLength.Value = value;
	}

	/// <summary>
	/// Length of the third smoothing stage.
	/// </summary>
	public int ThirdSmoothingLength
	{
		get => _thirdSmoothingLength.Value;
		set => _thirdSmoothingLength.Value = value;
	}

	/// <summary>
	/// Number of bars back used for signal evaluation.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = Math.Max(0, value);
	}

	/// <summary>
	/// Enables long entries.
	/// </summary>
	public bool AllowLongEntry
	{
		get => _allowLongEntry.Value;
		set => _allowLongEntry.Value = value;
	}

	/// <summary>
	/// Enables short entries.
	/// </summary>
	public bool AllowShortEntry
	{
		get => _allowShortEntry.Value;
		set => _allowShortEntry.Value = value;
	}

	/// <summary>
	/// Enables exits from long positions.
	/// </summary>
	public bool AllowLongExit
	{
		get => _allowLongExit.Value;
		set => _allowLongExit.Value = value;
	}

	/// <summary>
	/// Enables exits from short positions.
	/// </summary>
	public bool AllowShortExit
	{
		get => _allowShortExit.Value;
		set => _allowShortExit.Value = value;
	}

	/// <summary>
	/// Enables trading within the configured time range only.
	/// </summary>
	public bool UseTimeFilter
	{
		get => _useTimeFilter.Value;
		set => _useTimeFilter.Value = value;
	}

	/// <summary>
	/// Start hour of the trading window.
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Start minute of the trading window.
	/// </summary>
	public int StartMinute
	{
		get => _startMinute.Value;
		set => _startMinute.Value = value;
	}

	/// <summary>
	/// End hour of the trading window.
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// End minute of the trading window.
	/// </summary>
	public int EndMinute
	{
		get => _endMinute.Value;
		set => _endMinute.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in points (price steps).
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = Math.Max(0, value);
	}

	/// <summary>
	/// Take-profit distance expressed in points (price steps).
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = Math.Max(0, value);
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public BlauErgodicMdiTimeStrategy()
	{
		_mode = Param(nameof(Mode), BlauErgodicMdiModes.Twist)
		.SetDisplay("Mode", "Signal mode", "General");

		_priceMode = Param(nameof(PriceMode), PriceInputModes.Close)
		.SetDisplay("Price Mode", "Price input used for the oscillator", "General");

		_baseLength = Param(nameof(BaseLength), 20)
		.SetGreaterThanZero()
		.SetDisplay("Base Length", "Length of the base EMA", "Indicator")
		;

		_firstSmoothingLength = Param(nameof(FirstSmoothingLength), 5)
		.SetGreaterThanZero()
		.SetDisplay("First Smooth", "Length of the first smoothing", "Indicator")
		;

		_secondSmoothingLength = Param(nameof(SecondSmoothingLength), 3)
		.SetGreaterThanZero()
		.SetDisplay("Second Smooth", "Length of the second smoothing", "Indicator")
		;

		_thirdSmoothingLength = Param(nameof(ThirdSmoothingLength), 8)
		.SetGreaterThanZero()
		.SetDisplay("Third Smooth", "Length of the third smoothing", "Indicator")
		;

		_signalBar = Param(nameof(SignalBar), 1)
		.SetDisplay("Signal Bar", "Number of bars back used for the signal", "Indicator");

		_allowLongEntry = Param(nameof(AllowLongEntry), true)
		.SetDisplay("Allow Long Entry", "Enable long entries", "Trading");

		_allowShortEntry = Param(nameof(AllowShortEntry), true)
		.SetDisplay("Allow Short Entry", "Enable short entries", "Trading");

		_allowLongExit = Param(nameof(AllowLongExit), true)
		.SetDisplay("Allow Long Exit", "Enable exits from long positions", "Trading");

		_allowShortExit = Param(nameof(AllowShortExit), true)
		.SetDisplay("Allow Short Exit", "Enable exits from short positions", "Trading");

		_useTimeFilter = Param(nameof(UseTimeFilter), true)
		.SetDisplay("Use Time Filter", "Restrict trading to the configured session", "Time Filter");

		_startHour = Param(nameof(StartHour), 0)
		.SetDisplay("Start Hour", "Hour when trading can start", "Time Filter");

		_startMinute = Param(nameof(StartMinute), 0)
		.SetDisplay("Start Minute", "Minute when trading can start", "Time Filter");

		_endHour = Param(nameof(EndHour), 23)
		.SetDisplay("End Hour", "Hour when trading stops", "Time Filter");

		_endMinute = Param(nameof(EndMinute), 59)
		.SetDisplay("End Minute", "Minute when trading stops", "Time Filter");

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
		.SetDisplay("Stop Loss", "Protective stop distance in points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
		.SetDisplay("Take Profit", "Target distance in points", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for calculations", "General");

	}

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

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

		_priceEma = null;
		_diffEma1 = null;
		_diffEma2 = null;
		_diffEma3 = null;

		_histBuffer = Array.Empty<decimal>();
		_signalBuffer = Array.Empty<decimal>();
		_timeBuffer = Array.Empty<DateTimeOffset>();
		_bufferCount = 0;

		_entryPrice = null;
		_longStopPrice = null;
		_longTakePrice = null;
		_shortStopPrice = null;
		_shortTakePrice = null;

		_candleSpan = TimeSpan.Zero;
		_barsProcessed = 0;
	}

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

		_candleSpan = GetCandleSpan();

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
	}

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

		if (CheckRiskManagement(candle))
		{
			return;
		}

		var price = GetAppliedPrice(candle);

		var baseLength = Math.Max(1, BaseLength);
		var firstLength = Math.Max(1, FirstSmoothingLength);
		var secondLength = Math.Max(1, SecondSmoothingLength);
		var thirdLength = Math.Max(1, ThirdSmoothingLength);

		var baseSmoothed = UpdateEma(ref _priceEma, price, baseLength);
		var diff = price - baseSmoothed;
		var diffSmoothed1 = UpdateEma(ref _diffEma1, diff, firstLength);
		var diffSmoothed2 = UpdateEma(ref _diffEma2, diffSmoothed1, secondLength);
		var diffSmoothed3 = UpdateEma(ref _diffEma3, diffSmoothed2, thirdLength);

		var point = GetPointValue();
		var histValue = diffSmoothed2 / point;
		var signalValue = diffSmoothed3 / point;
		var closeTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime + _candleSpan;

		_barsProcessed++;
		var minimumBars = GetMinimumBars();
		var requiredLength = GetRequiredBufferLength();

		PushValues(histValue, signalValue, closeTime, requiredLength);

		if (_barsProcessed < minimumBars)
		{
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			return;
		}

		var tradeWindow = !UseTimeFilter || InTradeWindow(closeTime);

		if (UseTimeFilter && !tradeWindow && Position != 0)
		{
			CloseAllPositions();
			return;
		}

		var signalBar = Math.Max(0, SignalBar);

		var buyOpen = false;
		var sellOpen = false;
		var buyClose = false;
		var sellClose = false;
		DateTimeOffset? upSignalTime = null;
		DateTimeOffset? downSignalTime = null;

		switch (Mode)
		{
			case BlauErgodicMdiModes.Breakdown:
			{
				if (!HasSufficientData(signalBar + 1))
				{
					break;
				}

				var current = _histBuffer[signalBar];
				var previous = _histBuffer[signalBar + 1];
				var signalTime = _timeBuffer[signalBar];

				if (previous > 0m)
				{
					if (AllowLongEntry && current <= 0m)
					{
						buyOpen = true;
					}

					if (AllowShortExit)
					{
						sellClose = true;
					}

					upSignalTime = signalTime;
				}

				if (previous < 0m)
				{
					if (AllowShortEntry && current >= 0m)
					{
						sellOpen = true;
					}

					if (AllowLongExit)
					{
						buyClose = true;
					}

					downSignalTime = signalTime;
				}

				break;
			}

			case BlauErgodicMdiModes.Twist:
			{
				if (!HasSufficientData(signalBar + 2))
				{
					break;
				}

				var current = _histBuffer[signalBar];
				var prev1 = _histBuffer[signalBar + 1];
				var prev2 = _histBuffer[signalBar + 2];
				var signalTime = _timeBuffer[signalBar];

				if (prev1 < prev2)
				{
					if (AllowLongEntry && current > prev1)
					{
						buyOpen = true;
					}

					if (AllowShortExit)
					{
						sellClose = true;
					}

					upSignalTime = signalTime;
				}

				if (prev1 > prev2)
				{
					if (AllowShortEntry && current < prev1)
					{
						sellOpen = true;
					}

					if (AllowLongExit)
					{
						buyClose = true;
					}

					downSignalTime = signalTime;
				}

				break;
			}

			case BlauErgodicMdiModes.CloudTwist:
			{
				if (!HasSufficientData(signalBar + 1))
				{
					break;
				}

				var currentUp = _histBuffer[signalBar];
				var currentDown = _signalBuffer[signalBar];
				var prevUp = _histBuffer[signalBar + 1];
				var prevDown = _signalBuffer[signalBar + 1];
				var signalTime = _timeBuffer[signalBar];

				if (prevUp > prevDown)
				{
					if (AllowLongEntry && currentUp <= currentDown)
					{
						buyOpen = true;
					}

					if (AllowShortExit)
					{
						sellClose = true;
					}

					upSignalTime = signalTime;
				}

				if (prevUp < prevDown)
				{
					if (AllowShortEntry && currentUp >= currentDown)
					{
						sellOpen = true;
					}

					if (AllowLongExit)
					{
						buyClose = true;
					}

					downSignalTime = signalTime;
				}

				break;
			}
		}

		if (buyClose && Position > 0)
		{
			SellMarket(Math.Abs(Position));
			ResetRiskLevels();
		}

		if (sellClose && Position < 0)
		{
			BuyMarket(Math.Abs(Position));
			ResetRiskLevels();
		}

		if (!tradeWindow)
		{
			return;
		}

		if (buyOpen && Position <= 0)
		{
			var volume = Volume + (Position < 0 ? -Position : 0m);
			BuyMarket(volume);
			_entryPrice = candle.ClosePrice;
			SetRiskForLong(candle.ClosePrice);
		}

		if (sellOpen && Position >= 0)
		{
			var volume = Volume + (Position > 0 ? Position : 0m);
			SellMarket(volume);
			_entryPrice = candle.ClosePrice;
			SetRiskForShort(candle.ClosePrice);
		}
	}

	private decimal GetAppliedPrice(ICandleMessage candle)
	{
		var open = candle.OpenPrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;
		var close = candle.ClosePrice;

		return PriceMode switch
		{
			PriceInputModes.Open => open,
			PriceInputModes.High => high,
			PriceInputModes.Low => low,
			PriceInputModes.Median => (high + low) / 2m,
			PriceInputModes.Typical => (close + high + low) / 3m,
			PriceInputModes.Weighted => (2m * close + high + low) / 4m,
			PriceInputModes.Simple => (open + close) / 2m,
			PriceInputModes.Quarter => (open + high + low + close) / 4m,
			PriceInputModes.TrendFollow0 => close > open ? high : close < open ? low : close,
			PriceInputModes.TrendFollow1 => close > open ? (high + close) / 2m : close < open ? (low + close) / 2m : close,
			PriceInputModes.Demark => CalculateDemarkPrice(open, high, low, close),
			_ => close,
		};
	}

	private static decimal CalculateDemarkPrice(decimal open, decimal high, decimal low, decimal close)
	{
		var res = high + low + close;

		if (close < open)
		{
			res = (res + low) / 2m;
		}
		else if (close > open)
		{
			res = (res + high) / 2m;
		}
		else
		{
			res = (res + close) / 2m;
		}

		return ((res - low) + (res - high)) / 2m;
	}

	private static decimal UpdateEma(ref decimal? previous, decimal value, int length)
	{
		if (length <= 1)
		{
			previous = value;
			return value;
		}

		var alpha = 2m / (length + 1m);
		var current = previous.HasValue ? previous.Value + alpha * (value - previous.Value) : value;
		previous = current;
		return current;
	}

	private decimal GetPointValue()
	{
		var step = Security?.PriceStep ?? 0m;
		return step > 0m ? step : 1m;
	}

	private int GetMinimumBars()
	{
		var baseCount = BaseLength + FirstSmoothingLength + SecondSmoothingLength + ThirdSmoothingLength + SignalBar + 3;
		return Math.Max(baseCount, GetRequiredBufferLength());
	}

	private int GetRequiredBufferLength()
	{
		var signalBar = Math.Max(0, SignalBar);

		return Mode switch
		{
			BlauErgodicMdiModes.Twist => signalBar + 3,
			_ => signalBar + 2,
		};
	}

	private void PushValues(decimal hist, decimal signal, DateTimeOffset time, int requiredLength)
	{
		if (requiredLength <= 0)
		{
			requiredLength = 1;
		}

		if (_histBuffer.Length < requiredLength)
		{
			Array.Resize(ref _histBuffer, requiredLength);
		}

		if (_signalBuffer.Length < requiredLength)
		{
			Array.Resize(ref _signalBuffer, requiredLength);
		}

		if (_timeBuffer.Length < requiredLength)
		{
			Array.Resize(ref _timeBuffer, requiredLength);
		}

		var limit = Math.Min(_bufferCount, requiredLength - 1);

		for (var i = limit; i > 0; i--)
		{
			_histBuffer[i] = _histBuffer[i - 1];
			_signalBuffer[i] = _signalBuffer[i - 1];
			_timeBuffer[i] = _timeBuffer[i - 1];
		}

		_histBuffer[0] = hist;
		_signalBuffer[0] = signal;
		_timeBuffer[0] = time;

		_bufferCount = Math.Min(requiredLength, _bufferCount + 1);
	}

	private bool HasSufficientData(int index)
	{
		return _bufferCount > index;
	}

	private bool InTradeWindow(DateTimeOffset time)
	{
		if (!UseTimeFilter)
		{
			return true;
		}

		var hour = time.Hour;
		var minute = time.Minute;

		if (StartHour < EndHour)
		{
			if (hour == StartHour && minute >= StartMinute)
			{
				return true;
			}

			if (hour > StartHour && hour < EndHour)
			{
				return true;
			}

			if (hour > StartHour && hour == EndHour && minute < EndMinute)
			{
				return true;
			}

			return false;
		}

		if (StartHour == EndHour)
		{
			return hour == StartHour && minute >= StartMinute && minute < EndMinute;
		}

		if (hour >= StartHour && minute >= StartMinute)
		{
			return true;
		}

		if (hour < EndHour)
		{
			return true;
		}

		if (hour == EndHour && minute < EndMinute)
		{
			return true;
		}

		return false;
	}

	private void CloseAllPositions()
	{
		if (Position > 0)
		{
			SellMarket(Math.Abs(Position));
			ResetRiskLevels();
		}
		else if (Position < 0)
		{
			BuyMarket(Math.Abs(Position));
			ResetRiskLevels();
		}
	}

	private void SetRiskForLong(decimal entryPrice)
	{
		var step = GetPointValue();

		_longStopPrice = StopLossPoints > 0 ? entryPrice - StopLossPoints * step : null;
		_longTakePrice = TakeProfitPoints > 0 ? entryPrice + TakeProfitPoints * step : null;

		_shortStopPrice = null;
		_shortTakePrice = null;
	}

	private void SetRiskForShort(decimal entryPrice)
	{
		var step = GetPointValue();

		_shortStopPrice = StopLossPoints > 0 ? entryPrice + StopLossPoints * step : null;
		_shortTakePrice = TakeProfitPoints > 0 ? entryPrice - TakeProfitPoints * step : null;

		_longStopPrice = null;
		_longTakePrice = null;
	}

	private void ResetRiskLevels()
	{
		_entryPrice = null;
		_longStopPrice = null;
		_longTakePrice = null;
		_shortStopPrice = null;
		_shortTakePrice = null;
	}

	private bool CheckRiskManagement(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket(Math.Abs(Position));
				ResetRiskLevels();
				return true;
			}

			if (_longTakePrice is decimal take && candle.HighPrice >= take)
			{
				SellMarket(Math.Abs(Position));
				ResetRiskLevels();
				return true;
			}
		}
		else if (Position < 0)
		{
			if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket(Math.Abs(Position));
				ResetRiskLevels();
				return true;
			}

			if (_shortTakePrice is decimal take && candle.LowPrice <= take)
			{
				BuyMarket(Math.Abs(Position));
				ResetRiskLevels();
				return true;
			}
		}

		return false;
	}

	private TimeSpan GetCandleSpan()
	{
		return CandleType.Arg switch
		{
			TimeSpan span => span,
			_ => TimeSpan.Zero,
		};
	}
}