Открыть на GitHub

Стратегия Plateau

Plateau — это перенос оригинального советника MetaTrader 5. Стратегия использует пару линейно-взвешенных скользящих средних и полосы Боллинджера, чтобы находить потенциальные развороты рядом с нижней полосой.

Торговая идея

  • Рассчитать быструю и медленную средние с выбранным методом сглаживания и ценой.
  • Построить полосы Боллинджера по той же ценовой серии.
  • Когда быстрая средняя пересекает медленную снизу вверх и предыдущая свеча закрылась ниже нижней полосы, открывается длинная позиция.
  • Когда быстрая средняя пересекает медленную сверху вниз и предыдущая свеча закрылась выше нижней полосы, открывается короткая позиция.
  • При включённом флаге Reverse логика сигналов инвертируется.

Управление сделкой

  • Объём позиции задаётся фиксированным лотом или через риск в процентах от стоимости портфеля.
  • Стоп-лосс и тейк-профит задаются в пунктах и выставляются сразу после исполнения рыночного ордера.
  • При наличии положительных значений дистанции и шага активируется трейлинг-стоп.
  • Если включён параметр Close Opposite, то перед новым входом закрывается встречная позиция.

Параметры

Параметр Описание
Stop Loss Дистанция стоп-лосса в пунктах.
Take Profit Дистанция тейк-профита в пунктах.
Trailing Stop Дистанция трейлинг-стопа в пунктах.
Trailing Step Минимальный шаг перемещения трейлинг-стопа (в пунктах).
Money Mode Режим управления капиталом: фиксированный лот или процент риска.
Lot / Risk Фиксированный лот либо процент риска в зависимости от режима.
Fast MA / Slow MA Периоды быстрой и медленной средних.
MA Shift Горизонтальный сдвиг обеих средних.
MA Method Метод сглаживания средних.
MA Price Источник цены для расчёта средних.
Bands Period Период полос Боллинджера.
Bands Shift Горизонтальный сдвиг значений полос.
Bands Deviation Множитель стандартного отклонения.
Bands Price Тип цены для расчёта полос Боллинджера.
Reverse Инвертировать логику входов.
Close Opposite Закрывать противоположную позицию перед новым входом.
Verbose Log Печатать подробные сообщения в журнал.
Candle Type Тип свечей, используемых в расчётах.

Дополнительные замечания

  • Размер пункта автоматически подстраивается для инструментов с тремя или пятью знаками после запятой, как в исходном советнике.
  • Если трейлинг-стоп включён, шаг трейлинга должен быть строго положительным, иначе стратегия завершит запуск с ошибкой.
  • При управлении по риску необходимы корректный стоп-лосс и информация о стоимости портфеля. При их отсутствии используется объём по умолчанию.
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;
	}
}