Открыть на GitHub

Стратегия Pedro Mod

Обзор

Стратегия представляет собой портирование MetaTrader 4 советника Pedroxxmod в экосистему StockSharp. Исходный робот ждёт, когда цена отклонится от опорного уровня на заданное число пунктов, и открывает встречную позицию. При возврате цены на указанную дистанцию он добавляет новые сделки в ту же сторону. В StockSharp сохранена эта логика, а параметры вынесены в типизированный интерфейс Strategy для удобства оптимизации и бэктестинга.

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

  1. Подписаться на лучшую котировку Level1 и кэшировать текущие значения бид/аск.
  2. При отсутствии позиций сохранить текущий аск в качестве опорной цены. Торговля разрешена только между StartHour и EndHour, а также начиная с календарного года StartYear.
  3. Если аск поднимается выше опорного уровня на Gap пунктов MetaTrader, отправляется рыночная продажа. Если опускается ниже на Gap пунктов — отправляется рыночная покупка. Для сделок автоматически выставляются стоп и тейк через SetStopLoss и SetTakeProfit с теми же расстояниями, что и в советнике.
  4. После выбора направления стратегия ведёт FIFO-список виртуальных сделок, имитируя хеджирующие счета MT4. Пока число сделок в корзине меньше MaxTrades, добавление в позицию происходит при возврате аска к последней цене входа внутри диапазона ReEntryGap пунктов.
  5. Управление объёмом может опираться на фиксированное значение Lots либо на правило money management floor(Equity / 20000) с ограничением сверху MaxLots. Перед регистрацией ордеров объём нормализуется по VolumeStep, MinVolume и MaxVolume инструмента.
  6. Во время простоя (вне торгового окна) опорные уровни сбрасываются, чтобы при следующем сеансе не возникли ложные сигналы.

Параметры

Имя Описание
Lots Фиксированный объём, если отключено динамическое управление.
StopLoss Расстояние до защитного стопа в пунктах MetaTrader (0 отключает стоп).
TakeProfit Расстояние до тейк-профита в пунктах (0 отключает тейк).
Gap Отклонение от опорной цены, необходимое для открытия первой сделки.
MaxTrades Максимальное число одновременно открытых сделок (размер корзины).
ReEntryGap Возврат цены в пунктах, при котором добавляются новые сделки в корзину.
MoneyManagement Включает формулу floor(Equity / 20000) для расчёта объёма.
MaxLots Верхний предел для динамически рассчитанного объёма.
StartHour / EndHour Временные границы торговли по серверному времени (включительно).
StartYear Минимальный год, начиная с которого разрешено торговать.

Примечания

  • Стратегия использует только поток Level1 и не подписывается на свечи, поэтому реагирует на котировки так же оперативно, как обработчик start() в MT4.
  • Стопы и тейки вычисляются стандартными методами Strategy, что требует корректных торговых параметров инструмента (PriceStep, StepPrice, VolumeStep).
  • FIFO-учёт виртуальных сделок позволяет воспроизвести поведение хеджирующих счетов, несмотря на то, что StockSharp хранит агрегированную позицию. Частичное закрытие и срабатывание защитных ордеров обрабатывается в OnPositionChanged.
  • Python-версия намеренно не создаётся во исполнение требований репозитория.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// Pedro Mod mean reversion strategy using Bollinger Bands.
/// Buy when price touches the lower band, sell when price touches the upper band.
/// </summary>
public class PedroModStrategy : Strategy
{
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerWidth;
	private readonly StrategyParam<DataType> _candleType;

	public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
	public decimal BollingerWidth { get => _bollingerWidth.Value; set => _bollingerWidth.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public PedroModStrategy()
	{
		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators");

		_bollingerWidth = Param(nameof(BollingerWidth), 1.5m)
			.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators");

		_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();
	}

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

		var bb = new BollingerBands { Length = BollingerPeriod, Width = BollingerWidth };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bb, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!bbValue.IsFinal)
			return;

		var bb = (BollingerBandsValue)bbValue;
		if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower || bb.MovingAverage is not decimal middle)
			return;

		// Buy when price touches lower band (mean reversion)
		if (Position <= 0 && candle.ClosePrice <= lower)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Sell when price touches upper band
		else if (Position >= 0 && candle.ClosePrice >= upper)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
		// Exit at middle band
		else if (Position > 0 && candle.ClosePrice >= middle)
		{
			SellMarket();
		}
		else if (Position < 0 && candle.ClosePrice <= middle)
		{
			BuyMarket();
		}
	}
}