This strategy is a faithful conversion of the MetaTrader expert advisor "MA MACD Position averaging". It combines a weighted moving average filter with a MACD ratio check and adds a martingale-style averaging module that increases the position size whenever price moves adversely by a configurable number of pips. All risk parameters are configured in pip units and internally converted to price offsets using the instrument metadata provided by StockSharp.
Trading Logic
Indicator preparation
A configurable moving average (MaPeriod, MaMethod, MaAppliedPrice) is sampled on completed candles. The SignalBar and MaShift parameters emulate MetaTrader's ability to look back by a given number of bars and to plot the moving average with a horizontal offset.
A MACD indicator (MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod, MacdAppliedPrice) is processed on the same candles. The strategy stores the MACD main and signal lines in a small rolling buffer so that historical values can be accessed without calling indicator APIs directly.
Entry conditions
Long: both MACD lines are below zero, the ratio MACDmain / MACDsignal is greater than or equal to MacdRatio, the candle close is above the sampled moving average and the distance between price and the average is at least IndentPips pips.
Short: both MACD lines are above zero, the ratio is above MacdRatio, the candle close is below the moving average and the distance between them is at least IndentPips pips.
New entries are only allowed when the strategy has no exposure. When an averaging cycle is already in progress, the signal logic is skipped and only the averaging rules apply.
Averaging module
When a long position exists and price moves down by at least StepLossingPips from the best (lowest) long entry, the strategy opens an additional long trade whose volume equals the last leg volume multiplied by LotCoefficient (rounded by the instrument volume step).
When a short position exists and price moves up by at least StepLossingPips from the best (highest) short entry, a new short leg is added using the same LotCoefficient multiplier.
If exposure is detected in both directions (should never happen under normal conditions) the strategy immediately closes every leg to restore consistency.
Protective exits
Each leg stores individual stop-loss and take-profit levels expressed in price units (StopLossPips, TakeProfitPips). On every finished candle the strategy checks whether the candle range crossed any of the stored levels and, if so, exits the leg with a market order.
A trailing stop (TrailingStopPips, TrailingStepPips) is optional. Once price advances in favour of a leg by TrailingStopPips + TrailingStepPips, the stop is moved to TrailingStopPips pips behind the current close. The stop only tightens if price makes additional progress of at least TrailingStepPips pips.
Housekeeping
Volume commands are aligned to the instrument volume step and clipped to the allowed minimum/maximum. The strategy executes only on fully formed candles (CandleStates.Finished) to avoid double processing.
Parameters
Parameter
Type
Default
Description
CandleType
DataType
TimeSpan.FromHours(1).TimeFrame()
Timeframe used for indicator calculations.
OrderVolume
decimal
0.1
Base lot size for the initial entry.
StopLossPips
int
50
Stop-loss distance in pips (0 disables the stop).
TakeProfitPips
int
50
Take-profit distance in pips (0 disables the target).
TrailingStopPips
int
5
Trailing stop offset in pips. Must be positive to enable trailing.
TrailingStepPips
int
5
Extra pip distance required before the trailing stop moves again.
StepLossingPips
int
30
Price setback in pips that triggers a new averaging leg.
LotCoefficient
decimal
2.0
Multiplier applied to the previous leg volume when averaging.
SignalBar
int
0
Number of completed bars to look back when sampling indicators.
MaPeriod
int
15
Moving average length in bars.
MaShift
int
0
Horizontal shift (in bars) applied to the moving average values.
MaMethod
MovingAverageMethod
Weighted
Moving average smoothing algorithm (simple, exponential, smoothed, weighted).
MaAppliedPrice
AppliedPriceType
Weighted
Candle price used as input for the moving average.
IndentPips
int
4
Minimum pip gap required between price and the moving average before entering.
MacdFastPeriod
int
12
Fast EMA length of the MACD filter.
MacdSlowPeriod
int
26
Slow EMA length of the MACD filter.
MacdSignalPeriod
int
9
Signal line length of the MACD filter.
MacdAppliedPrice
AppliedPriceType
Weighted
Applied price used for the MACD calculation.
MacdRatio
decimal
0.9
Minimum MACD main/signal ratio required to allow trading.
Pip conversion
All pip-based settings (StopLossPips, TakeProfitPips, TrailingStopPips, TrailingStepPips, StepLossingPips, IndentPips) are multiplied by the security PriceStep. When the instrument has 3 or 5 decimal places the value is further multiplied by 10 to reproduce MetaTrader's "pip" definition for fractional quotes. If no price step is available a fallback of 0.0001 is used.
Implementation Notes
The strategy keeps an internal list of position legs because StockSharp operates in netting mode. Each leg tracks its own entry price, stop and take levels so that averaging behaves like the original MetaTrader EA.
Protective orders are simulated in software: when a candle touches a stop-loss or take-profit level the position is closed with a market order on that bar.
Averaging is disabled automatically when StepLossingPips is zero. Otherwise, every additional leg uses the previous leg volume multiplied by LotCoefficient and rounded down to the nearest volume step.
Trailing stop updates use the candle close as the current price proxy. The stop never moves in the adverse direction and remains inactive until the price progress exceeds TrailingStopPips + TrailingStepPips.
Indicator buffers honour the SignalBar and MaShift offsets so the decision logic sees exactly the same values that the MetaTrader expert would obtain from its indicator buffers.
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>
/// MA MACD strategy using WMA crossover with SL/TP management.
/// Buys when fast WMA crosses above slow WMA, sells on reverse cross.
/// </summary>
public class MaMacdPositionAveragingStrategy : 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 MaMacdPositionAveragingStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 100)
.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 ma_macd_position_averaging_strategy(Strategy):
"""
WMA crossover with SL/TP in price steps.
"""
def __init__(self):
super(ma_macd_position_averaging_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15).SetDisplay("Fast WMA", "Fast WMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 100).SetDisplay("Slow WMA", "Slow WMA period", "Indicators")
self._sl_points = self.Param("StopLossPoints", 200).SetDisplay("Stop Loss", "SL in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 400).SetDisplay("Take Profit", "TP in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "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(ma_macd_position_averaging_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(ma_macd_position_averaging_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
f = float(fast_val)
s = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = f
self._prev_slow = s
return
close = float(candle.ClosePrice)
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._prev_fast <= self._prev_slow and f > s 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 f < s and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
def CreateClone(self):
return ma_macd_position_averaging_strategy()