Detailed C# port of the classic MetaTrader 4 expert advisor MQL/7696/Fractured_fractals.mq4. The strategy watches for newly confirmed
Williams fractal levels, queues breakout stop orders, and trails risk using the previous fractal swings. Position sizing follows the
original risk-per-trade logic with the adaptive "DecreaseFactor" volume reduction after drawdowns.
Details
Source: Converted from MQL/7696/Fractured_fractals.mq4.
Market Regime: Breakout continuation, works on any instrument that forms reliable fractal structures.
Order Types: Uses stop orders for entries and protective stop orders for exits.
Position Sizing: Percentage risk model controlled by MaximumRiskPercent with loss-streak damping via DecreaseFactor.
Default Parameters:
MaximumRiskPercent = 2%
DecreaseFactor = 3
CandleType = 1-hour time frame
Core Indicators: Native five-bar Williams fractal detection implemented in the strategy.
Strategy Type: Symmetric long/short breakout with fractal-based trailing stops.
Strategy Logic
Fractal detection
Maintains a rolling window of five candle highs and lows to reproduce MetaTrader's iFractals buffers.
A new up fractal is confirmed when the middle high exceeds the surrounding two highs on each side; a down fractal requires the
middle low to be the lowest in the five-bar sequence.
When a fresh fractal appears, it is stored together with the three previous values, mirroring the EA's cfu, pfu, and
pfu.1 style buffers for later comparisons and trailing calculations.
Entry setup
Long trades require the most recent up fractal to exceed the previous one and the latest down fractal to define a risk floor.
The strategy then places a buy stop slightly above the fractal (spread compensation) with a protective stop below the opposing
down fractal.
Short trades mirror the logic: a lower low fractal combined with a higher up fractal generates a sell stop and a protective
stop above the up fractal plus spread.
Only one pending order per direction is allowed. If fractal structure invalidates the pattern—for example, the latest fractal no
longer exceeds the previous one—the pending order is cancelled immediately.
Stop management
Once positioned, the bot trails the protective stop using the previous fractal on the entry side, subtracting/adding the current
spread. The stop only moves in the trade's favour.
When the position direction changes or closes, the unused stop order is cancelled to prevent stale exposure.
Risk management
CalculateOrderVolume replicates the EA's risk-per-trade calculation: position size is the ratio of monetary risk allowance to
the distance between entry and stop levels.
Account valuation prefers Portfolio.CurrentValue; if unavailable the routine falls back to the strategy's Volume property
multiplied by price.
After two or more consecutive losing trades the volume is reduced by losses / DecreaseFactor, emulating the MetaTrader
DecreaseFactor behaviour.
Trade cycle tracking
OnOwnTradeReceived aggregates fills into trade cycles, tracks floating PnL, and updates the loss streak once the volume returns
to flat. This keeps the risk logic aligned with the MT4 expert where HistoryTotal was used to analyse previous outcomes.
Usage Notes
Attach the strategy to any security/portfolio pair and choose an appropriate CandleType resolution that matches the original
EA setup.
Ensure level-1 quotes are available—spread estimation relies on best bid/ask; if unavailable the strategy falls back to
PriceStep.
The stop orders assume the broker supports server-side stops. Replace BuyStop/SellStop registration with market orders if
required by your adapter.
Because processing occurs on candle close, intrabar fractal signals are only acted upon at the end of each bar, reproducing the
expert advisor's bar-by-bar evaluation.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fractured Fractals strategy - Highest/Lowest channel breakout with ATR filter.
/// Buys when close crosses above channel midpoint and ATR confirms volatility.
/// Sells when close crosses below channel midpoint.
/// </summary>
public class FracturedFractalsMql4Strategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevMid;
private bool _hasPrev;
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public FracturedFractalsMql4Strategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 15)
.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "ATR lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevClose = 0m; _prevMid = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var highest = new Highest { Length = ChannelPeriod };
var lowest = new Lowest { Length = ChannelPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var mid = (highest + lowest) / 2;
if (!_hasPrev)
{
_prevClose = close;
_prevMid = mid;
_hasPrev = true;
return;
}
// Close crosses above midpoint = buy
if (_prevClose <= _prevMid && close > mid && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Close crosses below midpoint = sell
else if (_prevClose >= _prevMid && close < mid && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevClose = close;
_prevMid = mid;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class fractured_fractals_mql4_strategy(Strategy):
def __init__(self):
super(fractured_fractals_mql4_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 15).SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14).SetDisplay("ATR Period", "ATR lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
@property
def channel_period(self): return self._channel_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(fractured_fractals_mql4_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(fractured_fractals_mql4_strategy, self).OnStarted2(time)
self._has_prev = False
highest = Highest()
highest.Length = self.channel_period
lowest = Lowest()
lowest.Length = self.channel_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, self.process_candle).Start()
def process_candle(self, candle, highest, lowest):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
mid = (float(highest) + float(lowest)) / 2.0
if not self._has_prev:
self._prev_close = close
self._prev_mid = mid
self._has_prev = True
return
if self._prev_close <= self._prev_mid and close > mid and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close >= self._prev_mid and close < mid and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_mid = mid
def CreateClone(self):
return fractured_fractals_mql4_strategy()