Ver en GitHub

Estrategia SV Rompimiento Diario

Descripción general

La Estrategia SV Rompimiento Diario es una conversión directa en C# del asesor experto de MetaTrader 5 "SV v.4.2.5". El sistema evalúa la acción del precio una vez por barra completada y permite como máximo una operación por día de bolsa. El trading comienza solo después de la hora de inicio configurada y se basa en la relación entre el rango reciente de máximo/mínimo y dos medias móviles suavizadas. Se abre una posición larga cuando el rango analizado completo permanece por debajo de ambas medias, señalando un rebote anticipado desde condiciones de sobreventa. Por el contrario, se abre una posición corta cuando el rango permanece por encima de ambas medias, señalando una posible reversión desde territorio de sobrecompra.

Reglas de trading

Condiciones de entrada

  • Filtro diario – no se evalúan operaciones hasta que el tiempo actual del servidor sea posterior a Start Hour/Start Minute. Solo se permite una entrada por día.
  • Ventana de datos – la estrategia omite las Shift barras más recientes y analiza las siguientes Interval barras. Sus precios más altos y más bajos se comparan con las medias móviles desplazadas.
  • Entrada larga – si el precio más alto en la ventana analizada está estrictamente por debajo de la MA lenta y el precio más bajo está estrictamente por debajo de la MA rápida, entrar largo (cerrando primero cualquier posición corta existente).
  • Entrada corta – si el precio más bajo en la ventana analizada está estrictamente por encima de la MA lenta y el precio más alto está estrictamente por encima de la MA rápida, entrar corto (cerrando primero cualquier posición larga existente).

Gestión de salida

  • Stop loss inicial – colocado a Stop Loss (pips) del precio de entrada. Si se alcanza el nivel, la posición se cierra.
  • Take profit – colocado a Take Profit (pips) del precio de entrada. Si se alcanza el nivel, la posición se cierra.
  • Trailing stop – cuando está habilitado (tanto la distancia de trailing como el paso son mayores que cero), el stop se mueve en la dirección del beneficio. Para largos, el stop se eleva a Cierre − Trailing Stop una vez que el precio avanza más de Trailing Stop + Trailing Step; los cortos replican la lógica.
  • Bloqueo diario – independientemente de cómo salga una operación, la estrategia no abrirá una nueva posición hasta el siguiente día de trading.

Dimensionamiento de posición

  • Modo manual – cuando Use Manual Volume es true, la estrategia envía el valor fijo de Volume (ajustado al paso de volumen del instrumento).
  • Modo basado en riesgo – cuando Use Manual Volume es false, la estrategia estima el tamaño de la operación a partir del capital de la cuenta y el Risk %. Divide el capital en riesgo por el valor monetario de la distancia de stop configurada, usando información del paso del instrumento cuando esté disponible.

Parámetros

Parámetro Valor predeterminado Descripción
Use Manual Volume false Usar el valor fijo de Volume en lugar del dimensionamiento basado en riesgo.
Volume 0.1 Volumen de operación cuando el dimensionamiento manual está habilitado.
Risk % 5 Porcentaje del capital de la cuenta arriesgado por operación cuando el dimensionamiento manual está activo.
Stop Loss (pips) 50 Distancia del stop-loss en pips. Establezca en 0 para deshabilitar.
Take Profit (pips) 50 Distancia del take-profit en pips. Establezca en 0 para deshabilitar.
Trailing Stop (pips) 5 Distancia del trailing stop en pips. Requiere que Trailing Step sea mayor que cero.
Trailing Step (pips) 5 Incremento mínimo de beneficio antes de que se mueva el trailing stop.
Start Hour 19 Hora (tiempo de bolsa) en que pueden comenzar las entradas.
Start Minute 0 Minuto (tiempo de bolsa) en que pueden comenzar las entradas.
Shift 6 Número de barras más recientes excluidas antes de analizar el rango.
Interval 27 Número de barras históricas usadas para calcular la ventana de máximo/mínimo.
Fast MA Period 14 Longitud de la media móvil rápida.
Fast MA Shift 0 Desplazamiento horizontal (barras atrás) usado para el valor de la MA rápida.
Fast MA Method Smma Método de media móvil para la MA rápida.
Fast Applied Price Median Fuente de precio para la MA rápida.
Slow MA Period 41 Longitud de la media móvil lenta.
Slow MA Shift 0 Desplazamiento horizontal (barras atrás) usado para el valor de la MA lenta.
Slow MA Method Smma Método de media móvil para la MA lenta.
Slow Applied Price Median Fuente de precio para la MA lenta.
Candle Type 1 hour Serie de velas usada para los cálculos.

