Ver en GitHub

Estrategia del Indicador de Potencia Total X

Descripción general

La estrategia recrea el comportamiento del experto MetaTrader "Exp_TotalPowerIndicatorX" usando las APIs de alto nivel de StockSharp. Se basa en una implementación personalizada del Indicador de Potencia Total que mide el dominio de toros y osos contando cuántas velas en una ventana deslizante cierran por encima o por debajo de una línea base EMA interna. Las decisiones de trading se toman cuando las líneas de fortaleza alcista y bajista se cruzan entre sí.

El indicador funciona con cualquier símbolo y marco temporal. Por defecto la estrategia se suscribe a velas de 4 horas, coincidiendo con la configuración del asesor experto original, pero el marco temporal puede ajustarse a través de un parámetro.

Lógica de trading

  1. Para cada vela finalizada, la estrategia alimenta el Indicador de Potencia Total con los datos de la vela. El indicador:
    • Calcula una EMA con período Power Period.
    • Cuenta cuántas velas dentro de Lookback Period tuvieron High > EMA (toros) y Low < EMA (osos).
    • Convierte los conteos en valores de fortaleza al estilo porcentual en el rango 0–100.
  2. Un cruce alcista (fortaleza alcista subiendo por encima de la bajista) desencadena una entrada larga cuando el trading largo está habilitado y no hay posiciones abiertas.
  3. Un cruce bajista (fortaleza bajista subiendo por encima de la alcista) desencadena una entrada corta cuando el trading corto está habilitado y no hay posiciones abiertas.
  4. Los cruces opuestos cierran posiciones existentes cuando los interruptores de salida relevantes están habilitados.
  5. Un filtro de sesión de trading opcional fuerza el cierre de todas las posiciones fuera de la ventana de tiempo configurada y deshabilita nuevas entradas durante ese período.
  6. Los niveles opcionales de stop-loss y take-profit se expresan en múltiplos del paso de precio de la seguridad. Se recalculan después de cada entrada y salen tan pronto como el máximo o mínimo de la vela supera el nivel.

Parámetros

  • Candle Type – marco temporal usado para los cálculos del indicador. Por defecto: velas de 4 horas.
  • Power Period – longitud de la EMA dentro del indicador; refleja el input MQL. Por defecto: 10.
  • Lookback – número de velas usadas para contar el dominio alcista y bajista. Por defecto: 45.
  • Volume – tamaño de la orden enviada al exchange o simulador. Por defecto: 1.
  • Enable Long Entry / Enable Short Entry – permitir o prohibir nuevas posiciones en la dirección correspondiente.
  • Enable Long Exit / Enable Short Exit – cerrar posiciones en señales opuestas. Deshabilitar para mantener posiciones abiertas hasta cierre manual o stop-out.
  • Use Trading Hours – habilitar el filtro de tiempo. Cuando está activo, la estrategia opera solo entre Start Hour/Minute y End Hour/Minute y cierra cualquier posición abierta fuera de ese intervalo. Se admiten ventanas nocturnas (inicio posterior al fin).
  • Stop Loss Points / Take Profit Points – distancias desde el precio de entrada medidas en pasos de precio. Establecer en cero para deshabilitar el nivel. El cálculo usa Security.PriceStep, por lo que asegúrese de que los metadatos de la seguridad estén disponibles.

