Diese Strategie ist eine getreue Konvertierung des MetaTrader Expert Advisors "MA MACD Position averaging". Sie kombiniert
einen gewichteten gleitenden Durchschnittsfilter mit einer MACD-Verhältnisprüfung und fügt ein Martingal-artiges Mittelungsmodul
hinzu, das die Positionsgröße erhöht, wenn sich der Preis um eine konfigurierbare Anzahl von Pips ungünstig bewegt. Alle
Risikoparameter werden in Pip-Einheiten konfiguriert und intern in Preisabstände umgerechnet, unter Verwendung der von
StockSharp bereitgestellten Instrument-Metadaten.
Handelslogik
Indikatorvorbereitung
Ein konfigurierbarer gleitender Durchschnitt (MaPeriod, MaMethod, MaAppliedPrice) wird auf abgeschlossenen Kerzen
abgetastet. Die Parameter SignalBar und MaShift emulieren die Fähigkeit von MetaTrader, um eine bestimmte Anzahl von
Bars zurückzublicken und den gleitenden Durchschnitt mit einem horizontalen Offset darzustellen.
Ein MACD-Indikator (MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod, MacdAppliedPrice) wird auf denselben Kerzen
verarbeitet. Die Strategie speichert die MACD-Haupt- und Signallinie in einem kleinen rollierenden Puffer, damit historische
Werte ohne direkte Aufrufe von Indikator-APIs zugänglich sind.
Eintrittsbedingungen
Long: Beide MACD-Linien sind unter null, das Verhältnis MACDmain / MACDsignal ist größer oder gleich MacdRatio,
der Kerzenschlusskurs liegt über dem abgetasteten gleitenden Durchschnitt und der Abstand zwischen Preis und Durchschnitt
beträgt mindestens IndentPips Pips.
Short: Beide MACD-Linien sind über null, das Verhältnis ist über MacdRatio, der Kerzenschlusskurs liegt unter dem
gleitenden Durchschnitt und der Abstand zwischen ihnen beträgt mindestens IndentPips Pips.
Neue Einträge sind nur erlaubt, wenn die Strategie kein Exposure hat. Wenn ein Mittelungszyklus bereits im Gange ist,
wird die Signallogik übersprungen und nur die Mittelungsregeln gelten.
Mittelungsmodul
Wenn eine Long-Position besteht und der Preis mindestens StepLossingPips vom besten (niedrigsten) Long-Eintrag fällt,
öffnet die Strategie einen zusätzlichen Long-Trade, dessen Volumen gleich dem letzten Leg-Volumen multipliziert mit
LotCoefficient ist (gerundet nach dem Instrument-Volumenschritt).
Wenn eine Short-Position besteht und der Preis mindestens StepLossingPips vom besten (höchsten) Short-Eintrag steigt,
wird ein neues Short-Leg mit demselben LotCoefficient-Multiplikator hinzugefügt.
Wenn Exposure in beiden Richtungen erkannt wird (sollte unter normalen Bedingungen nie passieren), schließt die Strategie
sofort jedes Leg, um die Konsistenz wiederherzustellen.
Schutzausstiege
Jedes Leg speichert individuelle Stop-Loss- und Take-Profit-Niveaus, ausgedrückt in Preiseinheiten (StopLossPips,
TakeProfitPips). Bei jeder fertigen Kerze prüft die Strategie, ob der Kerzenbereich eines der gespeicherten Niveaus
gekreuzt hat, und wenn ja, wird das Leg mit einer Marktorder geschlossen.
Ein Trailing Stop (TrailingStopPips, TrailingStepPips) ist optional. Sobald sich der Preis zugunsten eines Legs um
TrailingStopPips + TrailingStepPips bewegt, wird der Stop auf TrailingStopPips Pips hinter dem aktuellen Schlusskurs
verschoben. Der Stop zieht sich nur dann enger, wenn der Preis einen zusätzlichen Fortschritt von mindestens
TrailingStepPips Pips macht.
Housekeeping
Volumen-Befehle werden am Instrument-Volumenschritt ausgerichtet und auf das erlaubte Minimum/Maximum begrenzt. Die
Strategie führt nur auf vollständig geformten Kerzen aus (CandleStates.Finished), um Doppelverarbeitung zu vermeiden.
Parameter
Parameter
Typ
Standard
Beschreibung
CandleType
DataType
TimeSpan.FromHours(1).TimeFrame()
Zeitrahmen für Indikatorberechnungen.
OrderVolume
decimal
0.1
Basis-Lotgröße für den ersten Eintrag.
StopLossPips
int
50
Stop-Loss-Abstand in Pips (0 deaktiviert den Stop).
TakeProfitPips
int
50
Take-Profit-Abstand in Pips (0 deaktiviert das Ziel).
TrailingStopPips
int
5
Trailing-Stop-Offset in Pips. Muss positiv sein, um Trailing zu aktivieren.
TrailingStepPips
int
5
Zusätzlicher Pip-Abstand, der erforderlich ist, bevor der Trailing Stop wieder bewegt wird.
StepLossingPips
int
30
Preisrückgang in Pips, der ein neues Mittelungs-Leg auslöst.
LotCoefficient
decimal
2.0
Multiplikator, der auf das vorherige Leg-Volumen bei der Mittelung angewendet wird.
SignalBar
int
0
Anzahl abgeschlossener Bars zum Zurückblicken beim Abtasten von Indikatoren.
MaPeriod
int
15
Länge des gleitenden Durchschnitts in Bars.
MaShift
int
0
Horizontale Verschiebung (in Bars) der gleitenden Durchschnittswerte.
MaMethod
MovingAverageMethod
Weighted
Glättungsalgorithmus für gleitenden Durchschnitt (einfach, exponentiell, geglättet, gewichtet).
MaAppliedPrice
AppliedPriceType
Weighted
Kerzenpreis als Eingabe für den gleitenden Durchschnitt.
IndentPips
int
4
Minimale Pip-Lücke zwischen Preis und gleitendem Durchschnitt vor dem Eintritt.
MacdFastPeriod
int
12
Schnelle EMA-Länge des MACD-Filters.
MacdSlowPeriod
int
26
Langsame EMA-Länge des MACD-Filters.
MacdSignalPeriod
int
9
Signallinienlänge des MACD-Filters.
MacdAppliedPrice
AppliedPriceType
Weighted
Angewendeter Preis für die MACD-Berechnung.
MacdRatio
decimal
0.9
Mindest-MACD-Haupt-/Signal-Verhältnis für den Handel.
Pip-Umrechnung
Alle pip-basierten Einstellungen (StopLossPips, TakeProfitPips, TrailingStopPips, TrailingStepPips, StepLossingPips,
IndentPips) werden mit dem PriceStep des Instruments multipliziert. Wenn das Instrument 3 oder 5 Dezimalstellen hat, wird
der Wert zusätzlich mit 10 multipliziert, um MetaTraders "Pip"-Definition für Bruchwährungsnotierungen zu reproduzieren. Wenn
kein Preisschritt verfügbar ist, wird ein Fallback-Wert von 0.0001 verwendet.
Implementierungshinweise
Die Strategie hält eine interne Liste von Positions-Legs, da StockSharp im Netting-Modus arbeitet. Jedes Leg verfolgt seinen
eigenen Eintrittspreis, Stop- und Take-Niveaus, damit das Mitteln wie der originale MetaTrader EA verhält.
Schutzaufträge werden softwareseitig simuliert: wenn eine Kerze ein Stop-Loss- oder Take-Profit-Niveau berührt, wird die
Position mit einer Marktorder auf diesem Bar geschlossen.
Das Mitteln wird automatisch deaktiviert, wenn StepLossingPips null ist. Andernfalls verwendet jedes zusätzliche Leg das
vorherige Leg-Volumen multipliziert mit LotCoefficient und abgerundet auf den nächsten Volumenschritt.
Trailing-Stop-Updates verwenden den Kerzenschlusskurs als aktuellen Preis-Proxy. Der Stop bewegt sich nie in der nachteiligen
Richtung und bleibt inaktiv, bis der Preisfortschritt TrailingStopPips + TrailingStepPips überschreitet.
Indikator-Buffer berücksichtigen die SignalBar- und MaShift-Offsets, sodass die Entscheidungslogik genau dieselben Werte
sieht, die der MetaTrader Expert aus seinen Indikator-Buffern erhalten würde.
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 strategy using WMA crossover with SL/TP management.
/// Buys when fast WMA crosses above slow WMA, sells on reverse cross.
/// </summary>
public class MaMacdPositionAveragingStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
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>
/// 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 MaMacdPositionAveragingStrategy()
{
_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");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance 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;
_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 };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.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
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && 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
from StockSharp.Algo.Strategies import Strategy
class ma_macd_position_averaging_strategy(Strategy):
"""
WMA crossover with SL/TP in price steps.
"""
def __init__(self):
super(ma_macd_position_averaging_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15).SetDisplay("Fast WMA", "Fast WMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 100).SetDisplay("Slow WMA", "Slow WMA period", "Indicators")
self._sl_points = self.Param("StopLossPoints", 200).SetDisplay("Stop Loss", "SL in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 400).SetDisplay("Take Profit", "TP in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_macd_position_averaging_strategy, self).OnReseted()
self._fast = None
self._slow = 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_strategy, self).OnStarted2(time)
self._fast = WeightedMovingAverage()
self._fast.Length = self._fast_period.Value
self._slow = WeightedMovingAverage()
self._slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast, self._slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
f = float(fast_val)
s = float(slow_val)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = f
self._prev_slow = s
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = f
self._prev_slow = s
return
close = float(candle.ClosePrice)
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._prev_fast <= self._prev_slow and f > s 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 f < s and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
def CreateClone(self):
return ma_macd_position_averaging_strategy()