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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Extreme Strength Reversal strategy converted from MQL.
/// Enters counter-trend trades when price pierces Bollinger Bands and RSI shows an extreme reading.
/// Uses percent-based risk sizing with fixed stop-loss and take-profit distances expressed in pips.
/// </summary>
public class ExtremeStrengthReversalStrategy : Strategy
{
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<DataType> _candleType;
private BollingerBands _bollinger;
private RelativeStrengthIndex _rsi;
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
private decimal? _entryPrice;
/// <summary>
/// Risk percentage applied to portfolio equity for sizing.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in MetaTrader pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance expressed in MetaTrader pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Bollinger Bands lookback period.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for Bollinger Bands.
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// RSI lookback period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Overbought threshold for RSI.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// Oversold threshold for RSI.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public ExtremeStrengthReversalStrategy()
{
_riskPercent = Param(nameof(RiskPercent), 1m)
.SetGreaterThanZero()
.SetDisplay("Risk Percent", "Risk per trade as percentage of equity.", "Risk Management")
.SetOptimize(0.5m, 5m, 0.5m);
_stopLossPips = Param(nameof(StopLossPips), 150)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips.", "Risk Management")
.SetOptimize(50, 250, 10);
_takeProfitPips = Param(nameof(TakeProfitPips), 300)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips.", "Risk Management")
.SetOptimize(100, 400, 20);
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Number of candles used for Bollinger Bands.", "Indicators")
.SetOptimize(10, 40, 5);
_bollingerDeviation = Param(nameof(BollingerDeviation), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier for Bollinger Bands.", "Indicators")
.SetOptimize(1m, 3m, 0.25m);
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Number of candles used for RSI.", "Indicators")
.SetOptimize(7, 28, 1);
_rsiOverbought = Param(nameof(RsiOverbought), 65m)
.SetDisplay("RSI Overbought", "RSI level that marks extreme overbought conditions.", "Indicators")
.SetOptimize(60m, 90m, 5m);
_rsiOversold = Param(nameof(RsiOversold), 35m)
.SetDisplay("RSI Oversold", "RSI level that marks extreme oversold conditions.", "Indicators")
.SetOptimize(10m, 40m, 5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe used for analysis.", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bollinger = null;
_rsi = null;
ResetTradeState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollinger = new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerDeviation
};
_rsi = new RelativeStrengthIndex
{
Length = RsiPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bollinger, _rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue, IIndicatorValue rsiInd)
{
if (candle.State != CandleStates.Finished)
return;
if (bbValue is not IBollingerBandsValue bb)
return;
var middleBand = bb.MovingAverage ?? 0m;
var upperBand = bb.UpBand ?? 0m;
var lowerBand = bb.LowBand ?? 0m;
var rsiValue = rsiInd.ToDecimal();
if (middleBand == 0m)
return;
ManageOpenPosition(candle);
if (Position != 0m)
return;
var closePrice = candle.ClosePrice;
var openPrice = candle.OpenPrice;
var bullishReversal = rsiValue < RsiOversold && rsiValue > 0m && candle.LowPrice < lowerBand && closePrice > openPrice;
if (bullishReversal)
{
TryEnterLong(closePrice);
return;
}
var bearishReversal = rsiValue > RsiOverbought && candle.HighPrice > upperBand && closePrice < openPrice;
if (bearishReversal)
TryEnterShort(closePrice);
}
private void TryEnterLong(decimal closePrice)
{
var volume = CalculateOrderVolume();
if (volume <= 0m)
return;
if (Position < 0m)
BuyMarket(-Position);
BuyMarket(volume);
_entryPrice = closePrice;
_stopLossPrice = StopLossPips > 0 ? closePrice - GetPipOffset(StopLossPips) : null;
_takeProfitPrice = TakeProfitPips > 0 ? closePrice + GetPipOffset(TakeProfitPips) : null;
}
private void TryEnterShort(decimal closePrice)
{
var volume = CalculateOrderVolume();
if (volume <= 0m)
return;
if (Position > 0m)
SellMarket(Position);
SellMarket(volume);
_entryPrice = closePrice;
_stopLossPrice = StopLossPips > 0 ? closePrice + GetPipOffset(StopLossPips) : null;
_takeProfitPrice = TakeProfitPips > 0 ? closePrice - GetPipOffset(TakeProfitPips) : null;
}
private void ManageOpenPosition(ICandleMessage candle)
{
if (Position > 0m)
{
if (_stopLossPrice.HasValue && candle.LowPrice <= _stopLossPrice.Value)
{
SellMarket(Position);
ResetTradeState();
return;
}
if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
{
SellMarket(Position);
ResetTradeState();
}
}
else if (Position < 0m)
{
var shortPosition = -Position;
if (_stopLossPrice.HasValue && candle.HighPrice >= _stopLossPrice.Value)
{
BuyMarket(shortPosition);
ResetTradeState();
return;
}
if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
{
BuyMarket(shortPosition);
ResetTradeState();
}
}
else if (_stopLossPrice.HasValue || _takeProfitPrice.HasValue || _entryPrice.HasValue)
{
ResetTradeState();
}
}
private decimal CalculateOrderVolume()
{
if (Volume > 0m)
return Volume;
if (RiskPercent <= 0m)
return 0m;
var stopDistance = GetStopDistance();
if (stopDistance <= 0m)
return 0m;
var portfolio = Portfolio;
if (portfolio is null)
return 0m;
var equity = portfolio.CurrentValue ?? portfolio.BeginValue ?? 0m;
if (equity <= 0m)
return 0m;
var riskAmount = equity * RiskPercent / 100m;
if (riskAmount <= 0m)
return 0m;
var priceStep = Security?.PriceStep ?? 0m;
var stepPrice = 1m;
decimal perUnitRisk;
if (priceStep > 0m && stepPrice > 0m)
{
perUnitRisk = stopDistance / priceStep * stepPrice;
}
else
{
perUnitRisk = stopDistance;
}
if (perUnitRisk <= 0m)
return 0m;
var rawVolume = riskAmount / perUnitRisk;
if (rawVolume <= 0m)
return 0m;
rawVolume = NormalizeVolume(rawVolume);
return rawVolume;
}
private decimal NormalizeVolume(decimal volume)
{
var minVolume = Security?.MinVolume ?? 0m;
var maxVolume = Security?.MaxVolume ?? 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m && volume > 0m)
{
var steps = decimal.Floor(volume / step);
if (steps <= 0m)
steps = 1m;
volume = steps * step;
}
if (minVolume > 0m && volume < minVolume)
volume = minVolume;
if (maxVolume > 0m && volume > maxVolume)
volume = maxVolume;
return Math.Max(volume, 0m);
}
private decimal GetStopDistance()
{
if (StopLossPips <= 0)
return 0m;
return GetPipOffset(StopLossPips);
}
private decimal GetPipOffset(int pips)
{
var pipSize = GetPipSize();
if (pipSize <= 0m)
return 0m;
return pips * pipSize;
}
private decimal GetPipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step > 0m)
return step;
var decimals = Security?.Decimals;
if (decimals.HasValue && decimals.Value > 0)
{
var value = Math.Pow(10, -decimals.Value);
return Convert.ToDecimal(value);
}
return 0.0001m;
}
private void ResetTradeState()
{
_stopLossPrice = null;
_takeProfitPrice = null;
_entryPrice = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class extreme_strength_reversal_strategy(Strategy):
"""BB + RSI extreme reversal with pip-based SL/TP."""
def __init__(self):
super(extreme_strength_reversal_strategy, self).__init__()
self._risk_percent = self.Param("RiskPercent", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Risk Percent", "Risk per trade as percentage of equity", "Risk Management")
self._stop_loss_pips = self.Param("StopLossPips", 150) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk Management")
self._take_profit_pips = self.Param("TakeProfitPips", 300) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk Management")
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Bollinger Period", "Number of candles used for Bollinger Bands", "Indicators")
self._bollinger_deviation = self.Param("BollingerDeviation", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier for Bollinger Bands", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "Number of candles used for RSI", "Indicators")
self._rsi_overbought = self.Param("RsiOverbought", 65.0) \
.SetDisplay("RSI Overbought", "RSI level that marks extreme overbought conditions", "Indicators")
self._rsi_oversold = self.Param("RsiOversold", 35.0) \
.SetDisplay("RSI Oversold", "RSI level that marks extreme oversold conditions", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe used for analysis", "General")
self._stop_loss_price = None
self._take_profit_price = None
self._entry_price = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RiskPercent(self):
return self._risk_percent.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@property
def BollingerDeviation(self):
return self._bollinger_deviation.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@property
def RsiOverbought(self):
return self._rsi_overbought.Value
@property
def RsiOversold(self):
return self._rsi_oversold.Value
def OnReseted(self):
super(extreme_strength_reversal_strategy, self).OnReseted()
self._stop_loss_price = None
self._take_profit_price = None
self._entry_price = None
def OnStarted2(self, time):
super(extreme_strength_reversal_strategy, self).OnStarted2(time)
bollinger = BollingerBands()
bollinger.Length = self.BollingerPeriod
bollinger.Width = self.BollingerDeviation
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(bollinger, rsi, self._process_candle).Start()
def _process_candle(self, candle, bb_value, rsi_ind):
if candle.State != CandleStates.Finished:
return
upper_band = bb_value.UpBand
lower_band = bb_value.LowBand
if upper_band is None or lower_band is None:
return
upper = float(upper_band)
lower = float(lower_band)
rsi_value = float(rsi_ind)
self._manage_open_position(candle)
if self.Position != 0:
return
close_price = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
bullish_reversal = (rsi_value < float(self.RsiOversold) and rsi_value > 0
and float(candle.LowPrice) < lower and close_price > open_price)
if bullish_reversal:
self._try_enter_long(close_price)
return
bearish_reversal = (rsi_value > float(self.RsiOverbought)
and float(candle.HighPrice) > upper and close_price < open_price)
if bearish_reversal:
self._try_enter_short(close_price)
def _try_enter_long(self, close_price):
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self.BuyMarket()
pip_size = self._get_pip_size()
self._entry_price = close_price
self._stop_loss_price = close_price - self.StopLossPips * pip_size if self.StopLossPips > 0 else None
self._take_profit_price = close_price + self.TakeProfitPips * pip_size if self.TakeProfitPips > 0 else None
def _try_enter_short(self, close_price):
if self.Position > 0:
self.SellMarket(self.Position)
self.SellMarket()
pip_size = self._get_pip_size()
self._entry_price = close_price
self._stop_loss_price = close_price + self.StopLossPips * pip_size if self.StopLossPips > 0 else None
self._take_profit_price = close_price - self.TakeProfitPips * pip_size if self.TakeProfitPips > 0 else None
def _manage_open_position(self, candle):
if self.Position > 0:
if self._stop_loss_price is not None and float(candle.LowPrice) <= self._stop_loss_price:
self.SellMarket(self.Position)
self._reset_trade_state()
return
if self._take_profit_price is not None and float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket(self.Position)
self._reset_trade_state()
elif self.Position < 0:
short_position = abs(self.Position)
if self._stop_loss_price is not None and float(candle.HighPrice) >= self._stop_loss_price:
self.BuyMarket(short_position)
self._reset_trade_state()
return
if self._take_profit_price is not None and float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket(short_position)
self._reset_trade_state()
elif self._stop_loss_price is not None or self._take_profit_price is not None or self._entry_price is not None:
self._reset_trade_state()
def _get_pip_size(self):
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step > 0:
return step
return 0.0001
def _reset_trade_state(self):
self._stop_loss_price = None
self._take_profit_price = None
self._entry_price = None
def CreateClone(self):
return extreme_strength_reversal_strategy()