Ver en GitHub

Estrategia de Vela de 30 Minutos

Este enfoque compara el precio de apertura de la vela actual de 30 minutos con el cierre de la vela anterior. Si una nueva vela abre por encima del cierre anterior, se abre una posición larga. Cuando ya se está largo y la siguiente vela abre por debajo del cierre anterior, la estrategia se invierte a una posición corta. Todas las posiciones abiertas se cierran un minuto antes de que termine la vela actual.

Detalles

  • Criterios de entrada:
    • Largo: apertura de la vela actual > cierre de la vela anterior.
    • Corto: apertura de la vela actual < cierre de la vela anterior mientras se mantiene una posición larga.
  • Largo/Corto: Ambos lados.
  • Criterios de salida:
    • Cerrar cualquier posición un minuto antes de que cierre la vela.
  • Stops: Ninguno.
  • Valores predeterminados:
    • CandleType = TimeSpan.FromMinutes(30).TimeFrame().
  • Filtros:
    • Categoría: Momentum
    • Dirección: Ambos
    • Indicadores: Price action
    • Stops: No
    • Complejidad: Básico
    • Marco temporal: Intradía
    • 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>
/// 30 Minute Candle Strategy.
/// Compares current close with previous close.
/// Buys when close > previous close, sells when close < previous close.
/// Uses EMA as trend filter.
/// </summary>
public class ThirtyMinuteCandleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _cooldownBars;

	private ExponentialMovingAverage _ema;

	private decimal _prevClose;
	private bool _hasPrevClose;
	private int _cooldownRemaining;

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

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

	public ThirtyMinuteCandleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_emaLength = Param(nameof(EmaLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA trend filter period", "Indicators");

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

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

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

		_ema = null;
		_prevClose = 0;
		_hasPrevClose = false;
		_cooldownRemaining = 0;
	}

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

		_ema = new ExponentialMovingAverage { Length = EmaLength };

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

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

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

		if (!_ema.IsFormed)
			return;

		var close = candle.ClosePrice;

		if (!_hasPrevClose)
		{
			_prevClose = close;
			_hasPrevClose = true;
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevClose = close;
			return;
		}

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevClose = close;
			return;
		}

		// Buy: close > previous close + above EMA
		if (close > _prevClose && close > emaVal && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		// Sell: close < previous close + below EMA
		else if (close < _prevClose && close < emaVal && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Math.Abs(Position));
			SellMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		// Exit long: close drops below EMA
		else if (Position > 0 && close < emaVal)
		{
			SellMarket(Math.Abs(Position));
			_cooldownRemaining = CooldownBars;
		}
		// Exit short: close rises above EMA
		else if (Position < 0 && close > emaVal)
		{
			BuyMarket(Math.Abs(Position));
			_cooldownRemaining = CooldownBars;
		}

		_prevClose = close;
	}
}