Открыть на GitHub

Стратегия Rnd Trade

Обзор

  • Конвертация советника MetaTrader 5 RndTrade.mq5 на высокоуровневый API StockSharp.
  • Через фиксированные интервалы времени закрывает текущую позицию и тут же открывает новую в случайно выбранном направлении.
  • Вместо таймера MetaTrader используется подписка на свечи с соответствующим таймфреймом.

Параметры

Имя Тип Значение по умолчанию Описание
IntervalMinutes int 60 Количество минут между закрытием текущей позиции и открытием новой случайной позиции. Значение должно быть больше нуля.
Volume decimal 1 Размер позиции для рыночного входа. Свойство базового класса Strategy.

Подписки на данные

  • Подписка на свечи с таймфреймом, равным IntervalMinutes (например, 60 → часовые свечи).
  • Обработка выполняется только при закрытии свечи (CandleStates.Finished), что гарантирует единичное срабатывание за интервал.

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

  1. Ожидаем формирования каждой интервалной свечи.
  2. Пропускаем обработку, пока стратегия не сформирована, находится онлайн и ей разрешена торговля.
  3. Закрываем позицию, открытую на предыдущем интервале.
  4. Генерируем случайное число для выбора длинной или короткой позиции.
  5. Отправляем рыночную заявку (BuyMarket или SellMarket) с заданным объёмом в выбранном направлении.

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

  • Используется цепочка SubscribeCandles().Bind(ProcessCandle), что исключает ручную работу со списками или индикаторами.
  • В OnStarted вызывается StartProtection(), чтобы активировать модуль защиты позиций (стопы и тейки не задаются явно).
  • Генератор Random повторяет поведение MathRand() из оригинального MQL-кода.
  • В код добавлены английские комментарии, объясняющие связь между шагами конверсии и функциональностью StockSharp.

Отличия от оригинальной стратегии MQL

  • Таймер OnTimer заменён таймфреймовыми свечами, которые задают частоту исполнения логики.
  • Закрытие позиции выполняется методом ClosePosition() вместо перебора тикетов и вызова PositionClose для каждого из них.
  • Размер позиций берётся из свойства Volume, а не из минимального лота инструмента.
  • Параметры проскальзывания и типа исполнения делегируются брокеру/эмулятору и не настраиваются из стратегии.

Использование

  1. Привяжите стратегию к портфелю и инструменту в среде StockSharp.
  2. Настройте IntervalMinutes и Volume под желаемую частоту торговли и размер позиции.
  3. Запустите стратегию — она автоматически будет закрывать и открывать позиции на каждом интервале.
  4. Python-версия отсутствует: доступна только реализация на C#.
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;
	}
}