Auf GitHub ansehen

Nevalyashka Flip Strategy

A direct StockSharp port of the MetaTrader expert "Nevalyashka". The strategy always alternates between long and short trades: it starts with a market sell order, waits for the position to close by stop loss or take profit, and then immediately opens a market order in the opposite direction. Protective orders are recreated for every entry using the same pip-based offsets as in the original code.

Strategy Logic

  1. Initialization
    • Detects the instrument price step and decimals to derive a pip size identical to the MQL version (3/5 digit pairs are multiplied by 10).
    • Multiplies the exchange MinVolume by the LotMultiplier parameter to obtain the order size and rounds it to the volume step if necessary.
  2. Quote Handling
    • Subscribes to order book updates to capture the latest best bid/ask prices, mirroring the RefreshRates() call from the expert.
  3. Order Flow
    • Places an initial sell market order once best bid/ask quotes are available.
    • After a position is closed, flips the side (buy after sell, sell after buy) and issues a new market order with the same volume.
    • For every filled entry the strategy places separate stop-loss and take-profit orders using the pip distance parameters.

Risk Management

  • Stop Loss: Optional. When StopLossPips is greater than zero, the strategy submits a protective stop order (SellStop for long positions, BuyStop for short positions) at entry ± StopLossPips * pip.
  • Take Profit: Optional. When TakeProfitPips is greater than zero, the strategy submits a protective limit order (SellLimit for long positions, BuyLimit for short positions) at entry ± TakeProfitPips * pip.
  • Both protective orders are cancelled whenever the position is flat to avoid dangling orders before the next flip.

Parameters

Name Description Default
LotMultiplier Multiplier applied to the instrument minimum volume. The result is rounded to the exchange volume step. 1
StopLossPips Stop-loss distance in pips. Set to 0 to disable the stop. 50
TakeProfitPips Take-profit distance in pips. Set to 0 to disable the target. 50

Operational Notes

  • The approach continuously alternates exposure and therefore suits mean-reverting markets where a completed move is likely to reverse.
  • Works with any symbol that provides top-of-book quotes; pip calculations adapt automatically based on price precision.
  • Slippage handling is delegated to the exchange—orders are sent at market without additional checks just like in the original expert.
  • The strategy does not include trading-hour filters, news filters or trailing stops. Such logic can be added by extending TryOpenNextPosition or 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;
		}
	}
}