View on GitHub

Rnd Trade Strategy

Overview

  • Conversion of the MetaTrader 5 expert advisor RndTrade.mq5 to the StockSharp high-level strategy API.
  • Closes any existing position on a fixed time interval and immediately opens a new market position in a randomly selected direction.
  • Uses time-based candle subscriptions as a deterministic replacement for the original timer callbacks.

Parameters

Name Type Default Description
IntervalMinutes int 60 Number of minutes between the close of the current position and the opening of a new random position. Must be greater than zero.
Volume decimal 1 Position size used for market entries. Derived from the base Strategy class.

Data Subscriptions

  • Subscribes to time frame candles whose length matches IntervalMinutes (e.g., 60 → 60-minute candles).
  • The candle close event (CandleStates.Finished) is used to trigger the logic exactly once per interval.

Trading Logic

  1. Wait for the completion of each interval candle.
  2. Skip processing until the strategy is formed, online, and trading is allowed.
  3. Close any open position created during the previous interval.
  4. Generate a random value to decide between a long or short entry.
  5. Submit a market order (BuyMarket or SellMarket) with the configured volume in the selected direction.

Implementation Notes

  • Relies on SubscribeCandles().Bind(ProcessCandle) to avoid manual polling of indicator values or collections.
  • Calls StartProtection() during startup so that the built-in risk module is active, even though no explicit stop loss or take profit is configured.
  • Uses Random from the standard library to mirror the MathRand() behavior found in the original MQL strategy.
  • The code contains English comments that explain how each conversion step maps to StockSharp features.

Differences from the Original MQL Strategy

  • Timer events (OnTimer) are emulated through candle subscriptions instead of MetaTrader's timer API.
  • Position closing is handled with ClosePosition() rather than iterating over position lists and calling PositionClose for each ticket.
  • The StockSharp version relies on the built-in Volume property for position sizing instead of the symbol's minimum lot query.
  • Order filling rules and slippage settings are managed by the connected broker or simulator, so they are not explicitly configured in the strategy.

Usage

  1. Attach the strategy to a portfolio and security within the StockSharp environment.
  2. Configure IntervalMinutes and Volume according to the desired trading frequency and size.
  3. Start the strategy. It will automatically flatten and reopen positions at each interval without any additional input.
  4. No Python implementation is provided at this time; only the C# version is available.
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>
/// Random direction trading strategy converted from the MetaTrader RndTrade EA.
/// The strategy closes any open position on each interval and immediately opens a new random position.
/// </summary>
public class RndTradeStrategy : Strategy
{
	private readonly StrategyParam<int> _intervalMinutes;

	/// <summary>
	/// Interval in minutes between closing the current position and opening a new random one.
	/// </summary>
	public int IntervalMinutes
	{
		get => _intervalMinutes.Value;
		set => _intervalMinutes.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="RndTradeStrategy"/> class.
	/// </summary>
	public RndTradeStrategy()
	{
		_intervalMinutes = Param(nameof(IntervalMinutes), 360)
			.SetGreaterThanZero()
			.SetDisplay("Interval Minutes", "Minutes between closing and opening positions", "General");

		Volume = 1;
	}

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

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

		// Use time-based candles as a deterministic timer replacement.
		var timeFrame = TimeSpan.FromMinutes(IntervalMinutes).TimeFrame();

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

	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Process only final candles to execute logic exactly once per interval.
		if (candle.State != CandleStates.Finished)
			return;

		// Always close the existing position before selecting a new random direction.
		if (Position > 0)
			SellMarket(Position);
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));

		// Derive a deterministic pseudo-random direction from the candle data.
		if (ShouldBuy(candle))
		{
			// Enter long after flattening the previous position.
			if (Position <= 0)
				BuyMarket(Volume);
		}
		else
		{
			// Enter short after flattening the previous position.
			if (Position >= 0)
				SellMarket(Volume);
		}
	}

	private static bool ShouldBuy(ICandleMessage candle)
	{
		var hash = HashCode.Combine(candle.OpenTime.Ticks, candle.ClosePrice, candle.TotalVolume);
		return (hash & 1) == 0;
	}
}