View on GitHub

Reduce Risks Strategy

Overview

The Reduce Risks Strategy is a multi-timeframe trend-following system converted from the MetaTrader expert advisor "Reduce_risks.mq5". It analyses one-minute candles to trigger entries while filtering the market regime with 15-minute and 1-hour averages. The original algorithm was designed for highly liquid forex majors (EURUSD, USDCHF, USDJPY) and focuses on entering trends only when volatility is muted and structure confirms continuation.

Market and Timeframes

  • Primary timeframe: 1-minute candles for signal generation.
  • Confirmation timeframe: 15-minute candles for momentum validation and wave positioning.
  • Trend filter: 1-hour candles to ensure trading in the broader trend direction.
  • Recommended instruments: EURUSD, USDCHF, USDJPY or instruments with similar pip structure (4 or 5 decimal pricing).

Indicators and Data

  • Four simple moving averages (SMA) on M1: periods 5, 8, 13 and 60 calculated on typical price.
  • Three SMAs on M15: periods 4, 5 and 8 calculated on typical price.
  • One SMA on H1: period 24 calculated on typical price.
  • Candlestick statistics (body size, range, shadows) for both M1 and M15.
  • Internal counters track the highest or lowest price since entry to emulate the MQL trailing logic.

Entry Rules

Long setup

  1. Recent M1 and M15 candles must display low volatility: three previous bars on each timeframe have ranges below 20 and 30 pips respectively, and the 15-minute channel width is capped at 30 pips.
  2. The latest completed M1 candle is more active than its predecessor but not three times larger, and the current price breaks both the recent M1 and M15 highs (local resistance cleared).
  3. SMA hierarchy points upward: SMA5 > SMA8 > SMA13 and SMA60 rising; the closing price sits above all four averages.
  4. SMA4 on M15 is rising and positioned above SMA8, while the closing price is above both the M15 and H1 averages.
  5. Wave confirmation: SMA8 on M1 crossed inside any of the previous three candles, and SMA5 on M15 lies within the prior M15 candle range.
  6. Candle structure filters: previous M1 and M15 candles have bullish bodies exceeding half of their ranges, maintain higher highs, show acceptable pullbacks (<25% of the previous candle range), and contain intrabar shadows (no marubozu).
  7. All conditions above must be satisfied simultaneously with no open position before issuing a market buy order.

Short setup

  1. The same volatility filters apply, but the breakout must occur below recent lows (support violation).
  2. SMA hierarchy flips: SMA5 < SMA8 < SMA13 with SMA60 falling; the closing price sits below all four averages.
  3. SMA4 on M15 declines and is below SMA8; the closing price is below both the M15 and H1 averages.
  4. Wave validation: SMA8 on M1 lies within any of the previous three M1 candle ranges, SMA5 on M15 resides inside the last M15 candle, and recent candles show persistent bearish structure (lower lows, bearish bodies, limited pullbacks, shadows present).
  5. With no active position, a market sell order is sent once all conditions align.

Exit Rules

  • Protective stop-loss and take-profit orders are attached automatically using the configured pip distances (mirrors the original EA behaviour).
  • Additional discretionary exits replicate the MQL logic:
    • Close longs if the current M1 candle collapses by at least 10 pips from its open or if a strong bearish M1 candle appears after the trade has been open for more than one minute.
    • Take profit early when price advances at least 10 pips, or when a trailing reversal occurs: after the first bar following entry, if price retraces 20 pips from the highest level reached since entry while that high sits above the entry price.
    • Close longs on a 20 pip adverse excursion or whenever the portfolio equity falls below the configured drawdown threshold. Short positions use symmetrical logic with inverted comparisons.

Risk Management

  • Trading halts automatically when the portfolio equity drops below (InitialDeposit * (100% - RiskPercent)). The limit is checked on every signal attempt and reset once equity recovers above the threshold.
  • The original MQL script included extensive terminal checks; those are omitted because StockSharp handles connectivity and permissions natively.

Parameters

