ComFracti Fractal RSI Strategy is a StockSharp port of the MetaTrader expert ComFracti. The algorithm searches for directional bias using Bill Williams fractals on two timeframes and filters the signals with a fast RSI calculated on daily candles. Once a valid setup appears, the strategy opens a single position, protects it with configurable stop-loss and take-profit distances, and can optionally exit when the signal reverses or when a holding time limit is reached.
The default configuration replicates the 15-minute trading timeframe with a 1-hour confirmation timeframe and a daily RSI length of three periods using the candle open price, just like the original expert.
Trading Logic
Fractal bias detection
Finished candles from the trading timeframe and the higher timeframe are processed through a five-bar fractal window.
The Primary*Shift and Higher*Shift parameters define how many bars back the strategy inspects for the latest confirmed fractal. The defaults match the original value of 3, meaning the code evaluates the fractal that was confirmed three candles ago.
A down fractal (swing low) without an accompanying up fractal is treated as bullish (+1). An up fractal without a down fractal is treated as bearish (-1).
Daily RSI filter
A RelativeStrengthIndex with the configurable RsiPeriod (default 3) runs on the daily timeframe and uses the candle open price, matching the MetaTrader implementation.
Long setups require the RSI to be below 50 - RsiBuyOffset; short setups require the RSI to be above 50 + RsiSellOffset.
Entry conditions
Buy: Both fractal trackers report +1 and the RSI filter is bullish. The strategy opens a long position if it is flat or short, sending enough volume to flip to the long side.
Sell: Both fractal trackers report -1 and the RSI filter is bearish. The strategy opens a short position if it is flat or long, sending enough volume to flip to the short side.
Position management
Protective stop-loss and take-profit levels are computed immediately after the position changes based on StopLossPips and TakeProfitPips multiplied by the instrument pip size.
The position can be closed when the price hits the stop or target, when ExpiryMinutes elapses, or when CloseOnOppositeSignal is enabled and the signal reverses.
Parameters
Name
Description
Default
Volume
Order volume used for every entry.
0.1
TakeProfitPips
Profit target distance in pips. Set to 0 to disable.
700
StopLossPips
Stop-loss distance in pips. Set to 0 to disable.
2500
ExpiryMinutes
Maximum holding time in minutes before forcing an exit. 0 disables the timer.
5555
CloseOnOppositeSignal
Close the active position when the signal flips to the opposite direction.
false
PrimaryBuyShift
Bars back to inspect the bullish fractal on the trading timeframe.
3
HigherBuyShift
Bars back to inspect the bullish fractal on the higher timeframe.
3
PrimarySellShift
Bars back to inspect the bearish fractal on the trading timeframe.
3
HigherSellShift
Bars back to inspect the bearish fractal on the higher timeframe.
3
RsiBuyOffset
Offset below 50 required for long setups.
3
RsiSellOffset
Offset above 50 required for short setups.
3
RsiPeriod
RSI length on the daily timeframe.
3
CandleType
Trading timeframe candle type.
15-minute candles
HigherTimeFrame
Confirmation timeframe candle type.
1-hour candles
DailyTimeFrame
Candle type used for the daily RSI.
1-day candles
Implementation Notes
The strategy uses the high-level candle subscription API (SubscribeCandles().Bind(...)) and manages indicators internally without exposing them through Strategy.Indicators, as required by the guidelines.
Fractals are computed via an internal helper that stores a rolling five-candle window and updates the signal only after a fractal is confirmed.
RSI values are retrieved via RelativeStrengthIndex.Process(...) with the candle open price, matching the MetaTrader PRICE_OPEN mode.
Only one position is maintained at a time. Market orders flip the position when needed by adding the volume required to cover an existing exposure.
Pip size is estimated from Security.PriceStep and Security.Decimals, using a 10x multiplier for assets quoted with three or more decimal places, reproducing the MetaTrader Point to pip conversion.
Usage Tips
The fractal shifts must be large enough to ensure the requested candle index exists. With a shift of three, the tracker requires at least five finished candles per timeframe before generating signals.
When trading instruments with different tick sizes (e.g., indices or stocks), adjust TakeProfitPips and StopLossPips to match the instrument pip definition.
Disabling CloseOnOppositeSignal replicates the original expert adviser behaviour (it was disabled by default) and relies solely on stops, targets, or the expiry timer for exits.
The strategy does not implement martingale or risk-based sizing; the MetaTrader lot calculation relied on account margin functions that are not available in StockSharp. Use the Volume parameter or wrap the strategy in a portfolio manager if dynamic position sizing is required.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ComFracti Fractal RSI: Fractal breakout with RSI filter and ATR stops.
/// </summary>
public class ComFractiFractalRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _entryPrice;
private decimal _prevHigh5;
private decimal _prevLow5;
private decimal _high1, _high2, _high3, _high4, _high5;
private decimal _low1, _low2, _low3, _low4, _low5;
private int _barCount;
public ComFractiFractalRsiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_barCount = 0;
_prevHigh5 = 0;
_prevLow5 = 0;
_high1 = _high2 = _high3 = _high4 = _high5 = 0;
_low1 = _low2 = _low3 = _low4 = _low5 = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_barCount = 0;
_prevHigh5 = 0;
_prevLow5 = 0;
_high1 = _high2 = _high3 = _high4 = _high5 = 0;
_low1 = _low2 = _low3 = _low4 = _low5 = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
// Shift fractal window
_high5 = _high4; _high4 = _high3; _high3 = _high2; _high2 = _high1;
_high1 = candle.HighPrice;
_low5 = _low4; _low4 = _low3; _low3 = _low2; _low2 = _low1;
_low1 = candle.LowPrice;
_barCount++;
if (_barCount < 5 || atrVal <= 0)
return;
var close = candle.ClosePrice;
// Detect fractal high (center bar _high3 is highest)
var fractalUp = _high3 > _high1 && _high3 > _high2 && _high3 > _high4 && _high3 > _high5;
// Detect fractal low (center bar _low3 is lowest)
var fractalDown = _low3 < _low1 && _low3 < _low2 && _low3 < _low4 && _low3 < _low5;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || fractalDown)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || fractalUp)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fractalDown && rsiVal < 45)
{
_entryPrice = close;
BuyMarket();
}
else if (fractalUp && rsiVal > 55)
{
_entryPrice = close;
SellMarket();
}
}
_prevHigh5 = _high5;
_prevLow5 = _low5;
}
}
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, AverageTrueRange
class com_fracti_fractal_rsi_strategy(Strategy):
def __init__(self):
super(com_fracti_fractal_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._entry_price = 0.0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = 0.0
self._high2 = 0.0
self._high3 = 0.0
self._high4 = 0.0
self._high5 = 0.0
self._low1 = 0.0
self._low2 = 0.0
self._low3 = 0.0
self._low4 = 0.0
self._low5 = 0.0
self._bar_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(com_fracti_fractal_rsi_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._bar_count = 0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = self._high2 = self._high3 = self._high4 = self._high5 = 0.0
self._low1 = self._low2 = self._low3 = self._low4 = self._low5 = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
av = float(atr_val)
# Shift fractal window
self._high5 = self._high4
self._high4 = self._high3
self._high3 = self._high2
self._high2 = self._high1
self._high1 = float(candle.HighPrice)
self._low5 = self._low4
self._low4 = self._low3
self._low3 = self._low2
self._low2 = self._low1
self._low1 = float(candle.LowPrice)
self._bar_count += 1
if self._bar_count < 5 or av <= 0:
return
close = float(candle.ClosePrice)
# Detect fractal high (center bar _high3 is highest)
fractal_up = (self._high3 > self._high1 and self._high3 > self._high2 and
self._high3 > self._high4 and self._high3 > self._high5)
# Detect fractal low (center bar _low3 is lowest)
fractal_down = (self._low3 < self._low1 and self._low3 < self._low2 and
self._low3 < self._low4 and self._low3 < self._low5)
if self.Position > 0:
if close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 2.0 or fractal_down:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 2.0 or fractal_up:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_high5 = self._high5
self._prev_low5 = self._low5
return
if self.Position == 0:
if fractal_down and rv < 45:
self._entry_price = close
self.BuyMarket()
elif fractal_up and rv > 55:
self._entry_price = close
self.SellMarket()
self._prev_high5 = self._high5
self._prev_low5 = self._low5
def OnReseted(self):
super(com_fracti_fractal_rsi_strategy, self).OnReseted()
self._entry_price = 0.0
self._bar_count = 0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = self._high2 = self._high3 = self._high4 = self._high5 = 0.0
self._low1 = self._low2 = self._low3 = self._low4 = self._low5 = 0.0
def CreateClone(self):
return com_fracti_fractal_rsi_strategy()