Открыть на GitHub

Стратегия At Random Full

Обзор

At Random Full — это аккуратный перенос советника MetaTrader 5 «At random Full». В конверсии сохранены случайный выбор направления, ограничения по количеству усреднений, временные фильтры и логика минимального шага между позициями. В StockSharp стратегия построена на высокоуровневом API: вся обработка происходит в подписке на свечи, а стоп-лосс и тейк-профит оформляются через StartProtection.

Логика торговли

  1. После закрытия каждой свечи проверяется, разрешена ли торговля: учитывается фильтр торговой сессии, состояние портфеля и флаг «Только одна позиция».
  2. Генератор псевдослучайных чисел выбирает направление сделки. Параметр ReverseSignals позволяет инвертировать результат и тем самым повторить поведение режима Reverse в оригинале.
  3. Параметр Mode блокирует сигналы в запрещённом направлении. Чтобы не открывать несколько сделок на одном баре, стратегия запоминает время открытия последней свечи для покупок и продаж.
  4. Управление сеткой полностью совпадает с MQL-версией:
    • MaxPositions ограничивает количество усреднений в одну сторону.
    • MinStepPoints требует минимального ценового шага между позициями; расстояние переводится в цену через шаг котировки.
    • CloseOpposite закрывает встречную позицию перед открытием новой.
  5. Рыночные заявки отправляются методами BuyMarket и SellMarket с нормализованным объёмом OrderVolume.

Управление позицией и рисками

  • StartProtection выставляет стоп-лосс и тейк-профит в соответствии с входными параметрами. Если TrailingStopPoints больше нуля, активируется встроенный трейлинг StockSharp. Значения TrailingActivatePoints и TrailingStepPoints переводятся в денежное выражение и выводятся в журнал, а дальнейшее сопровождение позиции выполняет платформа.
  • Нормализация объёмов учитывает VolumeStep, MinVolume и MaxVolume, как и функции из MQL-советника.
  • Временной фильтр повторяет блок InpTimeControl: при активном параметре сделки разрешены только в пределах [SessionStart, SessionEnd]. Поддерживаются интервалы, переходящие через полночь.

Параметры

Параметр Описание Значение по умолчанию
CandleType Тип свечей, по которым выполняется логика. Таймфрейм 15 минут
OrderVolume Базовый объём рыночной заявки в лотах. 0.1
MaxPositions Максимум усреднений в одном направлении (0 — без лимита). 5
MinStepPoints Минимальный шаг между сделками в пунктах MetaTrader. 150
StopLossPoints Дистанция стоп-лосса в пунктах. 150
TakeProfitPoints Дистанция тейк-профита в пунктах. 460
TrailingActivatePoints Порог прибыли для запуска трейлинга; значение фиксируется в логе. 70
TrailingStopPoints Дистанция трейлинг-стопа, передаваемая в StartProtection. 250
TrailingStepPoints Минимальный шаг смещения трейлинга (журналируется). 50
OnlyOnePosition Запрет на открытие новых сделок при ненулевой позиции. false
CloseOpposite Закрывать встречную позицию перед входом. false
ReverseSignals Инвертировать случайный сигнал. false
UseTimeControl Активировать фильтр по времени. false
SessionStart Начало торговой сессии (включительно). 10:01
SessionEnd Конец торговой сессии (включительно). 15:02
Mode Разрешённое направление (Both, BuyOnly, SellOnly). Both
RandomSeed Фиксированное значение seed для повторяемых тестов (0 — брать Environment.TickCount). 0

Особенности реализации

  • Комментарии в коде написаны на английском, отступы выполнены табуляцией согласно правилам репозитория.
  • Подписка SubscribeCandles().Bind(...) гарантирует, что обработка происходит только после закрытия свечи, как и в MQL.
  • Для контроля шага между входами запоминаются последние цены покупок и продаж, что позволяет корректно обрабатывать усреднения.
  • При запуске стратегия пишет в журнал рассчитанные дистанции стоп-лосса, тейк-профита и трейлинга, что облегчает анализ.

Рекомендации по использованию

  • Стратегия изначально случайна, поэтому её удобно применять для тестирования инфраструктуры, демонстрации риск-менеджмента или отладки платформы.
  • Подбирайте параметры OrderVolume, StopLossPoints и TakeProfitPoints с учётом шага цены и волатильности инструмента.
  • Включайте UseTimeControl, если торговля должна вестись только в определённые часы (например, во время европейской сессии).
  • Параметр RandomSeed полезен при оптимизации: он фиксирует последовательность псевдослучайных чисел и делает результаты повторяемыми.
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();
	}
}