Flawless Victory Strategy
Flawless Victory is a modular momentum system that blends oscillators with Bollinger Bands. Depending on the selected version it can operate with simple RSI signals, apply fixed take-profit and stop-loss targets, or demand confirmation from the Money Flow Index. The goal is to exploit exhaustion at the edges of volatility bands and ride mean-reversion swings.
Version 1 enters when RSI leaves oversold or overbought zones near the Bollinger extremes. Version 2 adds explicit risk control via percentage based targets. Version 3 requires both RSI and MFI to agree, filtering out weak reversals.
The strategy performs best on intraday markets with clear volatility boundaries.
Details
- Entry Criteria:
- Long: see version rules (RSI <30 near lower band; version 3 also
MFI < 20) - Short: RSI >70 near upper band (version 3 also
MFI > 80)
- Long: see version rules (RSI <30 near lower band; version 3 also
- Long/Short: Both sides
- Exit Criteria:
- Version 1: opposite RSI signal
- Version 2: take-profit or stop-loss percentages
- Version 3: opposite RSI/MFI combo
- Stops: Optional in version 2
- Default Values:
RSI_length= 14MFI_length= 14BBLength= 20BBMultiplier= 2.0TakeProfitPct= 1.5StopLossPct= 1.0
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: RSI, MFI, Bollinger Bands
- Stops: Optional
- Complexity: Medium
- Timeframe: Short-term
- Seasonality: No
- Neural networks: No
- Divergence: Yes
- Risk level: Medium
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>
/// Flawless Victory strategy.
/// Goes long when the close is below the lower Bollinger band with RSI under 30 and short when it is above the upper band
/// with RSI over 70; the opposite signal reverses the position. Version 2 adds percent take-profit and stop-loss exits,
/// and Version 3 additionally requires MFI under 20 for longs and over 80 for shorts.
/// </summary>
public class FlawlessVictoryStrategy : Strategy
{
private const decimal _rsiOversold = 30m;
private const decimal _rsiOverbought = 70m;
private const decimal _mfiOversold = 20m;
private const decimal _mfiOverbought = 80m;
private readonly StrategyParam<int> _version;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _mfiLength;
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Strategy version: 1, 2 or 3.
/// </summary>
public int Version
{
get => _version.Value;
set => _version.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RSI_length
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// MFI period.
/// </summary>
public int MFI_length
{
get => _mfiLength.Value;
set => _mfiLength.Value = value;
}
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BBLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier.
/// </summary>
public decimal BBMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
/// <summary>
/// Take-profit percentage used by version 2.
/// </summary>
public decimal TakeProfitPct
{
get => _takeProfitPct.Value;
set => _takeProfitPct.Value = value;
}
/// <summary>
/// Stop-loss percentage used by version 2.
/// </summary>
public decimal StopLossPct
{
get => _stopLossPct.Value;
set => _stopLossPct.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FlawlessVictoryStrategy()
{
_version = Param(nameof(Version), 1)
.SetRange(1, 3)
.SetDisplay("Version", "1: RSI signals, 2: adds take-profit/stop-loss, 3: adds MFI confirmation", "General");
_rsiLength = Param(nameof(RSI_length), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_mfiLength = Param(nameof(MFI_length), 14)
.SetGreaterThanZero()
.SetDisplay("MFI Length", "MFI period", "Indicators");
_bbLength = Param(nameof(BBLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bbMultiplier = Param(nameof(BBMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("BB Multiplier", "Bollinger Bands standard deviation multiplier", "Indicators");
_takeProfitPct = Param(nameof(TakeProfitPct), 1.5m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take-profit percentage for version 2", "Risk");
_stopLossPct = Param(nameof(StopLossPct), 1.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage for version 2", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bollinger = new BollingerBands
{
Length = BBLength,
Width = BBMultiplier
};
var rsi = new RelativeStrengthIndex { Length = RSI_length };
var mfi = new MoneyFlowIndex { Length = MFI_length };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, rsi, mfi, ProcessCandle)
.Start();
if (Version == 2)
{
StartProtection(
TakeProfitPct > 0 ? new Unit(TakeProfitPct, UnitTypes.Percent) : new Unit(),
StopLossPct > 0 ? new Unit(StopLossPct, UnitTypes.Percent) : new Unit(),
useMarketOrders: true);
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, rsi);
DrawIndicator(oscillators, mfi);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue rsiValue, IIndicatorValue mfiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bollingerValue.IsFormed || !rsiValue.IsFormed || !mfiValue.IsFormed)
return;
var bb = (BollingerBandsValue)bollingerValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var rsi = rsiValue.ToDecimal();
var mfi = mfiValue.ToDecimal();
var close = candle.ClosePrice;
var useMfi = Version == 3;
var longSignal = close < lower && rsi < _rsiOversold && (!useMfi || mfi < _mfiOversold);
var shortSignal = close > upper && rsi > _rsiOverbought && (!useMfi || mfi > _mfiOverbought);
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex, MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
RSI_OVERSOLD = 30.0
RSI_OVERBOUGHT = 70.0
MFI_OVERSOLD = 20.0
MFI_OVERBOUGHT = 80.0
class flawless_victory_strategy(Strategy):
"""
Flawless Victory strategy.
Goes long when the close is below the lower Bollinger band with RSI under 30 and short when it is above the upper band
with RSI over 70; the opposite signal reverses the position. Version 2 adds percent take-profit and stop-loss exits,
and Version 3 additionally requires MFI under 20 for longs and over 80 for shorts.
"""
def __init__(self):
super(flawless_victory_strategy, self).__init__()
self._version = self.Param("Version", 1) \
.SetRange(1, 3) \
.SetDisplay("Version", "1: RSI signals, 2: adds take-profit/stop-loss, 3: adds MFI confirmation", "General")
self._rsi_length = self.Param("RSI_length", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period", "Indicators")
self._mfi_length = self.Param("MFI_length", 14) \
.SetGreaterThanZero() \
.SetDisplay("MFI Length", "MFI period", "Indicators")
self._bb_length = self.Param("BBLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators")
self._bb_multiplier = self.Param("BBMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("BB Multiplier", "Bollinger Bands standard deviation multiplier", "Indicators")
self._take_profit_pct = self.Param("TakeProfitPct", 1.5) \
.SetNotNegative() \
.SetDisplay("Take Profit %", "Take-profit percentage for version 2", "Risk")
self._stop_loss_pct = self.Param("StopLossPct", 1.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop-loss percentage for version 2", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candle type for strategy calculation", "General")
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnStarted2(self, time):
super(flawless_victory_strategy, self).OnStarted2(time)
bollinger = BollingerBands()
bollinger.Length = self._bb_length.Value
bollinger.Width = Decimal(self._bb_multiplier.Value)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
mfi = MoneyFlowIndex()
mfi.Length = self._mfi_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(bollinger, rsi, mfi, self._process_candle).Start()
if int(self._version.Value) == 2:
tp = float(self._take_profit_pct.Value)
sl = float(self._stop_loss_pct.Value)
self.StartProtection(
Unit(Decimal(tp), UnitTypes.Percent) if tp > 0 else Unit(),
Unit(Decimal(sl), UnitTypes.Percent) if sl > 0 else Unit(),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
self.DrawIndicator(oscillators, mfi)
def _process_candle(self, candle, bollinger_value, rsi_value, mfi_value):
if candle.State != CandleStates.Finished:
return
if not bollinger_value.IsFormed or not rsi_value.IsFormed or not mfi_value.IsFormed:
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
upper = float(bollinger_value.UpBand)
lower = float(bollinger_value.LowBand)
rsi = float(rsi_value.GetValue[Decimal](None))
mfi = float(mfi_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
use_mfi = int(self._version.Value) == 3
long_signal = close < lower and rsi < RSI_OVERSOLD and (not use_mfi or mfi < MFI_OVERSOLD)
short_signal = close > upper and rsi > RSI_OVERBOUGHT and (not use_mfi or mfi > MFI_OVERBOUGHT)
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return flawless_victory_strategy()