Открыть на GitHub

Стратегия Day Trading Impulse

Общее описание

DayTrading Strategy — это точная конвертация советника MetaTrader 4 «DayTrading», опубликованного компанией NazFunds в 2005 году. Алгоритм рассчитан на пятиминутные графики Forex и объединяет несколько трендовых и моментум-индикаторов, чтобы брать краткосрочные импульсы с небольшим тейк-профитом и при необходимости сопровождать сделку трейлинг-стопом. Версия на StockSharp полностью повторяет логику MQL-скрипта и одновременно делает ключевые параметры доступными для оптимизации.

Набор индикаторов

Для выбранной свечной серии рассчитываются четыре индикатора:

  • Parabolic SAR (ParabolicSar) с настраиваемыми коэффициентами ускорения, шага и максимума. Индикатор определяет базовый тренд и должен сменить сторону относительно цены, чтобы разрешить новую сделку.
  • MACD (12, 26, 9) (MovingAverageConvergenceDivergenceSignal). Линия MACD должна находиться ниже сигнальной для покупок и выше для продаж, что соответствует сравнению гистограммы и сигнальной линии в оригинальном советнике.
  • Стохастик (5, 3, 3) (StochasticOscillator). Линия %K должна быть ниже 35 для лонгов и выше 60 для шортов, подтверждая выход из зоны перепроданности/перекупленности.
  • Momentum (14) (Momentum). Значение ниже 100 разрешает покупки, выше 100 — продажи.

Все индикаторы подключены через высокоуровневый метод BindEx, поэтому нет необходимости вручную управлять буферами или обращаться к прошлым значениям.

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

Условия входа

Лонг открывается на последней сформированной свече, если выполняются условия:

  1. Точка Parabolic SAR находится на уровне или ниже текущей цены ask, а предыдущая точка была выше текущей (свежий разворот SAR вверх).
  2. Momentum < 100.
  3. Линия MACD ниже сигнальной.
  4. Стохастик %K < 35.

Шорт открывается зеркально:

  1. Точка Parabolic SAR находится на уровне или выше текущей цены bid, а предыдущая точка была ниже текущей (разворот вниз).
  2. Momentum > 100.
  3. Линия MACD выше сигнальной.
  4. Стохастик %K > 60.

В позиции может находиться только одна сделка. При появлении противоположного сигнала активная позиция закрывается, и на той же свече новая сделка не открывается — именно так работает сканирование OrdersTotal в оригинале.

Управление выходом

  • Стоп-лосс / тейк-профит: Необязательные фиксированные дистанции в пунктах переводятся в цену с учётом шага котировки и проверяются на каждой свече. При пробое позиция закрывается.
  • Трейлинг-стоп: После прохождения ценой заданного числа пунктов активируется сопровождение. Для лонга стоп подтягивается под цену закрытия, для шорта — над ценой. Стоп никогда не откатывается назад, тем самым фиксируя прибыль.
  • Противоположный сигнал: Валидный сигнал противоположного направления немедленно закрывает текущую позицию до рассмотрения новой сделки.

Стратегия не использует усреднение, сетки или хеджирование — логика остаётся минималистичной, как в исходном EA.

Параметры

Параметр Значение по умолчанию Описание
LotSize 1 Объём каждой рыночной заявки. При запуске значение синхронизируется со свойством Strategy.Volume.
TrailingStopPoints 15 Дистанция трейлинг-стопа в пунктах. Ноль — отключить сопровождение.
TakeProfitPoints 20 Фиксированный тейк-профит в пунктах. Ноль — без цели.
StopLossPoints 0 Стоп-лосс в пунктах. Ноль воспроизводит оригинальную работу без стопа.
SlippagePoints 3 Допустимое проскальзывание (параметр сохранён для совместимости, но не используется напрямую).
CandleType 5-минутные свечи Свечная серия для расчёта индикаторов. Для повторения оригинала оставьте M5.
MacdFastPeriod 12 Период быстрой EMA в MACD.
MacdSlowPeriod 26 Период медленной EMA в MACD.
MacdSignalPeriod 9 Период сигнальной EMA MACD.
StochasticLength 5 Длина %K стохастика.
StochasticSignal 3 Период сглаживания %D.
StochasticSlow 3 Дополнительное сглаживание %K.
MomentumPeriod 14 Длина расчёта Momentum.
SarAcceleration 0.02 Начальный коэффициент ускорения Parabolic SAR.
SarStep 0.02 Шаг увеличения ускорения.
SarMaximum 0.2 Максимальное ускорение Parabolic SAR.

Все числовые параметры помечены для оптимизации, поэтому их легко исследовать в оптимизаторе StockSharp.

