Ver en GitHub

Estrategia de Vlado

Estrategia de reversión de momentum basada en el clásico oscilador Williams %R de Larry Williams. El sistema espera a que el oscilador alcance lecturas extremas de sobreventa o sobrecompra y luego revierte la posición en la siguiente barra completada. El port de StockSharp mantiene el carácter discrecional de la implementación original de MetaTrader, exponiendo cada ajuste importante como un parámetro.

Visión general

  • Categoría: Estrategia de oscilador de reversión a la media.
  • Mercado: Cualquier instrumento líquido que ofrezca datos de velas estables (pares de forex, futuros de índices, pares spot de criptomonedas).
  • Marco temporal: Configurable mediante CandleType. Por defecto velas de 1 hora, coincidiendo con el ejemplo de uso original.
  • Dirección: Largo y corto. El motor siempre mantiene como máximo una posición y gira cuando aparece la señal opuesta.
  • Indicador: Williams %R con longitud de lookback y niveles de umbral configurables.

Cómo Funciona

  1. Se suscribe al feed de velas seleccionado y calcula Williams %R en cada vela terminada.
  2. Usa el nivel de sobreventa predeterminado de -75 y el nivel de sobrecompra de -25 (los valores son negativos debido a la escala del oscilador).
  3. Cuando %R cae por debajo del nivel de sobreventa, la estrategia entra o revierte a una posición larga.
  4. Cuando %R sube por encima del nivel de sobrecompra, la estrategia entra o revierte a una posición corta.
  5. Las órdenes se dimensionan con Volume + Math.Abs(Position) de modo que una reversión cierra la posición existente y abre la nueva en una única orden de mercado.
  6. No se utiliza stop-loss ni take-profit explícito. El riesgo se controla mediante los niveles del indicador y el marco temporal elegido.
  7. Cada acción se registra mediante LogInfo, lo que facilita auditar las operaciones en la GUI de StockSharp o en los archivos de log.

Parámetros

  • WilliamsPeriod: Número de velas utilizadas para calcular el oscilador. Valores más altos suavizan la señal, valores más bajos reaccionan más rápido.
  • OverboughtLevel: Umbral que define cuándo se considera que el mercado está sobrecomprado (por defecto -25). Se puede optimizar.
  • OversoldLevel: Umbral que define cuándo se considera que el mercado está sobrevendido (por defecto -75). Se puede optimizar.
  • CandleType: Tipo de vela y marco temporal aplicado a todos los cálculos. Funciona con marcos de tiempo, velas de volumen o barras de rango.
  • Volume (heredado de Strategy): Define el tamaño base de la orden. Ajustar según el tamaño de la cuenta y el apetito de riesgo.

Reglas de Trading

  • Entrada larga: Se activa cuando %R <= OversoldLevel y la posición actual es plana o corta.
  • Entrada corta: Se activa cuando %R >= OverboughtLevel y la posición actual es plana o larga.
  • Salida: Realizada implícitamente por la orden de reversión cuando aparece una señal opuesta.
  • Gestión de posición: Siempre una única posición abierta. El algoritmo no hace piramidación ni escala de salida.

Notas Adicionales

  • Funciona mejor en mercados laterales o de tendencia lenta donde los osciladores pueden oscilar entre extremos.
  • Se recomienda combinar la estrategia con controles de riesgo externos (stops de patrimonio, filtros de sesión) para el trading en vivo.
  • La implementación incluye renderizado de gráficos: el área principal muestra velas y operaciones, mientras que un panel secundario traza Williams %R.
  • Diseñada para mayor investigación: cada parámetro admite optimización dentro de los optimizadores de StockSharp.
using System;
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>
/// Vlado momentum strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class VladoStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="VladoStrategy"/> class.
	/// </summary>
	public VladoStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

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

		if (!_fast.IsFormed || !_slow.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// EMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}