Notas adicionales

  • La conversión mantiene el comportamiento original de analizar una ventana de precio retrasada (Shift + Interval) para evitar las barras más recientes al determinar rompimientos.
  • La lógica de trailing usa el precio de cierre de la vela para aproximar las actualizaciones de trailing basadas en ticks de MetaTrader. Ajuste las distancias en pips si su instrumento requiere diferente precisión.
  • El dimensionamiento basado en riesgo depende de Security.PriceStep, Security.StepPrice y Security.VolumeStep. Proporcione estos valores en la configuración de su instrumento para un dimensionamiento de lotes preciso.
  • La estrategia llama a StartProtection() para que pueda adjuntar reglas de riesgo globales adicionales si es necesario.
  • Para reflejar el EA original, asegúrese de que su feed de datos y cuenta de trading operen en la misma zona horaria del servidor referenciada por los parámetros Start Hour y Start Minute.
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>
/// Daily breakout strategy converted from the "SV v.4.2.5" MetaTrader 5 expert advisor.
/// Evaluates one trade per day after a configurable start time using moving average filters.
/// </summary>
public class SvDailyBreakoutStrategy : Strategy
{
	private readonly StrategyParam<bool> _useManualVolume;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _startMinute;
	private readonly StrategyParam<int> _shift;
	private readonly StrategyParam<int> _interval;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _fastMaShift;
	private readonly StrategyParam<MovingAverageMethods> _fastMaMethod;
	private readonly StrategyParam<AppliedPrices> _fastAppliedPrice;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _slowMaShift;
	private readonly StrategyParam<MovingAverageMethods> _slowMaMethod;
	private readonly StrategyParam<AppliedPrices> _slowAppliedPrice;
	private readonly StrategyParam<DataType> _candleType;

	private IIndicator _fastMa;
	private IIndicator _slowMa;

	private readonly List<decimal> _fastMaValues = new();
	private readonly List<decimal> _slowMaValues = new();
	private readonly List<decimal> _highHistory = new();
	private readonly List<decimal> _lowHistory = new();

	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takeProfitPrice;
	private decimal? _trailingStopPrice;
	private DateTime? _currentDay;
	private decimal _pipSize;

	/// <summary>
	/// Use manual volume instead of the risk-based sizing model.
	/// </summary>
	public bool UseManualVolume
	{
		get => _useManualVolume.Value;
		set => _useManualVolume.Value = value;
	}


	/// <summary>
	/// Risk percentage of account equity used when calculating the dynamic position size.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance expressed in pips.
	/// </summary>
	public int TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Trailing step distance expressed in pips.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Hour of the day (exchange time) when the strategy starts searching for entries.
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Minute of the hour when the strategy starts searching for entries.
	/// </summary>
	public int StartMinute
	{
		get => _startMinute.Value;
		set => _startMinute.Value = value;
	}

	/// <summary>
	/// Number of recent bars excluded from the high/low analysis window.
	/// </summary>
	public int Shift
	{
		get => _shift.Value;
		set => _shift.Value = value;
	}

	/// <summary>
	/// Number of bars that are analysed when computing the breakout range.
	/// </summary>
	public int Interval
	{
		get => _interval.Value;
		set => _interval.Value = value;
	}

	/// <summary>
	/// Fast moving average period.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	/// <summary>
	/// Fast moving average horizontal shift.
	/// </summary>
	public int FastMaShift
	{
		get => _fastMaShift.Value;
		set => _fastMaShift.Value = value;
	}

	/// <summary>
	/// Fast moving average calculation method.
	/// </summary>
	public MovingAverageMethods FastMaMethod
	{
		get => _fastMaMethod.Value;
		set => _fastMaMethod.Value = value;
	}

	/// <summary>
	/// Applied price used for the fast moving average.
	/// </summary>
	public AppliedPrices FastAppliedPrice
	{
		get => _fastAppliedPrice.Value;
		set => _fastAppliedPrice.Value = value;
	}

	/// <summary>
	/// Slow moving average period.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// Slow moving average horizontal shift.
	/// </summary>
	public int SlowMaShift
	{
		get => _slowMaShift.Value;
		set => _slowMaShift.Value = value;
	}