Особенности реализации

  • Цены bid/ask берутся из Level1, если данные доступны, либо подставляется цена закрытия свечи — это делает стратегию устойчивой при тестировании на исторических свечах.
  • Перевод пунктов в цену выполняется через Security.Step/PriceStep. При отсутствии шага используется запасное значение 0.0001, соответствующее стандартному форексному пункту.
  • Позиция всегда одиночная: стратегия не переворачивается в обе стороны одновременно и не наращивает объём.
  • Все комментарии в коде даны на английском в соответствии с требованиями репозитория, а расширенная документация приведена в README.

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

  1. Назначьте нужную валютную пару, оставьте таймфрейм 5 минут и запустите стратегию — индикаторы прогреются автоматически.
  2. Для реальной торговли рекомендуем задать ненулевой стоп-лосс: оригинальный советник предлагал торговать без него, но трейлинг-стоп может не защитить счёт от резкого разворота.
  3. Стратегию можно включать в корзину BasketStrategy и управлять капиталом извне, пользуясь тем, что все ключевые пороги доступны как параметры.

Подробные описания на английском и китайском языках находятся в этой же папке.

namespace StockSharp.Samples.Strategies;

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

/// <summary>
/// Intraday trend strategy converted from the MetaTrader "DayTrading" expert advisor.
/// Combines Parabolic SAR, MACD, Stochastic and Momentum filters with trailing exits.
/// </summary>
public class DayTradingImpulseStrategy : Strategy
{
	private readonly StrategyParam<decimal> _lotSize;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _slippagePoints;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<int> _stochasticLength;
	private readonly StrategyParam<int> _stochasticSignal;
	private readonly StrategyParam<int> _stochasticSlow;
	private readonly StrategyParam<decimal> _stochasticBuyThreshold;
	private readonly StrategyParam<decimal> _stochasticSellThreshold;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<decimal> _momentumNeutralLevel;
	private readonly StrategyParam<decimal> _sarAcceleration;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMaximum;

	private ParabolicSar _parabolicSar = null!;
	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private StochasticOscillator _stochastic = null!;
	private Momentum _momentum = null!;

	private decimal? _previousSar;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longTakeProfit;
	private decimal? _shortTakeProfit;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;
	private decimal _pointSize;

