Auf GitHub ansehen

Wave Power EA Strategy

The Wave Power EA Strategy is a C# port of the MQL4 expert advisor "Wave Power EA1". The original robot builds a position in direction of a stochastic or MACD signal and then adds additional market orders every fixed number of pips while adjusting the shared take-profit level. The StockSharp version reproduces this behaviour using the high-level strategy API, indicator binding and built-in order helpers. All comments remain in English as required.

How the strategy works

  1. Signal selection – the first trade is opened only when one of the indicator filters generates a direction:

    • Stochastic – %K crossing %D inside oversold/overbought regions.
    • MacdSlope – MACD line rising above or falling below its previous value.
    • CciLevels – CCI dropping below –120 or rising above +120.
    • AwesomeBreakout – Awesome Oscillator breaking the adaptive historic low/high that was captured during initialisation.
    • RsiMa – fast SMA crosses slow SMA while RSI confirms momentum (above/below 50).
    • SmaTrend – a 15/20/25/50 SMA fan pointing in the same direction with a minimum slope difference.
  2. Grid expansion – after the first market order is filled the strategy remembers the fill price. Whenever the market moves by GridStepPips against the current position and the maximum order count is not exceeded, the strategy submits a new market order in the same direction. Each new layer multiplies the volume by the Multiplier parameter.

  3. Shared targets – every new order recalculates a common take-profit and (optionally) stop-loss price. When the number of active orders approaches the OrdersToProtect threshold the take-profit distance is replaced with ReboundProfitPrimary. After the threshold is exceeded the distance switches to ReboundProfitSecondary to encourage faster recovery.

  4. Basket monitoring – on every candle close the strategy converts the open P&L into pips per lot. If the rebound profit or loss protection thresholds are reached the whole basket is liquidated using market orders. The same happens when the oldest trade is older than OrdersTimeAliveSeconds or when trading on Friday is disabled.

  5. Lifecycle – once the basket is flat all internal counters are reset, allowing the next signal to start a new averaging cycle.

Compared to the original EA this port intentionally avoids opening opposite (hedging) positions after a certain number of grid layers. All additional entries follow the initial direction. The rest of the money-management rules, protection logic and indicator filters remain compatible with the MQL4 reference implementation.

Parameters

Parameter Description
EntryLogic Indicator mode used for the very first order.
CandleType Timeframe that feeds all indicators (default: 1 hour).
InitialVolume Volume of the first order in lots/contracts.
GridStepPips Minimal distance in pips between grid layers.
MaxOrders Maximum number of simultaneous orders in the basket.
TakeProfitPips Shared take-profit distance in pips (0 disables the target).
StopLossPips Shared stop-loss distance in pips (0 disables the stop).
Multiplier Volume multiplier applied to each additional order.
SecureProfitProtection Enables the rebound profit logic.
OrdersToProtect Number of orders required before rebound protection starts.
ReboundProfitPrimary Profit per lot (in pips) for the first protection stage.
ReboundProfitSecondary Profit per lot (in pips) once the protected order count is exceeded.
LossProtection Enables the floating-loss guard.
LossThreshold Loss per lot (in pips) that triggers the guard when the basket is full.
ReverseCondition Inverts buy/sell signals.
TradeOnFriday Allows opening new orders on Fridays.
OrdersTimeAliveSeconds Maximum lifetime of the newest order in seconds (0 disables the timer).
TrendSlopeThreshold Minimal SMA slope difference used by the SmaTrend logic.

Usage tips

  1. Attach the strategy to a security with a configured price step so the pip conversion works correctly.
  2. Adjust GridStepPips, Multiplier and MaxOrders according to the instrument volatility and the broker margin policy.
  3. Enable the protection blocks when running on a live account to prevent runaway losses during prolonged trends.
  4. The strategy relies on closed candles; pick a timeframe that reflects the desired trading rhythm (the original EA uses M30 and H1 combinations but the default H1 candles work well).
  5. Because hedging after the fifth layer is not implemented, consider lowering MaxOrders if you require the exact original behaviour.

Files

  • CS/WavePowerEAStrategy.cs – StockSharp implementation of the Wave Power EA grid logic.
  • README.md / README_ru.md / README_zh.md – documentation in English, Russian and Chinese.

The Python version is intentionally omitted per the task requirements.

using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Wave Power strategy using RSI + EMA crossover for entry
/// with grid-like averaging on drawdown.
/// </summary>
public class WavePowerEAStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _gridStepPercent;
	private readonly StrategyParam<int> _maxGridOrders;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _gridCount;

	public WavePowerEAStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 12)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetDisplay("RSI Period", "RSI period.", "Indicators");

		_gridStepPercent = Param(nameof(GridStepPercent), 0.5m)
			.SetDisplay("Grid Step %", "Price move % to add to position.", "Grid");

		_maxGridOrders = Param(nameof(MaxGridOrders), 5)
			.SetDisplay("Max Grid Orders", "Maximum averaging orders.", "Grid");
	}

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal GridStepPercent
	{
		get => _gridStepPercent.Value;
		set => _gridStepPercent.Value = value;
	}

	public int MaxGridOrders
	{
		get => _maxGridOrders.Value;
		set => _maxGridOrders.Value = value;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_gridCount = 0;
	}

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

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, rsi, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;
		var bullishCross = _prevFast <= _prevSlow && fastVal > slowVal;
		var bearishCross = _prevFast >= _prevSlow && fastVal < slowVal;

		// Exit on opposite cross
		if (Position > 0 && bearishCross)
		{
			SellMarket();
			_gridCount = 0;
			_entryPrice = 0;
		}
		else if (Position < 0 && bullishCross)
		{
			BuyMarket();
			_gridCount = 0;
			_entryPrice = 0;
		}

		// Grid averaging: add to position if price moved against us
		if (Position > 0 && _entryPrice > 0 && _gridCount < MaxGridOrders)
		{
			var dropPercent = (_entryPrice - close) / _entryPrice * 100;
			if (dropPercent >= GridStepPercent * (_gridCount + 1))
			{
				BuyMarket();
				_gridCount++;
			}
		}
		else if (Position < 0 && _entryPrice > 0 && _gridCount < MaxGridOrders)
		{
			var risePercent = (close - _entryPrice) / _entryPrice * 100;
			if (risePercent >= GridStepPercent * (_gridCount + 1))
			{
				SellMarket();
				_gridCount++;
			}
		}

		// New entry
		if (Position == 0)
		{
			if (bullishCross && rsiVal > 50)
			{
				_entryPrice = close;
				_gridCount = 0;
				BuyMarket();
			}
			else if (bearishCross && rsiVal < 50)
			{
				_entryPrice = close;
				_gridCount = 0;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}