Открыть на GitHub

Стратегия Exp Adaptive Renko MMRec Duplex

Стратегия переносит советник MetaTrader 5 Exp_AdaptiveRenko_MMRec_Duplex.mq5 на высокоуровневый API StockSharp. Две независимые ветки Adaptive Renko — одна для покупок, другая для продаж — отслеживают, как кирпичные каналы меняют статус между поддержкой и сопротивлением. Когда «длинный» канал формирует свежую поддержку, а «короткий» канал теряет сопротивление (или наоборот), стратегия открывает соответствующую рыночную позицию. C# версия полностью воспроизводит модуль перерасчёта лота (MM Recounter), уменьшающий объём после серии убыточных сделок и возвращающий его к нормальному значению после восстановления.

Основные этапы работы

  1. Подписки на данные. Каждая сторона подписывается на свой тип свечей (таймфрейм) и привязывает к нему индикатор волатильности (ATR или стандартное отклонение) через SubscribeCandles().BindEx(...). Индикатор управляет высотой адаптивных кирпичей.
  2. Обработка Adaptive Renko. Вспомогательный класс AdaptiveRenkoProcessor воссоздаёт логику индикатора из MQL и возвращает снимок с текущим трендом, поддержкой и сопротивлением. Сигналы учитываются только на закрытых свечах.
  3. Логика входа. Когда длинный поток фиксирует бычий разворот (поддержка на сигнальной свече), открывается длинная позиция. Для короткой позиции требуется медвежий сигнал из короткого потока.
  4. Логика выхода. Обратные события Renko закрывают активную позицию. Дополнительно контролируются уровни стоп-лосса и тейк-профита в шагах цены.
  5. MMRec управление капиталом. Для каждой стороны ведётся очередь последних результатов сделок. Если число убыточных исходов в заданном окне достигает порога LossTrigger, следующая сделка исполняется с уменьшенным значением манименеджмента (LongSmallMoneyManagement / ShortSmallMoneyManagement). В остальных случаях используется базовое значение (LongMoneyManagement / ShortMoneyManagement). Перечисление MarginModeOption повторяет режимы расчёта из оригинального советника (лоты, доля баланса, доля убытка и т.д.).
  6. Учёт сделок. При каждом выходе вызывается RegisterTradeResult, который пополняет MMRec очереди. Обрезка очередей полностью соответствует функциям BuyTradeMMRecounterS и SellTradeMMRecounterS без обращения к истории терминала.

Группы параметров

Группа Ключевые параметры Описание
Лонг LongCandleType, LongVolatilityMode, LongVolatilityPeriod, LongSensitivity, LongPriceMode, LongMinimumBrickPoints, LongSignalBarOffset Управляют Adaptive Renko потоком, генерирующим длинные сигналы.
Шорт ShortCandleType, ShortVolatilityMode, ShortVolatilityPeriod, ShortSensitivity, ShortPriceMode, ShortMinimumBrickPoints, ShortSignalBarOffset Аналогичные настройки для короткой ветки.
MMRec LongTotalTrigger, LongLossTrigger, LongSmallMoneyManagement, LongMoneyManagement, LongMarginMode, ShortTotalTrigger, ShortLossTrigger, ShortSmallMoneyManagement, ShortMoneyManagement, ShortMarginMode Настройки модуля восстановления объёма. TotalTrigger задаёт длину окна, LossTrigger — количество убытков для переключения на уменьшенный объём.
Риск LongStopLossPoints, LongTakeProfitPoints, ShortStopLossPoints, ShortTakeProfitPoints, LongDeviationSteps, ShortDeviationSteps Параметры защитных уровней и информативного слиппеджа в шагах цены.

Поведенческие особенности

  • Стратегия рассчитана на неттинговый режим: перед открытием новой длинной позиции закрывается существующий шорт и наоборот.
  • Объём рассчитывается функцией CalculateVolume, поддерживающей все режимы оригинального манименеджмента, включая схемы с учётом стоп-лосса.
  • Вся обработка индикаторов выполняется только на завершённых свечах, как и в исходном советнике.
  • В журнал пишется выбранный множитель манименеджмента и ожидаемый слиппедж в шагах цены для удобства анализа.

Состав репозитория

  • CS/ExpAdaptiveRenkoMmrecDuplexStrategy.cs — реализация стратегии с адаптивным Renko и блоком MMRec.
  • README.md — документация на английском языке.
  • README_ru.md — эта страница.
  • README_zh.md — документация на китайском языке.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class ExpAdaptiveRenkoMmrecDuplexStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ExpAdaptiveRenkoMmrecDuplexStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}