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();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, VolumeWeightedAveragePrice, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class intraday_momentum_strategy(Strategy):
"""
Intraday momentum using EMA crossover, RSI and VWAP filters.
Trades within session hours with percentage-based SL/TP.
"""
def __init__(self):
super(intraday_momentum_strategy, self).__init__()
self._ema_fast_length = self.Param("EmaFastLength", 9) \
.SetDisplay("Fast EMA Length", "Period for fast EMA", "Indicators")
self._ema_slow_length = self.Param("EmaSlowLength", 21) \
.SetDisplay("Slow EMA Length", "Period for slow EMA", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period", "Indicators")
self._rsi_overbought = self.Param("RsiOverbought", 70) \
.SetDisplay("RSI Overbought", "Overbought level", "Indicators")
self._rsi_oversold = self.Param("RsiOversold", 30) \
.SetDisplay("RSI Oversold", "Oversold level", "Indicators")
self._stop_loss_perc = self.Param("StopLossPerc", 1.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management")
self._take_profit_perc = self.Param("TakeProfitPerc", 2.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk Management")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Session Start Hour", "Trading session start hour", "Session")
self._start_minute = self.Param("StartMinute", 0) \
.SetDisplay("Session Start Minute", "Trading session start minute", "Session")
self._end_hour = self.Param("EndHour", 23) \
.SetDisplay("Session End Hour", "Trading session end hour", "Session")
self._end_minute = self.Param("EndMinute", 59) \
.SetDisplay("Session End Minute", "Trading session end minute", "Session")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_set = False
self._entry_price = 0.0
self._stop_loss = 0.0
self._take_profit = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(intraday_momentum_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_set = False
self._entry_price = 0.0
self._stop_loss = 0.0
self._take_profit = 0.0
def OnStarted2(self, time):
super(intraday_momentum_strategy, self).OnStarted2(time)
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self._ema_fast_length.Value
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self._ema_slow_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
vwap = VolumeWeightedAveragePrice()
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema_fast, ema_slow, rsi, vwap, self._process_candle).Start()
def _process_candle(self, candle, ema_fast_val, ema_slow_val, rsi_val, vwap_val):
if candle.State != CandleStates.Finished:
return
if ema_fast_val.IsEmpty or ema_slow_val.IsEmpty or rsi_val.IsEmpty or vwap_val.IsEmpty:
return
ema_fast = float(IndicatorHelper.ToDecimal(ema_fast_val))
ema_slow = float(IndicatorHelper.ToDecimal(ema_slow_val))
rsi = float(IndicatorHelper.ToDecimal(rsi_val))
vwap = float(IndicatorHelper.ToDecimal(vwap_val))
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
time_of_day = candle.OpenTime.TimeOfDay
start = TimeSpan(self._start_hour.Value, self._start_minute.Value, 0)
end = TimeSpan(self._end_hour.Value, self._end_minute.Value, 0)
in_session = time_of_day >= start and time_of_day <= end
if not in_session:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
return
if not self._prev_set:
self._prev_fast = ema_fast
self._prev_slow = ema_slow
self._prev_set = True
return
crossover = self._prev_fast <= self._prev_slow and ema_fast > ema_slow
crossunder = self._prev_fast >= self._prev_slow and ema_fast < ema_slow
self._prev_fast = ema_fast
self._prev_slow = ema_slow
sl_pct = self._stop_loss_perc.Value
tp_pct = self._take_profit_perc.Value
long_cond = crossover and rsi < self._rsi_overbought.Value and close > vwap
short_cond = crossunder and rsi > self._rsi_oversold.Value and close < vwap
if long_cond and self.Position <= 0:
self._entry_price = close
self._stop_loss = close * (1.0 - sl_pct / 100.0)
self._take_profit = close * (1.0 + tp_pct / 100.0)
self.BuyMarket()
elif short_cond and self.Position >= 0:
self._entry_price = close
self._stop_loss = close * (1.0 + sl_pct / 100.0)
self._take_profit = close * (1.0 - tp_pct / 100.0)
self.SellMarket()
if self.Position > 0:
if low <= self._stop_loss or high >= self._take_profit:
self.SellMarket()
elif self.Position < 0:
if high >= self._stop_loss or low <= self._take_profit:
self.BuyMarket()
def CreateClone(self):
return intraday_momentum_strategy()