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
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.
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.
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.
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.
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
- Attach the strategy to a security with a configured price step so the pip conversion works correctly.
- Adjust
GridStepPips, Multiplier and MaxOrders according to the instrument volatility and the broker margin policy.
- Enable the protection blocks when running on a live account to prevent runaway losses during prolonged trends.
- 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).
- 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex
class wave_power_ea_strategy(Strategy):
def __init__(self):
super(wave_power_ea_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 5) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 12) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period", "Indicators")
self._grid_step_percent = self.Param("GridStepPercent", 0.5) \
.SetDisplay("Grid Step %", "Price move % to add to position", "Grid")
self._max_grid_orders = self.Param("MaxGridOrders", 5) \
.SetDisplay("Max Grid Orders", "Maximum averaging orders", "Grid")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._grid_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@property
def GridStepPercent(self):
return self._grid_step_percent.Value
@property
def MaxGridOrders(self):
return self._max_grid_orders.Value
def OnStarted2(self, time):
super(wave_power_ea_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._grid_count = 0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastPeriod
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowPeriod
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
if self._prev_fast == 0 or self._prev_slow == 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
bullish_cross = self._prev_fast <= self._prev_slow and fv > sv
bearish_cross = self._prev_fast >= self._prev_slow and fv < sv
# Exit on opposite cross
if self.Position > 0 and bearish_cross:
self.SellMarket()
self._grid_count = 0
self._entry_price = 0.0
elif self.Position < 0 and bullish_cross:
self.BuyMarket()
self._grid_count = 0
self._entry_price = 0.0
# Grid averaging: add to position if price moved against us
grid_step = float(self.GridStepPercent)
max_grid = self.MaxGridOrders
if self.Position > 0 and self._entry_price > 0 and self._grid_count < max_grid:
drop_percent = (self._entry_price - close) / self._entry_price * 100.0
if drop_percent >= grid_step * (self._grid_count + 1):
self.BuyMarket()
self._grid_count += 1
elif self.Position < 0 and self._entry_price > 0 and self._grid_count < max_grid:
rise_percent = (close - self._entry_price) / self._entry_price * 100.0
if rise_percent >= grid_step * (self._grid_count + 1):
self.SellMarket()
self._grid_count += 1
# New entry
if self.Position == 0:
if bullish_cross and rv > 50:
self._entry_price = close
self._grid_count = 0
self.BuyMarket()
elif bearish_cross and rv < 50:
self._entry_price = close
self._grid_count = 0
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(wave_power_ea_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._grid_count = 0
def CreateClone(self):
return wave_power_ea_strategy()