Auf GitHub ansehen

New Random Strategy

Overview

The New Random Strategy emulates the original MetaTrader "New Random" expert by offering three distinct entry selection modes. It opens only a single position at a time and waits until the current position is closed before generating the next order direction. Market entries are triggered on top-of-book updates (Level 1 data) using the best bid/ask prices as execution anchors. The strategy automatically calculates stop-loss and take-profit offsets in pips, adapting to 3 and 5 digit forex quotes the same way as the MQL version.

Entry Modes

  1. Generator – the next direction is chosen by a pseudo random generator seeded on strategy start. Each opportunity is an independent coin toss between buying and selling.
  2. Buy-Sell-Buy – positions alternate strictly between buy and sell. The very first order is a buy, followed by a sell, and so on.
  3. Sell-Buy-Sell – positions alternate strictly starting from a sell, followed by a buy, and repeating.

Parameters

  • Random Mode (Mode) – selects one of the three entry mechanisms described above. Defaults to the random generator.
  • Minimal Lot Count (MinimalLotCount) – multiplies the instrument's minimum tradable volume. A value of 1 means the strategy trades exactly Security.VolumeMin, while higher values scale the order size by whole multiples.
  • Stop Loss (pips) (StopLossPips) – distance in pips below/above the filled price where the strategy will exit the position. Set to 0 to disable the stop-loss.
  • Take Profit (pips) (TakeProfitPips) – distance in pips where the strategy will realize profits. Set to 0 to disable the take-profit.

Trading Logic

  1. Subscribes to Level 1 data for the configured security and constantly stores the latest bid, ask, and last trade prices.
  2. When no position is open and no order is pending, the strategy evaluates the selected mode to determine the next direction.
  3. Orders are placed at market using the most recent best bid/ask snapshot. The stop-loss and take-profit targets are calculated immediately from the entry price using the pip distance parameters.
  4. Only one position may exist at a time. Subsequent entries are suppressed until the active position is completely closed.

Position Management

  • Long positions exit early when the current price falls to or below the stop-loss, or rises to or above the take-profit.
  • Short positions exit when the current price rises to or above the stop-loss, or falls to or below the take-profit.
  • Price comparisons always use the freshest Level 1 information: the last trade price if available, otherwise the best bid/ask for the respective side.
  • After closing a trade the strategy resets the internal state, optionally alternates the next direction (for sequence modes), and waits for the next quote update before re-entering.

Notes

  • The strategy never pyramids positions and keeps the behavior deterministic for the sequence-based modes.
  • Random mode is seeded with the current tick count so each run produces a unique order stream.
  • All internal comments and logs are in English to align with the repository guidelines.
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>
/// Randomized entry strategy that mimics the MetaTrader "New Random" expert.
/// </summary>
public class NewRandomStrategy : Strategy
{
	/// <summary>
	/// Available direction selection modes.
	/// </summary>
	public enum RandomModes
	{
		/// <summary>Use a pseudo random generator for every entry decision.</summary>
		Generator,
		/// <summary>Alternate buy-sell-buy.</summary>
		BuySellBuy,
		/// <summary>Alternate sell-buy-sell.</summary>
		SellBuySell
	}

	private readonly StrategyParam<RandomModes> _mode;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private Sides? _sequenceLastSide;
	private Sides? _positionSide;
	private decimal _entryPrice;
	private int _candleCount;

	/// <summary>Direction selection mode.</summary>
	public RandomModes Mode
	{
		get => _mode.Value;
		set => _mode.Value = value;
	}

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

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

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

	public NewRandomStrategy()
	{
		_mode = Param(nameof(Mode), RandomModes.Generator)
			.SetDisplay("Random Mode", "Direction selection mode", "General");

		_stopLossPoints = Param(nameof(StopLossPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pts)", "Stop loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pts)", "Take profit 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();
		_sequenceLastSide = null;
		_positionSide = null;
		_entryPrice = 0m;
		_candleCount = 0;
	}

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

		_sequenceLastSide = Mode switch
		{
			RandomModes.BuySellBuy => Sides.Sell,
			RandomModes.SellBuySell => Sides.Buy,
			_ => null
		};

		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;

		_candleCount++;
		if (_candleCount < 3)
			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 (_positionSide == Sides.Buy)
			{
				if (stopDistance > 0 && candle.LowPrice <= _entryPrice - stopDistance)
					hit = true;
				if (takeDistance > 0 && candle.HighPrice >= _entryPrice + takeDistance)
					hit = true;
			}
			else if (_positionSide == Sides.Sell)
			{
				if (stopDistance > 0 && candle.HighPrice >= _entryPrice + stopDistance)
					hit = true;
				if (takeDistance > 0 && candle.LowPrice <= _entryPrice - takeDistance)
					hit = true;
			}

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

				_positionSide = null;
				_entryPrice = 0m;
			}
		}

		// If flat, open new random position
		if (Position == 0 && _positionSide == null)
		{
			var side = DetermineNextSide();

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

			_positionSide = side;
			_entryPrice = price;

			_sequenceLastSide = side;
		}
	}

	private Sides DetermineNextSide()
	{
		// All modes use deterministic alternating logic
		return _sequenceLastSide == Sides.Buy ? Sides.Sell : Sides.Buy;
	}
}