This strategy ports the 15M Scalper MetaTrader expert advisor to the StockSharp high level API. It recreates the multi-filter
entry logic (weighted moving averages, stochastic oscillator, Parabolic SAR, multi-timeframe momentum and monthly MACD) and the
rich exit stack that combines money-based targets, trailing stops, break-even moves and an equity drawdown guard. The StockSharp
version operates on completed candles exactly like the EA and keeps the code event-driven while preserving the original
parameters.
How it Works
Trend Filter – fast and slow weighted moving averages calculated on the current timeframe (default 15 minutes) must be
aligned with the trade direction. The averages use the typical price ((High + Low + Close) / 3) to match the MQL PRICE_TYPICAL input.
Stochastic Reversal – a 5/3/3 stochastic oscillator is sampled at the previous two closed candles. Long signals require a
%K cross back above 20 while shorts require a cross below 80, mirroring the Stoc1/Stoc2 checks from the script.
Parabolic SAR Confirmation – the SAR value from the completed bar must be below the previous open for longs and above for
shorts, reproducing the safety filter SAR < Open[1] / SAR > Open[1].
Higher Timeframe Momentum – a 14-period momentum indicator on the configurable higher timeframe (default 1 hour) must
deviate from 100 on any of the last three closed bars by at least the buy/sell thresholds. This implements the
MomLevelB/MomLevelS trio without accessing indicator buffers directly.
Monthly MACD – a MACD series on the monthly candle stream (default 30-day bars) keeps the main line above the signal for
longs and below for shorts. The same MACD filter also powers the optional exit logic that closes positions when the lines
cross in the opposite direction.
Order Handling – when an opposite setup appears the strategy first flattens the existing position, then waits for the next
bar to open trades in the new direction. Volume scaling follows the EA’s martingale rule via LotExponent and the
loss-sensitive IncreaseFactor.
Risk Management
Stop Loss / Take Profit – distances are entered in MetaTrader "points" and are converted to absolute prices through
Security.PriceStep. For fractional FX ticks (price step < 1) the implementation multiplies the step by 10 to mimic the EA’s
pip handling.
Break-Even (“no loss”) – once price moves by BreakEvenTriggerSteps, the stop is virtually moved to the entry plus the
configured offset. If price retraces through that level the position is closed at market.
Trailing Stop – a candle-based trailing stop watches the highest high (for longs) or lowest low (for shorts). When the
retracement exceeds TrailingStopSteps, the position is closed, duplicating the original OrderModify behaviour.
Money Targets – UseProfitTargetMoney, UseProfitTargetPercent and EnableMoneyTrailing work with floating P&L measured
via PriceStep × StepPrice. The port keeps the take-profit, percentage target and trailing drawdown (MoneyTrailingStop)
logic untouched.
Equity Stop – UseEquityStop tracks the peak of (initial capital + realised P&L + floating profit). If the current drawdown
exceeds TotalEquityRisk percent of that peak, every position is closed, replicating AccountEquityHigh() and
TotalEquityRisk from the EA.
Martingale Sizing – each additional trade in the same direction scales the volume by LotExponent. Consecutive losses boost
the next base volume by IncreaseFactor per loss, providing the same “adaptive” lot sizing as the MQL IncreaseFactor branch.
Parameters
Parameter
Description
CandleType
Primary working timeframe (default 15-minute candles).
MomentumCandleType
Higher timeframe for the momentum filter (default 1-hour candles).
MacdCandleType
Timeframe for the MACD trend filter (default 30-day candles).
FastMaPeriod, SlowMaPeriod
Lengths of the weighted moving averages that define the trend filter.
MomentumPeriod
Momentum length on the higher timeframe.
MomentumBuyThreshold, MomentumSellThreshold
Minimum absolute deviation from 100 required to allow long/short trades.
StopLossSteps, TakeProfitSteps
Protective stop and target distances in price steps. Set to zero to disable.
Martingale sizing options: initial lot, multiplier, loss-based increment and maximum additions.
UseExitByMacd
Close positions when the MACD main line crosses the signal against the trade.
Usage
Attach the strategy to a security and make sure Security.PriceStep and Security.StepPrice are filled. These values are used
to translate pip-based inputs and money targets into absolute numbers.
Adjust CandleType, MomentumCandleType and MacdCandleType if you want to run the scalper on different timeframes. The
defaults replicate the original 15 minute / 1 hour / monthly setup.
Tune the pip-based distances (StopLossSteps, TakeProfitSteps, TrailingStopSteps, break-even settings) to suit the tick
size of the instrument. Start with the provided defaults and increase them for more volatile markets.
Set money management preferences: decide whether to use monetary or percentage take profits, activate money trailing and
configure the equity stop if you want a safety net against deep drawdowns.
Launch the strategy. It will automatically subscribe to all required candle streams, plot indicators (if a chart is available)
and begin evaluating signals once every indicator has enough history.
Notes & Differences from the Original EA
The port uses StockSharp’s aggregated position model. When an opposite signal appears the current position is closed first and
the new direction is evaluated on the next candle, keeping behaviour deterministic.
Money-based calculations rely on Security.PriceStep and Security.StepPrice. If the venue does not provide these values the
money targets are skipped (floating profit is reported as zero), exactly as noted in the code comments.
IncreaseFactor adds IncreaseFactor × consecutiveLosses to the next base volume instead of using free margin (which is not
available in the sandbox environment). This still captures the intention of enlarging size after streaks of losses.
All decisions are made on finished candles to avoid double-counting signals, matching the bar-by-bar checks of the MetaTrader
implementation.
The strategy draws the same indicators on the chart when a visualiser is available, aiding debugging and making the port easy to
compare with the EA.
Carefully review the tick size, step price and volume constraints of your broker before live trading. These values directly impact
how pip-based distances and money targets are converted inside the strategy.
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>
/// 15-minute scalper strategy using WMA crossover with ParabolicSar confirmation.
/// Buys when fast WMA crosses above slow WMA and SAR is below price.
/// Sells on reverse conditions.
/// </summary>
public class FifteenMinuteScalperStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast WMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow WMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public FifteenMinuteScalperStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 85)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow WMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// WMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 80;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class fifteen_minute_scalper_strategy(Strategy):
"""
15-minute scalper using WMA crossover with SL/TP and cooldown.
Buys when fast WMA crosses above slow WMA, sells on reverse.
"""
def __init__(self):
super(fifteen_minute_scalper_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20) \
.SetDisplay("Fast Period", "Fast WMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 85) \
.SetDisplay("Slow Period", "Slow WMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Trading timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fifteen_minute_scalper_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(fifteen_minute_scalper_strategy, self).OnStarted2(time)
fast = WeightedMovingAverage()
fast.Length = self._fast_period.Value
slow = WeightedMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = float(fast_val)
self._prev_slow = float(slow_val)
return
fast_val = float(fast_val)
slow_val = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
sl_pts = self._stop_loss_points.Value
tp_pts = self._take_profit_points.Value
if self.Position > 0 and self._entry_price > 0:
if sl_pts > 0 and close <= self._entry_price - sl_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close >= self._entry_price + tp_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if sl_pts > 0 and close >= self._entry_price + sl_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close <= self._entry_price - tp_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return fifteen_minute_scalper_strategy()