GitHub で見る

Trickerless RHMP Strategy (StockSharp Port)

This strategy ports the MetaTrader expert advisor Trickerless RHMP to StockSharp's high level API. It keeps the multi-stage entry logic of the original robot – combining Average Directional Index confirmation, smoothed moving average structure and volatility driven position management – while following the framework conventions documented in AGENTS.md.

Trading Logic

  1. Indicators

    • Average True Range (ATR) with configurable period for volatility sizing.
    • Average Directional Index (ADX) with full +DI/-DI components to qualify trend strength.
    • Two smoothed moving averages (SMMA) representing the fast and slow trend filters.
  2. Trend evaluation

    • The slow SMMA slope must be within the MinSlopePipsMaxSlopePips corridor (measured in instrument pips).
    • ADX must exceed AdxThreshold and rise compared with the previous candle.
    • Price has to stay at least TrendSpacePips away from the fast SMMA to avoid congestion.
    • A bullish bias requires the fast SMMA above the slow SMMA, +DI ≥ -DI and a rising fast average. Bearish bias mirrors these checks.
  3. Primary entries

    • When the bullish (or bearish) bias is active, the strategy opens a long (or short) order with volume OrderVolume, respecting MaxNetPositions and waiting at least SleepInterval between entries.
    • If an opposite net position exists it is flattened first to keep hedging disabled.
  4. Spike entries

    • If the current candle range exceeds CandleSpikeMultiplier times the previous range, the strategy can fire an auxiliary position in the direction of the candle body when the ADX components agree. The position uses OrderVolume * SpikeVolumeMultiplier.

Risk Management

  • ATR-based stop-loss, take-profit and optional trailing-stop (StopLossAtrMultiplier, TakeProfitAtrMultiplier, TrailingAtrMultiplier).
  • Session-wide protection: once realized PnL reaches DailyProfitTarget (fraction of starting equity), new entries are blocked.
  • Global emergency switch EmergencyExit closes all positions immediately when toggled.

Parameters

Name Description Default
CandleType Timeframe used for all calculations. 5-minute candles
OrderVolume Base volume for each entry. 0.03
AtrPeriod ATR lookback length. 14
AdxPeriod ADX lookback length. 14
AdxThreshold Minimum ADX value to enable trading. 10
FastMaPeriod Fast smoothed moving average period. 60
SlowMaPeriod Slow smoothed moving average period. 120
MinSlopePips / MaxSlopePips Allowed slope corridor for the slow SMMA. 2 / 9
TrendSpacePips Minimal price distance from the fast SMMA (in pips). 5
CandleSpikeMultiplier How much larger the candle range must be to trigger spike entries. 7
TakeProfitAtrMultiplier ATR multiples for take-profit. 1.0
StopLossAtrMultiplier ATR multiples for stop-loss. 1.5
TrailingAtrMultiplier ATR multiples for trailing-stop (0 disables). 0
MaxNetPositions Maximum number of simultaneous net position units. 1
SleepInterval Minimum time between consecutive entries. 24 minutes
DailyProfitTarget Fraction of initial equity that blocks trading once reached. 0.045
AllowNewEntries Master switch to enable/disable entries. true
SpikeVolumeMultiplier Volume multiplier for spike entries. 1.0
EmergencyExit Closes all positions immediately when true. false

Notes

  • The StockSharp port focuses on the clean high level API instead of the ticket-by-ticket micro-management from MetaTrader. All money management logic is implemented through Volume and ATR based levels.
  • The original EA had several balance and margin checks. These are approximated with the DailyProfitTarget, MaxNetPositions and ATR sizing parameters so that the behaviour stays aligned without direct MT4 account calls.
  • Because the strategy uses smoothed averages, make sure a sufficient warm-up period is present before evaluating trades.
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();
	}
}