Стратегия MACD EA
Стратегия представляет собой портирование советника MetaTrader 5 MACD EA (barabashkakvn's edition).mq5 из папки MQL/20010 на платформу StockSharp. Реализована вся логика оригинала: сигналы по MACD, частичная фиксация прибыли и управление объёмом, но с использованием высокоуровневого API StockSharp.
Торговая логика
- Источник сигнала. Индикатор MACD с настраиваемыми периодами быстрых, медленных и сигнальных средних. Анализируется разница между линией MACD и сигнальной линией на двух и четырёх завершённых свечах назад. Переход разницы из отрицательной области в положительную открывает покупку, обратное условие — продажу.
- Сопровождение позиции. Для каждого входа задаются стоп-лосс и тейк-профит в пунктах. Значения переводятся в цены через шаг цены инструмента; при трёх или пяти знаках после запятой шаг дополнительно умножается на десять, как и в исходном коде.
- Частичное закрытие. При достижении прибыли в
PartialProfitPipsпунктов стратегия закрывает половину объёма, оставляя остальную часть позиции в рынке. - Перевод в безубыток. После прохождения
BreakevenPipsпунктов активируется защита безубытка. Если цена возвращается к цене входа, позиция закрывается по ней — аналогично переносу стопа в оригинальном советнике. - Обратный сигнал MACD. Любое оставшееся плечо закрывается при обратном пересечении MACD, чтобы не держать позицию против индикатора.
Управление капиталом
При включении UseMoneyManagement объём следующей сделки увеличивается после серии убытков. Множитель зависит от длины серии (x2 после одного убытка, x3 после двух и т.д. до x7 после шести и более). Множитель перемножается с параметром RiskMultiplier, что повторяет мартингейл-подход оригинала. Любая прибыльная сделка обнуляет счётчик убытков.
Параметры
| Параметр | Описание |
|---|---|
FastPeriod / SlowPeriod / SignalPeriod |
Периоды расчёта MACD. |
StopLossPips |
Дистанция до стоп-лосса в пунктах (0 — без стопа). |
TakeProfitPips |
Дистанция до тейк-профита в пунктах (0 — без тейка). |
PartialProfitPips |
Пункты до частичного закрытия (0 — без частичного выхода). |
BreakevenPips |
Порог для перевода в безубыток (0 — отключено). |
UseMoneyManagement |
Включает динамическое наращивание объёма. |
RiskMultiplier |
Дополнительный множитель при активном управлении капиталом. |
BaseVolume |
Базовый объём позиции до масштабирования. |
CandleType |
Тип свечей, по которым рассчитывается индикатор. |
Примечания
- Используется подписка на свечи и привязка индикаторов через высокоуровневый API.
- Версия на Python не подготовлена — реализован только C# вариант в каталоге
CS. - Модульные тесты не добавлялись и не изменялись согласно заданию.
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>
/// MACD crossover strategy converted from the "MACD EA" MetaTrader expert advisor.
/// Implements partial profit taking, breakeven logic, and optional money management scaling.
/// </summary>
public class MacdEaStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _partialProfitPips;
private readonly StrategyParam<int> _breakevenPips;
private readonly StrategyParam<bool> _useMoneyManagement;
private readonly StrategyParam<decimal> _riskMultiplier;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd;
private readonly List<decimal> _macdDiffs = new();
private decimal? _entryPrice;
private decimal _currentPositionVolume;
private int _entryDirection;
private bool _partialTaken;
private bool _breakevenActive;
private decimal _tradePnl;
private int _consecutiveLosses;
/// <summary>
/// Fast moving average period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow moving average period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Signal moving average period.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Profit target for closing half of the position in pips.
/// </summary>
public int PartialProfitPips
{
get => _partialProfitPips.Value;
set => _partialProfitPips.Value = value;
}
/// <summary>
/// Breakeven activation distance in pips.
/// </summary>
public int BreakevenPips
{
get => _breakevenPips.Value;
set => _breakevenPips.Value = value;
}
/// <summary>
/// Enables money management scaling when true.
/// </summary>
public bool UseMoneyManagement
{
get => _useMoneyManagement.Value;
set => _useMoneyManagement.Value = value;
}
/// <summary>
/// Multiplier applied to the base volume when money management is enabled.
/// </summary>
public decimal RiskMultiplier
{
get => _riskMultiplier.Value;
set => _riskMultiplier.Value = value;
}
/// <summary>
/// Base order volume.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MacdEaStrategy"/>.
/// </summary>
public MacdEaStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 55)
.SetGreaterThanZero()
.SetDisplay("Fast MA", "Fast moving average period", "Indicators")
.SetOptimize(10, 120, 5);
_slowPeriod = Param(nameof(SlowPeriod), 69)
.SetGreaterThanZero()
.SetDisplay("Slow MA", "Slow moving average period", "Indicators")
.SetOptimize(20, 200, 5);
_signalPeriod = Param(nameof(SignalPeriod), 90)
.SetGreaterThanZero()
.SetDisplay("Signal MA", "Signal moving average period", "Indicators")
.SetOptimize(10, 150, 5);
_stopLossPips = Param(nameof(StopLossPips), 80)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Risk")
.SetOptimize(0, 200, 10);
_takeProfitPips = Param(nameof(TakeProfitPips), 500)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in pips", "Risk")
.SetOptimize(0, 800, 20);
_partialProfitPips = Param(nameof(PartialProfitPips), 70)
.SetNotNegative()
.SetDisplay("Partial Profit", "Pips to close half the position", "Risk")
.SetOptimize(0, 200, 10);
_breakevenPips = Param(nameof(BreakevenPips), 0)
.SetNotNegative()
.SetDisplay("Breakeven", "Distance to activate breakeven", "Risk")
.SetOptimize(0, 200, 10);
_useMoneyManagement = Param(nameof(UseMoneyManagement), false)
.SetDisplay("Use MM", "Enable money management scaling", "Money Management");
_riskMultiplier = Param(nameof(RiskMultiplier), 1m)
.SetGreaterThanZero()
.SetDisplay("Risk Multiplier", "Multiplier applied to base volume", "Money Management")
.SetOptimize(0.5m, 5m, 0.5m);
_baseVolume = Param(nameof(BaseVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Default order size", "General")
.SetOptimize(0.1m, 5m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macdDiffs.Clear();
_entryPrice = null;
_currentPositionVolume = 0m;
_entryDirection = 0;
_partialTaken = false;
_breakevenActive = false;
_tradePnl = 0m;
_consecutiveLosses = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = BaseVolume;
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = FastPeriod }, LongMa = { Length = SlowPeriod } },
SignalMa = { Length = SignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var result = _macd.Process(candle);
if (!_macd.IsFormed)
return;
var macdValue = result as MovingAverageConvergenceDivergenceSignalValue;
if (macdValue == null)
return;
var macdLine = macdValue.Macd ?? 0m;
var signalLine = macdValue.Signal ?? 0m;
var diff = macdLine - signalLine;
_macdDiffs.Add(diff);
if (_macdDiffs.Count > 50)
_macdDiffs.RemoveRange(0, _macdDiffs.Count - 50);
if (_macdDiffs.Count < 5)
return;
var diffTwo = _macdDiffs[^3];
var diffFour = _macdDiffs[^5];
var bullish = diffTwo > 0m && diffFour < 0m;
var bearish = diffTwo < 0m && diffFour > 0m;
var pip = GetPipSize();
if (Position > 0m)
{
if (HandleLongPosition(candle, bearish, pip))
return;
}
else if (Position < 0m)
{
if (HandleShortPosition(candle, bullish, pip))
return;
}
if (Position != 0m)
return;
var volume = CalculateOrderVolume();
if (volume <= 0m)
return;
if (bullish)
{
BuyMarket(volume);
InitializeTradeState(candle.ClosePrice, volume, 1);
}
else if (bearish)
{
SellMarket(volume);
InitializeTradeState(candle.ClosePrice, volume, -1);
}
}
private bool HandleLongPosition(ICandleMessage candle, bool bearishSignal, decimal pip)
{
if (_entryPrice is not decimal entry)
return false;
var remainingVolume = _currentPositionVolume > 0m ? _currentPositionVolume : Math.Abs(Position);
remainingVolume = NormalizeVolume(remainingVolume);
if (remainingVolume <= 0m)
return false;
var stop = StopLossPips > 0 ? entry - StopLossPips * pip : (decimal?)null;
var take = TakeProfitPips > 0 ? entry + TakeProfitPips * pip : (decimal?)null;
var partial = PartialProfitPips > 0 ? entry + PartialProfitPips * pip : (decimal?)null;
var breakeven = BreakevenPips > 0 ? entry + BreakevenPips * pip : (decimal?)null;
if (stop is decimal stopPrice && candle.LowPrice <= stopPrice)
{
CloseLong(remainingVolume, stopPrice);
return true;
}
if (take is decimal takePrice && candle.HighPrice >= takePrice)
{
CloseLong(remainingVolume, takePrice);
return true;
}
if (!_partialTaken && partial is decimal partialPrice && candle.HighPrice >= partialPrice)
{
var halfVolume = NormalizeVolume(remainingVolume / 2m);
if (halfVolume > 0m)
{
SellMarket(halfVolume);
RegisterPnl(partialPrice, halfVolume);
_currentPositionVolume = Math.Max(0m, _currentPositionVolume - halfVolume);
_partialTaken = true;
return true;
}
}
if (breakeven is decimal breakevenPrice && !_breakevenActive && candle.HighPrice >= breakevenPrice)
_breakevenActive = true;
if (_breakevenActive && candle.LowPrice <= entry)
{
CloseLong(remainingVolume, entry);
return true;
}
if (bearishSignal)
{
CloseLong(remainingVolume, candle.ClosePrice);
return true;
}
return false;
}
private bool HandleShortPosition(ICandleMessage candle, bool bullishSignal, decimal pip)
{
if (_entryPrice is not decimal entry)
return false;
var remainingVolume = _currentPositionVolume > 0m ? _currentPositionVolume : Math.Abs(Position);
remainingVolume = NormalizeVolume(remainingVolume);
if (remainingVolume <= 0m)
return false;
var stop = StopLossPips > 0 ? entry + StopLossPips * pip : (decimal?)null;
var take = TakeProfitPips > 0 ? entry - TakeProfitPips * pip : (decimal?)null;
var partial = PartialProfitPips > 0 ? entry - PartialProfitPips * pip : (decimal?)null;
var breakeven = BreakevenPips > 0 ? entry - BreakevenPips * pip : (decimal?)null;
if (stop is decimal stopPrice && candle.HighPrice >= stopPrice)
{
CloseShort(remainingVolume, stopPrice);
return true;
}
if (take is decimal takePrice && candle.LowPrice <= takePrice)
{
CloseShort(remainingVolume, takePrice);
return true;
}
if (!_partialTaken && partial is decimal partialPrice && candle.LowPrice <= partialPrice)
{
var halfVolume = NormalizeVolume(remainingVolume / 2m);
if (halfVolume > 0m)
{
BuyMarket(halfVolume);
RegisterPnl(partialPrice, halfVolume);
_currentPositionVolume = Math.Max(0m, _currentPositionVolume - halfVolume);
_partialTaken = true;
return true;
}
}
if (breakeven is decimal breakevenPrice && !_breakevenActive && candle.LowPrice <= breakevenPrice)
_breakevenActive = true;
if (_breakevenActive && candle.HighPrice >= entry)
{
CloseShort(remainingVolume, entry);
return true;
}
if (bullishSignal)
{
CloseShort(remainingVolume, candle.ClosePrice);
return true;
}
return false;
}
private void CloseLong(decimal volume, decimal exitPrice)
{
volume = NormalizeVolume(volume);
if (volume <= 0m)
return;
SellMarket(volume);
RegisterPnl(exitPrice, volume);
_currentPositionVolume = Math.Max(0m, _currentPositionVolume - volume);
FinalizeTradeIfClosed();
}
private void CloseShort(decimal volume, decimal exitPrice)
{
volume = NormalizeVolume(volume);
if (volume <= 0m)
return;
BuyMarket(volume);
RegisterPnl(exitPrice, volume);
_currentPositionVolume = Math.Max(0m, _currentPositionVolume - volume);
FinalizeTradeIfClosed();
}
private void InitializeTradeState(decimal entryPrice, decimal volume, int direction)
{
_entryPrice = entryPrice;
_currentPositionVolume = NormalizeVolume(Math.Abs(volume));
_entryDirection = direction;
_partialTaken = false;
_breakevenActive = false;
_tradePnl = 0m;
}
private decimal CalculateOrderVolume()
{
var volume = BaseVolume;
if (UseMoneyManagement)
{
var multiplier = _consecutiveLosses switch
{
0 => 1m,
1 => 2m,
2 => 3m,
3 => 4m,
4 => 5m,
5 => 6m,
_ => 7m,
};
volume *= multiplier * RiskMultiplier;
}
return NormalizeVolume(volume);
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var sec = Security;
if (sec == null)
return volume;
var step = sec.VolumeStep ?? 1m;
if (step <= 0m)
step = 1m;
var steps = Math.Floor(volume / step);
volume = steps * step;
if (volume < step)
return 0m;
return volume;
}
private decimal GetPipSize()
{
var sec = Security;
var step = sec?.PriceStep ?? 1m;
if (step <= 0m)
return 1m;
var tmp = step;
var decimals = 0;
while (decimals < 10 && decimal.Truncate(tmp) != tmp)
{
tmp *= 10m;
decimals++;
}
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
private void RegisterPnl(decimal exitPrice, decimal volume)
{
if (_entryPrice is not decimal entry || _entryDirection == 0)
return;
var pnl = (exitPrice - entry) * volume * _entryDirection;
_tradePnl += pnl;
}
private void FinalizeTradeIfClosed()
{
if (_currentPositionVolume > 0m)
return;
if (_tradePnl > 0m)
_consecutiveLosses = 0;
else if (_tradePnl < 0m)
_consecutiveLosses++;
else
_consecutiveLosses = 0;
ResetTradeState();
}
private void ResetTradeState()
{
_entryPrice = null;
_currentPositionVolume = 0m;
_entryDirection = 0;
_partialTaken = false;
_breakevenActive = false;
_tradePnl = 0m;
}
}
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
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class macd_ea_strategy(Strategy):
def __init__(self):
super(macd_ea_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 55)
self._slow_period = self.Param("SlowPeriod", 69)
self._signal_period = self.Param("SignalPeriod", 90)
self._stop_loss_pips = self.Param("StopLossPips", 80)
self._take_profit_pips = self.Param("TakeProfitPips", 500)
self._partial_profit_pips = self.Param("PartialProfitPips", 70)
self._breakeven_pips = self.Param("BreakevenPips", 0)
self._use_money_management = self.Param("UseMoneyManagement", False)
self._risk_multiplier = self.Param("RiskMultiplier", 1.0)
self._base_volume = self.Param("BaseVolume", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._macd = None
self._macd_diffs = []
self._entry_price = None
self._current_position_volume = 0.0
self._entry_direction = 0
self._partial_taken = False
self._breakeven_active = False
self._trade_pnl = 0.0
self._consecutive_losses = 0
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def SignalPeriod(self):
return self._signal_period.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def PartialProfitPips(self):
return self._partial_profit_pips.Value
@property
def BreakevenPips(self):
return self._breakeven_pips.Value
@property
def UseMoneyManagement(self):
return self._use_money_management.Value
@property
def RiskMultiplier(self):
return self._risk_multiplier.Value
@property
def BaseVolume(self):
return self._base_volume.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(macd_ea_strategy, self).OnStarted2(time)
self.Volume = float(self.BaseVolume)
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = self.FastPeriod
self._macd.Macd.LongMa.Length = self.SlowPeriod
self._macd.SignalMa.Length = self.SignalPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ = CandleIndicatorValue(self._macd, candle)
civ.IsFinal = True
result = self._macd.Process(civ)
if not self._macd.IsFormed:
return
try:
macd_line = float(result.Macd) if result.Macd is not None else 0.0
signal_line = float(result.Signal) if result.Signal is not None else 0.0
except:
return
diff = macd_line - signal_line
self._macd_diffs.append(diff)
if len(self._macd_diffs) > 50:
self._macd_diffs = self._macd_diffs[-50:]
if len(self._macd_diffs) < 5:
return
diff_two = self._macd_diffs[-3]
diff_four = self._macd_diffs[-5]
bullish = diff_two > 0 and diff_four < 0
bearish = diff_two < 0 and diff_four > 0
pip = self._get_pip_size()
pos = float(self.Position)
if pos > 0:
if self._handle_long_position(candle, bearish, pip):
return
elif pos < 0:
if self._handle_short_position(candle, bullish, pip):
return
if float(self.Position) != 0:
return
volume = self._calculate_order_volume()
if volume <= 0:
return
if bullish:
self.BuyMarket(volume)
self._init_trade_state(float(candle.ClosePrice), volume, 1)
elif bearish:
self.SellMarket(volume)
self._init_trade_state(float(candle.ClosePrice), volume, -1)
def _handle_long_position(self, candle, bearish_signal, pip):
if self._entry_price is None:
return False
entry = self._entry_price
remaining = self._current_position_volume if self._current_position_volume > 0 else abs(float(self.Position))
if remaining <= 0:
return False
stop = entry - self.StopLossPips * pip if self.StopLossPips > 0 else None
take = entry + self.TakeProfitPips * pip if self.TakeProfitPips > 0 else None
partial = entry + self.PartialProfitPips * pip if self.PartialProfitPips > 0 else None
breakeven = entry + self.BreakevenPips * pip if self.BreakevenPips > 0 else None
if stop is not None and float(candle.LowPrice) <= stop:
self.SellMarket(remaining)
self._register_pnl(stop, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if take is not None and float(candle.HighPrice) >= take:
self.SellMarket(remaining)
self._register_pnl(take, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if not self._partial_taken and partial is not None and float(candle.HighPrice) >= partial:
half = remaining / 2.0
if half > 0:
self.SellMarket(half)
self._register_pnl(partial, half)
self._current_position_volume = max(0.0, self._current_position_volume - half)
self._partial_taken = True
return True
if breakeven is not None and not self._breakeven_active and float(candle.HighPrice) >= breakeven:
self._breakeven_active = True
if self._breakeven_active and float(candle.LowPrice) <= entry:
self.SellMarket(remaining)
self._register_pnl(entry, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if bearish_signal:
self.SellMarket(remaining)
self._register_pnl(float(candle.ClosePrice), remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
return False
def _handle_short_position(self, candle, bullish_signal, pip):
if self._entry_price is None:
return False
entry = self._entry_price
remaining = self._current_position_volume if self._current_position_volume > 0 else abs(float(self.Position))
if remaining <= 0:
return False
stop = entry + self.StopLossPips * pip if self.StopLossPips > 0 else None
take = entry - self.TakeProfitPips * pip if self.TakeProfitPips > 0 else None
partial = entry - self.PartialProfitPips * pip if self.PartialProfitPips > 0 else None
breakeven = entry - self.BreakevenPips * pip if self.BreakevenPips > 0 else None
if stop is not None and float(candle.HighPrice) >= stop:
self.BuyMarket(remaining)
self._register_pnl(stop, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if take is not None and float(candle.LowPrice) <= take:
self.BuyMarket(remaining)
self._register_pnl(take, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if not self._partial_taken and partial is not None and float(candle.LowPrice) <= partial:
half = remaining / 2.0
if half > 0:
self.BuyMarket(half)
self._register_pnl(partial, half)
self._current_position_volume = max(0.0, self._current_position_volume - half)
self._partial_taken = True
return True
if breakeven is not None and not self._breakeven_active and float(candle.LowPrice) <= breakeven:
self._breakeven_active = True
if self._breakeven_active and float(candle.HighPrice) >= entry:
self.BuyMarket(remaining)
self._register_pnl(entry, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if bullish_signal:
self.BuyMarket(remaining)
self._register_pnl(float(candle.ClosePrice), remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
return False
def _init_trade_state(self, entry_price, volume, direction):
self._entry_price = entry_price
self._current_position_volume = abs(volume)
self._entry_direction = direction
self._partial_taken = False
self._breakeven_active = False
self._trade_pnl = 0.0
def _calculate_order_volume(self):
volume = float(self.BaseVolume)
if self.UseMoneyManagement:
losses = self._consecutive_losses
if losses == 0:
mult = 1.0
elif losses == 1:
mult = 2.0
elif losses == 2:
mult = 3.0
elif losses == 3:
mult = 4.0
elif losses == 4:
mult = 5.0
elif losses == 5:
mult = 6.0
else:
mult = 7.0
volume *= mult * float(self.RiskMultiplier)
return volume
def _get_pip_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if step <= 0:
return 1.0
tmp = step
decimals = 0
while decimals < 10 and int(tmp) != tmp:
tmp *= 10.0
decimals += 1
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def _register_pnl(self, exit_price, volume):
if self._entry_price is None or self._entry_direction == 0:
return
pnl = (exit_price - self._entry_price) * volume * self._entry_direction
self._trade_pnl += pnl
def _finalize_trade(self):
if self._current_position_volume > 0:
return
if self._trade_pnl > 0:
self._consecutive_losses = 0
elif self._trade_pnl < 0:
self._consecutive_losses += 1
else:
self._consecutive_losses = 0
self._reset_trade_state()
def _reset_trade_state(self):
self._entry_price = None
self._current_position_volume = 0.0
self._entry_direction = 0
self._partial_taken = False
self._breakeven_active = False
self._trade_pnl = 0.0
def OnReseted(self):
super(macd_ea_strategy, self).OnReseted()
self._macd_diffs = []
self._entry_price = None
self._current_position_volume = 0.0
self._entry_direction = 0
self._partial_taken = False
self._breakeven_active = False
self._trade_pnl = 0.0
self._consecutive_losses = 0
def CreateClone(self):
return macd_ea_strategy()