View on GitHub

Money Rain Recovery Strategy

Overview

  • Conversion of the MetaTrader 4 expert advisor MoneyRain.mq4 to the StockSharp high-level API.
  • Trades on the close of finished candles using a DeMarker oscillator filter.
  • Keeps the original fixed stop-loss / take-profit exits and the volume-recovery block that increases the next order size after a loss sequence.

Trading Logic

  1. Subscribe to the configured CandleType (default: 1-hour candles) and compute DeMarker with period DeMarkerPeriod.
  2. When no position is active and no order is pending:
    • Buy if the current DeMarker value is above Threshold.
    • Sell otherwise.
    • The order size is either the base volume or the recovery volume calculated from previous losses.
  3. While a position is open the strategy watches each completed candle:
    • Longs close when the candle low touches the stop level (StopLossPoints below the entry) or the candle high reaches the target (TakeProfitPoints above the entry).
    • Shorts mirror the same rules with inverted levels.
  4. After every exit the money-management block updates the consecutive loss counters and prepares the next order size. When the losing streak reaches LossesLimit the strategy stops opening new positions and logs a warning.

Money Management

  • BaseVolume is normalized to the exchange rules (Security.VolumeStep, Security.MinVolume, Security.MaxVolume). If the normalized size drops below the minimum lot, the entry is skipped.
  • After each losing trade the strategy stores the volume used (scaled by the base lot) and resets the consecutive-profit counter. The very next profitable trade uses the original MoneyRain formula baseLot × lossesVolume × (StopLoss + spread) / (TakeProfit − spread) to recover losses. Subsequent wins revert to the base volume, and the loss accumulator is cleared after two or more consecutive profits.
  • If FastOptimization is enabled the recovery block is bypassed and every entry uses the normalized base volume.
  • Spread for the recovery formula is estimated from the latest level-1 best bid/ask. If quotes are unavailable the spread falls back to zero.

Parameters

Parameter Description Default Notes
DeMarkerPeriod Length of the DeMarker oscillator. 10 Must be greater than zero.
TakeProfitPoints Distance to the take-profit in price steps. 50 Converted by multiplying with Security.PriceStep.
StopLossPoints Distance to the stop-loss in price steps. 50 Must stay positive so the recovery formula remains valid.
BaseVolume Baseline order volume. 1 Normalized to instrument limits before submission.
LossesLimit Maximum consecutive losing trades allowed. 1 000 000 When reached, entries are paused until the strategy is reset.
FastOptimization Disable recovery sizing during optimizer runs. true Keeps the model lightweight for bulk tests.
Threshold DeMarker threshold separating buy and sell signals. 0.5 Matching the MT4 constant from the source code.
CandleType Candle data series used for signals. 1h Change for other timeframes or custom aggregations.

Usage Notes

  • Set correct Security.PriceStep, Security.VolumeStep, Security.MinVolume and Security.MaxVolume values so price/volume conversions remain valid.
  • Positive StopLossPoints and TakeProfitPoints are required. Leaving them at zero prevents exits, diverging from the original EA.
  • The strategy waits for actual fills before updating its internal state, so it handles partial fills by tracking the weighted exit price.
  • When the loss limit triggers, the next profitable trade is not taken—restart or reset the strategy to resume trading.
using System;

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

namespace StockSharp.Samples.Strategies;

public class MoneyRainRecoveryStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MoneyRainRecoveryStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20).SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 80).SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
		_momentumPeriod = Param(nameof(MomentumPeriod), 12).SetDisplay("Momentum", "Momentum period", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 100).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = default;
		_prevSlow = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevFast = fast;
		_prevSlow = slow;
	}
}