Ver en GitHub

Estrategia Nevalyashka Flip

Un port directo de StockSharp del experto de MetaTrader "Nevalyashka". La estrategia siempre alterna entre trades largos y cortos: comienza con una orden de venta a mercado, espera a que la posición se cierre por stop loss o take profit, y luego abre inmediatamente una orden a mercado en la dirección opuesta. Las órdenes protectoras se recrean para cada entrada usando los mismos offsets basados en pips que en el código original.

Lógica de la estrategia

  1. Inicialización
    • Detecta el paso de precio del instrumento y los decimales para derivar un tamaño de pip idéntico a la versión MQL (los pares de 3/5 dígitos se multiplican por 10).
    • Multiplica el MinVolume del exchange por el parámetro LotMultiplier para obtener el tamaño de la orden y lo redondea al step de volumen si es necesario.
  2. Manejo de cotizaciones
    • Se suscribe a actualizaciones del libro de órdenes para capturar los últimos precios bid/ask, imitando la llamada RefreshRates() del experto.
  3. Flujo de órdenes
    • Coloca una orden de venta a mercado inicial una vez que las mejores cotizaciones bid/ask están disponibles.
    • Después de que una posición se cierra, invierte el lado (compra después de venta, venta después de compra) y emite una nueva orden a mercado con el mismo volumen.
    • Para cada entrada ejecutada la estrategia coloca órdenes separadas de stop-loss y take-profit usando los parámetros de distancia en pips.

Gestión de riesgos

  • Stop Loss: Opcional. Cuando StopLossPips es mayor que cero, la estrategia envía una orden stop protectora (SellStop para posiciones largas, BuyStop para posiciones cortas) en entrada ± StopLossPips * pip.
  • Take Profit: Opcional. Cuando TakeProfitPips es mayor que cero, la estrategia envía una orden límite protectora (SellLimit para posiciones largas, BuyLimit para posiciones cortas) en entrada ± TakeProfitPips * pip.
  • Ambas órdenes protectoras se cancelan siempre que la posición esté plana para evitar órdenes pendientes antes del siguiente flip.

Parámetros

Nombre Descripción Valor predeterminado
LotMultiplier Multiplicador aplicado al volumen mínimo del instrumento. El resultado se redondea al step de volumen del exchange. 1
StopLossPips Distancia de stop-loss en pips. Establecer en 0 para deshabilitar el stop. 50
TakeProfitPips Distancia de take-profit en pips. Establecer en 0 para deshabilitar el objetivo. 50

Notas operativas

  • El enfoque alterna continuamente la exposición y por lo tanto se adapta a mercados de reversión a la media donde es probable que un movimiento completado se revierta.
  • Funciona con cualquier símbolo que proporcione cotizaciones del libro de órdenes; los cálculos de pips se adaptan automáticamente según la precisión del precio.
  • El manejo del deslizamiento se delega al exchange—las órdenes se envían a mercado sin verificaciones adicionales como en el experto original.
  • La estrategia no incluye filtros de horario de trading, filtros de noticias o trailing stops. Tal lógica puede añadirse extendiendo TryOpenNextPosition o RegisterProtectionOrders.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Alternating long-short strategy that mirrors the original Nevalyashka MQL logic.
/// Opens an initial sell position and flips direction each time the position is closed.
/// </summary>
public class NevalyashkaFlipStrategy : Strategy
{
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private Sides? _currentSide;
	private Sides? _lastCompletedSide;

	/// <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>
	/// Candle type for monitoring price.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="NevalyashkaFlipStrategy"/> class.
	/// </summary>
	public NevalyashkaFlipStrategy()
	{
		_stopLossPoints = Param(nameof(StopLossPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pts)", "Stop loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pts)", "Take profit distance in price steps", "Risk");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_currentSide = null;
		_lastCompletedSide = null;
	}

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

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

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

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

		var step = Security?.PriceStep ?? 1m;
		var stopDistance = StopLossPoints * step;
		var takeDistance = TakeProfitPoints * step;
		var price = candle.ClosePrice;

		// Check SL/TP for current position
		if (Position != 0 && _entryPrice > 0)
		{
			var hit = false;

			if (_currentSide == Sides.Buy)
			{
				if (stopDistance > 0 && candle.LowPrice <= _entryPrice - stopDistance)
					hit = true;
				if (takeDistance > 0 && candle.HighPrice >= _entryPrice + takeDistance)
					hit = true;
			}
			else if (_currentSide == Sides.Sell)
			{
				if (stopDistance > 0 && candle.HighPrice >= _entryPrice + stopDistance)
					hit = true;
				if (takeDistance > 0 && candle.LowPrice <= _entryPrice - takeDistance)
					hit = true;
			}

			if (hit)
			{
				// Close position
				if (Position > 0)
					SellMarket();
				else if (Position < 0)
					BuyMarket();

				_lastCompletedSide = _currentSide;
				_currentSide = null;
				_entryPrice = 0m;
			}
		}

		// If flat, open next position
		if (Position == 0 && _currentSide == null)
		{
			// Alternate direction: start with sell, then flip
			var sideToOpen = _lastCompletedSide switch
			{
				Sides.Buy => Sides.Sell,
				Sides.Sell => Sides.Buy,
				_ => Sides.Sell,
			};

			if (sideToOpen == Sides.Buy)
				BuyMarket();
			else
				SellMarket();

			_currentSide = sideToOpen;
			_entryPrice = price;
		}
	}
}