GitHub で見る

Plateau Strategy

The Plateau strategy is a conversion of the original MetaTrader 5 expert advisor. It combines a pair of linear-weighted moving averages with Bollinger Bands to detect potential reversals when price trades near the lower band.

Trading idea

  • Calculate fast and slow moving averages using the selected smoothing method and price source.
  • Build Bollinger Bands around the same price series.
  • When the fast average crosses above the slow average while the previous candle closed below the lower band, open a long position.
  • When the fast average crosses below the slow average while the previous candle closed above the lower band, open a short position.
  • Optionally reverse the signals if the Reverse switch is enabled.

Order management

  • Positions can be sized either with a fixed lot or by risking a percentage of the portfolio value per trade.
  • Stop-loss and take-profit levels are expressed in pips and immediately attached after the market order is filled.
  • A trailing stop can be activated when both trailing distance and step are positive.
  • When Close Opposite is enabled the strategy automatically closes the opposing position before entering a new trade.

Parameters

Parameter Description
Stop Loss Stop-loss distance in pips.
Take Profit Take-profit distance in pips.
Trailing Stop Trailing stop distance in pips.
Trailing Step Minimal increment (in pips) required to move the trailing stop.
Money Mode Choose between fixed lot and risk percentage sizing.
Lot / Risk Either the fixed lot size or the risk percentage depending on the selected money mode.
Fast MA / Slow MA Periods for the moving average pair.
MA Shift Horizontal shift applied to both moving averages.
MA Method Moving average smoothing algorithm.
MA Price Price source used for moving average calculations.
Bands Period Averaging period for Bollinger Bands.
Bands Shift Horizontal shift applied to Bollinger Band values.
Bands Deviation Standard deviation multiplier for Bollinger Bands.
Bands Price Price source used for Bollinger Bands calculations.
Reverse Invert the long and short signal logic.
Close Opposite Close an existing position in the opposite direction before opening a new one.
Verbose Log Print detailed execution information to the log.
Candle Type Candle data series used for indicator calculations.

Notes

  • The pip size is automatically adjusted to instruments with three or five decimal digits to match the original expert behaviour.
  • When the trailing stop is enabled the trailing step must be strictly positive, otherwise the strategy throws an error on start.
  • Risk-based position sizing requires both a valid stop-loss distance and portfolio valuation data. When unavailable the strategy falls back to the default volume.
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 PlateauStrategy : 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 PlateauStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 15).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 60).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;
	}
}