Name Description Default
StopLossPips Protective stop distance in pips (mirrored by trailing logic). 30
TakeProfitPips Take-profit distance in pips. 60
InitialDeposit Reference equity used to compute the drawdown stop. 10000
RiskPercent Maximum percentage of the initial deposit that can be lost before blocking new trades and force-closing active positions. 5
M1CandleType Data type for the 1-minute candle subscription. 1 minute time-frame
M15CandleType Data type for the 15-minute confirmation subscription. 15 minutes time-frame
H1CandleType Data type for the 1-hour trend filter subscription. 1 hour time-frame

Notes

  • The strategy expects instruments quoted with pip sizes similar to major forex pairs. Adjust the pip-based parameters when using other markets.
  • Only the C# implementation is provided; the Python version is intentionally omitted per requirements.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend-following strategy converted from the "Reduce risks" MQL5 expert.
/// Uses SMA hierarchy (short/medium/long) for trend detection with risk control exits.
/// Enters on confirmed SMA crossover, exits on reverse cross or stop/take profit.
/// </summary>
public class ReduceRisksStrategy : Strategy
{
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<decimal> _initialDeposit;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _smaShort;
	private SimpleMovingAverage _smaMedium;
	private SimpleMovingAverage _smaLong;

	private decimal? _smaShortCurr;
	private decimal? _smaShortPrev;
	private decimal? _smaMediumCurr;
	private decimal? _smaMediumPrev;
	private decimal? _smaLongCurr;
	private decimal? _smaLongPrev;

	private decimal _riskThreshold;
	private int _riskExceededCounter;
	private int _barsSinceEntry;
	private decimal _entryPrice;
	private int _barsShortAboveMedium;
	private int _barsShortBelowMedium;
	private bool _enteredLong;
	private bool _enteredShort;

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Reference initial deposit used for equity based risk limitation.
	/// </summary>
	public decimal InitialDeposit
	{
		get => _initialDeposit.Value;
		set => _initialDeposit.Value = value;
	}

	/// <summary>
	/// Percentage of the initial deposit allowed to be lost before new entries are blocked.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Candle timeframe for trading.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public ReduceRisksStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 30)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Protective stop distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 60)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Target distance in pips", "Risk");

		_initialDeposit = Param(nameof(InitialDeposit), 1000000m)
			.SetGreaterThanZero()
			.SetDisplay("Initial Deposit", "Reference equity for drawdown protection", "Risk");

		_riskPercent = Param(nameof(RiskPercent), 5m)
			.SetRange(0m, 100m)
			.SetDisplay("Risk Percent", "Maximum loss allowed relative to the initial deposit", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Timeframe", "Trading timeframe", "Timeframes");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return new[] { (Security, CandleType) };
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_smaShort = null;
		_smaMedium = null;
		_smaLong = null;

		_smaShortCurr = null;
		_smaShortPrev = null;
		_smaMediumCurr = null;
		_smaMediumPrev = null;
		_smaLongCurr = null;
		_smaLongPrev = null;

		_riskThreshold = 0m;
		_riskExceededCounter = 0;
		_barsSinceEntry = 0;
		_entryPrice = 0m;
		_barsShortAboveMedium = 0;
		_barsShortBelowMedium = 0;
		_enteredLong = false;
		_enteredShort = false;
	}

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

		_riskThreshold = InitialDeposit * (100m - RiskPercent) / 100m;

