Ver en GitHub

Estrategia Heikin Ashi V2

Esta segunda versión del sistema Heikin Ashi añade un filtro EMA. Las operaciones ocurren solo cuando la dirección de la vela Heikin Ashi concuerda con la tendencia definida por la EMA. El filtro ayuda a evitar señales en contra de la tendencia que el enfoque HA puro podría generar.

Detalles

  • Criterios de entrada:
    • Largo: HA_Close > HA_Open y Close > EMA
    • Corto: HA_Close < HA_Open y Close < EMA
  • Largo/Corto: Ambos
  • Criterios de salida:
    • Señal opuesta
  • Stops: Ninguno
  • Valores predeterminados:
    • EmaLength = 20
  • Filtros:
    • Categoría: Seguimiento de tendencia
    • Dirección: Ambos
    • Indicadores: Heikin Ashi, EMA
    • Stops: No
    • Complejidad: Bajo
    • Marco temporal: Corto plazo
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

/// <summary>
/// Heikin Ashi Strategy V2.
/// Uses fast and slow EMA crossover with Heikin-Ashi color confirmation.
/// Buys on bullish EMA cross when HA candle is green.
/// Sells on bearish EMA cross when HA candle is red.
/// </summary>
public class HeikinAshiV2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleTypeParam;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _cooldownBars;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _prevHaOpen;
	private decimal _prevHaClose;
	private int _cooldownRemaining;

	public HeikinAshiV2Strategy()
	{
		_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Moving Averages");

		_slowPeriod = Param(nameof(SlowPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Moving Averages");

		_cooldownBars = Param(nameof(CooldownBars), 10)
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
	}

	public DataType CandleType
	{
		get => _candleTypeParam.Value;
		set => _candleTypeParam.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

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

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

		_fastEma = null;
		_slowEma = null;
		_prevFast = 0;
		_prevSlow = 0;
		_prevHaOpen = 0;
		_prevHaClose = 0;
		_cooldownRemaining = 0;
	}

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

		_fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		_slowEma = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_fastEma, _slowEma, OnProcess)
			.Start();

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

	private void OnProcess(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Calculate Heikin-Ashi
		decimal haOpen, haClose;
		if (_prevHaOpen == 0)
		{
			haOpen = (candle.OpenPrice + candle.ClosePrice) / 2;
			haClose = (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4;
		}
		else
		{
			haOpen = (_prevHaOpen + _prevHaClose) / 2;
			haClose = (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4;
		}

		_prevHaOpen = haOpen;
		_prevHaClose = haClose;

		if (!_fastEma.IsFormed || !_slowEma.IsFormed)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast == 0)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var haGreen = haClose > haOpen;
		var haRed = haClose < haOpen;

		// Bullish: fast crosses above slow + HA is green
		var bullishCross = fast > slow && _prevFast <= _prevSlow && haGreen;
		// Bearish: fast crosses below slow + HA is red
		var bearishCross = fast < slow && _prevFast >= _prevSlow && haRed;

		if (bullishCross && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		else if (bearishCross && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Math.Abs(Position));
			SellMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}

		_prevFast = fast;
		_prevSlow = slow;
	}
}