Внутридневная стратегия импульса
Торгуется в заданной сессии, используя пересечение EMA, фильтр RSI и подтверждение VWAP. Открывает лонг при пересечении быстрой EMA выше медленной, когда RSI ниже уровня перекупленности и цена выше VWAP. Шорт открывается при противоположных условиях. Используются фиксированные проценты стоп-лосса и тейк-профита, а в конце сессии позиции закрываются.
Параметры
- EmaFastLength: период быстрой EMA.
- EmaSlowLength: период медленной EMA.
- RsiLength: период RSI.
- RsiOverbought: уровень перекупленности RSI.
- RsiOversold: уровень перепроданности RSI.
- StopLossPerc: процент стоп-лосса.
- TakeProfitPerc: процент тейк-профита.
- StartHour: час начала сессии.
- StartMinute: минута начала сессии.
- EndHour: час окончания сессии.
- EndMinute: минута окончания сессии.
- CandleType: тип свечей.
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()