Открыть на GitHub

Стратегия Wave Power EA

Wave Power EA Strategy — это порт советника MQL4 «Wave Power EA1» на C#. Оригинальный робот строит позицию по сигналу стохастика или MACD, а затем добавляет новые рыночные ордера через фиксированное число пунктов, одновременно переставляя общий тейк-профит. Версия на StockSharp повторяет эту логику с использованием высокоуровневого API, привязки индикаторов и готовых методов для регистрации заявок. Все комментарии в коде оставлены на английском языке согласно требованиям.

Логика работы

  1. Выбор сигнала. Первый ордер открывается только при появлении направления от одного из фильтров:

    • Stochastic — пересечение %K и %D в зоне перепроданности/перекупленности.
    • MacdSlope — наклон основной линии MACD меняет знак.
    • CciLevels — CCI опускается ниже –120 или поднимается выше +120.
    • AwesomeBreakout — индикатор Awesome Oscillator пробивает адаптивный минимум или максимум.
    • RsiMa — пересечение быстрой и медленной SMA при подтверждении от RSI (выше/ниже 50).
    • SmaTrend — веер SMA (15/20/25/50) выстраивается в одном направлении с минимальным требуемым наклоном.
  2. Расширение сетки. После исполнения первого ордера стратегия запоминает цену. Как только рынок проходит против позиции на GridStepPips и лимит по количеству ордеров не достигнут, отправляется новая рыночная заявка в том же направлении. Объем умножается на коэффициент Multiplier.

  3. Общие цели. Каждый новый уровень пересчитывает общий тейк-профит и при необходимости стоп-лосс. Когда число ордеров приближается к OrdersToProtect, дистанция до цели заменяется на ReboundProfitPrimary. После превышения порога используется значение ReboundProfitSecondary, чтобы ускорить выход из просадки.

  4. Контроль корзины. На закрытии свечи плавающий результат переводится в пункты на один лот. При достижении порогов защитного профита или убытка вся серия закрывается по рынку. То же самое произойдет, если время жизни последнего ордера превысит OrdersTimeAliveSeconds или запрещено открывать сделки по пятницам.

  5. Новый цикл. После полного закрытия позиции внутренние счетчики обнуляются, и стратегия готова к следующему сигналу.

По сравнению с оригиналом порт не открывает встречные (хеджирующие) ордера после пятого уровня сетки — все добавления идут в сторону исходного сигнала. Остальные правила управления капиталом, защитные механизмы и фильтры индикаторов соответствуют референсной реализации на MQL4.

Параметры

Параметр Описание
EntryLogic Режим индикатора для первого ордера.
CandleType Таймфрейм для расчёта всех индикаторов (по умолчанию 1 час).
InitialVolume Объем первого ордера в лотах/контрактах.
GridStepPips Минимальное расстояние между уровнями сетки в пунктах.
MaxOrders Максимальное число одновременных ордеров.
TakeProfitPips Общий тейк-профит в пунктах (0 — отключить).
StopLossPips Общий стоп-лосс в пунктах (0 — отключить).
Multiplier Множитель объема для каждого нового уровня.
SecureProfitProtection Включить логику защитного профита.
OrdersToProtect Количество ордеров до активации защитного профита.
ReboundProfitPrimary Порог профита (в пунктах на лот) для первой стадии защиты.
ReboundProfitSecondary Порог профита после превышения защищаемого количества ордеров.
LossProtection Включить защиту по плавающему убытку.
LossThreshold Убыток (в пунктах на лот), при котором корзина закрывается при полном заполнении.
ReverseCondition Инвертировать сигналы покупки/продажи.
TradeOnFriday Разрешить открывать новые ордера в пятницу.
OrdersTimeAliveSeconds Максимальное время жизни последнего ордера в секундах (0 — без ограничения).
TrendSlopeThreshold Минимальный наклон SMA для режима SmaTrend.

Рекомендации по применению

  1. Запускайте стратегию на инструменте с корректно настроенным шагом цены, чтобы пересчет пунктов работал корректно.
  2. Подбирайте GridStepPips, Multiplier и MaxOrders под волатильность инструмента и требования по марже.
  3. На реальном счёте включайте защитные блоки, чтобы избежать чрезмерных просадок во время длительных трендов.
  4. Стратегия работает по закрытым свечам — выберите таймфрейм, соответствующий желаемому стилю торговли (в оригинале использовались комбинации M30 и H1, но стандартные часовые свечи подходят).
  5. Так как хеджирование после пятого уровня отсутствует, при необходимости точного повторения оригинала уменьшите значение MaxOrders.

Содержимое папки

  • CS/WavePowerEAStrategy.cs — реализация логики Wave Power EA на StockSharp.
  • README.md / README_ru.md / README_zh.md — документация на английском, русском и китайском языках.

Версия на Python специально не создавалась, как требовалось в задании.

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;
	}
}