Ver en GitHub

Estrategia de Cruce Ultra WPR

Esta estrategia aplica un oscilador Williams %R suavizado por dos medias móviles. El cruce de las líneas rápida y lenta suavizadas genera señales de trading. Una posición larga se abre cuando la línea rápida sube por encima de la línea lenta, y una posición corta se abre cuando la línea rápida cae por debajo de la línea lenta.

El enfoque busca seguir el momentum emergente mientras limita el riesgo con niveles de take-profit y stop-loss configurables.

Detalles

  • Criterios de entrada:
    • Largo: La línea rápida cruza por encima de la línea lenta
    • Corto: La línea rápida cruza por debajo de la línea lenta
  • Largo/Corto: Ambos
  • Criterios de salida:
    • Largo: Salida cuando la línea rápida cruza por debajo de la línea lenta
    • Corto: Salida cuando la línea rápida cruza por encima de la línea lenta
  • Stops: Sí, take-profit y stop-loss basados en precio
  • Valores predeterminados:
    • CandleType = TimeSpan.FromHours(4)
    • WprPeriod = 13
    • FastLength = 3
    • SlowLength = 53
    • TakeProfit = 0.2m
    • StopLoss = 0.1m
  • Filtros:
    • Categoría: Seguimiento de tendencia
    • Dirección: Ambos
    • Indicadores: Williams %R, Moving Average
    • Stops: Sí
    • Complejidad: Básico
    • Marco temporal: H4
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
using System;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on the crossover of fast and slow smoothed Williams %R lines.
/// </summary>
public class UltraWprCrossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _wprPeriod;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<int> _cooldownBars;

	private readonly SimpleMovingAverage _fastMa = new();
	private readonly SimpleMovingAverage _slowMa = new();
	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _isInitialized;
	private int _barsSinceTrade;

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

	/// <summary>
	/// Williams %R period.
	/// </summary>
	public int WprPeriod
	{
		get => _wprPeriod.Value;
		set => _wprPeriod.Value = value;
	}

	/// <summary>
	/// Fast smoothing length.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow smoothing length.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	/// <summary>
	/// Take profit in price.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Stop loss in price.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Bars to wait after a completed trade.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public UltraWprCrossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");

		_wprPeriod = Param(nameof(WprPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("WPR Period", "Williams %R period", "Indicators")
			.SetOptimize(5, 40, 1);

		_fastLength = Param(nameof(FastLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast Length", "Fast smoothing length", "Indicators")
			.SetOptimize(1, 20, 1);

		_slowLength = Param(nameof(SlowLength), 53)
			.SetGreaterThanZero()
			.SetDisplay("Slow Length", "Slow smoothing length", "Indicators")
			.SetOptimize(10, 100, 5);

		_takeProfit = Param(nameof(TakeProfit), 900m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take profit in price", "Risk")
			.SetOptimize(300m, 1500m, 100m);

		_stopLoss = Param(nameof(StopLoss), 450m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop loss in price", "Risk")
			.SetOptimize(200m, 900m, 50m);

		_cooldownBars = Param(nameof(CooldownBars), 1)
			.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
	}

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

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

		_fastMa.Length = FastLength;
		_slowMa.Length = SlowLength;
		_fastMa.Reset();
		_slowMa.Reset();
		_prevFast = 0m;
		_prevSlow = 0m;
		_isInitialized = false;
		_barsSinceTrade = CooldownBars;
	}

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

		_fastMa.Length = FastLength;
		_slowMa.Length = SlowLength;

		var wpr = new WilliamsR { Length = WprPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(wpr, ProcessCandle).Start();

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

		StartProtection(new Unit(TakeProfit, UnitTypes.Absolute), new Unit(StopLoss, UnitTypes.Absolute));
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var fast = _fastMa.Process(new DecimalIndicatorValue(_fastMa, wprValue, candle.OpenTime) { IsFinal = true }).ToDecimal();
		var slow = _slowMa.Process(new DecimalIndicatorValue(_slowMa, wprValue, candle.OpenTime) { IsFinal = true }).ToDecimal();

		if (!_fastMa.IsFormed || !_slowMa.IsFormed)
			return;

		if (_barsSinceTrade < CooldownBars)
			_barsSinceTrade++;

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

		var crossUp = _prevFast <= _prevSlow && fast > slow && fast < -55m;
		var crossDown = _prevFast >= _prevSlow && fast < slow && fast > -45m;

		if (_barsSinceTrade >= CooldownBars)
		{
			if (crossUp && Position <= 0)
			{
				BuyMarket(Volume + Math.Abs(Position));
				_barsSinceTrade = 0;
			}
			else if (crossDown && Position >= 0)
			{
				SellMarket(Volume + Math.Abs(Position));
				_barsSinceTrade = 0;
			}
		}

		_prevFast = fast;
		_prevSlow = slow;
	}
}