Ver en GitHub

Estrategia Ichimoku Barabashkakvn

Esta estrategia recrea el asesor experto Ichimoku de Vladimir Karputov (edición barabashkakvn) sobre la API de alto nivel de StockSharp. Combina el clásico cruce Tenkan/Kijun con la confirmación de la nube Kumo y añade una gestión de riesgo detallada idéntica al original de MetaTrader.

Cómo funciona

  • Pila de indicadores – un único indicador Ichimoku Kinko Hyo suministra los valores de Tenkan-sen, Kijun-sen, Senkou Span A y Senkou Span B. Los períodos predeterminados siguen siendo 9/26/52.
  • Entradas largas – se activan cuando Tenkan cruza hacia arriba a través de Kijun y el precio de cierre está por encima de Senkou Span B. La detección del cruce usa el valor anterior de Tenkan, reflejando la lógica barra por barra del EA.
  • Entradas cortas – aparecen cuando Tenkan cruza hacia abajo a través de Kijun mientras el cierre está por debajo de Senkou Span A.
  • Gestión de posición – solo se mantiene una posición neta. Las señales opuestas cierran primero las operaciones existentes, reproduciendo el flujo de reversión de dos pasos del script.
  • Ventana de trading – un filtro de horas opcional permite al sistema operar solo entre horas de inicio/fin configuradas (inclusive) usando la misma comparación que la versión MQL.

Gestión de riesgo

  • Stops y objetivos direccionales – las posiciones largas y cortas usan distancias independientes de stop-loss/take-profit en pips. Los pips se convierten a unidades de precio usando el tamaño de paso del instrumento con un ajuste de ×10 para cotizaciones de 3 y 5 decimales, coincidiendo con el manejo de puntos del EA.
  • Trailing stop – cada dirección tiene su propia distancia de trailing más un paso de trailing común. El stop avanza solo después de que el movimiento supere (distancia de trailing + paso de trailing), exactamente como en el código original.
  • Ejecución de protección – las verificaciones de stop-loss y take-profit ocurren en cada vela completada para que los niveles de protección virtuales se comporten como las órdenes gestionadas por el broker de MetaTrader.

Parámetros

  • TenkanPeriod (predeterminado 9) – longitud de Tenkan-sen.
  • KijunPeriod (predeterminado 26) – longitud de Kijun-sen.
  • SenkouSpanBPeriod (predeterminado 52) – longitud de Senkou Span B.
  • CandleType (predeterminado velas de 1 hora) – fuente de datos para los cálculos.
  • OrderVolume (predeterminado 1 lote) – tamaño de la operación.
  • BuyStopLossPips / SellStopLossPips (predeterminado 100) – distancias de stop-loss en pips.
  • BuyTakeProfitPips / SellTakeProfitPips (predeterminado 300) – distancias de take-profit en pips.
  • BuyTrailingStopPips / SellTrailingStopPips (predeterminado 50) – distancias de trailing en pips.
  • TrailingStepPips (predeterminado 5) – incremento mínimo de ganancia requerido para desplazar el trailing stop.
  • UseTradeHours (predeterminado false) – habilitar el filtro de sesión.
  • StartHour / EndHour (predeterminados 0/23) – límites inclusivos de la ventana de trading (0–23).

Estos valores predeterminados coinciden con el EA publicado. Todos los parámetros están expuestos a través de objetos StrategyParam<T>, por lo que pueden optimizarse o ajustarse dentro de StockSharp Designer sin tocar el código fuente.

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>
/// Ichimoku cloud strategy inspired by barabashkakvn's MQL implementation.
/// Executes Tenkan/Kijun crosses with Kumo confirmation and optional timed trading window.
/// Includes independent stop-loss/take-profit targets per direction and pip-based trailing stops.
/// </summary>
public class IchimokuBarabashkakvnStrategy : Strategy
{
	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;
	private readonly StrategyParam<int> _senkouSpanBPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _buyStopLossPips;
	private readonly StrategyParam<int> _buyTakeProfitPips;
	private readonly StrategyParam<int> _sellStopLossPips;
	private readonly StrategyParam<int> _sellTakeProfitPips;
	private readonly StrategyParam<int> _buyTrailingStopPips;
	private readonly StrategyParam<int> _sellTrailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<bool> _useTradeHours;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _endHour;

