Открыть на GitHub

Стратегия Trickerless RHMP (порт StockSharp)

Стратегия переносит советник MetaTrader Trickerless RHMP на высокоуровневый API StockSharp. Сохранена многоступенчатая логика входа из оригинала: подтверждение тренда индикатором ADX, фильтрация направления сглаженными скользящими средними и управление рискoм через ATR. Реализация полностью соответствует требованиям AGENTS.md.

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

  1. Используемые индикаторы

    • Average True Range (ATR) с настраиваемым периодом для расчёта защитных расстояний.
    • Average Directional Index (ADX) с компонентами +DI/-DI для оценки силы тренда.
    • Две сглаженные скользящие средние (SMMA), выступающие быстрым и медленным фильтрами.
  2. Оценка тренда

    • Наклон медленной SMMA должен находиться внутри коридора MinSlopePipsMaxSlopePips (в пунктах инструмента).
    • Значение ADX обязано превышать AdxThreshold и расти относительно предыдущей свечи.
    • Цена должна располагаться минимум на TrendSpacePips пунктов от быстрой SMMA, чтобы избегать флетовых фаз.
    • Для длинного приоритета быстрая SMMA выше медленной, +DI ≥ -DI и быстрая средняя растёт. Условия для короткого приоритета зеркальные.
  3. Основные входы

    • При активном бычьем/медвежьем сигнале открывается позиция объёмом OrderVolume. Учитавается ограничение MaxNetPositions и выдерживается пауза SleepInterval между входами.
    • Если открыта противоположная позиция, она закрывается перед сменой направления (хеджирование отключено).
  4. Дополнительные входы по «шипам»

    • Если диапазон текущей свечи превышает диапазон предыдущей более чем в CandleSpikeMultiplier раз и направление подтверждено компонентами ADX, стратегия может добавить позицию объёмом OrderVolume * SpikeVolumeMultiplier по направлению тела свечи.

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

  • Стоп-лосс, тейк-профит и, при необходимости, трейлинг-стоп рассчитываются через ATR (StopLossAtrMultiplier, TakeProfitAtrMultiplier, TrailingAtrMultiplier).
  • Сессионный контроль: после достижения DailyProfitTarget (доля стартового капитала) новые сделки блокируются.
  • Флаг EmergencyExit мгновенно закрывает все позиции при срабатывании.

Параметры

Параметр Описание Значение по умолчанию
CandleType Таймфрейм анализа. 5-минутные свечи
OrderVolume Базовый объём сделки. 0.03
AtrPeriod Период ATR. 14
AdxPeriod Период ADX. 14
AdxThreshold Минимальное значение ADX для торговли. 10
FastMaPeriod Период быстрой SMMA. 60
SlowMaPeriod Период медленной SMMA. 120
MinSlopePips / MaxSlopePips Допустимый наклон медленной SMMA. 2 / 9
TrendSpacePips Минимальная дистанция цены от быстрой SMMA (в пунктах). 5
CandleSpikeMultiplier Множитель диапазона для активации «шиповых» входов. 7
TakeProfitAtrMultiplier ATR-множитель тейк-профита. 1.0
StopLossAtrMultiplier ATR-множитель стоп-лосса. 1.5
TrailingAtrMultiplier ATR-множитель трейлинг-стопа (0 — отключено). 0
MaxNetPositions Максимальное число нетто-позиций. 1
SleepInterval Минимальная пауза между входами. 24 минуты
DailyProfitTarget Доля стартового капитала, после которой торговля останавливается. 0.045
AllowNewEntries Разрешение на открытие новых позиций. true
SpikeVolumeMultiplier Коэффициент объёма для «шиповых» сделок. 1.0
EmergencyExit При true закрывает все позиции и завершает торговлю. false

Примечания

  • Версия для StockSharp использует высокоуровневый API и не повторяет построчное управление ордерами MetaTrader. Масштабирование и защита выполняются через базовый объём и ATR.
  • Проверки баланса и маржи из оригинального советника заменены комбинацией DailyProfitTarget, MaxNetPositions и ATR-защит, что сохраняет идеологию управления риском без прямых MT4-вызовов.
  • Сглаженные скользящие требуют тёплого периода: перед оценкой результатов убедитесь, что в тесте/онлайне достаточно исторических данных.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Trickerless RHMP" MetaTrader expert.
/// Uses fast/slow EMA crossover for entries.
/// Fast EMA above slow = buy, below = sell.
/// </summary>
public class TrickerlessRhmpStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;

	private ExponentialMovingAverage _fastMa;
	private ExponentialMovingAverage _slowMa;
	private decimal? _prevFast;
	private decimal? _prevSlow;

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

	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

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

		_fastMaPeriod = Param(nameof(FastMaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA", "Fast EMA period", "Indicators");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA", "Slow SMA period (computed manually)", "Indicators");
	}

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

		_prevFast = null;
		_prevSlow = null;

		_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_fastMa, _slowMa, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastMa);
			DrawIndicator(area, _slowMa);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_fastMa.IsFormed || !_slowMa.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		if (_prevFast is null || _prevSlow is null)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

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

		var crossUp = _prevFast.Value <= _prevSlow.Value && fastValue > slowValue;
		var crossDown = _prevFast.Value >= _prevSlow.Value && fastValue < slowValue;

		if (crossUp)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_fastMa = null;
		_slowMa = null;
		_prevFast = null;
		_prevSlow = null;

		base.OnReseted();
	}
}