		// SMA periods: ~2h / ~6h / ~12h on 5-min candles
		_smaShort = new SimpleMovingAverage { Length = 24 };
		_smaMedium = new SimpleMovingAverage { Length = 72 };
		_smaLong = new SimpleMovingAverage { Length = 144 };

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _smaShort);
			DrawIndicator(area, _smaMedium);
			DrawIndicator(area, _smaLong);
			DrawOwnTrades(area);
		}
	}

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

		if (_smaShort is null || _smaMedium is null || _smaLong is null)
			return;

		var typical = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;

		UpdateSma(_smaShort, typical, candle.OpenTime, ref _smaShortCurr, ref _smaShortPrev);
		UpdateSma(_smaMedium, typical, candle.OpenTime, ref _smaMediumCurr, ref _smaMediumPrev);
		UpdateSma(_smaLong, typical, candle.OpenTime, ref _smaLongCurr, ref _smaLongPrev);

		if (_smaShortCurr is not decimal smaS ||
			_smaMediumCurr is not decimal smaM ||
			_smaLongCurr is not decimal smaL)
			return;

		// Track consecutive bars of SMA position
		if (smaS > smaM)
		{
			_barsShortAboveMedium++;
			_barsShortBelowMedium = 0;
		}
		else
		{
			_barsShortBelowMedium++;
			_barsShortAboveMedium = 0;
		}

		// Risk check
		var equity = Portfolio?.CurrentValue ?? InitialDeposit;
		var riskExceeded = equity <= _riskThreshold && InitialDeposit > 0m;

		if (riskExceeded)
		{
			if (_riskExceededCounter < 15)
			{
				LogWarning("Entry blocked. Risk limit of {0}% reached (equity={1:0.##}).", RiskPercent, equity);
				_riskExceededCounter++;
			}
		}
		else
		{
			_riskExceededCounter = 0;
		}

		// When SMA crosses in opposite direction, allow new entry of that type
		if (_barsShortBelowMedium >= 72)
			_enteredLong = false;
		if (_barsShortAboveMedium >= 72)
			_enteredShort = false;

		if (Position == 0 && !riskExceeded)
		{
			// LONG: short crosses above medium, not already entered on this cross
			if (_barsShortAboveMedium == 1 && candle.ClosePrice > smaS && !_enteredLong)
			{
				BuyMarket();
				_barsSinceEntry = 0;
				_enteredLong = true;
			}
			// SHORT: short crosses below medium, not already entered on this cross
			else if (_barsShortBelowMedium == 1 && candle.ClosePrice < smaS && !_enteredShort)
			{
				SellMarket();
				_barsSinceEntry = 0;
				_enteredShort = true;
			}
		}
		else if (Position != 0)
		{
			_barsSinceEntry++;

			if (Position > 0)
			{
				var entryPrice = _entryPrice;
				// Exit on reverse cross after min hold
				var reverseCross = _barsShortBelowMedium >= 3 && _barsSinceEntry >= 30;
				// Stop loss: 4%
				var stopLoss = entryPrice > 0 && candle.ClosePrice < entryPrice * 0.96m;
				// Take profit: 6%
				var takeProfit = entryPrice > 0 && candle.ClosePrice > entryPrice * 1.06m;

				if (reverseCross || stopLoss || takeProfit || riskExceeded)
				{
					SellMarket(Position.Abs());
				}
			}
			else if (Position < 0)
			{
				var entryPrice = _entryPrice;
				// Exit on reverse cross after min hold
				var reverseCross = _barsShortAboveMedium >= 3 && _barsSinceEntry >= 30;
				// Stop loss: 4%
				var stopLoss = entryPrice > 0 && candle.ClosePrice > entryPrice * 1.04m;
				// Take profit: 6%
				var takeProfit = entryPrice > 0 && candle.ClosePrice < entryPrice * 0.94m;

				if (reverseCross || stopLoss || takeProfit || riskExceeded)
				{
					BuyMarket(Position.Abs());
				}
			}
		}

		if (Position == 0)
		{
			_entryPrice = 0m;
			_barsSinceEntry = 0;
		}
	}

	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);
		if (trade?.Trade == null) return;
		if (Position != 0 && _entryPrice == 0m)
			_entryPrice = trade.Trade.Price;
	}

	private void UpdateSma(SimpleMovingAverage sma, decimal input, DateTimeOffset time, ref decimal? curr, ref decimal? prev)
	{
		var indicatorValue = sma.Process(new DecimalIndicatorValue(sma, input, time.UtcDateTime) { IsFinal = true });
		if (!sma.IsFormed || indicatorValue is not DecimalIndicatorValue decimalValue)
			return;

		prev = curr;
		curr = decimalValue.Value;
	}
}