This strategy is a C# port of the MetaTrader 4 expert Frbestexp02_1_maloma_mod.mq4. It combines OsMA momentum, fractal reversals, tick volume confirmation and a rolling daily pivot filter to fade exhausted moves on the M15 timeframe.
Trading logic
Session pivot – a rolling pivot point is computed from the highest high, lowest low and the oldest close inside a configurable window (96 candles by default, equal to one trading day on M15). Only trades that agree with the pivot bias are allowed: shorts above the pivot and longs below it.
Fractal pattern – the strategy waits for a confirmed Bill Williams fractal three candles back. Down fractals (swing lows) enable shorts, while up fractals (swing highs) enable longs.
OsMA histogram – a MACD histogram (fast 12, slow 26, signal 9 by default) must be sloping further into negative territory for shorts and higher into positive territory for longs. The previous histogram reading also has to be on the same side of zero.
Volume filter – the volume of the previous finished candle must exceed a configurable threshold and be larger than the volume two candles ago. This reproduces the tick volume spike requirement from the original expert.
Order timing – trades are throttled by a minimum interval (20 seconds by default) between entries.
Risk management – configurable stop-loss, take-profit and optional trailing stop are expressed in points and converted to instrument prices. Protective orders are updated with the built-in SetStopLoss/SetTakeProfit helpers.
Parameters
Name
Description
Default
Volume
Order volume used for every entry.
1
StopLossPoints
Stop-loss distance in instrument points.
1000
TakeProfitPoints
Take-profit distance in instrument points.
1000
TrailingStopPoints
Optional trailing stop distance in points (0 disables trailing).
0
VolumeThreshold
Minimum previous candle volume required to enable a signal.
MACD parameters used to compute the OsMA histogram.
12 / 26 / 9
PivotWindow
Number of finished candles included in the pivot calculation.
96
MinTradeIntervalSeconds
Minimum number of seconds between new entries.
20
CandleType
Primary timeframe (defaults to 15-minute candles).
M15
Differences versus the MQL4 expert
The original code supported hedging orders multiplied by kh and complex profit recycling logic. The StockSharp version executes a single directional position and closes or reverses it before opening a new trade.
Trailing stop handling is simplified to use the standard SetStopLoss helper instead of manually modifying orders per tick.
Profit aggregation and martingale-style recovery blocks are omitted. Exit management relies on stop-loss, take-profit or trailing stop.
All indicator calculations are event-driven on finished candles. There is no intrabar order modification.
Usage notes
Attach the strategy to an instrument that supplies tick volume data if the volume filter should match the original behaviour.
Keep the timeframe at 15 minutes to reproduce the original calibration of the pivot window and fractal lookback.
Adjust the VolumeThreshold and OsMA periods to fit symbols with different volatility or volume profiles.
Enable the trailing stop only when a tighter exit is desired; otherwise leave it at zero to rely on the static stop/target.
The code follows the high-level StockSharp API guidelines: candle subscriptions via SubscribeCandles, indicator binding for the MACD histogram, and safe execution through BuyMarket/SellMarket with automatic protective orders.
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>
/// FrBestExp02 Maloma Mod strategy - MACD histogram crossover with EMA trend filter.
/// Buys when MACD histogram crosses above zero while price is above EMA.
/// Sells when MACD histogram crosses below zero while price is below EMA.
/// </summary>
public class FrBestExp02MalomaModStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevMacd;
private decimal _prevSignal;
private bool _hasPrev;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public FrBestExp02MalomaModStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetDisplay("EMA Period", "EMA trend filter", "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(); _prevMacd = 0m; _prevSignal = 0m; _hasPrev = false; _currentEma = 0m; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var macd = new MovingAverageConvergenceDivergenceSignal();
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessMacd)
.Bind(ema, ProcessEma)
.Start();
}
private decimal _currentEma;
private void ProcessEma(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished)
return;
_currentEma = ema;
}
private void ProcessMacd(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!value.IsFinal || value.IsEmpty)
return;
var macdVal = value as MovingAverageConvergenceDivergenceSignalValue;
if (macdVal == null)
return;
var macdLine = macdVal.Macd;
var signalLine = macdVal.Signal;
if (macdLine == null || signalLine == null)
return;
var histogram = macdLine.Value - signalLine.Value;
if (!_hasPrev)
{
_prevMacd = macdLine.Value;
_prevSignal = signalLine.Value;
_hasPrev = true;
return;
}
var prevHist = _prevMacd - _prevSignal;
var close = candle.ClosePrice;
// Histogram crosses above zero + bullish trend
if (prevHist <= 0 && histogram > 0 && close > _currentEma && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Histogram crosses below zero + bearish trend
else if (prevHist >= 0 && histogram < 0 && close < _currentEma && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevMacd = macdLine.Value;
_prevSignal = signalLine.Value;
}
}
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 MovingAverageConvergenceDivergenceSignal, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class fr_best_exp02_maloma_mod_strategy(Strategy):
def __init__(self):
super(fr_best_exp02_maloma_mod_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev = False
self._current_ema = 0.0
@property
def ema_period(self):
return self._ema_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fr_best_exp02_maloma_mod_strategy, self).OnReseted()
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev = False
self._current_ema = 0.0
def OnStarted2(self, time):
super(fr_best_exp02_maloma_mod_strategy, self).OnStarted2(time)
self._has_prev = False
macd = MovingAverageConvergenceDivergenceSignal()
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription \
.BindEx(macd, self.process_macd) \
.Bind(ema, self.process_ema) \
.Start()
def process_ema(self, candle, ema):
if candle.State != CandleStates.Finished:
return
self._current_ema = float(ema)
def process_macd(self, candle, value):
if candle.State != CandleStates.Finished:
return
if not value.IsFinal or value.IsEmpty:
return
if value.Macd is None or value.Signal is None:
return
macd_line = float(value.Macd)
signal_line = float(value.Signal)
histogram = macd_line - signal_line
if not self._has_prev:
self._prev_macd = macd_line
self._prev_signal = signal_line
self._has_prev = True
return
prev_hist = self._prev_macd - self._prev_signal
close = float(candle.ClosePrice)
if prev_hist <= 0 and histogram > 0 and close > self._current_ema and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_hist >= 0 and histogram < 0 and close < self._current_ema and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_macd = macd_line
self._prev_signal = signal_line
def CreateClone(self):
return fr_best_exp02_maloma_mod_strategy()