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()