Auf GitHub ansehen

Nevalyashka Direction Strategy

Overview

The Nevalyashka strategy is a C# port of the original MetaTrader 4 expert advisor Nevalyashka.mq4. The EA repeatedly flips its trading direction: it opens a single market order, waits until the position is closed by a stop-loss, take-profit or manual action, and instantly re-enters in the opposite direction with the same volume. The StockSharp implementation reproduces this behaviour while exposing all critical settings as strategy parameters.

Trading Logic

  1. Initialization

    • When the strategy starts it calculates the pip size from the instrument's PriceStep. For 3- and 5-digit Forex symbols the step is multiplied by 10 to match the MetaTrader point definition.
    • StartProtection is configured with stop-loss and take-profit distances converted from pips into price points. Protective orders are attached to every subsequent position.
    • An initial market order is sent in the direction defined by InitialDirection (default: short). The requested volume is rounded to the nearest valid lot using the security's VolumeStep, MinVolume, and MaxVolume values.
  2. Position tracking

    • OnPositionChanged captures every change in net exposure. When a new position opens the strategy stores the filled volume and remembers the trade side.
    • Once the position fully returns to flat the strategy immediately issues a new market order in the opposite direction, reusing the previously stored lot size.
  3. Failure handling

    • If the broker rejects an order registration the pending direction flag is cleared, allowing the platform operator to retry manually or adjust the parameters without stale internal state.

The resulting workflow mirrors the "roly-poly" idea of the original script: the bot is always in the market, alternating between long and short positions with fixed exits.

Parameters

Name Description Default Notes
StopLossPips Distance of the protective stop in pips. 50 Converted to price points via the pip size calculation; set to 0 to disable the stop.
TakeProfitPips Distance of the protective take-profit in pips. 50 Converted in the same way as the stop-loss; set to 0 to disable the take-profit.
Volume Lot size used for the very first trade. 1 After the first fill the strategy reuses the actually executed volume for all future entries.
InitialDirection Side of the initial market order. Sell Choose between Buy and Sell to match the desired starting bias.

Implementation Notes

  • No candle or indicator subscriptions are required; the strategy reacts solely to position events and order confirmations.
  • IsFormedAndOnlineAndAllowTrading() is consulted before every entry to ensure the connector is ready to trade.
  • Volume rounding uses MidpointRounding.AwayFromZero so that fractional lots always snap to a tradable level instead of zero.
  • The pip conversion logic relies on instrument metadata rather than hard-coded assumptions, which makes the port work across FX, CFD, or futures symbols with different price formats.

Differences vs. the MQL Version

  • The StockSharp variant exposes the starting direction as a parameter instead of forcing the initial short from the MT4 script.
  • Stop-loss and take-profit orders are managed through StartProtection, which produces native protective orders compatible with any StockSharp connector.
  • Order rejections clear the internal pending state to avoid repeated submission of invalid requests.

These adjustments keep the spirit of the original advisor while integrating seamlessly with the StockSharp high-level API.

using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Nevalyashka Direction: RSI reversal with ATR stops.
/// </summary>
public class NevalyashkaDirectionStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevRsi;
	private decimal _entryPrice;

	public NevalyashkaDirectionStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_prevRsi = 0;
		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevRsi = 0;
		_entryPrice = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (_prevRsi == 0 || atrVal <= 0)
		{
			_prevRsi = rsiVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 70)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 30)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (rsiVal < 35 && _prevRsi >= 35)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (rsiVal > 65 && _prevRsi <= 65)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevRsi = rsiVal;
	}
}