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;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RSI and EMA based strategy converted from the original MQL implementation.
/// Combines a custom RSI*EMA momentum oscillator with basic risk management.
/// </summary>
public class RsiMaStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _oversoldActivationLevel;
private readonly StrategyParam<decimal> _oversoldExtremeLevel;
private readonly StrategyParam<decimal> _overboughtActivationLevel;
private readonly StrategyParam<decimal> _overboughtExtremeLevel;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<bool> _useStopLoss;
private readonly StrategyParam<bool> _useTakeProfit;
private readonly StrategyParam<bool> _useTrailingStop;
private readonly StrategyParam<bool> _useMoneyManagement;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema;
private decimal? _previousIndicatorValue;
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
private decimal _entryPrice;
public RsiMaStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetDisplay("RSI Period", string.Empty, "Oscillator")
;
_oversoldActivationLevel = Param(nameof(OversoldActivationLevel), 40m)
.SetDisplay("Oversold Activation", string.Empty, "Oscillator")
;
_oversoldExtremeLevel = Param(nameof(OversoldExtremeLevel), 30m)
.SetDisplay("Oversold Extreme", string.Empty, "Oscillator");
_overboughtActivationLevel = Param(nameof(OverboughtActivationLevel), 60m)
.SetDisplay("Overbought Activation", string.Empty, "Oscillator")
;
_overboughtExtremeLevel = Param(nameof(OverboughtExtremeLevel), 70m)
.SetDisplay("Overbought Extreme", string.Empty, "Oscillator");
_stopLossPips = Param(nameof(StopLossPips), 399m)
.SetDisplay("Stop Loss (pips)", string.Empty, "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 999m)
.SetDisplay("Take Profit (pips)", string.Empty, "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 299m)
.SetDisplay("Trailing Stop (pips)", string.Empty, "Risk");
_useStopLoss = Param(nameof(UseStopLoss), true)
.SetDisplay("Use Stop Loss", string.Empty, "Risk");
_useTakeProfit = Param(nameof(UseTakeProfit), true)
.SetDisplay("Use Take Profit", string.Empty, "Risk");
_useTrailingStop = Param(nameof(UseTrailingStop), true)
.SetDisplay("Use Trailing Stop", string.Empty, "Risk");
_useMoneyManagement = Param(nameof(UseMoneyManagement), false)
.SetDisplay("Use Risk Percent Position Sizing", string.Empty, "Position");
_riskPercent = Param(nameof(RiskPercent), 10m)
.SetDisplay("Risk Percent", string.Empty, "Position");
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetDisplay("Fixed Volume", string.Empty, "Position");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle TimeFrame", string.Empty, "General");
}
/// <summary>
/// RSI calculation period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Activation threshold after an oversold extreme.
/// </summary>
public decimal OversoldActivationLevel
{
get => _oversoldActivationLevel.Value;
set => _oversoldActivationLevel.Value = value;
}
/// <summary>
/// Oversold extreme required before a long setup becomes valid.
/// </summary>
public decimal OversoldExtremeLevel
{
get => _oversoldExtremeLevel.Value;
set => _oversoldExtremeLevel.Value = value;
}
/// <summary>
/// Activation threshold after an overbought extreme.
/// </summary>
public decimal OverboughtActivationLevel
{
get => _overboughtActivationLevel.Value;
set => _overboughtActivationLevel.Value = value;
}
/// <summary>
/// Overbought extreme required before a short setup becomes valid.
/// </summary>
public decimal OverboughtExtremeLevel
{
get => _overboughtExtremeLevel.Value;
set => _overboughtExtremeLevel.Value = value;
}
/// <summary>
/// Stop-loss distance measured in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance measured in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing-stop distance measured in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Enable or disable stop-loss management.
/// </summary>
public bool UseStopLoss
{
get => _useStopLoss.Value;
set => _useStopLoss.Value = value;
}
/// <summary>
/// Enable or disable take-profit management.
/// </summary>
public bool UseTakeProfit
{
get => _useTakeProfit.Value;
set => _useTakeProfit.Value = value;
}
/// <summary>
/// Enable or disable trailing stop adjustments.
/// </summary>
public bool UseTrailingStop
{
get => _useTrailingStop.Value;
set => _useTrailingStop.Value = value;
}
/// <summary>
/// Enable or disable percent based position sizing.
/// </summary>
public bool UseMoneyManagement
{
get => _useMoneyManagement.Value;
set => _useMoneyManagement.Value = value;
}
/// <summary>
/// Portfolio risk percentage when money management is enabled.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Fixed volume used when money management is disabled.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Candle type used for signal generation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_ema = null;
_previousIndicatorValue = null;
_stopLossPrice = null;
_takeProfitPrice = null;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex
{
Length = RsiPeriod
};
_ema = new ExponentialMovingAverage
{
Length = RsiPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed)
return;
var indicatorValue = rsiValue;
if (_previousIndicatorValue is decimal previousValue)
{
ManageOpenPosition(candle, previousValue, indicatorValue);
EvaluateEntries(candle, previousValue, indicatorValue);
}
_previousIndicatorValue = indicatorValue;
}
private void ManageOpenPosition(ICandleMessage candle, decimal previousValue, decimal currentValue)
{
if (Position > 0)
{
var exitSignal = previousValue > OverboughtExtremeLevel && currentValue < OverboughtActivationLevel;
if (exitSignal)
{
SellMarket();
ResetRiskLevels();
return;
}
UpdateTrailingStopForLong(candle);
if (ShouldCloseLong(candle))
{
SellMarket();
ResetRiskLevels();
}
}
else if (Position < 0)
{
var exitSignal = previousValue < OversoldExtremeLevel && currentValue > OversoldActivationLevel;
if (exitSignal)
{
BuyMarket();
ResetRiskLevels();
return;
}
UpdateTrailingStopForShort(candle);
if (ShouldCloseShort(candle))
{
BuyMarket();
ResetRiskLevels();
}
}
}
private void EvaluateEntries(ICandleMessage candle, decimal previousValue, decimal currentValue)
{
var price = candle.ClosePrice;
if (price <= 0m)
return;
if (previousValue < OversoldExtremeLevel && currentValue > OversoldActivationLevel && Position <= 0)
{
_entryPrice = price;
InitializeRiskLevelsForLong(price);
BuyMarket();
}
else if (previousValue > OverboughtExtremeLevel && currentValue < OverboughtActivationLevel && Position >= 0)
{
_entryPrice = price;
InitializeRiskLevelsForShort(price);
SellMarket();
}
}
private void InitializeRiskLevelsForLong(decimal price)
{
var pipDistance = GetPipSize();
if (UseStopLoss && StopLossPips > 0m)
_stopLossPrice = price - pipDistance * StopLossPips;
else
_stopLossPrice = null;
if (UseTakeProfit && TakeProfitPips > 0m)
_takeProfitPrice = price + pipDistance * TakeProfitPips;
else
_takeProfitPrice = null;
}
private void InitializeRiskLevelsForShort(decimal price)
{
var pipDistance = GetPipSize();
if (UseStopLoss && StopLossPips > 0m)
_stopLossPrice = price + pipDistance * StopLossPips;
else
_stopLossPrice = null;
if (UseTakeProfit && TakeProfitPips > 0m)
_takeProfitPrice = price - pipDistance * TakeProfitPips;
else
_takeProfitPrice = null;
}
private void UpdateTrailingStopForLong(ICandleMessage candle)
{
if (!UseTrailingStop || TrailingStopPips <= 0m)
return;
var pipDistance = GetPipSize() * TrailingStopPips;
if (pipDistance <= 0m)
return;
var profit = candle.ClosePrice - _entryPrice;
if (profit <= pipDistance)
return;
var newStop = candle.ClosePrice - pipDistance;
if (_stopLossPrice is null || newStop > _stopLossPrice)
_stopLossPrice = newStop;
}
private void UpdateTrailingStopForShort(ICandleMessage candle)
{
if (!UseTrailingStop || TrailingStopPips <= 0m)
return;
var pipDistance = GetPipSize() * TrailingStopPips;
if (pipDistance <= 0m)
return;
var profit = _entryPrice - candle.ClosePrice;
if (profit <= pipDistance)
return;
var newStop = candle.ClosePrice + pipDistance;
if (_stopLossPrice is null || newStop < _stopLossPrice)
_stopLossPrice = newStop;
}
private bool ShouldCloseLong(ICandleMessage candle)
{
var stopHit = UseStopLoss && _stopLossPrice is decimal stop && candle.LowPrice <= stop;
var takeProfitHit = UseTakeProfit && _takeProfitPrice is decimal takeProfit && candle.HighPrice >= takeProfit;
return stopHit || takeProfitHit;
}
private bool ShouldCloseShort(ICandleMessage candle)
{
var stopHit = UseStopLoss && _stopLossPrice is decimal stop && candle.HighPrice >= stop;
var takeProfitHit = UseTakeProfit && _takeProfitPrice is decimal takeProfit && candle.LowPrice <= takeProfit;
return stopHit || takeProfitHit;
}
private void ResetRiskLevels()
{
_stopLossPrice = null;
_takeProfitPrice = null;
_entryPrice = 0m;
}
private decimal GetPipSize()
{
var priceStep = Security?.PriceStep;
if (priceStep is null || priceStep == 0m)
return 0.0001m;
return priceStep.Value;
}
private decimal GetOrderVolume(decimal price)
{
var volume = TradeVolume;
if (!UseMoneyManagement || Portfolio is null || price <= 0m)
return volume;
var portfolioValue = Portfolio.CurrentValue ?? 0m;
if (portfolioValue <= 0m)
return volume;
var riskAmount = portfolioValue * RiskPercent / 100m;
if (riskAmount <= 0m)
return volume;
var estimatedVolume = riskAmount / price;
var volumeStep = Security?.VolumeStep ?? 0m;
if (volumeStep > 0m)
{
estimatedVolume = Math.Floor(estimatedVolume / volumeStep) * volumeStep;
}
if (estimatedVolume <= 0m)
estimatedVolume = volume;
return estimatedVolume;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import RelativeStrengthIndex
class rsi_ma_strategy(Strategy):
def __init__(self):
super(rsi_ma_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle TimeFrame", "", "General")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "", "Oscillator")
self._oversold_activation = self.Param("OversoldActivationLevel", 40.0) \
.SetDisplay("Oversold Activation", "", "Oscillator")
self._oversold_extreme = self.Param("OversoldExtremeLevel", 30.0) \
.SetDisplay("Oversold Extreme", "", "Oscillator")
self._overbought_activation = self.Param("OverboughtActivationLevel", 60.0) \
.SetDisplay("Overbought Activation", "", "Oscillator")
self._overbought_extreme = self.Param("OverboughtExtremeLevel", 70.0) \
.SetDisplay("Overbought Extreme", "", "Oscillator")
self._stop_loss_pips = self.Param("StopLossPips", 399.0) \
.SetDisplay("Stop Loss (pips)", "", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 999.0) \
.SetDisplay("Take Profit (pips)", "", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 299.0) \
.SetDisplay("Trailing Stop (pips)", "", "Risk")
self._use_stop_loss = self.Param("UseStopLoss", True) \
.SetDisplay("Use Stop Loss", "", "Risk")
self._use_take_profit = self.Param("UseTakeProfit", True) \
.SetDisplay("Use Take Profit", "", "Risk")
self._use_trailing_stop = self.Param("UseTrailingStop", True) \
.SetDisplay("Use Trailing Stop", "", "Risk")
self._prev_rsi = None
self._entry_price = 0.0
self._stop_loss_price = None
self._take_profit_price = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@property
def OversoldActivationLevel(self):
return float(self._oversold_activation.Value)
@property
def OversoldExtremeLevel(self):
return float(self._oversold_extreme.Value)
@property
def OverboughtActivationLevel(self):
return float(self._overbought_activation.Value)
@property
def OverboughtExtremeLevel(self):
return float(self._overbought_extreme.Value)
@property
def StopLossPips(self):
return float(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return float(self._take_profit_pips.Value)
@property
def TrailingStopPips(self):
return float(self._trailing_stop_pips.Value)
@property
def UseStopLoss(self):
return self._use_stop_loss.Value
@property
def UseTakeProfit(self):
return self._use_take_profit.Value
@property
def UseTrailingStop(self):
return self._use_trailing_stop.Value
def OnStarted2(self, time):
super(rsi_ma_strategy, self).OnStarted2(time)
self._prev_rsi = None
self._entry_price = 0.0
self._stop_loss_price = None
self._take_profit_price = None
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self.ProcessCandle).Start()
def _get_pip_size(self):
sec = self.Security
if sec is not None:
ps = sec.PriceStep
if ps is not None and float(ps) > 0:
return float(ps)
return 0.0001
def ProcessCandle(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
if self._prev_rsi is None:
self._prev_rsi = rv
return
close = float(candle.ClosePrice)
low = float(candle.LowPrice)
high = float(candle.HighPrice)
prev = self._prev_rsi
# manage open position
if self.Position > 0:
exit_signal = prev > self.OverboughtExtremeLevel and rv < self.OverboughtActivationLevel
if exit_signal:
self.SellMarket()
self._reset_risk()
self._prev_rsi = rv
return
self._update_trailing_long(close)
if self._should_close_long(low, high):
self.SellMarket()
self._reset_risk()
self._prev_rsi = rv
return
elif self.Position < 0:
exit_signal = prev < self.OversoldExtremeLevel and rv > self.OversoldActivationLevel
if exit_signal:
self.BuyMarket()
self._reset_risk()
self._prev_rsi = rv
return
self._update_trailing_short(close)
if self._should_close_short(low, high):
self.BuyMarket()
self._reset_risk()
self._prev_rsi = rv
return
# evaluate entries
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
if close > 0:
if prev < self.OversoldExtremeLevel and rv > self.OversoldActivationLevel and self.Position <= 0:
self._entry_price = close
self._init_risk_long(close)
self.BuyMarket()
elif prev > self.OverboughtExtremeLevel and rv < self.OverboughtActivationLevel and self.Position >= 0:
self._entry_price = close
self._init_risk_short(close)
self.SellMarket()
self._prev_rsi = rv
def _init_risk_long(self, price):
pip = self._get_pip_size()
if self.UseStopLoss and self.StopLossPips > 0:
self._stop_loss_price = price - pip * self.StopLossPips
else:
self._stop_loss_price = None
if self.UseTakeProfit and self.TakeProfitPips > 0:
self._take_profit_price = price + pip * self.TakeProfitPips
else:
self._take_profit_price = None
def _init_risk_short(self, price):
pip = self._get_pip_size()
if self.UseStopLoss and self.StopLossPips > 0:
self._stop_loss_price = price + pip * self.StopLossPips
else:
self._stop_loss_price = None
if self.UseTakeProfit and self.TakeProfitPips > 0:
self._take_profit_price = price - pip * self.TakeProfitPips
else:
self._take_profit_price = None
def _update_trailing_long(self, close):
if not self.UseTrailingStop or self.TrailingStopPips <= 0:
return
pip = self._get_pip_size()
pip_dist = pip * self.TrailingStopPips
if pip_dist <= 0:
return
profit = close - self._entry_price
if profit <= pip_dist:
return
new_stop = close - pip_dist
if self._stop_loss_price is None or new_stop > self._stop_loss_price:
self._stop_loss_price = new_stop
def _update_trailing_short(self, close):
if not self.UseTrailingStop or self.TrailingStopPips <= 0:
return
pip = self._get_pip_size()
pip_dist = pip * self.TrailingStopPips
if pip_dist <= 0:
return
profit = self._entry_price - close
if profit <= pip_dist:
return
new_stop = close + pip_dist
if self._stop_loss_price is None or new_stop < self._stop_loss_price:
self._stop_loss_price = new_stop
def _should_close_long(self, low, high):
stop_hit = self.UseStopLoss and self._stop_loss_price is not None and low <= self._stop_loss_price
tp_hit = self.UseTakeProfit and self._take_profit_price is not None and high >= self._take_profit_price
return stop_hit or tp_hit
def _should_close_short(self, low, high):
stop_hit = self.UseStopLoss and self._stop_loss_price is not None and high >= self._stop_loss_price
tp_hit = self.UseTakeProfit and self._take_profit_price is not None and low <= self._take_profit_price
return stop_hit or tp_hit
def _reset_risk(self):
self._stop_loss_price = None
self._take_profit_price = None
self._entry_price = 0.0
def OnReseted(self):
super(rsi_ma_strategy, self).OnReseted()
self._prev_rsi = None
self._entry_price = 0.0
self._stop_loss_price = None
self._take_profit_price = None
def CreateClone(self):
return rsi_ma_strategy()