	private Ichimoku _ichimoku = null!;
	private decimal? _prevTenkan;
	private decimal? _entryPrice;
	private decimal? _stopLossPrice;
	private decimal? _takeProfitPrice;
	private decimal _pipValue;

	/// <summary>
	/// Tenkan-sen period.
	/// </summary>
	public int TenkanPeriod
	{
		get => _tenkanPeriod.Value;
		set => _tenkanPeriod.Value = value;
	}

	/// <summary>
	/// Kijun-sen period.
	/// </summary>
	public int KijunPeriod
	{
		get => _kijunPeriod.Value;
		set => _kijunPeriod.Value = value;
	}

	/// <summary>
	/// Senkou Span B period.
	/// </summary>
	public int SenkouSpanBPeriod
	{
		get => _senkouSpanBPeriod.Value;
		set => _senkouSpanBPeriod.Value = value;
	}

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

	/// <summary>
	/// Order volume.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Stop-loss distance for long trades in pips.
	/// </summary>
	public int BuyStopLossPips
	{
		get => _buyStopLossPips.Value;
		set => _buyStopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance for long trades in pips.
	/// </summary>
	public int BuyTakeProfitPips
	{
		get => _buyTakeProfitPips.Value;
		set => _buyTakeProfitPips.Value = value;
	}

	/// <summary>
	/// Stop-loss distance for short trades in pips.
	/// </summary>
	public int SellStopLossPips
	{
		get => _sellStopLossPips.Value;
		set => _sellStopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance for short trades in pips.
	/// </summary>
	public int SellTakeProfitPips
	{
		get => _sellTakeProfitPips.Value;
		set => _sellTakeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance for long trades in pips.
	/// </summary>
	public int BuyTrailingStopPips
	{
		get => _buyTrailingStopPips.Value;
		set => _buyTrailingStopPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance for short trades in pips.
	/// </summary>
	public int SellTrailingStopPips
	{
		get => _sellTrailingStopPips.Value;
		set => _sellTrailingStopPips.Value = value;
	}

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

	/// <summary>
	/// Enable trading only within the configured hour window.
	/// </summary>
	public bool UseTradeHours
	{
		get => _useTradeHours.Value;
		set => _useTradeHours.Value = value;
	}

	/// <summary>
	/// First trading hour (0-23).
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Last trading hour (0-23).
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="IchimokuBarabashkakvnStrategy"/>.
	/// </summary>
	public IchimokuBarabashkakvnStrategy()
	{
		_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("Tenkan Period", "Tenkan-sen periods", "Ichimoku");

		_kijunPeriod = Param(nameof(KijunPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Kijun Period", "Kijun-sen periods", "Ichimoku");

		_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
			.SetGreaterThanZero()
			.SetDisplay("Senkou Span B Period", "Senkou Span B periods", "Ichimoku");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Source candles for processing", "General");

		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Position size in lots", "Trading");

		_buyStopLossPips = Param(nameof(BuyStopLossPips), 100)
			.SetNotNegative()
			.SetDisplay("Buy Stop Loss (pips)", "Long stop-loss distance", "Risk");

		_buyTakeProfitPips = Param(nameof(BuyTakeProfitPips), 300)
			.SetNotNegative()
			.SetDisplay("Buy Take Profit (pips)", "Long take-profit distance", "Risk");

		_sellStopLossPips = Param(nameof(SellStopLossPips), 100)
			.SetNotNegative()
			.SetDisplay("Sell Stop Loss (pips)", "Short stop-loss distance", "Risk");

		_sellTakeProfitPips = Param(nameof(SellTakeProfitPips), 300)
			.SetNotNegative()
			.SetDisplay("Sell Take Profit (pips)", "Short take-profit distance", "Risk");

		_buyTrailingStopPips = Param(nameof(BuyTrailingStopPips), 50)
			.SetNotNegative()
			.SetDisplay("Buy Trailing Stop (pips)", "Long trailing distance", "Risk");

		_sellTrailingStopPips = Param(nameof(SellTrailingStopPips), 50)
			.SetNotNegative()
			.SetDisplay("Sell Trailing Stop (pips)", "Short trailing distance", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
			.SetNotNegative()
			.SetDisplay("Trailing Step (pips)", "Step for trailing adjustments", "Risk");

		_useTradeHours = Param(nameof(UseTradeHours), false)
			.SetDisplay("Use Trade Hours", "Restrict trading to a time range", "Timing");

		_startHour = Param(nameof(StartHour), 0)
			.SetDisplay("Start Hour", "First hour allowed for trading", "Timing")
			.SetRange(0, 23);

		_endHour = Param(nameof(EndHour), 23)
			.SetDisplay("End Hour", "Last hour allowed for trading", "Timing")
			.SetRange(0, 23);
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevTenkan = null;
		_pipValue = 0m;
		ResetPositionState();
	}

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

		if (UseTradeHours && StartHour >= EndHour)
			throw new InvalidOperationException("Start hour must be less than end hour when time filter is enabled.");

		_ichimoku = new Ichimoku
		{
			Tenkan = { Length = TenkanPeriod },
			Kijun = { Length = KijunPeriod },
			SenkouB = { Length = SenkouSpanBPeriod }
		};

		_pipValue = CalculatePipValue();

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

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

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

		var ichimokuValue = _ichimoku.Process(candle);

		if (!ichimokuValue.IsFinal)
			return;

		var ichimokuTyped = (IchimokuValue)ichimokuValue;

		if (ichimokuTyped.Tenkan is not decimal tenkan ||
			ichimokuTyped.Kijun is not decimal kijun ||
			ichimokuTyped.SenkouA is not decimal senkouA ||
			ichimokuTyped.SenkouB is not decimal senkouB)
		{
			return;
		}

		if (!_ichimoku.IsFormed)
		{
			_prevTenkan = tenkan;
			return;
		}

		if (_prevTenkan is null)
		{
			_prevTenkan = tenkan;
			return;
		}

		// Ensure state is cleared if positions were closed externally.
		if (Position == 0 && (_entryPrice.HasValue || _stopLossPrice.HasValue || _takeProfitPrice.HasValue))
			ResetPositionState();

		if (Position != 0 && CheckProtectiveLevels(candle))
		{
			_prevTenkan = tenkan;
			return;
		}

		if (UseTradeHours)
		{
			var hour = candle.OpenTime.Hour;
			if (!(hour >= StartHour && hour <= EndHour))
			{
				_prevTenkan = tenkan;
				return;
			}
		}


		var buySignal = _prevTenkan < kijun && tenkan >= kijun && candle.ClosePrice > senkouB;
		var sellSignal = _prevTenkan > kijun && tenkan <= kijun && candle.ClosePrice < senkouA;

		if (Position == 0)
		{
			if (buySignal)
			{
				OpenLong(candle);
			}
			else if (sellSignal)
			{
				OpenShort(candle);
			}
		}
		else if (Position < 0)
		{
			if (buySignal)
			{
				CloseShort();
				_prevTenkan = tenkan;
				return;
			}
		}
		else if (Position > 0)
		{
			if (sellSignal)
			{
				CloseLong();
				_prevTenkan = tenkan;
				return;
			}
		}

		UpdateTrailingStops(candle);
		_prevTenkan = tenkan;
	}

	private void OpenLong(ICandleMessage candle)
	{
		if (OrderVolume <= 0)
			return;

		BuyMarket(OrderVolume);
		_entryPrice = candle.ClosePrice;

		var stopOffset = GetPriceOffset(BuyStopLossPips);
		var takeOffset = GetPriceOffset(BuyTakeProfitPips);

		_stopLossPrice = stopOffset > 0 ? candle.ClosePrice - stopOffset : null;
		_takeProfitPrice = takeOffset > 0 ? candle.ClosePrice + takeOffset : null;
	}

	private void OpenShort(ICandleMessage candle)
	{
		if (OrderVolume <= 0)
			return;

		SellMarket(OrderVolume);
		_entryPrice = candle.ClosePrice;

		var stopOffset = GetPriceOffset(SellStopLossPips);
		var takeOffset = GetPriceOffset(SellTakeProfitPips);

		_stopLossPrice = stopOffset > 0 ? candle.ClosePrice + stopOffset : null;
		_takeProfitPrice = takeOffset > 0 ? candle.ClosePrice - takeOffset : null;
	}

	private void CloseLong()
	{
		if (Position <= 0)
			return;

		SellMarket(Math.Abs(Position));
		ResetPositionState();
	}

	private void CloseShort()
	{
		if (Position >= 0)
			return;

		BuyMarket(Math.Abs(Position));
		ResetPositionState();
	}

	private void UpdateTrailingStops(ICandleMessage candle)
	{
		if (_entryPrice is null)
			return;

		if (Position > 0)
		{
			var trailingStop = GetPriceOffset(BuyTrailingStopPips);
			var trailingStep = GetPriceOffset(TrailingStepPips);

			if (trailingStop > 0 && trailingStep >= 0)
			{
				var profit = candle.ClosePrice - _entryPrice.Value;
				if (profit > trailingStop + trailingStep)
				{
					var threshold = candle.ClosePrice - (trailingStop + trailingStep);
					if (!_stopLossPrice.HasValue || _stopLossPrice.Value < threshold)
					{
						_stopLossPrice = candle.ClosePrice - trailingStop;
					}
				}
			}

			CheckProtectiveLevels(candle);
		}
		else if (Position < 0)
		{
			var trailingStop = GetPriceOffset(SellTrailingStopPips);
			var trailingStep = GetPriceOffset(TrailingStepPips);

			if (trailingStop > 0 && trailingStep >= 0)
			{
				var profit = _entryPrice.Value - candle.ClosePrice;
				if (profit > trailingStop + trailingStep)
				{
					var threshold = candle.ClosePrice + (trailingStop + trailingStep);
					if (!_stopLossPrice.HasValue || _stopLossPrice.Value > threshold)
					{
						_stopLossPrice = candle.ClosePrice + trailingStop;
					}
				}
			}

			CheckProtectiveLevels(candle);
		}
	}

	private bool CheckProtectiveLevels(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_stopLossPrice is decimal sl && candle.LowPrice <= sl)
			{
				SellMarket(Math.Abs(Position));
				ResetPositionState();
				return true;
			}

			if (_takeProfitPrice is decimal tp && candle.HighPrice >= tp)
			{
				SellMarket(Math.Abs(Position));
				ResetPositionState();
				return true;
			}
		}
		else if (Position < 0)
		{
			if (_stopLossPrice is decimal sl && candle.HighPrice >= sl)
			{
				BuyMarket(Math.Abs(Position));
				ResetPositionState();
				return true;
			}

			if (_takeProfitPrice is decimal tp && candle.LowPrice <= tp)
			{
				BuyMarket(Math.Abs(Position));
				ResetPositionState();
				return true;
			}
		}

		return false;
	}

	private void ResetPositionState()
	{
		_entryPrice = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
	}

	private decimal GetPriceOffset(int pips)
	{
		if (pips <= 0)
			return 0m;

		return pips * _pipValue;
	}

	private decimal CalculatePipValue()
	{
		var priceStep = Security?.PriceStep ?? 0m;

		if (priceStep <= 0m)
			priceStep = 1m;

		var decimals = GetDecimalPlaces(priceStep);
		var multiplier = decimals is 3 or 5 ? 10m : 1m;

		return priceStep * multiplier;
	}

	private static int GetDecimalPlaces(decimal value)
	{
		var bits = decimal.GetBits(value);
		return (bits[3] >> 16) & 0xFF;
	}
}