Ver en GitHub

Estrategia Parabolic SAR Bug5

Descripción general

La Estrategia Parabolic SAR Bug5 opera reversiones de precio detectadas por el indicador Parabolic SAR. Abre una posición larga cuando el precio cruza por encima del SAR y una posición corta cuando el precio cruza por debajo. La estrategia opcionalmente invierte la dirección de trading, cierra posiciones abiertas cuando el SAR cambia de lado, y admite stop trailing, toma de ganancias y reglas de stop-loss.

Reglas de entrada

  • Comprar cuando el precio cruza por encima del SAR y no hay posición larga abierta.
  • Vender cuando el precio cruza por debajo del SAR y no hay posición corta abierta.
  • Si Reverse está habilitado, las señales se invierten.

Reglas de salida

  • Cerrar posición cuando aparece la señal SAR opuesta si SarClose está habilitado.
  • Aplicar objetivos fijos de stop-loss y toma de ganancias.
  • Si Trailing está habilitado, el stop-loss sigue el precio más alto (para largos) o más bajo (para cortos) desde la entrada.

Parámetros

Parámetro Descripción
Step Factor de aceleración inicial para Parabolic SAR.
Maximum Factor de aceleración máximo para Parabolic SAR.
StopLossPoints Distancia del stop-loss en puntos.
TakeProfitPoints Distancia de la toma de ganancias en puntos.
Trailing Habilitar gestión de stop trailing.
TrailPoints Distancia del stop trailing en puntos.
Reverse Invertir dirección de trading.
SarClose Cerrar posición al cambio de SAR.
CandleType Marco temporal de las velas a procesar.
using System;
using System.Linq;
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>
/// Parabolic SAR strategy with optional reversal and trailing stop.
/// Based on conversion of MQL script pSAR_bug_5.
/// </summary>
public class ParabolicSarBug5Strategy : Strategy
{
	private readonly StrategyParam<decimal> _step;
	private readonly StrategyParam<decimal> _maximum;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<bool> _trailing;
	private readonly StrategyParam<decimal> _trailPoints;
	private readonly StrategyParam<bool> _reverse;
	private readonly StrategyParam<bool> _sarClose;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevSar;
	private bool _prevAbove;
	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal _highestPrice;
	private decimal _lowestPrice;

	/// <summary>
	/// Parabolic SAR acceleration factor.
	/// </summary>
	public decimal Step { get => _step.Value; set => _step.Value = value; }

	/// <summary>
	/// Parabolic SAR maximum acceleration factor.
	/// </summary>
	public decimal Maximum { get => _maximum.Value; set => _maximum.Value = value; }

	/// <summary>
	/// Stop loss distance in points.
	/// </summary>
	public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }

	/// <summary>
	/// Take profit distance in points.
	/// </summary>
	public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	/// <summary>
	/// Enable trailing stop.
	/// </summary>
	public bool Trailing { get => _trailing.Value; set => _trailing.Value = value; }

	/// <summary>
	/// Trailing distance in points.
	/// </summary>
	public decimal TrailPoints { get => _trailPoints.Value; set => _trailPoints.Value = value; }

	/// <summary>
	/// Reverse trading direction.
	/// </summary>
	public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }

	/// <summary>
	/// Close position on SAR switch.
	/// </summary>
	public bool SarClose { get => _sarClose.Value; set => _sarClose.Value = value; }

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

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public ParabolicSarBug5Strategy()
	{
		_step = Param(nameof(Step), 0.001m)
			.SetDisplay("Step", "Initial acceleration factor", "Indicators");

		_maximum = Param(nameof(Maximum), 0.2m)
			.SetDisplay("Maximum", "Maximum acceleration factor", "Indicators");

		_stopLossPoints = Param(nameof(StopLossPoints), 90m)
			.SetDisplay("Stop Loss", "Stop loss distance in points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
			.SetDisplay("Take Profit", "Take profit distance in points", "Risk");

		_trailing = Param(nameof(Trailing), false)
			.SetDisplay("Use Trailing", "Enable trailing stop", "Risk");

		_trailPoints = Param(nameof(TrailPoints), 10m)
			.SetDisplay("Trail Points", "Trailing distance in points", "Risk");

		_reverse = Param(nameof(Reverse), false)
			.SetDisplay("Reverse", "Reverse trading direction", "General");

		_sarClose = Param(nameof(SarClose), true)
			.SetDisplay("SAR Close", "Close position on SAR switch", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevSar = 0m;
		_prevAbove = false;
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
		_highestPrice = 0m;
		_lowestPrice = 0m;
	}

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

		var psar = new ParabolicSar
		{
			Acceleration = Step,
			AccelerationMax = Maximum
		};

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

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

	}

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

		var priceAbove = candle.ClosePrice > sar;
		var crossing = _prevSar > 0 && priceAbove != _prevAbove;

		if (crossing)
		{
			var isBuySignal = priceAbove;
			if (Reverse)
				isBuySignal = !isBuySignal;

			if (isBuySignal && Position <= 0)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_takePrice = _entryPrice + TakeProfitPoints;
				_stopPrice = _entryPrice - StopLossPoints;
				_highestPrice = candle.HighPrice;
			}
			else if (!isBuySignal && Position >= 0)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_takePrice = _entryPrice - TakeProfitPoints;
				_stopPrice = _entryPrice + StopLossPoints;
				_lowestPrice = candle.LowPrice;
			}
		}

		if (Position > 0 && _entryPrice is decimal)
		{
			_highestPrice = Math.Max(_highestPrice, candle.HighPrice);
			if (Trailing)
				_stopPrice = Math.Max(_stopPrice ?? 0m, _highestPrice - TrailPoints);

			if (_stopPrice is decimal sl && candle.LowPrice <= sl)
			{
				SellMarket();
				ResetState();
			}
			else if (_takePrice is decimal tp && candle.HighPrice >= tp)
			{
				SellMarket();
				ResetState();
			}
		}
		else if (Position < 0 && _entryPrice is decimal)
		{
			_lowestPrice = Math.Min(_lowestPrice, candle.LowPrice);
			if (Trailing)
				_stopPrice = Math.Min(_stopPrice ?? decimal.MaxValue, _lowestPrice + TrailPoints);

			if (_stopPrice is decimal sl && candle.HighPrice >= sl)
			{
				BuyMarket();
				ResetState();
			}
			else if (_takePrice is decimal tp && candle.LowPrice <= tp)
			{
				BuyMarket();
				ResetState();
			}
		}

		_prevSar = sar;
		_prevAbove = priceAbove;
	}

	private void ResetState()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
		_highestPrice = 0m;
		_lowestPrice = 0m;
	}
}