Открыть на GitHub

Стратегия Exp Slow Stoch Duplex

Эта стратегия представляет собой перенос MetaTrader 5 эксперта Exp_Slow-Stoch_Duplex на высокоуровневый API StockSharp. Используются два медленных стохастика на разных таймфреймах, которые синхронно формируют сигналы на открытие и закрытие длинных и коротких позиций. Защитные ордера реализуют те же стоп-лосс и тейк-профит, что и оригинальный советник.

Правила торговли

  • Длинный модуль
    • Стохастик рассчитывается на таймфрейме LongCandleType.
    • Значения %K и %D дополнительно сглаживаются выбранным методом и сдвигаются на LongSignalBar баров назад.
    • Позиция покупается, когда %K пересекает %D снизу вверх (previousK <= previousD и currentK > currentD).
    • Длинная позиция закрывается, если %K вновь опускается ниже %D (currentK < currentD).
  • Короткий модуль
    • Стохастик рассчитывается на таймфрейме ShortCandleType.
    • Короткая позиция открывается при пересечении %K выше %D вниз (previousK >= previousD и currentK < currentD).
    • Короткая позиция закрывается, когда %K возвращается выше %D (currentK > currentD).
  • Все заявки отправляются по рынку. Объём равен TradeVolume плюс абсолютное значение текущей позиции, поэтому при разворотах старая позиция закрывается полностью.
  • Команда StartProtection добавляет тейк-профит и стоп-лосс в пунктах, как в MT5.

Параметры

Параметр Тип Значение по умолчанию Описание
LongCandleType DataType Свечи H8 Таймфрейм длинного стохастика.
LongKPeriod int 5 Период расчёта %K для длинного стохастика.
LongDPeriod int 3 Период сглаживания %D для длинного стохастика.
LongSlowing int 3 Дополнительное внутреннее сглаживание стохастика.
LongSignalBar int 1 Количество закрытых баров, используемых для поиска пересечений.
LongSmoothingMethod SmoothingMethod Smoothed Вторичное сглаживание %K и %D (None, Simple, Exponential, Smoothed, Weighted).
LongSmoothingLength int 5 Длина вторичного сглаживания длинного стохастика.
LongEnableOpen bool true Разрешение на открытие длинных позиций.
LongEnableClose bool true Разрешение на закрытие длинных позиций.
ShortCandleType DataType Свечи H8 Таймфрейм короткого стохастика.
ShortKPeriod int 5 Период расчёта %K для короткого стохастика.
ShortDPeriod int 3 Период сглаживания %D для короткого стохастика.
ShortSlowing int 3 Дополнительное внутреннее сглаживание стохастика.
ShortSignalBar int 1 Количество баров для анализа короткого пересечения.
ShortSmoothingMethod SmoothingMethod Smoothed Вторичное сглаживание %K и %D короткого стохастика.
ShortSmoothingLength int 5 Длина вторичного сглаживания короткого стохастика.
ShortEnableOpen bool true Разрешение на открытие коротких позиций.
ShortEnableClose bool true Разрешение на закрытие коротких позиций.
TradeVolume decimal 0.1 Базовый объём ордеров.
TakeProfitPoints decimal 2000 Дистанция тейк-профита в пунктах.
StopLossPoints decimal 1000 Дистанция стоп-лосса в пунктах.

Особенности

  • Параметр SmoothingMethod использует стандартные средние StockSharp и служит приближением оригинального JJMA. При необходимости вторичное сглаживание можно отключить, выбрав None.
  • Длинный и короткий модули независимы и включаются/отключаются собственными флагами.
  • В StockSharp применяется неттинг, поэтому при смене направления противоположные позиции закрываются автоматически.
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;

/// <summary>
/// Slow Stochastic Duplex strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ExpSlowStochDuplexStrategy : 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 ExpSlowStochDuplexStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).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;
	}
}