Ver en GitHub

Estrategia de Bronze Warrioir

Descripción general

  • Conversión del experto MetaTrader 5 Bronze Warrioir.mq5 a la API de alto nivel de StockSharp.
  • Opera con un único símbolo usando velas completadas y combina CCI, Williams %R y un oscilador propietario "DayImpuls".
  • Orientado a capturar impulsos de momentum cuando la pendiente de DayImpuls, los extremos de Williams %R y las lecturas de CCI se alinean.

Conjunto de indicadores

  • Commodity Channel Index (CCI) – CCI clásico con el IndicatorPeriod configurado. Las señales largas requieren un valor por debajo de -CciLevel; las señales cortas necesitan un valor por encima de CciLevel.
  • Williams %R – aplicado en el mismo período. Un valor por encima de WilliamsLevelUp confirma territorio de sobrecompra, mientras que valores por debajo de WilliamsLevelDown confirman niveles de sobreventa.
  • Oscilador DayImpuls – réplica del indicador personalizado incluido. Convierte cada cuerpo de vela en puntos (cierre menos apertura dividido entre el valor de punto del instrumento) y aplica dos medias móviles exponenciales consecutivas con el mismo período. Los valores crecientes indican presión alcista; los valores decrecientes indican presión bajista.

Lógica de trading

  1. Protección del patrimonio – antes de generar cualquier señal, la estrategia acumula el PnL flotante de la exposición actual. Si sube por encima de ProfitTarget o cae por debajo de LossTarget, todas las posiciones abiertas se cierran inmediatamente.
  2. Filtro de entrada – las velas completadas son obligatorias. El algoritmo requiere un valor DayImpuls almacenado de la barra anterior para emular el look-back original usando custom[1].
  3. Configuración corta – activada cuando:
    • No hay exposición corta activa.
    • DayImpuls está por encima de DayImpulsLevel y es mayor que su valor anterior (momentum positivo).
    • Williams %R está por encima de WilliamsLevelUp (sobrecompra) y CCI es mayor que CciLevel.
    • Las órdenes usan TradeVolume más cualquier volumen largo abierto para revertir en una sola transacción dentro del modelo de netting de StockSharp.
  4. Configuración larga – condiciones simétricas:
    • Sin exposición larga activa.
    • DayImpuls está por debajo de DayImpulsLevel y es menor que su valor anterior (momentum decreciente).
    • Williams %R está por debajo de WilliamsLevelDown y CCI es menor que -CciLevel.
    • Usa TradeVolume más cualquier volumen corto pendiente para una reversión completa cuando sea necesario.
  5. Reversiones tipo hedge – cuando solo hay exposición en una dirección y el PnL flotante sale del rango [-PredTarget / 2, PredTarget], el EA valida el paso martingala a través del parámetro LotCoefficient. En el port de StockSharp, la validación se preserva pero la ejecución real realiza una orden de cierre y reversión porque la plataforma mantiene posiciones netas en lugar de tickets independientes con hedge.

Gestión de riesgos

  • StopLossPips y TakeProfitPips se convierten en distancias de precio usando el PriceStep del instrumento. Para símbolos forex de 3 o 5 dígitos se aplica un factor adicional de 10 para emular los "pips" de MetaTrader.
  • Ambos valores se pasan al helper de alto nivel StartProtection, que adjunta niveles automáticos de stop-loss y take-profit a la posición activa.
  • La estrategia mantiene seguimiento interno de volumen largo/corto para que GetOpenPnL coincida con el cálculo de MetaTrader que suma Commission + Swap + Profit para cada ticket.

Parámetros

Nombre Descripción Predeterminado
TradeVolume Volumen base de la orden en lotes. 1
StopLossPips Stop protector en pips convertido a distancia de precio. 50
TakeProfitPips Objetivo de beneficio en pips convertido a distancia de precio. 50
IndicatorPeriod Período aplicado a CCI, Williams %R y DayImpuls. 14
CciLevel Umbral absoluto de CCI para operaciones. 150
WilliamsLevelUp Nivel de sobrecompra de Williams %R (valor negativo). -15
WilliamsLevelDown Nivel de sobreventa de Williams %R (valor negativo). -85
DayImpulsLevel Umbral de DayImpuls que separa regímenes alcistas/bajistas. 50
ProfitTarget Objetivo de beneficio flotante en divisa de cuenta. 100
LossTarget Límite de pérdida flotante en divisa de cuenta. -100
PredTarget Rango usado para activar reversiones de promediado. 40
LotCoefficient Coeficiente de validación heredado del EA. 2
CandleType Marco temporal usado para todos los indicadores. Velas de 15m

Notas de implementación

  • El oscilador DayImpuls está integrado como clase de indicador interno y refleja la lógica original de suavizado doble EMA.
  • Dado que las estrategias de StockSharp gestionan posiciones netas, los hedges simultáneos largo/corto de la versión MQL se emulan combinando volúmenes de cierre y apertura dentro de la misma orden de mercado.
  • La estrategia solo funciona con velas completadas y usa IsFormedAndOnlineAndAllowTrading() para respetar el ciclo de vida global de la estrategia.
  • Los precios promedio largo/corto se rastrean a través de OnOwnTradeReceived para que los cierres parciales y reversiones actualicen correctamente el PnL flotante.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bronze Warrior strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class BronzeWarrioirStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public BronzeWarrioirStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}