Dealers Trade MACD MQL4 Strategy
The Dealers Trade MACD MQL4 strategy is a direct conversion of the "Dealers Trade v7.74" expert advisor for MetaTrader 4. It keeps the pyramiding money management and the MACD slope logic of the original system while adapting position handling to StockSharp's netted accounts. The strategy is designed for swing trading on H4/D1 charts and continuously adds to the trend as long as momentum remains aligned with the MACD main line.
How the strategy works
- Signal detection – the strategy subscribes to candles of the configured timeframe and calculates a classic MACD indicator (fast EMA, slow EMA and signal EMA). A rising MACD main value compared to the previous bar signals bullish momentum, while a falling value signals bearish momentum. The
ReverseConditionparameter can be used to flip the direction when a contrarian approach is preferred. - Order spacing and scaling – only one directional basket is active at a time. When the MACD indicates a long trend, the strategy opens an initial market buy order. Additional buys are sent only when the price has moved down by at least
SpacingPips * PriceStepfrom the last entry price, mirroring the "averaging" behaviour from the MQL script. Short baskets behave symmetrically when the MACD slope turns negative. - Lot sizing – the base lot size is either the fixed
FixedVolumeor, ifUseRiskSizingis enabled, a value derived from the portfolio equity andRiskPercent. Mini accounts are supported through theIsStandardAccountflag that emulates the original "Account is normal" option. Every extra order within the same basket is multiplied byLotMultiplierand capped byMaxVolume. - Risk controls – hard stop loss and take profit levels are attached to each position using the
StopLossPipsandTakeProfitPipsdistances. Once a trade has moved byTrailingStopPips + SpacingPipsin profit the stop level is tightened to keep at leastTrailingStopPipsof profit, reproducing the trailing rule from the MetaTrader implementation. - Account protection – when the number of open trades reaches
MaxTrades - OrdersToProtectand the aggregate unrealised profit exceedsSecureProfit, the most recent trade is closed to lock in gains before new orders are considered. This corresponds to the "AccountProtection" block in the source EA.
Parameters
| Name | Default | Description |
|---|---|---|
CandleType |
H4 | Timeframe used for MACD calculations and signal evaluation. |
FixedVolume |
0.1 | Base lot size when UseRiskSizing is disabled. |
UseRiskSizing |
true | Enables balance based position sizing. |
RiskPercent |
2 | Percentage of equity used to size positions when UseRiskSizing is true. |
IsStandardAccount |
true | Set to false for mini accounts (lots divided by 10). |
MaxVolume |
5 | Maximum volume allowed for a single order. |
LotMultiplier |
1.5 | Multiplier applied to the base lot for each additional entry in the basket. |
MaxTrades |
5 | Maximum number of simultaneously open trades. |
SpacingPips |
4 | Minimum pip distance between consecutive entries. |
OrdersToProtect |
3 | Number of orders kept before the protection block can open new trades. |
AccountProtection |
true | Enables the secure profit protection logic. |
SecureProfit |
50 | Unrealised profit (in account currency) required to trigger protection. |
TakeProfitPips |
30 | Take profit distance per trade, expressed in pips. |
StopLossPips |
90 | Stop loss distance per trade, expressed in pips. |
TrailingStopPips |
15 | Trailing stop distance applied after activation. |
ReverseCondition |
false | Inverts the MACD slope interpretation. |
MacdFast |
14 | Fast EMA length for the MACD indicator. |
MacdSlow |
26 | Slow EMA length for the MACD indicator. |
MacdSignal |
1 | Signal EMA length for the MACD indicator. |
Notes and limitations
- StockSharp strategies manage a net position per security, therefore hedged long and short baskets cannot coexist. The original EA allowed hedging but the conversion closes the opposite side before switching direction.
- The secure profit logic calculates unrealised profit using the instrument
PriceStepandStepPricemetadata. Instruments without this information fallback to a nominal pip value of 0.0001 with a unit currency step, so adjust thresholds accordingly. - Risk based sizing requires a positive
StopLossPipsvalue. When the stop distance is zero the calculated risk amount becomes undefined and the strategy will skip trading. - The strategy works on closed candles only. Signals that relied on intrabar MACD movements in MetaTrader may appear a bar later in this implementation, but the behaviour is significantly more stable for backtesting.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Dealers Trade MACD strategy converted from the original MQL4 version (Dealers Trade v7.74).
/// </summary>
public class DealersTradeMacdMql4Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _fixedVolume;
private readonly StrategyParam<bool> _useRiskSizing;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<bool> _isStandardAccount;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<decimal> _lotMultiplier;
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<int> _spacingPips;
private readonly StrategyParam<int> _ordersToProtect;
private readonly StrategyParam<bool> _accountProtection;
private readonly StrategyParam<decimal> _secureProfit;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<bool> _reverseCondition;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private MovingAverageConvergenceDivergence _macd;
private List<PositionState> _positions;
private decimal? _previousMacd;
private decimal _pipSize;
private decimal _stepValue;
private int _cooldown;
/// <summary>
/// Initializes a new instance of <see cref="DealersTradeMacdMql4Strategy"/>.
/// </summary>
public DealersTradeMacdMql4Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for signals", "General");
_fixedVolume = Param(nameof(FixedVolume), 0.1m)
.SetDisplay("Fixed Volume", "Lot size when risk sizing is disabled", "Risk");
_useRiskSizing = Param(nameof(UseRiskSizing), true)
.SetDisplay("Use Risk Sizing", "Enable balance based money management", "Risk");
_riskPercent = Param(nameof(RiskPercent), 2m)
.SetDisplay("Risk Percent", "Percentage of equity used when sizing dynamically", "Risk");
_isStandardAccount = Param(nameof(IsStandardAccount), true)
.SetDisplay("Standard Account", "True for standard (1.0 lot) accounts, false for mini", "Risk");
_maxVolume = Param(nameof(MaxVolume), 5m)
.SetDisplay("Max Volume", "Upper cap for any single order", "Risk")
.SetGreaterThanZero();
_lotMultiplier = Param(nameof(LotMultiplier), 1.5m)
.SetDisplay("Lot Multiplier", "Multiplier applied to subsequent entries", "Money Management")
.SetGreaterThanZero();
_maxTrades = Param(nameof(MaxTrades), 1)
.SetDisplay("Max Trades", "Maximum simultaneous positions", "Money Management")
.SetGreaterThanZero();
_spacingPips = Param(nameof(SpacingPips), 200)
.SetDisplay("Spacing (pips)", "Minimum price movement before adding", "Money Management")
.SetNotNegative();
_ordersToProtect = Param(nameof(OrdersToProtect), 3)
.SetDisplay("Orders To Protect", "Number of trades kept when protection triggers", "Money Management")
.SetNotNegative();
_accountProtection = Param(nameof(AccountProtection), true)
.SetDisplay("Account Protection", "Close last trade once secure profit is reached", "Money Management");
_secureProfit = Param(nameof(SecureProfit), 50m)
.SetDisplay("Secure Profit", "Currency profit required to lock gains", "Money Management")
.SetNotNegative();
_takeProfitPips = Param(nameof(TakeProfitPips), 200)
.SetDisplay("Take Profit (pips)", "Take profit distance from entry", "Risk")
.SetNotNegative();
_stopLossPips = Param(nameof(StopLossPips), 500)
.SetDisplay("Stop Loss (pips)", "Initial stop loss distance", "Risk")
.SetNotNegative();
_trailingStopPips = Param(nameof(TrailingStopPips), 100)
.SetDisplay("Trailing Stop (pips)", "Trailing distance applied after activation", "Risk")
.SetNotNegative();
_reverseCondition = Param(nameof(ReverseCondition), false)
.SetDisplay("Reverse Condition", "Invert MACD slope interpretation", "General");
_macdFast = Param(nameof(MacdFast), 14)
.SetDisplay("MACD Fast", "Fast EMA length", "Indicators")
.SetGreaterThanZero();
_macdSlow = Param(nameof(MacdSlow), 26)
.SetDisplay("MACD Slow", "Slow EMA length", "Indicators")
.SetGreaterThanZero();
_macdSignal = Param(nameof(MacdSignal), 1)
.SetDisplay("MACD Signal", "Signal EMA length", "Indicators")
.SetGreaterThanZero();
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Fixed order volume in lots.
/// </summary>
public decimal FixedVolume
{
get => _fixedVolume.Value;
set => _fixedVolume.Value = value;
}
/// <summary>
/// Enables balance based position sizing.
/// </summary>
public bool UseRiskSizing
{
get => _useRiskSizing.Value;
set => _useRiskSizing.Value = value;
}
/// <summary>
/// Risk percentage applied when <see cref="UseRiskSizing"/> is true.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Indicates whether the account uses standard lot sizes.
/// </summary>
public bool IsStandardAccount
{
get => _isStandardAccount.Value;
set => _isStandardAccount.Value = value;
}
/// <summary>
/// Maximum volume allowed for a single order.
/// </summary>
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
/// <summary>
/// Multiplier applied to the base size for subsequent entries.
/// </summary>
public decimal LotMultiplier
{
get => _lotMultiplier.Value;
set => _lotMultiplier.Value = value;
}
/// <summary>
/// Maximum number of simultaneously open trades.
/// </summary>
public int MaxTrades
{
get => _maxTrades.Value;
set => _maxTrades.Value = value;
}
/// <summary>
/// Minimum spacing between entries expressed in pips.
/// </summary>
public int SpacingPips
{
get => _spacingPips.Value;
set => _spacingPips.Value = value;
}
/// <summary>
/// Number of orders that should remain protected before adding new exposure.
/// </summary>
public int OrdersToProtect
{
get => _ordersToProtect.Value;
set => _ordersToProtect.Value = value;
}
/// <summary>
/// Enables the secure profit exit block.
/// </summary>
public bool AccountProtection
{
get => _accountProtection.Value;
set => _accountProtection.Value = value;
}
/// <summary>
/// Profit target used by the protection block.
/// </summary>
public decimal SecureProfit
{
get => _secureProfit.Value;
set => _secureProfit.Value = value;
}
/// <summary>
/// Take profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Inverts the MACD slope interpretation.
/// </summary>
public bool ReverseCondition
{
get => _reverseCondition.Value;
set => _reverseCondition.Value = value;
}
/// <summary>
/// Fast EMA period for the MACD indicator.
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// Slow EMA period for the MACD indicator.
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// Signal EMA period for the MACD indicator.
/// </summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macd = null;
_positions = null;
_previousMacd = null;
_pipSize = 0;
_stepValue = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_positions = new List<PositionState>();
_macd = new MovingAverageConvergenceDivergence(
new ExponentialMovingAverage { Length = MacdSlow },
new ExponentialMovingAverage { Length = MacdFast }
);
_pipSize = GetPriceStep();
_stepValue = Security?.PriceStep ?? 0m;
if (_stepValue <= 0m)
_stepValue = 1m;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdResult)
{
if (candle.State != CandleStates.Finished)
return;
if (!macdResult.IsFinal || !_macd.IsFormed)
return;
var macdValue = macdResult.GetValue<decimal>();
UpdateTrailingAndStops(candle);
if (_cooldown > 0)
{
_cooldown--;
_previousMacd = macdValue;
return;
}
var openTrades = _positions.Count;
var allowNewTrade = openTrades < MaxTrades;
if (_previousMacd is null)
{
_previousMacd = macdValue;
return;
}
var direction = Math.Sign(macdValue - _previousMacd.Value);
if (ReverseCondition)
direction = -direction;
if (AccountProtection && openTrades >= Math.Max(1, MaxTrades - OrdersToProtect))
{
var totalProfit = CalculateTotalProfit(candle.ClosePrice);
if (totalProfit >= SecureProfit)
{
CloseLastPosition();
_cooldown = 3;
_previousMacd = macdValue;
return;
}
}
if (allowNewTrade && direction > 0)
TryOpen(Sides.Buy, candle);
else if (allowNewTrade && direction < 0)
TryOpen(Sides.Sell, candle);
_previousMacd = macdValue;
}
private void TryOpen(Sides side, ICandleMessage candle)
{
var price = candle.ClosePrice;
var spacing = SpacingPips * _pipSize;
if (side == Sides.Buy)
{
var reference = GetReferencePrice(Sides.Buy);
if (reference != 0m && reference - price < spacing)
return;
}
else
{
var reference = GetReferencePrice(Sides.Sell);
if (reference != 0m && price - reference < spacing)
return;
}
var volume = CalculateVolume();
if (volume <= 0m)
return;
var sameSideCount = CountPositions(side);
if (sameSideCount > 0)
{
volume *= Pow(LotMultiplier, sameSideCount);
}
volume = NormalizeVolume(Math.Min(volume, MaxVolume));
if (volume <= 0m)
return;
var stopDistance = StopLossPips * _pipSize;
var takeDistance = TakeProfitPips * _pipSize;
if (side == Sides.Buy)
BuyMarket();
else
SellMarket();
var state = new PositionState
{
Side = side,
Volume = volume,
EntryPrice = price,
StopPrice = stopDistance > 0m ? (side == Sides.Buy ? price - stopDistance : price + stopDistance) : (decimal?)null,
TakeProfitPrice = takeDistance > 0m ? (side == Sides.Buy ? price + takeDistance : price - takeDistance) : (decimal?)null
};
_positions.Add(state);
_cooldown = 3;
}
private void UpdateTrailingAndStops(ICandleMessage candle)
{
var trailingDistance = TrailingStopPips * _pipSize;
var activationDistance = (TrailingStopPips + SpacingPips) * _pipSize;
for (var i = _positions.Count - 1; i >= 0; i--)
{
var state = _positions[i];
if (state.Side == Sides.Buy)
{
if (state.TakeProfitPrice is decimal tp && candle.HighPrice >= tp)
{
SellMarket();
_positions.RemoveAt(i);
_cooldown = 3;
continue;
}
if (state.StopPrice is decimal sl && candle.LowPrice <= sl)
{
SellMarket();
_positions.RemoveAt(i);
_cooldown = 3;
continue;
}
if (TrailingStopPips > 0 && candle.ClosePrice - state.EntryPrice >= activationDistance)
{
var candidate = candle.ClosePrice - trailingDistance;
if (state.StopPrice is null || state.StopPrice < candidate)
state.StopPrice = candidate;
}
}
else
{
if (state.TakeProfitPrice is decimal tp && candle.LowPrice <= tp)
{
BuyMarket();
_positions.RemoveAt(i);
_cooldown = 3;
continue;
}
if (state.StopPrice is decimal sl && candle.HighPrice >= sl)
{
BuyMarket();
_positions.RemoveAt(i);
_cooldown = 3;
continue;
}
if (TrailingStopPips > 0 && state.EntryPrice - candle.ClosePrice >= activationDistance)
{
var candidate = candle.ClosePrice + trailingDistance;
if (state.StopPrice is null || state.StopPrice > candidate)
state.StopPrice = candidate;
}
}
}
}
private decimal CalculateVolume()
{
decimal baseVolume;
if (UseRiskSizing)
{
if (Portfolio is null)
return 0m;
var balance = Portfolio.CurrentValue ?? 0m;
if (balance <= 0m)
return 0m;
var rawLots = Math.Ceiling(balance * (RiskPercent / 100m) / 10000m);
if (!IsStandardAccount)
rawLots /= 10m;
baseVolume = rawLots;
}
else
{
baseVolume = FixedVolume;
}
return baseVolume;
}
private decimal CalculateTotalProfit(decimal currentPrice)
{
decimal profit = 0m;
foreach (var state in _positions)
{
var priceDifference = state.Side == Sides.Buy
? currentPrice - state.EntryPrice
: state.EntryPrice - currentPrice;
var steps = _pipSize > 0m ? priceDifference / _pipSize : priceDifference;
profit += steps * _stepValue * state.Volume;
}
return profit;
}
private void CloseLastPosition()
{
if (_positions.Count == 0)
return;
var index = _positions.Count - 1;
var state = _positions[index];
if (state.Side == Sides.Buy)
SellMarket();
else
BuyMarket();
_positions.RemoveAt(index);
}
private decimal GetReferencePrice(Sides side)
{
for (var i = _positions.Count - 1; i >= 0; i--)
{
var state = _positions[i];
if (state.Side == side)
return state.EntryPrice;
}
return 0m;
}
private int CountPositions(Sides side)
{
var count = 0;
for (var i = 0; i < _positions.Count; i++)
{
if (_positions[i].Side == side)
count++;
}
return count;
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m)
{
var steps = Math.Floor(volume / step);
if (steps <= 0)
steps = 1;
volume = steps * step;
}
else
{
volume = Math.Round(volume, 1, MidpointRounding.AwayFromZero);
if (volume <= 0m)
volume = 0.1m;
}
return volume;
}
private decimal GetPriceStep()
{
var step = Security?.PriceStep ?? 0m;
if (step > 0m)
return step;
var decimals = Security?.Decimals ?? 0;
if (decimals > 0)
return (decimal)Math.Pow(10, -decimals);
return 0.0001m;
}
private static decimal Pow(decimal value, int power)
{
if (power <= 0)
return 1m;
return (decimal)Math.Pow((double)value, power);
}
private sealed class PositionState
{
public Sides Side { get; set; }
public decimal Volume { get; set; }
public decimal EntryPrice { get; set; }
public decimal? StopPrice { get; set; }
public decimal? TakeProfitPrice { get; set; }
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergence, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class dealers_trade_macd_mql4_strategy(Strategy):
"""
Dealers Trade MACD strategy (v7.74 port from MQL4).
Uses MACD slope direction for trade signals with martingale grid,
trailing stops, account protection, and configurable money management.
"""
def __init__(self):
super(dealers_trade_macd_mql4_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe for signals", "General")
self._fixed_volume = self.Param("FixedVolume", 0.1) \
.SetDisplay("Fixed Volume", "Lot size when risk sizing is disabled", "Risk")
self._use_risk_sizing = self.Param("UseRiskSizing", True) \
.SetDisplay("Use Risk Sizing", "Enable balance based money management", "Risk")
self._risk_percent = self.Param("RiskPercent", 2.0) \
.SetDisplay("Risk Percent", "Percentage of equity used when sizing dynamically", "Risk")
self._is_standard_account = self.Param("IsStandardAccount", True) \
.SetDisplay("Standard Account", "True for standard accounts, false for mini", "Risk")
self._max_volume = self.Param("MaxVolume", 5.0) \
.SetDisplay("Max Volume", "Upper cap for any single order", "Risk")
self._lot_multiplier = self.Param("LotMultiplier", 1.5) \
.SetDisplay("Lot Multiplier", "Multiplier applied to subsequent entries", "Money Management")
self._max_trades = self.Param("MaxTrades", 1) \
.SetDisplay("Max Trades", "Maximum simultaneous positions", "Money Management")
self._spacing_pips = self.Param("SpacingPips", 200) \
.SetDisplay("Spacing (pips)", "Minimum price movement before adding", "Money Management")
self._orders_to_protect = self.Param("OrdersToProtect", 3) \
.SetDisplay("Orders To Protect", "Number of trades kept when protection triggers", "Money Management")
self._account_protection = self.Param("AccountProtection", True) \
.SetDisplay("Account Protection", "Close last trade once secure profit is reached", "Money Management")
self._secure_profit = self.Param("SecureProfit", 50.0) \
.SetDisplay("Secure Profit", "Currency profit required to lock gains", "Money Management")
self._take_profit_pips = self.Param("TakeProfitPips", 200) \
.SetDisplay("Take Profit (pips)", "Take profit distance from entry", "Risk")
self._stop_loss_pips = self.Param("StopLossPips", 500) \
.SetDisplay("Stop Loss (pips)", "Initial stop loss distance", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 100) \
.SetDisplay("Trailing Stop (pips)", "Trailing distance after activation", "Risk")
self._reverse_condition = self.Param("ReverseCondition", False) \
.SetDisplay("Reverse Condition", "Invert MACD slope interpretation", "General")
self._macd_fast = self.Param("MacdFast", 14) \
.SetDisplay("MACD Fast", "Fast EMA length", "Indicators")
self._macd_slow = self.Param("MacdSlow", 26) \
.SetDisplay("MACD Slow", "Slow EMA length", "Indicators")
self._macd_signal = self.Param("MacdSignal", 1) \
.SetDisplay("MACD Signal", "Signal EMA length", "Indicators")
self._positions = []
self._previous_macd = None
self._pip_size = 0.0
self._step_value = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dealers_trade_macd_mql4_strategy, self).OnReseted()
self._positions = []
self._previous_macd = None
def OnStarted2(self, time):
super(dealers_trade_macd_mql4_strategy, self).OnStarted2(time)
self._positions = []
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._macd_slow.Value
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._macd_fast.Value
macd = MovingAverageConvergenceDivergence(slow_ema, fast_ema)
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 = 0.0001
self._pip_size = step
self._step_value = step
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, self._process_candle).Start()
def _process_candle(self, candle, macd_result):
if candle.State != CandleStates.Finished:
return
if not macd_result.IsFinal:
return
macd_value = float(macd_result)
self._update_trailing_and_stops(candle)
open_trades = len(self._positions)
allow_new = open_trades < self._max_trades.Value
if self._previous_macd is None:
self._previous_macd = macd_value
return
direction = 0
if macd_value > self._previous_macd:
direction = 1
elif macd_value < self._previous_macd:
direction = -1
if self._reverse_condition.Value:
direction = -direction
if self._account_protection.Value and open_trades >= max(1, self._max_trades.Value - self._orders_to_protect.Value):
total_profit = self._calculate_total_profit(float(candle.ClosePrice))
if total_profit >= self._secure_profit.Value:
self._close_last_position()
self._previous_macd = macd_value
return
if allow_new and direction > 0:
self._try_open(True, candle)
elif allow_new and direction < 0:
self._try_open(False, candle)
self._previous_macd = macd_value
def _try_open(self, is_buy, candle):
price = float(candle.ClosePrice)
spacing = self._spacing_pips.Value * self._pip_size
side = Sides.Buy if is_buy else Sides.Sell
if is_buy:
ref = self._get_reference_price(Sides.Buy)
if ref != 0 and ref - price < spacing:
return
else:
ref = self._get_reference_price(Sides.Sell)
if ref != 0 and price - ref < spacing:
return
same_count = self._count_positions(side)
stop_distance = self._stop_loss_pips.Value * self._pip_size
take_distance = self._take_profit_pips.Value * self._pip_size
if is_buy:
self.BuyMarket()
else:
self.SellMarket()
state = {
"side": side,
"entry_price": price,
"stop_price": (price - stop_distance) if (is_buy and stop_distance > 0) else ((price + stop_distance) if (not is_buy and stop_distance > 0) else None),
"tp_price": (price + take_distance) if (is_buy and take_distance > 0) else ((price - take_distance) if (not is_buy and take_distance > 0) else None),
}
self._positions.append(state)
def _update_trailing_and_stops(self, candle):
trailing_distance = self._trailing_stop_pips.Value * self._pip_size
activation_distance = (self._trailing_stop_pips.Value + self._spacing_pips.Value) * self._pip_size
i = len(self._positions) - 1
while i >= 0:
state = self._positions[i]
if state["side"] == Sides.Buy:
if state["tp_price"] is not None and float(candle.HighPrice) >= state["tp_price"]:
self.SellMarket()
self._positions.pop(i)
i -= 1
continue
if state["stop_price"] is not None and float(candle.LowPrice) <= state["stop_price"]:
self.SellMarket()
self._positions.pop(i)
i -= 1
continue
if self._trailing_stop_pips.Value > 0 and float(candle.ClosePrice) - state["entry_price"] >= activation_distance:
candidate = float(candle.ClosePrice) - trailing_distance
if state.get("trailing") is None or state["trailing"] < candidate:
state["trailing"] = candidate
if state.get("trailing") is not None and float(candle.LowPrice) <= state["trailing"]:
self.SellMarket()
self._positions.pop(i)
i -= 1
continue
else:
if state["tp_price"] is not None and float(candle.LowPrice) <= state["tp_price"]:
self.BuyMarket()
self._positions.pop(i)
i -= 1
continue
if state["stop_price"] is not None and float(candle.HighPrice) >= state["stop_price"]:
self.BuyMarket()
self._positions.pop(i)
i -= 1
continue
if self._trailing_stop_pips.Value > 0 and state["entry_price"] - float(candle.ClosePrice) >= activation_distance:
candidate = float(candle.ClosePrice) + trailing_distance
if state.get("trailing") is None or state["trailing"] > candidate:
state["trailing"] = candidate
if state.get("trailing") is not None and float(candle.HighPrice) >= state["trailing"]:
self.BuyMarket()
self._positions.pop(i)
i -= 1
continue
i -= 1
def _calculate_total_profit(self, current_price):
profit = 0.0
for state in self._positions:
if state["side"] == Sides.Buy:
diff = current_price - state["entry_price"]
else:
diff = state["entry_price"] - current_price
steps = diff / self._pip_size if self._pip_size > 0 else diff
profit += steps * self._step_value
return profit
def _close_last_position(self):
if len(self._positions) == 0:
return
state = self._positions[-1]
if state["side"] == Sides.Buy:
self.SellMarket()
else:
self.BuyMarket()
self._positions.pop()
def _get_reference_price(self, side):
for i in range(len(self._positions) - 1, -1, -1):
if self._positions[i]["side"] == side:
return self._positions[i]["entry_price"]
return 0.0
def _count_positions(self, side):
count = 0
for p in self._positions:
if p["side"] == side:
count += 1
return count
def CreateClone(self):
return dealers_trade_macd_mql4_strategy()