Die MA MACD Positionsmittelungs-Strategie v2 ist eine direkte Übersetzung des MetaTrader Expert Advisors von Vladimir
Karputov. Sie kombiniert einen gewichteten gleitenden Durchschnittsfilter, einen MACD-Bestätigungsblock und ein Mittelungsmodul,
das die Exposition erhöht, wenn bestehende Trades gegen die Position laufen. Die StockSharp-Version behält die ursprüngliche
Signalhierarchie bei, verarbeitet Indikatoren auf fertigen Kerzen und verwaltet Schutzlogik (Stop-Loss, Take-Profit, Trailing)
im Code, um das brokerseitige Verhalten aus MQL zu reproduzieren.
Handelslogik
Indikatorvorbereitung
Ein konfigurierbarer gleitender Durchschnitt berechnet auf dem ausgewählten Kerzentyp und Preiskomponenten. Der Parameter
MaShift emuliert MetaTraders Vorwärtsverschiebung durch Lesen von Werten aus älteren Kerzen, während BarOffset die
Bewertung der aktuellen oder einer vorherigen Bar ermöglicht.
Ein MACD-Signalindikator produziert die Haupt- und Signallinie unter Verwendung anpassbarer schneller, langsamer und
Signal-Perioden und eines angewendeten Preises, der dem ursprünglichen Expert Advisor entspricht.
Signalvalidierung
Long-Setups erfordern, dass beide MACD-Linien negativ sind, der Preis über dem verschobenen gleitenden Durchschnitt liegt
und der Preisabstand zum Durchschnitt MaIndentPips überschreitet (umgerechnet in absoluten Preis unter Verwendung der
Pip-Größe des Instruments).
Short-Setups spiegeln die Bedingungen: Beide MACD-Linien müssen positiv sein, der Preis muss unter dem verschobenen
gleitenden Durchschnitt bleiben und die Lücke zum Durchschnitt muss mindestens MaIndentPips betragen.
Der Verhältnisfilter MacdRatio erzwingt MACD_main / MACD_signal >= MacdRatio (unter Verwendung absoluter
Dezimaldivision), bevor ein Trade erlaubt ist.
Wenn ReverseSignals = true, wird die Richtung der Marktorder invertiert, nachdem alle Bedingungen erfüllt sind.
Positions-Lebenszyklus
Wenn keine Position besteht, öffnet die Strategie eine Marktorder mit dem konfigurierten OrderVolume (gerundet
nach dem Instrument-Volumenschritt) in der berechneten Richtung. Stop-Loss- und Take-Profit-Niveaus werden sofort
gemäß StopLossPips und TakeProfitPips angewendet.
Wenn bereits eine Exposition besteht, öffnet die Strategie nie die Gegenseite. Stattdessen:
Schließt alles, wenn Longs und Shorts gleichzeitig erkannt werden (Sicherheitsnetz, das die MQL-Prüfung spiegelt), oder
Ruft den Mittelungsblock für die aktuelle Seite auf.
Mittelungsmodul
Für Longs findet der Algorithmus das günstigste offene Leg, dessen nicht realisierter Verlust StepLossPips überschreitet.
Für Shorts wählt er das verlustreichste Leg mit dem höchsten Preis.
Sobald ein Kandidat gefunden ist, wird eine neue Marktorder mit Volumen CandidateVolume × LotCoefficient gesendet
(nach Anpassung an den erlaubten Volumenschritt/Min/Max). Dies reproduziert die geometrische Progression des ursprünglichen
Experten.
Neue Legs erben dieselben Stop-Loss- und Take-Profit-Abstände und werden für Trailing-Updates geeignet.
Risikokontrollen
Ein Trailing Stop aktiviert sich nur, wenn sowohl TrailingStopPips als auch TrailingStepPips größer als null sind.
Für Longs bewegt sich der Stop auf Close - TrailingStopPips, sobald der Gewinn TrailingStopPips + TrailingStepPips
überschreitet; Shorts verhalten sich symmetrisch.
Manuelle Stop-Loss- und Take-Profit-Prüfungen werden bei jeder fertigen Kerze durchgeführt. Wenn ausgelöst, schließt
eine Marktorder das genaue Leg und entfernt es aus der Mittelungsliste.
Parameter
Parameter
Beschreibung
OrderVolume
Basisvolumen für den allerersten Trade in einem Zyklus.
StopLossPips
Stop-Loss-Abstand in Pips. Auf null setzen, um den Stop zu deaktivieren.
TakeProfitPips
Take-Profit-Abstand in Pips. Auf null setzen, um das Ziel zu deaktivieren.
TrailingStopPips
Abstand zwischen Preis und Trailing Stop. Funktioniert zusammen mit TrailingStepPips.
TrailingStepPips
Zusätzliche günstige Bewegung, die erforderlich ist, bevor der Trailing Stop aktualisiert wird.
StepLossPips
Mindestverlust (in Pips), bevor ein Mittelungs-Leg hinzugefügt wird.
LotCoefficient
Multiplikator, der auf das ausgewählte verlustsreiche Leg-Volumen bei der Mittelung angewendet wird.
BarOffset
Anzahl der Bars zurück zum Lesen von Indikatorwerten (0 = aktuelle fertige Bar).
ReverseSignals
Invertiert die Long/Short-Ausführung unter Beibehaltung derselben Filter.
MaPeriod
Periode des gleitenden Durchschnitts.
MaShift
Vorwärtsverschiebung des gleitenden Durchschnitts (MetaTrader-Stil).
MaMethod
Glättungsmethode des gleitenden Durchschnitts (Einfach, Exponentiell, Geglättet, Gewichtet).
MaPrice
Kerzenpreiskomponente für den gleitenden Durchschnitt.
MaIndentPips
Mindestpreisabstand vom gleitenden Durchschnitt vor dem Einstieg.
MacdFastPeriod
Schnelle EMA-Periode für MACD.
MacdSlowPeriod
Langsame EMA-Periode für MACD.
MacdSignalPeriod
Signal-EMA-Periode für MACD.
MacdPrice
Angewendeter Preis in der MACD-Berechnung.
MacdRatio
Mindestverhältnis zwischen MACD-Haupt- und Signallinie.
CandleType
Kerzenreihe für alle Berechnungen.
Implementierungshinweise
Die Pip-Größe wird aus dem Preisschritt des Instruments berechnet, was die 3/5-stellige Anpassung aus der MQL-Version
reproduziert. Dies hält pip-basierte Abstände über Forex-Symbole hinweg identisch.
Alle Indikator-Buffer verwenden Warteschlangen, um MetaTraders ma_shift- und bar-Indizierung zu emulieren, ohne
historische Suchmethoden aufzurufen, die durch die Projektregeln verboten sind.
Volumenanpassungen respektieren Security.VolumeStep, Security.MinVolume und Security.MaxVolume, um ungültige
Ordergrößen zu verhindern, wenn LotCoefficient die Exposition multipliziert.
Schutzlogik (Stops, Takes, Trailing) läuft vollständig in der Strategieschicht, sodass keine Abhängigkeit von brokerseitigen
Positionsmodifikations-APIs besteht.
Die Klasse befindet sich im Namespace StockSharp.Samples.Strategies und folgt der Repository-Anforderung, Tab-Einrückung
und ausschließlich englische Kommentare zu verwenden.
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 Position Averaging v2 strategy using WMA crossover with EMA trend filter.
/// Buys when fast WMA crosses above slow WMA and price above EMA.
/// Sells on reverse conditions.
/// </summary>
public class MaMacdPositionAveragingV2Strategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private ExponentialMovingAverage _ema;
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>
/// EMA trend filter period.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.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 MaMacdPositionAveragingV2Strategy()
{
_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");
_emaPeriod = Param(nameof(EmaPeriod), 200)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA trend filter period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit 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;
_ema = 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 };
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, _ema, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed || !_ema.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 with EMA trend filter
if (_prevFast <= _prevSlow && fastValue > slowValue && close > emaValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && close < emaValue && 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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_macd_position_averaging_v2_strategy(Strategy):
def __init__(self):
super(ma_macd_position_averaging_v2_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15) \
.SetDisplay("Fast Period", "Fast WMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 100) \
.SetDisplay("Slow Period", "Slow WMA period", "Indicator")
self._ema_period = self.Param("EmaPeriod", 200) \
.SetDisplay("EMA Period", "EMA trend filter period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._ema = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def ema_period(self):
return self._ema_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(ma_macd_position_averaging_v2_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._ema = None
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_v2_strategy, self).OnStarted2(time)
self._fast = WeightedMovingAverage()
self._fast.Length = self.fast_period
self._slow = WeightedMovingAverage()
self._slow.Length = self.slow_period
self._ema = ExponentialMovingAverage()
self._ema.Length = self.ema_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._ema, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value, ema_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
ema_val = float(ema_value)
if not self._fast.IsFormed or not self._slow.IsFormed or not self._ema.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# WMA crossover with EMA trend filter
if self._prev_fast <= self._prev_slow and fast_val > slow_val and close > ema_val 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 fast_val < slow_val and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return ma_macd_position_averaging_v2_strategy()