Notas

  • La estrategia abre una nueva posición solo cuando no hay ninguna posición activa en la seguridad, emulando el comportamiento del experto original.
  • Dado que los cálculos de stop-loss y take-profit dependen del paso de precio del instrumento, ejecutar la estrategia sin esos metadatos mantiene los niveles protectores deshabilitados automáticamente.
  • El valor del indicador se traza en el área del gráfico cuando la interfaz de usuario está disponible, lo que ayuda a visualizar los cruces entre la fortaleza alcista y bajista.
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 that replicates the Total Power Indicator expert advisor.
/// </summary>
public class TotalPowerIndicatorXStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _powerPeriod;
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<bool> _enableLongEntry;
	private readonly StrategyParam<bool> _enableShortEntry;
	private readonly StrategyParam<bool> _enableLongExit;
	private readonly StrategyParam<bool> _enableShortExit;
	private readonly StrategyParam<bool> _useTradingHours;
	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 TotalPowerIndicator _totalPower;
	private decimal? _previousDifference;
	private decimal? _longStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortStopPrice;
	private decimal? _shortTakePrice;

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

	/// <summary>
	/// Period for EMA inside Total Power Indicator.
	/// </summary>
	public int PowerPeriod
	{
		get => _powerPeriod.Value;
		set => _powerPeriod.Value = value;
	}

	/// <summary>
	/// Lookback period used for bull and bear strength counters.
	/// </summary>
	public int LookbackPeriod
	{
		get => _lookbackPeriod.Value;
		set => _lookbackPeriod.Value = value;
	}


	/// <summary>
	/// Enable opening long positions.
	/// </summary>
	public bool EnableLongEntry
	{
		get => _enableLongEntry.Value;
		set => _enableLongEntry.Value = value;
	}

	/// <summary>
	/// Enable opening short positions.
	/// </summary>
	public bool EnableShortEntry
	{
		get => _enableShortEntry.Value;
		set => _enableShortEntry.Value = value;
	}

	/// <summary>
	/// Enable closing long positions on opposite signals.
	/// </summary>
	public bool EnableLongExit
	{
		get => _enableLongExit.Value;
		set => _enableLongExit.Value = value;
	}

	/// <summary>
	/// Enable closing short positions on opposite signals.
	/// </summary>
	public bool EnableShortExit
	{
		get => _enableShortExit.Value;
		set => _enableShortExit.Value = value;
	}

	/// <summary>
	/// Enable time filter for trading sessions.
	/// </summary>
	public bool UseTradingHours
	{
		get => _useTradingHours.Value;
		set => _useTradingHours.Value = value;
	}

	/// <summary>
	/// Session start hour.
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Session start minute.
	/// </summary>
	public int StartMinute
	{
		get => _startMinute.Value;
		set => _startMinute.Value = value;
	}

	/// <summary>
	/// Session end hour.
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// Session end minute.
	/// </summary>
	public int EndMinute
	{
		get => _endMinute.Value;
		set => _endMinute.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="TotalPowerIndicatorXStrategy"/>.
	/// </summary>
	public TotalPowerIndicatorXStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe for calculations", "General");

		_powerPeriod = Param(nameof(PowerPeriod), 10)
		.SetGreaterThanZero()
		.SetDisplay("Power Period", "EMA length used by Total Power", "Indicator")
		;

		_lookbackPeriod = Param(nameof(LookbackPeriod), 50)
		.SetGreaterThanZero()
		.SetDisplay("Lookback", "Samples counted for bull/bear strength", "Indicator")
		;


		_enableLongEntry = Param(nameof(EnableLongEntry), true)
		.SetDisplay("Enable Long Entry", "Allow buying when bulls dominate", "Trading");

		_enableShortEntry = Param(nameof(EnableShortEntry), true)
		.SetDisplay("Enable Short Entry", "Allow selling when bears dominate", "Trading");

		_enableLongExit = Param(nameof(EnableLongExit), true)
		.SetDisplay("Enable Long Exit", "Close longs on bearish crossover", "Trading");

		_enableShortExit = Param(nameof(EnableShortExit), true)
		.SetDisplay("Enable Short Exit", "Close shorts on bullish crossover", "Trading");

		_useTradingHours = Param(nameof(UseTradingHours), false)
		.SetDisplay("Use Trading Hours", "Restrict trading to session window", "Schedule");

		_startHour = Param(nameof(StartHour), 0)
		.SetDisplay("Start Hour", "Session start hour", "Schedule");

		_startMinute = Param(nameof(StartMinute), 0)
		.SetDisplay("Start Minute", "Session start minute", "Schedule");

		_endHour = Param(nameof(EndHour), 23)
		.SetDisplay("End Hour", "Session end hour", "Schedule");

		_endMinute = Param(nameof(EndMinute), 59)
		.SetDisplay("End Minute", "Session end minute", "Schedule");

		_stopLossPoints = Param(nameof(StopLossPoints), 0)
		.SetDisplay("Stop Loss Points", "Stop loss distance in price steps (0=disabled)", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 0)
		.SetDisplay("Take Profit Points", "Take profit distance in price steps (0=disabled)", "Risk");
	}

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

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

		_totalPower?.Reset();
		_previousDifference = null;
		ResetLongTargets();
		ResetShortTargets();
	}

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

		_totalPower = new TotalPowerIndicator
		{
			PowerPeriod = PowerPeriod,
			LookbackPeriod = LookbackPeriod
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(_totalPower, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _totalPower);
			DrawOwnTrades(area);
		}
	}

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

		if (indicatorValue is not TotalPowerIndicatorValue powerValue)
		return;

		if (!_totalPower.IsFormed)
		{
			_previousDifference = powerValue.Bulls - powerValue.Bears;
			return;
		}

		var difference = powerValue.Bulls - powerValue.Bears;
		var previous = _previousDifference ?? difference;
		_previousDifference = difference;

		if (HandleStops(candle))
		return;

		var crossUp = difference > 0m && previous <= 0m;
		var crossDown = difference < 0m && previous >= 0m;

		var isTradingTime = !UseTradingHours || IsWithinTradingWindow(candle.OpenTime);

		if (UseTradingHours && !isTradingTime)
		{
			CloseAllPositions();
			return;
		}

		if (EnableLongExit && crossDown && Position > 0m)
		{
			SellMarket();
			ResetLongTargets();
		}

		if (EnableShortExit && crossUp && Position < 0m)
		{
			BuyMarket();
			ResetShortTargets();
		}

		if (!isTradingTime)
		return;

		if (EnableLongEntry && crossUp && Position == 0m)
		{
			BuyMarket();
			SetupLongTargets(candle.ClosePrice);
		}
		else if (EnableShortEntry && crossDown && Position == 0m)
		{
			SellMarket();
			SetupShortTargets(candle.ClosePrice);
		}
	}

	private bool HandleStops(ICandleMessage candle)
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
		return false;

		if (Position > 0m)
		{
			if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
			{
				SellMarket();
				ResetLongTargets();
				return true;
			}

			if (_longTakePrice.HasValue && candle.HighPrice >= _longTakePrice.Value)
			{
				SellMarket();
				ResetLongTargets();
				return true;
			}
		}
		else if (Position < 0m)
		{
			if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
			{
				BuyMarket();
				ResetShortTargets();
				return true;
			}

			if (_shortTakePrice.HasValue && candle.LowPrice <= _shortTakePrice.Value)
			{
				BuyMarket();
				ResetShortTargets();
				return true;
			}
		}

		return false;
	}

	private bool IsWithinTradingWindow(DateTimeOffset time)
	{
		var start = new TimeSpan(StartHour, StartMinute, 0);
		var end = new TimeSpan(EndHour, EndMinute, 0);
		var current = time.TimeOfDay;

		if (start == end)
			return true;

		if (start < end)
			return current >= start && current < end;

		return current >= start || current < end;
	}

	private void CloseAllPositions()
	{
		if (Position > 0m)
		{
			SellMarket();
			ResetLongTargets();
		}
		else if (Position < 0m)
		{
			BuyMarket();
			ResetShortTargets();
		}
	}

	private void SetupLongTargets(decimal entryPrice)
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
		{
			ResetLongTargets();
			return;
		}

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

	private void SetupShortTargets(decimal entryPrice)
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
		{
			ResetShortTargets();
			return;
		}

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

	private void ResetLongTargets()
	{
		_longStopPrice = null;
		_longTakePrice = null;
	}

	private void ResetShortTargets()
	{
		_shortStopPrice = null;
		_shortTakePrice = null;
	}

	private sealed class TotalPowerIndicator : BaseIndicator
	{
		private readonly List<int> _bullHistory = new();
		private readonly List<int> _bearHistory = new();
		private readonly ExponentialMovingAverage _ema = new();
		private int _bullCount;
		private int _bearCount;
		private int _powerPeriod = 10;
		private int _lookbackPeriod = 50;

		public int PowerPeriod
		{
			get => _powerPeriod;
			set
			{
				_powerPeriod = Math.Max(1, value);
				_ema.Length = _powerPeriod;
			}
		}

		public int LookbackPeriod
		{
			get => _lookbackPeriod;
			set => _lookbackPeriod = Math.Max(1, value);
		}

		protected override IIndicatorValue OnProcess(IIndicatorValue input)
		{
			var candle = input.GetValue<ICandleMessage>();
			var emaValue = _ema.Process(new DecimalIndicatorValue(_ema, candle.ClosePrice, input.Time) { IsFinal = input.IsFinal });

			if (!_ema.IsFormed)
			{
				IsFormed = false;
				return new TotalPowerIndicatorValue(this, input, 0m, 0m, 0m);
			}

			var ema = emaValue.ToDecimal();
			var bullContribution = candle.HighPrice > ema ? 1 : 0;
			var bearContribution = candle.LowPrice < ema ? 1 : 0;

			UpdateCounters(_bullHistory, ref _bullCount, bullContribution);
			UpdateCounters(_bearHistory, ref _bearCount, bearContribution);

			if (_bullHistory.Count < LookbackPeriod || _bearHistory.Count < LookbackPeriod)
			{
				IsFormed = false;
				return new TotalPowerIndicatorValue(this, input, 0m, 0m, 0m);
			}

			var bullPercent = (decimal)_bullCount * 100m / LookbackPeriod;
			var bearPercent = (decimal)_bearCount * 100m / LookbackPeriod;

			var bulls = Math.Clamp((bullPercent - 50m) * 2m, 0m, 100m);
			var bears = Math.Clamp((bearPercent - 50m) * 2m, 0m, 100m);
			var power = Math.Clamp(2m * Math.Abs(bullPercent - bearPercent), 0m, 100m);

			IsFormed = true;
			return new TotalPowerIndicatorValue(this, input, bulls, bears, power);
		}

		public override void Reset()
		{
			base.Reset();

			_bullHistory.Clear();
			_bearHistory.Clear();
			_bullCount = 0;
			_bearCount = 0;
			_ema.Reset();
		}

		private void UpdateCounters(List<int> list, ref int count, int value)
		{
			list.Add(value);
			count += value;

			while (list.Count > LookbackPeriod)
			{
				try { count -= list[0]; list.RemoveAt(0); }
				catch { break; }
			}
		}
	}

	private sealed class TotalPowerIndicatorValue : DecimalIndicatorValue
	{
		public TotalPowerIndicatorValue(IIndicator indicator, IIndicatorValue input, decimal bulls, decimal bears, decimal power)
		: base(indicator, bulls - bears, input.Time)
		{
			Bulls = bulls;
			Bears = bears;
			Power = power;
		}

		public decimal Bulls { get; }

		public decimal Bears { get; }

		public decimal Power { get; }
	}
}