View on GitHub

Intraday Momentum Strategy

Trades within a specified session using EMA crossover, RSI filter and VWAP confirmation. Goes long when the fast EMA crosses above the slow EMA, RSI is below the overbought level and price is above VWAP. Shorts on opposite conditions. Applies fixed stop-loss and take-profit percentages and closes any position at session end.

Parameters

  • EmaFastLength: Fast EMA length.
  • EmaSlowLength: Slow EMA length.
  • RsiLength: RSI period.
  • RsiOverbought: RSI overbought level.
  • RsiOversold: RSI oversold level.
  • StopLossPerc: Stop loss percentage.
  • TakeProfitPerc: Take profit percentage.
  • StartHour: Session start hour.
  • StartMinute: Session start minute.
  • EndHour: Session end hour.
  • EndMinute: Session end minute.
  • CandleType: Type of candles.
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>
/// Intraday momentum strategy using EMA crossover, RSI and VWAP.
/// </summary>
public class IntradayMomentumStrategy : Strategy
{
	private readonly StrategyParam<int> _emaFastLength;
	private readonly StrategyParam<int> _emaSlowLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _rsiOverbought;
	private readonly StrategyParam<int> _rsiOversold;
	private readonly StrategyParam<decimal> _stopLossPerc;
	private readonly StrategyParam<decimal> _takeProfitPerc;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _startMinute;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<int> _endMinute;
	private readonly StrategyParam<DataType> _candleType;


	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _prevSet;
	private decimal _entryPrice;
	private decimal _stopLoss;
	private decimal _takeProfit;

	/// <summary>
	/// Fast EMA period length.
	/// </summary>
	public int EmaFastLength { get => _emaFastLength.Value; set => _emaFastLength.Value = value; }

	/// <summary>
	/// Slow EMA period length.
	/// </summary>
	public int EmaSlowLength { get => _emaSlowLength.Value; set => _emaSlowLength.Value = value; }

