Ver en GitHub

Estrategia de Zonas SMC de Bloques de Órdenes

La estrategia identifica máximos y mínimos de swing para definir zonas premium y de descuento. Una media móvil simple actúa como filtro de tendencia y los bloques de órdenes recientes confirman las entradas. Las operaciones se ejecutan cuando el precio se mueve de una zona hacia el equilibrio con la confirmación del bloque de órdenes, usando un stop loss porcentual para protección.

Detalles

  • Criterios de entrada:
    • Cierre por debajo del equilibrio pero por encima de la zona de descuento y SMA para operaciones largas.
    • Cierre por encima del equilibrio pero por debajo de la zona premium y SMA para operaciones cortas.
    • El precio debe tocar el nivel del bloque de órdenes respectivo.
  • Largo/Corto: Largo, corto o ambos, configurable.
  • Criterios de salida: Señal opuesta o stop loss.
  • Stops: Stop loss porcentual.
  • Valores predeterminados:
    • SwingHighLength = 8
    • SwingLowLength = 8
    • SmaLength = 50
    • OrderBlockLength = 20
    • StopLossPercent = 2
  • Filtros:
    • Categoría: Tendencia y SMC
    • Dirección: Definido por el usuario
    • Indicadores: SMA, Highest, Lowest
    • Stops: Sí
    • Complejidad: Medio
    • Marco temporal: Cualquiera
    • 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>
/// SMC Order Block Zones Strategy.
/// Uses BB as order block zones and SMA as equilibrium.
/// Buys in discount zone (below SMA near lower BB).
/// Sells in premium zone (above SMA near upper BB).
/// </summary>
public class SmcOrderBlockZonesStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaLength;
	private readonly StrategyParam<int> _bbLength;
	private readonly StrategyParam<int> _cooldownBars;

	private SimpleMovingAverage _sma;
	private BollingerBands _bb;

	private int _cooldownRemaining;

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

	public int SmaLength
	{
		get => _smaLength.Value;
		set => _smaLength.Value = value;
	}

	public int BbLength
	{
		get => _bbLength.Value;
		set => _bbLength.Value = value;
	}

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

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

		_smaLength = Param(nameof(SmaLength), 50)
			.SetGreaterThanZero()
			.SetDisplay("SMA Length", "Equilibrium SMA period", "Indicators");

		_bbLength = Param(nameof(BbLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Length", "Bollinger Bands period", "Indicators");

		_cooldownBars = Param(nameof(CooldownBars), 10)
			.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();

		_sma = null;
		_bb = null;
		_cooldownRemaining = 0;
	}

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

		_sma = new SimpleMovingAverage { Length = SmaLength };
		_bb = new BollingerBands { Length = BbLength, Width = 2m };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_sma, _bb, OnProcess)
			.Start();

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

	private void OnProcess(ICandleMessage candle, IIndicatorValue smaValue, IIndicatorValue bbValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_sma.IsFormed || !_bb.IsFormed)
			return;

		if (smaValue.IsEmpty || bbValue.IsEmpty)
			return;

		var sma = smaValue.ToDecimal();
		var bb = (BollingerBandsValue)bbValue;
		if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			return;
		}

		var price = candle.ClosePrice;
		var equilibrium = sma;

		// Discount zone: below SMA, near lower BB -> buy
		if (price < equilibrium && price <= lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		// Premium zone: above SMA, near upper BB -> sell
		else if (price > equilibrium && price >= upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Math.Abs(Position));
			SellMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		// Exit long: price returns above equilibrium
		else if (Position > 0 && price > equilibrium)
		{
			SellMarket(Math.Abs(Position));
			_cooldownRemaining = CooldownBars;
		}
		// Exit short: price returns below equilibrium
		else if (Position < 0 && price < equilibrium)
		{
			BuyMarket(Math.Abs(Position));
			_cooldownRemaining = CooldownBars;
		}
	}
}