	/// <summary>
	/// Initializes a new instance of <see cref="DayTradingImpulseStrategy"/>.
	/// </summary>
	public DayTradingImpulseStrategy()
	{
		_lotSize = Param(nameof(LotSize), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Trade volume used for each market entry", "Trading")
			;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 15m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop (points)", "Distance used to trail profitable positions", "Risk")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
			.SetNotNegative()
			.SetDisplay("Take Profit (points)", "Fixed profit target measured in points", "Risk")
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 0m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (points)", "Protective stop distance measured in points", "Risk")
			;

		_slippagePoints = Param(nameof(SlippagePoints), 3m)
			.SetNotNegative()
			.SetDisplay("Slippage (points)", "Maximum acceptable execution slippage", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for indicator calculations", "Data");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast", "Length of the fast EMA in MACD", "Indicators")
			;

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow", "Length of the slow EMA in MACD", "Indicators")
			;

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal", "Length of the MACD signal EMA", "Indicators")
			;

		_stochasticLength = Param(nameof(StochasticLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %K", "Period of the %K line", "Indicators")
			;

		_stochasticSignal = Param(nameof(StochasticSignal), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %D", "Period of the %D smoothing", "Indicators")
			;

		_stochasticSlow = Param(nameof(StochasticSlow), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Slowing", "Final smoothing applied to %K", "Indicators")
			;
		_stochasticBuyThreshold = Param(nameof(StochasticBuyThreshold), 35m)
			.SetDisplay("Stochastic Buy", "Oversold %K threshold for long entries", "Indicators")
			;

		_stochasticSellThreshold = Param(nameof(StochasticSellThreshold), 60m)
			.SetDisplay("Stochastic Sell", "Overbought %K threshold for short entries", "Indicators")
			;


		_momentumPeriod = Param(nameof(MomentumPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Number of candles used for Momentum", "Indicators")
			;

		_momentumNeutralLevel = Param(nameof(MomentumNeutralLevel), 100m)
			.SetDisplay("Momentum Neutral", "Neutral momentum value used for signal confirmation", "Indicators")
			;

		_sarAcceleration = Param(nameof(SarAcceleration), 0.02m)
			.SetGreaterThanZero()
			.SetDisplay("SAR Acceleration", "Initial acceleration factor of Parabolic SAR", "Indicators")
			;

		_sarStep = Param(nameof(SarStep), 0.02m)
			.SetGreaterThanZero()
			.SetDisplay("SAR Step", "Increment applied to the acceleration factor", "Indicators")
			;

		_sarMaximum = Param(nameof(SarMaximum), 0.2m)
			.SetGreaterThanZero()
			.SetDisplay("SAR Maximum", "Maximum acceleration factor of Parabolic SAR", "Indicators")
			;
	}

	/// <summary>
	/// Trade volume used for each market entry.
	/// </summary>
	public decimal LotSize
	{
		get => _lotSize.Value;
		set => _lotSize.Value = value;
	}

	/// <summary>
	/// Distance used to trail profitable positions.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Fixed profit target measured in points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Protective stop distance measured in points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Maximum acceptable execution slippage.
	/// </summary>
	public decimal SlippagePoints
	{
		get => _slippagePoints.Value;
		set => _slippagePoints.Value = value;
	}

	/// <summary>
	/// Time frame used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Length of the fast EMA in MACD.
	/// </summary>
	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	/// <summary>
	/// Length of the slow EMA in MACD.
	/// </summary>
	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	/// <summary>
	/// Length of the MACD signal EMA.
	/// </summary>
	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	/// <summary>
	/// Period of the %K line.
	/// </summary>
	public int StochasticLength
	{
		get => _stochasticLength.Value;
		set => _stochasticLength.Value = value;
	}

	/// <summary>
	/// Period of the %D smoothing.
	/// </summary>
	public int StochasticSignal
	{
		get => _stochasticSignal.Value;
		set => _stochasticSignal.Value = value;
	}

	/// <summary>
	/// Final smoothing applied to %K.
	/// </summary>
	public int StochasticSlow
	{
		get => _stochasticSlow.Value;
		set => _stochasticSlow.Value = value;
	}

	/// <summary>
	/// Stochastic %K level that qualifies oversold conditions.
	/// </summary>
	public decimal StochasticBuyThreshold
	{
		get => _stochasticBuyThreshold.Value;
		set => _stochasticBuyThreshold.Value = value;
	}

	/// <summary>
	/// Stochastic %K level that qualifies overbought conditions.
	/// </summary>
	public decimal StochasticSellThreshold
	{
		get => _stochasticSellThreshold.Value;
		set => _stochasticSellThreshold.Value = value;
	}

	/// <summary>
	/// Number of candles used for Momentum.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// Momentum value considered neutral for trend confirmation.
	/// </summary>
	public decimal MomentumNeutralLevel
	{
		get => _momentumNeutralLevel.Value;
		set => _momentumNeutralLevel.Value = value;
	}

	/// <summary>
	/// Initial acceleration factor of Parabolic SAR.
	/// </summary>
	public decimal SarAcceleration
	{
		get => _sarAcceleration.Value;
		set => _sarAcceleration.Value = value;
	}

	/// <summary>
	/// Increment applied to the acceleration factor.
	/// </summary>
	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}

	/// <summary>
	/// Maximum acceleration factor of Parabolic SAR.
	/// </summary>
	public decimal SarMaximum
	{
		get => _sarMaximum.Value;
		set => _sarMaximum.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

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

		_previousSar = null;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longTakeProfit = null;
		_shortTakeProfit = null;
		_longEntryPrice = null;
		_shortEntryPrice = null;
		_pointSize = 0m;
	}

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

		Volume = LotSize;
		_pointSize = CalculatePointSize();

		_parabolicSar = new ParabolicSar
		{
			Acceleration = SarAcceleration,
			AccelerationStep = SarStep,
			AccelerationMax = SarMaximum,
		};

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastPeriod },
				LongMa = { Length = MacdSlowPeriod },
			},
			SignalMa = { Length = MacdSignalPeriod },
		};

		_stochastic = new StochasticOscillator();
		_stochastic.K.Length = StochasticLength;
		_stochastic.D.Length = StochasticSignal;

		_momentum = new Momentum
		{
			Length = MomentumPeriod,
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_parabolicSar, _macd, _stochastic, _momentum, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _parabolicSar);
			DrawIndicator(area, _macd);
			DrawIndicator(area, _stochastic);
			DrawIndicator(area, _momentum);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(
		ICandleMessage candle,
		IIndicatorValue sarValue,
		IIndicatorValue macdValue,
		IIndicatorValue stochasticValue,
		IIndicatorValue momentumValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!sarValue.IsFinal || !macdValue.IsFinal || !stochasticValue.IsFinal || !momentumValue.IsFinal)
			return;

		if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macd)
			return;

		if (stochasticValue is not StochasticOscillatorValue stochastic)
			return;

		var sar = sarValue.ToDecimal();
		var previousSar = _previousSar;
		_previousSar = sar;

		if (previousSar is null)
			return;

		var momentum = momentumValue.ToDecimal();
		var ask = GetAskPrice(candle);
		var bid = GetBidPrice(candle);

		var buySignal = sar <= ask && previousSar.Value > sar && momentum < MomentumNeutralLevel &&
			macd.Macd < macd.Signal && stochastic.K < StochasticBuyThreshold;
		var sellSignal = sar >= bid && previousSar.Value < sar && momentum > MomentumNeutralLevel &&
			macd.Macd > macd.Signal && stochastic.K > StochasticSellThreshold;

		var closedPosition = false;

		if (Position > 0)
		{
			if (sellSignal)
			{
				SellMarket(Math.Abs(Position));
				ResetLongState();
				closedPosition = true;
			}
			else if (HandleLongRisk(candle))
			{
				closedPosition = true;
			}
		}
		else if (Position < 0)
		{
			if (buySignal)
			{
				BuyMarket(Math.Abs(Position));
				ResetShortState();
				closedPosition = true;
			}
			else if (HandleShortRisk(candle))
			{
				closedPosition = true;
			}
		}

		if (closedPosition)
			return;

		if (Position == 0)
		{
			if (buySignal)
			{
				var entryPrice = ask;
				BuyMarket(Volume);
				_longEntryPrice = entryPrice;
				_longStopPrice = StopLossPoints > 0m ? entryPrice - ConvertPoints(StopLossPoints) : null;
				_longTakeProfit = TakeProfitPoints > 0m ? entryPrice + ConvertPoints(TakeProfitPoints) : null;
			}
			else if (sellSignal)
			{
				var entryPrice = bid;
				SellMarket(Volume);
				_shortEntryPrice = entryPrice;
				_shortStopPrice = StopLossPoints > 0m ? entryPrice + ConvertPoints(StopLossPoints) : null;
				_shortTakeProfit = TakeProfitPoints > 0m ? entryPrice - ConvertPoints(TakeProfitPoints) : null;
			}
		}
	}

	private bool HandleLongRisk(ICandleMessage candle)
	{
		if (Math.Abs(Position) <= 0m)
			return false;

		if (_longTakeProfit is decimal takeProfit && candle.HighPrice >= takeProfit)
		{
			SellMarket(Math.Abs(Position));
			ResetLongState();
			return true;
		}

		if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
		{
			SellMarket(Math.Abs(Position));
			ResetLongState();
			return true;
		}

		var trailingDistance = ConvertPoints(TrailingStopPoints);
		if (trailingDistance > 0m && _longEntryPrice is decimal entry)
		{
			var progressed = candle.HighPrice - entry;
			if (progressed >= trailingDistance)
			{
				var candidate = candle.ClosePrice - trailingDistance;
				if (!_longStopPrice.HasValue || candidate > _longStopPrice.Value)
					_longStopPrice = candidate;
			}
		}

		return false;
	}

	private bool HandleShortRisk(ICandleMessage candle)
	{
		if (Math.Abs(Position) <= 0m)
			return false;

		if (_shortTakeProfit is decimal takeProfit && candle.LowPrice <= takeProfit)
		{
			BuyMarket(Math.Abs(Position));
			ResetShortState();
			return true;
		}

		if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
		{
			BuyMarket(Math.Abs(Position));
			ResetShortState();
			return true;
		}

		var trailingDistance = ConvertPoints(TrailingStopPoints);
		if (trailingDistance > 0m && _shortEntryPrice is decimal entry)
		{
			var progressed = entry - candle.LowPrice;
			if (progressed >= trailingDistance)
			{
				var candidate = candle.ClosePrice + trailingDistance;
				if (!_shortStopPrice.HasValue || candidate < _shortStopPrice.Value)
					_shortStopPrice = candidate;
			}
		}

		return false;
	}

	private void ResetLongState()
	{
		_longEntryPrice = null;
		_longStopPrice = null;
		_longTakeProfit = null;
	}

	private void ResetShortState()
	{
		_shortEntryPrice = null;
		_shortStopPrice = null;
		_shortTakeProfit = null;
	}

	private decimal GetBidPrice(ICandleMessage candle)
	{
		return candle.ClosePrice;
	}

	private decimal GetAskPrice(ICandleMessage candle)
	{
		return candle.ClosePrice;
	}

	private decimal ConvertPoints(decimal points)
	{
		if (points <= 0m)
			return 0m;

		if (_pointSize > 0m)
			return points * _pointSize;

		var step = Security?.PriceStep ?? 0m;
		return step > 0m ? points * step : points;
	}

	private decimal CalculatePointSize()
	{
		var step = Security?.PriceStep ?? 0m;
		return step > 0m ? step : 0.0001m;
	}
}