	/// <summary>
	/// RSI period length.
	/// </summary>
	public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }

	/// <summary>
	/// RSI overbought level.
	/// </summary>
	public int RsiOverbought { get => _rsiOverbought.Value; set => _rsiOverbought.Value = value; }

	/// <summary>
	/// RSI oversold level.
	/// </summary>
	public int RsiOversold { get => _rsiOversold.Value; set => _rsiOversold.Value = value; }

	/// <summary>
	/// Stop-loss percentage.
	/// </summary>
	public decimal StopLossPerc { get => _stopLossPerc.Value; set => _stopLossPerc.Value = value; }

	/// <summary>
	/// Take-profit percentage.
	/// </summary>
	public decimal TakeProfitPerc { get => _takeProfitPerc.Value; set => _takeProfitPerc.Value = value; }

	/// <summary>
	/// Session start hour.
	/// </summary>
	public int StartHour { get => _startHour.Value; set => _startHour.Value = value; }

	/// <summary>
	/// Session start minute.
	/// </summary>
	public int StartMinute { get => _startMinute.Value; set => _startMinute.Value = value; }

	/// <summary>
	/// Session end hour.
	/// </summary>
	public int EndHour { get => _endHour.Value; set => _endHour.Value = value; }

	/// <summary>
	/// Session end minute.
	/// </summary>
	public int EndMinute { get => _endMinute.Value; set => _endMinute.Value = value; }

	/// <summary>
	/// Candle type for strategy calculation.
	/// </summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	/// <summary>
	/// Constructor.
	/// </summary>
	public IntradayMomentumStrategy()
	{
		_emaFastLength = Param(nameof(EmaFastLength), 9)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Length", "Period for fast EMA", "Indicators")
			
			.SetOptimize(5, 20, 1);

		_emaSlowLength = Param(nameof(EmaSlowLength), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA Length", "Period for slow EMA", "Indicators")
			
			.SetOptimize(10, 50, 1);

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "RSI period", "Indicators")
			
			.SetOptimize(7, 28, 1);

		_rsiOverbought = Param(nameof(RsiOverbought), 70)
			.SetRange(0, 100)
			.SetDisplay("RSI Overbought", "Overbought level", "Indicators")
			
			.SetOptimize(60, 90, 5);

		_rsiOversold = Param(nameof(RsiOversold), 30)
			.SetRange(0, 100)
			.SetDisplay("RSI Oversold", "Oversold level", "Indicators")
			
			.SetOptimize(10, 40, 5);

		_stopLossPerc = Param(nameof(StopLossPerc), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management")
			
			.SetOptimize(0.5m, 5m, 0.5m);

		_takeProfitPerc = Param(nameof(TakeProfitPerc), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit %", "Take profit percentage", "Risk Management")
			
			.SetOptimize(1m, 10m, 0.5m);

		_startHour = Param(nameof(StartHour), 0)
			.SetRange(0, 23)
			.SetDisplay("Session Start Hour", "Trading session start hour", "Session");

		_startMinute = Param(nameof(StartMinute), 0)
			.SetRange(0, 59)
			.SetDisplay("Session Start Minute", "Trading session start minute", "Session");

		_endHour = Param(nameof(EndHour), 23)
			.SetRange(0, 23)
			.SetDisplay("Session End Hour", "Trading session end hour", "Session");

		_endMinute = Param(nameof(EndMinute), 59)
			.SetRange(0, 59)
			.SetDisplay("Session End Minute", "Trading session end minute", "Session");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");
	}

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

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

		_prevFast = 0m;
		_prevSlow = 0m;
		_prevSet = false;
		_entryPrice = 0m;
		_stopLoss = 0m;
		_takeProfit = 0m;
	}

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

		var emaFastInd = new ExponentialMovingAverage { Length = EmaFastLength };
		var emaSlowInd = new ExponentialMovingAverage { Length = EmaSlowLength };
		var rsiInd = new RelativeStrengthIndex { Length = RsiLength };
		var vwapInd = new VolumeWeightedAveragePrice();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(emaFastInd, emaSlowInd, rsiInd, vwapInd, ProcessCandle)
			.Start();

		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue emaFastVal, IIndicatorValue emaSlowVal, IIndicatorValue rsiVal, IIndicatorValue vwapVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (emaFastVal.IsEmpty || emaSlowVal.IsEmpty || rsiVal.IsEmpty || vwapVal.IsEmpty)
			return;

		var emaFast = emaFastVal.ToDecimal();
		var emaSlow = emaSlowVal.ToDecimal();
		var rsi = rsiVal.ToDecimal();
		var vwap = vwapVal.ToDecimal();

		var time = candle.OpenTime.TimeOfDay;
		var start = new TimeSpan(StartHour, StartMinute, 0);
		var end = new TimeSpan(EndHour, EndMinute, 0);
		var inSession = time >= start && time <= end;

		if (!inSession)
		{
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();
			return;
		}

		if (!_prevSet)
		{
			_prevFast = emaFast;
			_prevSlow = emaSlow;
			_prevSet = true;
			return;
		}

		var crossover = _prevFast <= _prevSlow && emaFast > emaSlow;
		var crossunder = _prevFast >= _prevSlow && emaFast < emaSlow;

		_prevFast = emaFast;
		_prevSlow = emaSlow;

		var longCondition = crossover && rsi < RsiOverbought && candle.ClosePrice > vwap;
		var shortCondition = crossunder && rsi > RsiOversold && candle.ClosePrice < vwap;

		if (longCondition && Position <= 0)
		{
			_entryPrice = candle.ClosePrice;
			_stopLoss = _entryPrice * (1 - StopLossPerc / 100m);
			_takeProfit = _entryPrice * (1 + TakeProfitPerc / 100m);
			BuyMarket();
		}
		else if (shortCondition && Position >= 0)
		{
			_entryPrice = candle.ClosePrice;
			_stopLoss = _entryPrice * (1 + StopLossPerc / 100m);
			_takeProfit = _entryPrice * (1 - TakeProfitPerc / 100m);
			SellMarket();
		}

		if (Position > 0)
		{
			if (candle.LowPrice <= _stopLoss || candle.HighPrice >= _takeProfit)
				SellMarket();
		}
		else if (Position < 0)
		{
			if (candle.HighPrice >= _stopLoss || candle.LowPrice <= _takeProfit)
				BuyMarket();
		}
	}
}