Macd Pattern Trader DoubleTop Strategy
Обзор
Порт советника MetaTrader 4 MacdPatternTraderv04cb. Стратегия отслеживает основную линию MACD на выбранном таймфрейме и ищет фигуры двойная вершина/двойное дно. Когда второй экстремум оказывается слабее первого при сохранении значения MACD за пределами порогового уровня, открывается рыночная сделка в расчёте на разворот. Защитные приказы полностью повторяют исходные 100 пунктов стоп-лосса и 300 пунктов тейк-профита.
Правила торговли
- Подписаться на выбранные свечи (по умолчанию 30 минут) и рассчитать линию MACD с периодами 5, 13 и 1.
- Хранить три последних завершённых значения MACD. Медвежий сценарий активируется, когда MACD держится выше
TriggerLevel, формирует локальный максимум и начинает снижаться. Сигнал подтверждается, если следующий максимум MACD ниже сохранённого. В этот момент отправляется рыночная продажа. - Бычиий сценарий зеркален: при значении MACD ниже
-TriggerLevel, формировании впадины и последующей более высокой впадине открывается покупка. - При возвращении MACD в диапазон
[-TriggerLevel, TriggerLevel]накопленные экстремумы сбрасываются, как и в оригинальном советнике. - Объём позиции задаётся параметром
TradeVolume. При смене направления стратегия сначала закрывает противоположный объём, а затем открывает новую сделку. - Метод
StartProtectionзапускается один раз при старте, чтобы стоп-лосс и тейк-профит в 100 и 300 пунктов поддерживались платформой автоматически.
Параметры
| Имя | Описание |
|---|---|
FastPeriod |
Быстрый период EMA в MACD. |
SlowPeriod |
Медленный период EMA в MACD. |
SignalPeriod |
Период сглаживания сигнальной линии MACD. |
TriggerLevel |
Абсолютный уровень MACD, необходимый для активации поиска фигур. |
StopLossPips |
Размер стоп-лосса в пунктах (по умолчанию 100). |
TakeProfitPips |
Размер тейк-профита в пунктах (по умолчанию 300). |
TradeVolume |
Базовый объём рыночной заявки. |
CandleType |
Тип свечей, используемый для расчётов индикатора. |
Примечания
- Перед передачей в
StartProtectionзначения стопа и тейка пересчитываются из пунктов в шаги инструмента. - Все комментарии в исходном коде стратегии написаны на английском языке в соответствии с требованиями репозитория.
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>
/// Port of the MetaTrader 4 expert advisor MacdPatternTraderv04cb.
/// Detects bearish and bullish double-top/double-bottom patterns on the MACD main line.
/// The second swing needs to be weaker than the first one before a market order is sent.
/// Fixed protective orders replicate the original 100/300 pip stop-loss and take-profit distances.
/// </summary>
public class MacdPatternTraderDoubleTopStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<decimal> _triggerLevel;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd = null!;
private decimal? _previousMacd;
private decimal? _previousMacd2;
private decimal? _firstPeak;
private decimal? _firstTrough;
private bool _sellPatternArmed;
private bool _buyPatternArmed;
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public MacdPatternTraderDoubleTopStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast moving average length used by MACD", "MACD")
.SetOptimize(3, 15, 1);
_slowPeriod = Param(nameof(SlowPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow moving average length used by MACD", "MACD")
.SetOptimize(10, 30, 1);
_signalPeriod = Param(nameof(SignalPeriod), 1)
.SetGreaterThanZero()
.SetDisplay("Signal EMA", "Signal smoothing period for MACD", "MACD")
.SetOptimize(1, 9, 1);
_triggerLevel = Param(nameof(TriggerLevel), 50m)
.SetNotNegative()
.SetDisplay("Trigger Level", "Absolute MACD level that arms the pattern logic", "MACD");
_stopLossPips = Param(nameof(StopLossPips), 100m)
.SetNotNegative()
.SetDisplay("Stop-Loss (pips)", "Stop-loss distance expressed in pips", "Risk Management")
.SetOptimize(50m, 200m, 10m);
_takeProfitPips = Param(nameof(TakeProfitPips), 300m)
.SetNotNegative()
.SetDisplay("Take-Profit (pips)", "Take-profit distance expressed in pips", "Risk Management")
.SetOptimize(150m, 500m, 10m);
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order volume used for new entries", "Trading")
.SetOptimize(0.05m, 1m, 0.05m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for MACD calculations", "General");
}
/// <summary>
/// Fast EMA length used by MACD.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA length used by MACD.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Signal EMA length used by MACD.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Absolute MACD level that enables pattern tracking.
/// </summary>
public decimal TriggerLevel
{
get => _triggerLevel.Value;
set => _triggerLevel.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trading volume used for new market orders.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousMacd = null;
_previousMacd2 = null;
_firstPeak = null;
_firstTrough = null;
_sellPatternArmed = false;
_buyPatternArmed = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume;
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastPeriod },
LongMa = { Length = SlowPeriod },
},
SignalMa = { Length = SignalPeriod },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, ProcessCandle)
.Start();
var pipSize = Security?.PriceStep ?? 0.01m;
if (pipSize <= 0m) pipSize = 0.01m;
StartProtection(
takeProfit: TakeProfitPips > 0m ? new Unit(TakeProfitPips * pipSize, UnitTypes.Absolute) : null,
stopLoss: StopLossPips > 0m ? new Unit(StopLossPips * pipSize, UnitTypes.Absolute) : null,
useMarketOrders: true);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!macdValue.IsFinal || macdValue is not MovingAverageConvergenceDivergenceSignalValue typed)
return;
if (typed.Macd is not decimal macdLine)
return;
var previous = _previousMacd;
var previous2 = _previousMacd2;
if (previous.HasValue && previous2.HasValue)
{
ProcessSellPattern(macdLine, previous.Value, previous2.Value);
ProcessBuyPattern(macdLine, previous.Value, previous2.Value);
}
_previousMacd2 = previous;
_previousMacd = macdLine;
}
private void ProcessSellPattern(decimal current, decimal previous, decimal previous2)
{
if (current > TriggerLevel && current < previous && previous > previous2)
{
if (!_sellPatternArmed)
{
_firstPeak = previous;
_sellPatternArmed = true;
}
else if (_firstPeak is decimal referencePeak && previous < referencePeak)
{
EnterShort();
ResetSellPattern();
}
}
else if (current < TriggerLevel)
{
ResetSellPattern();
}
}
private void ProcessBuyPattern(decimal current, decimal previous, decimal previous2)
{
var negativeTrigger = -TriggerLevel;
if (current < negativeTrigger && current > previous && previous < previous2)
{
if (!_buyPatternArmed)
{
_firstTrough = previous;
_buyPatternArmed = true;
}
else if (_firstTrough is decimal referenceTrough && previous > referenceTrough)
{
EnterLong();
ResetBuyPattern();
}
}
else if (current > negativeTrigger)
{
ResetBuyPattern();
}
}
private void EnterShort()
{
if (!IsFormedAndOnlineAndAllowTrading())
return;
var volume = Volume + Math.Max(0m, Position);
if (volume <= 0m)
return;
SellMarket(volume);
}
private void EnterLong()
{
if (!IsFormedAndOnlineAndAllowTrading())
return;
var volume = Volume + Math.Max(0m, -Position);
if (volume <= 0m)
return;
BuyMarket(volume);
}
private void ResetSellPattern()
{
_sellPatternArmed = false;
_firstPeak = null;
}
private void ResetBuyPattern()
{
_buyPatternArmed = false;
_firstTrough = null;
}
}
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, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal
class macd_pattern_trader_double_top_strategy(Strategy):
def __init__(self):
super(macd_pattern_trader_double_top_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 5) \
.SetDisplay("Fast EMA", "Fast moving average length used by MACD", "MACD")
self._slow_period = self.Param("SlowPeriod", 13) \
.SetDisplay("Slow EMA", "Slow moving average length used by MACD", "MACD")
self._signal_period = self.Param("SignalPeriod", 1) \
.SetDisplay("Signal EMA", "Signal smoothing period for MACD", "MACD")
self._trigger_level = self.Param("TriggerLevel", 50.0) \
.SetDisplay("Trigger Level", "Absolute MACD level that arms the pattern logic", "MACD")
self._stop_loss_pips = self.Param("StopLossPips", 100.0) \
.SetDisplay("Stop-Loss (pips)", "Stop-loss distance expressed in pips", "Risk Management")
self._take_profit_pips = self.Param("TakeProfitPips", 300.0) \
.SetDisplay("Take-Profit (pips)", "Take-profit distance expressed in pips", "Risk Management")
self._trade_volume = self.Param("TradeVolume", 0.1) \
.SetDisplay("Trade Volume", "Order volume used for new entries", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe used for MACD calculations", "General")
self._previous_macd = None
self._previous_macd2 = None
self._first_peak = None
self._first_trough = None
self._sell_pattern_armed = False
self._buy_pattern_armed = False
@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 TriggerLevel(self):
return self._trigger_level.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def TradeVolume(self):
return self._trade_volume.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(macd_pattern_trader_double_top_strategy, self).OnStarted2(time)
self.Volume = float(self.TradeVolume)
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.BindEx(self._macd, self.ProcessCandle).Start()
ps = self.Security.PriceStep if self.Security is not None else None
pip_size = float(ps) if ps is not None else 0.01
if pip_size <= 0:
pip_size = 0.01
tp = float(self.TakeProfitPips)
sl = float(self.StopLossPips)
take_profit = Unit(tp * pip_size, UnitTypes.Absolute) if tp > 0 else None
stop_loss = Unit(sl * pip_size, UnitTypes.Absolute) if sl > 0 else None
self.StartProtection(take_profit, stop_loss, useMarketOrders=True)
def ProcessCandle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
if not macd_value.IsFinal:
return
macd_line = macd_value.Macd
if macd_line is None:
return
macd_line = float(macd_line)
previous = self._previous_macd
previous2 = self._previous_macd2
if previous is not None and previous2 is not None:
self._process_sell_pattern(macd_line, previous, previous2)
self._process_buy_pattern(macd_line, previous, previous2)
self._previous_macd2 = previous
self._previous_macd = macd_line
def _process_sell_pattern(self, current, previous, previous2):
trigger = float(self.TriggerLevel)
if current > trigger and current < previous and previous > previous2:
if not self._sell_pattern_armed:
self._first_peak = previous
self._sell_pattern_armed = True
elif self._first_peak is not None and previous < self._first_peak:
self._enter_short()
self._reset_sell_pattern()
elif current < trigger:
self._reset_sell_pattern()
def _process_buy_pattern(self, current, previous, previous2):
negative_trigger = -float(self.TriggerLevel)
if current < negative_trigger and current > previous and previous < previous2:
if not self._buy_pattern_armed:
self._first_trough = previous
self._buy_pattern_armed = True
elif self._first_trough is not None and previous > self._first_trough:
self._enter_long()
self._reset_buy_pattern()
elif current > negative_trigger:
self._reset_buy_pattern()
def _enter_short(self):
volume = self.Volume + max(0.0, float(self.Position))
if volume <= 0:
return
self.SellMarket(volume)
def _enter_long(self):
volume = self.Volume + max(0.0, -float(self.Position))
if volume <= 0:
return
self.BuyMarket(volume)
def _reset_sell_pattern(self):
self._sell_pattern_armed = False
self._first_peak = None
def _reset_buy_pattern(self):
self._buy_pattern_armed = False
self._first_trough = None
def OnReseted(self):
super(macd_pattern_trader_double_top_strategy, self).OnReseted()
self._previous_macd = None
self._previous_macd2 = None
self._first_peak = None
self._first_trough = None
self._sell_pattern_armed = False
self._buy_pattern_armed = False
def CreateClone(self):
return macd_pattern_trader_double_top_strategy()