	/// <summary>
	/// Slow moving average calculation method.
	/// </summary>
	public MovingAverageMethods SlowMaMethod
	{
		get => _slowMaMethod.Value;
		set => _slowMaMethod.Value = value;
	}

	/// <summary>
	/// Applied price used for the slow moving average.
	/// </summary>
	public AppliedPrices SlowAppliedPrice
	{
		get => _slowAppliedPrice.Value;
		set => _slowAppliedPrice.Value = value;
	}

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

	/// <summary>
	/// Initializes strategy parameters with defaults that match the original expert advisor.
	/// </summary>
	public SvDailyBreakoutStrategy()
	{
		_useManualVolume = Param(nameof(UseManualVolume), false)
			.SetDisplay("Use Manual Volume", "Use fixed volume instead of risk percentage", "Risk");


		_riskPercent = Param(nameof(RiskPercent), 5m)
			.SetGreaterThanZero()
			.SetDisplay("Risk %", "Risk percentage of account equity", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 50)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Stop loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 50)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Take profit distance in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 5)
			.SetNotNegative()
			.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
			.SetNotNegative()
			.SetDisplay("Trailing Step (pips)", "Trailing step increment in pips", "Risk");

		_startHour = Param(nameof(StartHour), 0)
			.SetDisplay("Start Hour", "Hour when trading may begin", "Trading Window");

		_startMinute = Param(nameof(StartMinute), 0)
			.SetDisplay("Start Minute", "Minute when trading may begin", "Trading Window");

		_shift = Param(nameof(Shift), 2)
			.SetNotNegative()
			.SetDisplay("Shift", "Number of newest bars excluded from range analysis", "Logic");

		_interval = Param(nameof(Interval), 10)
			.SetGreaterThanZero()
			.SetDisplay("Interval", "Number of historical bars analysed", "Logic");

		_fastMaPeriod = Param(nameof(FastMaPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Period", "Fast moving average length", "Indicators");

		_fastMaShift = Param(nameof(FastMaShift), 0)
			.SetNotNegative()
			.SetDisplay("Fast MA Shift", "Horizontal shift for the fast moving average", "Indicators");

		_fastMaMethod = Param(nameof(FastMaMethod), MovingAverageMethods.Ema)
			.SetDisplay("Fast MA Method", "Calculation method for the fast moving average", "Indicators");

		_fastAppliedPrice = Param(nameof(FastAppliedPrice), AppliedPrices.Median)
			.SetDisplay("Fast Applied Price", "Price type used for the fast moving average", "Indicators");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Slow moving average length", "Indicators");

		_slowMaShift = Param(nameof(SlowMaShift), 0)
			.SetNotNegative()
			.SetDisplay("Slow MA Shift", "Horizontal shift for the slow moving average", "Indicators");

		_slowMaMethod = Param(nameof(SlowMaMethod), MovingAverageMethods.Ema)
			.SetDisplay("Slow MA Method", "Calculation method for the slow moving average", "Indicators");

		_slowAppliedPrice = Param(nameof(SlowAppliedPrice), AppliedPrices.Median)
			.SetDisplay("Slow Applied Price", "Price type used for the slow moving average", "Indicators");

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

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

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

		_fastMaValues.Clear();
		_slowMaValues.Clear();
		_highHistory.Clear();
		_lowHistory.Clear();
		_entryPrice = null;
		_pipSize = 0m;
		_stopPrice = null;
		_takeProfitPrice = null;
		_trailingStopPrice = null;
		_currentDay = null;
	}

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

		// validation removed for flexibility

		_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };

		var decimals = Security?.Decimals ?? 2;
		var step = Security?.PriceStep ?? 0.01m;
		var factor = decimals is 3 or 5 ? 10m : 1m;
		_pipSize = step * factor;
		if (_pipSize <= 0m)
			_pipSize = step > 0m ? step : 0.01m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_fastMa, _slowMa, ProcessCandleWithMa)
			.Start();
	}

	private void ProcessCandleWithMa(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		UpdateDailyState(candle.CloseTime);

		UpdateRangeHistory(candle);

		UpdateTrailing(candle);

		if (CheckProtectiveExits(candle))
			return;

		if (Position != 0)
			return;

		if (!TryGetRangeExtremes(out var lowest, out var highest))
			return;

		if (highest < slowValue && lowest < fastValue)
		{
			EnterPosition(true, candle);
			return;
		}

		if (lowest > slowValue && highest > fastValue)
		{
			EnterPosition(false, candle);
		}
	}

	private void EnterPosition(bool isLong, ICandleMessage candle)
	{
		var entryPrice = candle.ClosePrice;
		var stopDistance = StopLossPips > 0 ? StopLossPips * _pipSize : 0m;

		var volume = UseManualVolume
			? NormalizeVolume(Volume)
			: CalculateRiskBasedVolume(stopDistance);

		if (volume <= 0m)
			return;

		if (isLong)
		{
			var totalVolume = volume + (Position < 0 ? Math.Abs(Position) : 0m);
			if (totalVolume <= 0m)
				return;

			BuyMarket(totalVolume);
			_entryPrice = entryPrice;
			_stopPrice = StopLossPips > 0 ? entryPrice - stopDistance : null;
			_takeProfitPrice = TakeProfitPips > 0 ? entryPrice + TakeProfitPips * _pipSize : null;
		}
		else
		{
			var totalVolume = volume + (Position > 0 ? Position : 0m);
			if (totalVolume <= 0m)
				return;

			SellMarket(totalVolume);
			_entryPrice = entryPrice;
			_stopPrice = StopLossPips > 0 ? entryPrice + stopDistance : null;
			_takeProfitPrice = TakeProfitPips > 0 ? entryPrice - TakeProfitPips * _pipSize : null;
		}

		_trailingStopPrice = TrailingStopPips > 0 ? _stopPrice : null;
	}

	private void UpdateDailyState(DateTimeOffset time)
	{
		var day = time.Date;
		if (_currentDay != day)
		{
			_currentDay = day;
		}
	}

	private void UpdateRangeHistory(ICandleMessage candle)
	{
		_highHistory.Add(candle.HighPrice);
		_lowHistory.Add(candle.LowPrice);

		var maxCount = Math.Max(Shift + Interval + 5, 50);
		if (_highHistory.Count > maxCount)
		{
			var remove = _highHistory.Count - maxCount;
			_highHistory.RemoveRange(0, remove);
			_lowHistory.RemoveRange(0, remove);
		}
	}

	private decimal? ProcessMovingAverage(IIndicator indicator, AppliedPrices priceMode, List<decimal> buffer, int shift, ICandleMessage candle)
	{
		var price = GetAppliedPrice(candle, priceMode);
		var result = indicator.Process(new DecimalIndicatorValue(indicator, price, candle.OpenTime));

		if (!result.IsFormed)
			return null;

		var value = result.GetValue<decimal>();
		buffer.Add(value);

		var maxSize = Math.Max(shift + 1, 100);
		if (buffer.Count > maxSize)
			buffer.RemoveAt(0);

		var index = buffer.Count - 1 - shift;
		if (index < 0 || index >= buffer.Count)
			return null;

		return buffer[index];
	}

	private bool TryGetRangeExtremes(out decimal lowest, out decimal highest)
	{
		lowest = 0m;
		highest = 0m;

		var required = Shift + Interval;
		if (required <= 0)
			return false;

		if (_lowHistory.Count < required || _highHistory.Count < required)
			return false;

		var low = decimal.MaxValue;
		var high = decimal.MinValue;
		var total = _lowHistory.Count;

		for (var offset = Shift; offset < Shift + Interval; offset++)
		{
			var index = total - 1 - offset;
			if (index < 0)
				return false;

			var lowValue = _lowHistory[index];
			var highValue = _highHistory[index];

			if (lowValue < low)
				low = lowValue;

			if (highValue > high)
				high = highValue;
		}

		if (low == decimal.MaxValue || high == decimal.MinValue)
			return false;

		lowest = low;
		highest = high;
		return true;
	}

	private void UpdateTrailing(ICandleMessage candle)
	{
		if (TrailingStopPips <= 0 || TrailingStepPips <= 0 || _entryPrice is null)
			return;

		var trailDistance = TrailingStopPips * _pipSize;
		var stepDistance = TrailingStepPips * _pipSize;

		if (Position > 0)
		{
			var current = candle.ClosePrice;
			var entry = _entryPrice.Value;
			if (current - entry > trailDistance + stepDistance)
			{
				var threshold = current - (trailDistance + stepDistance);
				if (_stopPrice is null || _stopPrice < threshold)
				{
					var newStop = current - trailDistance;
					if (_stopPrice is null || newStop > _stopPrice)
					{
						_stopPrice = newStop;
						_trailingStopPrice = newStop;
					}
				}
			}
		}
		else if (Position < 0)
		{
			var current = candle.ClosePrice;
			var entry = _entryPrice.Value;
			if (entry - current > trailDistance + stepDistance)
			{
				var threshold = current + trailDistance + stepDistance;
				if (_stopPrice is null || _stopPrice > threshold)
				{
					var newStop = current + trailDistance;
					if (_stopPrice is null || newStop < _stopPrice)
					{
						_stopPrice = newStop;
						_trailingStopPrice = newStop;
					}
				}
			}
		}
	}

	private bool CheckProtectiveExits(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket(Position);
				ResetTradeState();
				return true;
			}

			if (_takeProfitPrice is decimal take && candle.HighPrice >= take)
			{
				SellMarket(Position);
				ResetTradeState();
				return true;
			}
		}
		else if (Position < 0)
		{
			var volume = Math.Abs(Position);
			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket(volume);
				ResetTradeState();
				return true;
			}

			if (_takeProfitPrice is decimal take && candle.LowPrice <= take)
			{
				BuyMarket(volume);
				ResetTradeState();
				return true;
			}
		}
		else if (_entryPrice is not null)
		{
			ResetTradeState();
		}

		return false;
	}

	private decimal CalculateRiskBasedVolume(decimal stopDistance)
	{
		if (UseManualVolume || stopDistance <= 0m)
			return NormalizeVolume(Volume);

		var portfolioValue = Portfolio?.CurrentValue ?? 0m;
		if (portfolioValue <= 0m)
			return NormalizeVolume(Volume);

		var riskAmount = portfolioValue * RiskPercent / 100m;
		if (riskAmount <= 0m)
			return NormalizeVolume(Volume);

		var step = Security?.PriceStep ?? _pipSize;
		if (step <= 0m)
			step = _pipSize > 0m ? _pipSize : 1m;

		var stepValue = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? step;
		if (stepValue <= 0m)
			stepValue = step;

		var steps = stopDistance / step;
		if (steps <= 0m)
			return NormalizeVolume(Volume);

		var riskPerUnit = steps * stepValue;
		if (riskPerUnit <= 0m)
			return NormalizeVolume(Volume);

		var rawVolume = riskAmount / riskPerUnit;
		return NormalizeVolume(rawVolume);
	}

	private decimal NormalizeVolume(decimal volume)
	{
		if (Security is null)
			return volume;

		var step = Security.VolumeStep ?? 1m;
		if (step > 0m)
			volume = Math.Floor(volume / step) * step;

		if (volume < step)
			volume = step;

		return volume;
	}

	private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPrices mode)
	{
		return mode switch
		{
			AppliedPrices.Open => candle.OpenPrice,
			AppliedPrices.High => candle.HighPrice,
			AppliedPrices.Low => candle.LowPrice,
			AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPrices.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			AppliedPrices.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
			_ => candle.ClosePrice,
		};
	}

	private static IIndicator CreateMovingAverage(MovingAverageMethods method, int length)
	{
		return method switch
		{
			MovingAverageMethods.Sma => new SimpleMovingAverage { Length = length },
			MovingAverageMethods.Ema => new ExponentialMovingAverage { Length = length },
			MovingAverageMethods.Smma => new SmoothedMovingAverage { Length = length },
			MovingAverageMethods.Lwma => new WeightedMovingAverage { Length = length },
			_ => new SimpleMovingAverage { Length = length },
		};
	}

	private void ResetTradeState()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takeProfitPrice = null;
		_trailingStopPrice = null;
	}

	/// <summary>
	/// Available moving average calculation methods.
	/// </summary>
	public enum MovingAverageMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Sma,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Ema,

		/// <summary>
		/// Smoothed moving average (SMMA).
		/// </summary>
		Smma,

		/// <summary>
		/// Linear weighted moving average (LWMA).
		/// </summary>
		Lwma
	}

	/// <summary>
	/// Price sources supported by the moving averages.
	/// </summary>
	public enum AppliedPrices
	{
		/// <summary>
		/// Close price.
		/// </summary>
		Close,

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

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

		/// <summary>
		/// 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
	}
}