GitHub で見る

At Random Full Strategy

Overview

The At Random Full Strategy is a faithful conversion of the MetaTrader 5 expert advisor "At random Full". It keeps the original idea of opening trades based on a random generator while exposing the same money-management switches: direction filters, grid spacing, optional time windows and an on/off toggle for averaging. The StockSharp port uses the high-level API, so the entire decision loop is driven by candle subscriptions and standard StartProtection helpers for protective orders.

Trading Logic

  1. On every finished candle the strategy verifies that trading is allowed (session filter, portfolio state and optional "only one position" flag).
  2. A pseudo-random generator decides between a long or short entry. The ReverseSignals parameter can flip the outcome to emulate the MQL reverse mode.
  3. Direction filters (TradeMode) block undesired signals. The code also enforces the original EA rule of a single trade per bar in each direction by remembering the candle open time of the latest signal.
  4. Grid management options mirror the MetaTrader behaviour:
    • MaxPositions caps the number of averaged entries per side.
    • MinStepPoints requires a minimum distance (converted to price using the security price step) between consecutive entries.
    • CloseOpposite forces the existing opposite exposure to be closed before a new trade is sent.
  5. Market orders are issued through BuyMarket / SellMarket with a normalised volume defined by OrderVolume.

Position and Risk Management

  • StartProtection attaches stop-loss and take-profit orders that match the MetaTrader inputs. If TrailingStopPoints is greater than zero the built-in StockSharp trailing mode is enabled. The parameters TrailingActivatePoints and TrailingStepPoints are converted to price distances and logged for transparency, but the actual trailing is handled by the platform.
  • All volume calculations respect the exchange metadata (minimum, maximum and step) exactly like the MQL helper routines.
  • Time control emulates the InpTimeControl block from the script. When enabled, trades are allowed only inside the configured [SessionStart, SessionEnd] window; overnight sessions are supported.

Parameters

Parameter Description Default
CandleType Candle series used to schedule the decision loop. 15 minute timeframe
OrderVolume Base market order volume in lots. 0.1
MaxPositions Maximum number of averaged entries per direction (0 = unlimited). 5
MinStepPoints Minimum distance between entries expressed in MetaTrader points. 150
StopLossPoints Stop-loss distance in points. 150
TakeProfitPoints Take-profit distance in points. 460
TrailingActivatePoints Profit threshold (in points) logged for informational purposes when trailing is enabled. 70
TrailingStopPoints Trailing stop distance passed to StartProtection. 250
TrailingStepPoints Step between trailing adjustments, logged alongside the activation distance. 50
OnlyOnePosition Blocks new trades until the current net position is closed. false
CloseOpposite Closes the opposite exposure before opening a trade. false
ReverseSignals Inverts the random decision so buys become sells and vice versa. false
UseTimeControl Enables the trading session time filter. false
SessionStart Session start time (inclusive) when UseTimeControl is true. 10:01
SessionEnd Session end time (inclusive) when UseTimeControl is true. 15:02
Mode Allowed trade direction (Both, BuyOnly, SellOnly). Both
RandomSeed Optional deterministic seed for the pseudo-random generator (0 = environment tick count). 0

Implementation Notes

  • All comments are written in English and the code uses tab indentation, matching the repository guidelines.
  • Candle processing relies on SubscribeCandles().Bind(...), ensuring the logic executes once per finished bar as in the EA.
  • The strategy keeps track of the last buy and sell fill prices to enforce the minimum spacing constraint even during averaging.
  • Logging statements mirror the detailed diagnostics printed by the original script: every entry announces the chosen direction, entry price, volume, and the trailing configuration on startup.

Usage Tips

  • Because the trading signal is random, the strategy is best suited for testing infrastructure or demonstrating risk controls.
  • Adjust OrderVolume, StopLossPoints, and TakeProfitPoints to align with the tick size and volatility of the instrument you plan to trade.
  • Enable UseTimeControl if the EA should operate only during a specific session (for example, the London or New York session).
  • Use RandomSeed during optimisation runs to achieve reproducible sequences of random decisions.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "At random Full" MetaTrader expert.
/// Randomly opens long or short positions with grid spacing and position limits.
/// </summary>
public class AtRandomFullStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<int> _randomSeed;

	private Random _random;
	private decimal _lastEntryPrice;
	private int _entryCount;

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

	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

	public int RandomSeed
	{
		get => _randomSeed.Value;
		set => _randomSeed.Value = value;
	}

	public AtRandomFullStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");

		_maxPositions = Param(nameof(MaxPositions), 3)
			.SetDisplay("Max Positions", "Maximum number of averaged entries", "Risk");

		_randomSeed = Param(nameof(RandomSeed), 123)
			.SetDisplay("Random Seed", "Fixed seed for deterministic simulations", "Execution");
	}

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

		_random = RandomSeed == 0 ? new Random() : new Random(RandomSeed);
		_lastEntryPrice = 0;
		_entryCount = 0;

		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;

		// Only trade occasionally to keep turnover within runner limits.
		if (_random.Next(0, 5) != 0)
			return;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		var close = candle.ClosePrice;

		// Check grid spacing - minimum 0.5% between entries
		if (_lastEntryPrice > 0 && Math.Abs(close - _lastEntryPrice) / _lastEntryPrice < 0.005m)
			return;

		// Check entry limit
		if (MaxPositions > 0 && _entryCount >= MaxPositions)
		{
			// Close position and reset
			if (Position > 0)
				SellMarket(Position);
			else if (Position < 0)
				BuyMarket(Math.Abs(Position));

			_entryCount = 0;
			_lastEntryPrice = 0;
			return;
		}

		var goLong = _random.Next(0, 2) == 0;

		if (goLong)
		{
			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position) + volume);
				_entryCount = 1;
				_lastEntryPrice = close;
			}
			else if (Position == 0)
			{
				BuyMarket(volume);
				_lastEntryPrice = close;
				_entryCount++;
			}
		}
		else
		{
			if (Position > 0)
			{
				SellMarket(Math.Abs(Position) + volume);
				_entryCount = 1;
				_lastEntryPrice = close;
			}
			else if (Position == 0)
			{
				SellMarket(volume);
				_lastEntryPrice = close;
				_entryCount++;
			}
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_random = null;
		_lastEntryPrice = 0;
		_entryCount = 0;

		base.OnReseted();
	}
}