EMA Cross-2-Strategie
Überblick
Diese Strategie ist ein StockSharp-Port des MetaTrader 4 Expert Advisors "EMA_CROSS_2" aus dem MQL-Repository. Das Original EA überwacht zwei exponentielle gleitende Durchschnitte (EMAs) und platziert Marktaufträge, wann immer die Durchschnittswerte ausgetauscht werden. Die Konvertierung behält den konträren Charakter des Skripts bei – es kauft, wenn der Long-EMA über den Short-EMA steigt, und verkauft, wenn der Short-EMA über den Long-EMA steigt – und bindet gleichzeitig die Logik in die übergeordnete StockSharp-Strategieinfrastruktur ein.
Die Strategie arbeitet mit abgeschlossenen Kerzen, die vom konfigurierbaren Kerzendatentyp bereitgestellt werden. Signale werden bei Kerzenschluss ausgewertet, um wiederholte Auslöser innerhalb desselben Balkens zu vermeiden. Das Risikomanagement ahmt das MetaTrader-Verhalten nach, indem es Take-Profit-, Stop-Loss- und Trailing-Stop-Distanzen verwendet, die in Brokerpunkten (Preisschritten) ausgedrückt werden.
Handelslogik
- Indikatorberechnung
- Berechnen Sie die kurz- und langfristigen EMAs für jede abgeschlossene Kerze.
- Überspringen Sie die erste Indikatoraktualisierung und passen Sie sie an das ursprüngliche Flag
first_time an, das die allererste Auswertung ignoriert hat.
- Erkennen Sie anschließend eine Richtungsänderung, wenn die relative Reihenfolge zwischen dem langen und dem kurzen EMA umkehrt.
- Signalinterpretation
- Wenn sich der Long-Kurs EMA über den Short-Kurs EMA bewegt, eröffnet der ursprüngliche EA einen Kaufhandel. Der StockSharp-Port behält diese konträre Regel bei, obwohl er sich im Gegensatz zu einem klassischen Crossover-System verhält.
- Wenn der Short-Kurs EMA über dem Long-Kurs EMA schließt, eröffnet die Strategie einen Verkaufshandel.
- Neue Positionen sind nur zulässig, wenn derzeit kein Engagement offen ist, wodurch die Bedingung
OrdersTotal() < 1 reproduziert wird.
- Auftragsausführung
- Trades werden als Market Orders mit einem fest konfigurierbaren Volumen versendet.
- Beim Einstieg zeichnet die Strategie Stop-Loss- und Take-Profit-Preise unter Verwendung der durch Parameter bereitgestellten Pip-Distanz auf.
- Risikomanagement
- Bei jeder abgeschlossenen Kerze prüft die Strategie, ob die Preisbewegung die gespeicherten Stop-Loss- oder Take-Profit-Niveaus berührt. Bei Überschreitung eines dieser Level wird die gesamte Position mit einer Marktorder geschlossen.
- Ein Trailing-Stop (ebenfalls in Broker-Punkten definiert) wird angewendet, sobald sich der Preis um mehr als die Trailing-Distanz günstig bewegt. Bei langen Positionen wird der Schutzanschlag nach oben verschoben; Bei Short-Positionen sinkt der Preis.
- Wenn die Position flach wird, werden die gespeicherten Schutzniveaus gelöscht.
Parameter
| Name |
Beschreibung |
Standard |
CandleType |
Kerzenserien zur Indikatorberechnung und Signalerkennung. |
15-minütiger Zeitrahmen |
OrderVolume |
Volumen jeder Marktorder in Lots/Kontrakten. |
2 |
TakeProfitPoints |
Abstand zum Take-Profit-Niveau, ausgedrückt in Brokerpunkten (Preisschritten). Ein Wert von 0 deaktiviert den Take-Profit. |
20 |
StopLossPoints |
Abstand zum Stop-Loss-Level, ausgedrückt in Broker-Punkten. Ein Wert von 0 deaktiviert den Stop-Loss. |
30 |
TrailingStopPoints |
Distanz, die beim Verfolgen der offenen Position verwendet wird. 0 deaktiviert den Trailing Stop. |
50 |
ShortEmaPeriod |
Länge des schnellen EMA. |
5 |
LongEmaPeriod |
Länge des langsamen EMA. |
60 |
Implementierungshinweise
- Die Strategie verwendet
SubscribeCandles().Bind(shortEma, longEma, ProcessCandle), um Kerzendaten mit EMA-Indikatoren zu verbinden und folgt dabei dem bevorzugten übergeordneten API-Muster.
- Indikatorwerte werden im Bindungsrückruf als gebrauchsfertige Dezimalzahlen empfangen, sodass keine manuelle Pufferindizierung erforderlich ist.
- Schutzabstände werden durch Multiplikation mit dem Instrument
PriceStep von MetaTrader Punkten in StockSharp Preise umgerechnet. Wenn das Instrument gebrochene Pip-Preise (3 oder 5 Dezimalstellen) verwendet, berechnet der Helfer die Pip-Größe entsprechend.
- Stop-Loss-, Take-Profit- und Trailing-Verhalten werden intern bei Marktausstiegen implementiert, da StockSharp nicht den gleichen
OrderModify-Workflow wie MetaTrader 4 bereitstellt. Das resultierende Handelsmanagement spiegelt die ursprüngliche Logik wider: Niveaus werden bei jeder Kerze überprüft und Ausstiege erfolgen sofort, sobald sie durchbrochen werden.
- Die erste Crossover-Auswertung wird absichtlich ignoriert, um die
first_time-Schutzmaßnahme zu reproduzieren, die vorzeitige Trades im MQL-Skript verhinderte.
- Geldmanagement: Das ursprüngliche EA handelte immer mit dem Parameter
Lots. Die Konvertierung macht dasselbe Konzept über OrderVolume verfügbar und weist es außerdem der Strategieeigenschaft Volume zu, damit Designer und Optimierer es wiederverwenden können.
- Auftragserteilung: MetaTrader hat Stop-Loss und Take-Profit direkt innerhalb von
OrderSend angewendet. In StockSharp werden diese Niveaus von der Strategie verfolgt und bei Überschreitung mit Marktaufträgen geschlossen.
- Trailing-Stop-Präzision: Die EA verschobenen Stopps mithilfe von Tick-Daten (
Bid/Ask). Der Port aktualisiert die nachgestellte Logik beim Schließen der Kerze. Dies ist die feinste Granularität, die in diesem Beispielprojekt verfügbar ist. Die Abstands- und Aktivierungsregeln bleiben identisch.
- Fehlerbehandlung und Protokollierung wurden vereinfacht; Die StockSharp-Protokollierung liefert detaillierte Informationen über das Standardstrategieprotokoll.
Nutzungstipps
- Passen Sie
CandleType an den Zeitrahmen an, der bei Backtests des ursprünglichen EA verwendet wurde, um ein vergleichbares Indikatorverhalten aufrechtzuerhalten.
- Stellen Sie beim Handel mit Symbolen, die mit gebrochenen Pips notiert sind, sicher, dass die konfigurierten Punktabstände die gewünschte Anzahl von Pips widerspiegeln (z. B. entsprechen bei EURUSD
10 Punkte einem Pip).
- Stellen Sie
OrderVolume auf die von Ihrem Ausführungsplatz erwartete Kontraktgröße ein. Die Strategie führt keine automatische Volumenskalierung durch.
- Verwenden Sie die integrierten Optimierungsschalter für jeden Parameter, um Kombinationen von EMA Zeiträumen und Risikoentfernungen zu erkunden, genau wie Sie Eingaben in MetaTrader optimieren würden.
Dateien
CS/EmaCross2Strategy.cs – StockSharp Implementierung der Handelslogik.
README.md – Englische Dokumentation (diese Datei).
README_zh.md – Chinesische Übersetzung.
README_ru.md – Russische Übersetzung.
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>
/// Counter-trend EMA crossover strategy converted from the MetaTrader 4 expert "EMA_CROSS_2".
/// Buys when the long EMA rises above the short EMA, and sells when the short EMA climbs above the long EMA.
/// Incorporates MetaTrader-style risk management with point-based stop-loss, take-profit, and trailing stop levels.
/// </summary>
public class EmaCross2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<int> _shortEmaPeriod;
private readonly StrategyParam<int> _longEmaPeriod;
private ExponentialMovingAverage _shortEma;
private ExponentialMovingAverage _longEma;
private bool _skipFirstSignal = true;
private int _lastDirection;
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
private decimal _pointSize;
private decimal _entryPrice;
/// <summary>
/// Candle type used for signal detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Order volume applied to new market orders.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Take-profit distance expressed in broker points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in broker points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in broker points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Period of the short EMA.
/// </summary>
public int ShortEmaPeriod
{
get => _shortEmaPeriod.Value;
set => _shortEmaPeriod.Value = value;
}
/// <summary>
/// Period of the long EMA.
/// </summary>
public int LongEmaPeriod
{
get => _longEmaPeriod.Value;
set => _longEmaPeriod.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public EmaCross2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for EMA calculations", "General");
_orderVolume = Param(nameof(OrderVolume), 2m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume of each market order", "Trading")
.SetOptimize(0.1m, 5m, 0.1m);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Distance from entry to take-profit in broker points", "Risk")
.SetOptimize(0m, 200m, 5m);
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Distance from entry to stop-loss in broker points", "Risk")
.SetOptimize(0m, 200m, 5m);
_trailingStopPoints = Param(nameof(TrailingStopPoints), 500m)
.SetNotNegative()
.SetDisplay("Trailing Stop (points)", "Trailing distance maintained after entry", "Risk")
.SetOptimize(0m, 200m, 5m);
_shortEmaPeriod = Param(nameof(ShortEmaPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Short EMA", "Length of the fast EMA", "Indicators")
.SetOptimize(2, 40, 1);
_longEmaPeriod = Param(nameof(LongEmaPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Long EMA", "Length of the slow EMA", "Indicators")
.SetOptimize(10, 200, 5);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Volume = OrderVolume;
_shortEma = null;
_longEma = null;
_skipFirstSignal = true;
_lastDirection = 0;
_stopLossPrice = null;
_takeProfitPrice = null;
_pointSize = 0m;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = OrderVolume;
_pointSize = CalculatePointSize();
_shortEma = new EMA { Length = ShortEmaPeriod };
_longEma = new EMA { Length = LongEmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_shortEma, _longEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _shortEma);
DrawIndicator(area, _longEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal shortEmaValue, decimal longEmaValue)
{
// Work only with finished candles to avoid repeated signals inside the same bar.
if (candle.State != CandleStates.Finished)
return;
if (_pointSize <= 0m)
_pointSize = CalculatePointSize();
if (CheckRisk(candle))
return;
if (Position != 0)
UpdateTrailingStop(candle);
else if (_stopLossPrice.HasValue || _takeProfitPrice.HasValue)
ResetRiskLevels();
var signal = EvaluateCross(longEmaValue, shortEmaValue);
if (signal == 0)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0)
return;
var volume = OrderVolume;
if (volume <= 0m)
volume = 1m;
if (signal == 1)
{
BuyMarket(volume);
SetRiskLevels(candle.ClosePrice, true);
}
else if (signal == 2)
{
SellMarket(volume);
SetRiskLevels(candle.ClosePrice, false);
}
}
private int EvaluateCross(decimal longValue, decimal shortValue)
{
var currentDirection = 0;
if (longValue > shortValue)
currentDirection = 1;
else if (longValue < shortValue)
currentDirection = 2;
if (_skipFirstSignal)
{
_skipFirstSignal = false;
return 0;
}
if (currentDirection != 0 && currentDirection != _lastDirection)
{
_lastDirection = currentDirection;
return _lastDirection;
}
return 0;
}
private bool CheckRisk(ICandleMessage candle)
{
if (Position > 0)
{
var size = Math.Abs(Position);
if (_stopLossPrice.HasValue && candle.LowPrice <= _stopLossPrice.Value)
{
SellMarket(size);
ResetRiskLevels();
return true;
}
if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
{
SellMarket(size);
ResetRiskLevels();
return true;
}
}
else if (Position < 0)
{
var size = Math.Abs(Position);
if (_stopLossPrice.HasValue && candle.HighPrice >= _stopLossPrice.Value)
{
BuyMarket(size);
ResetRiskLevels();
return true;
}
if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
{
BuyMarket(size);
ResetRiskLevels();
return true;
}
}
else if (_stopLossPrice.HasValue || _takeProfitPrice.HasValue)
{
ResetRiskLevels();
}
return false;
}
private void UpdateTrailingStop(ICandleMessage candle)
{
if (TrailingStopPoints <= 0m || _pointSize <= 0m)
return;
var distance = TrailingStopPoints * _pointSize;
if (distance <= 0m)
return;
var entryPrice = _entryPrice > 0 ? _entryPrice : candle.ClosePrice;
if (Position > 0)
{
var profit = candle.ClosePrice - entryPrice;
if (profit > distance)
{
var candidate = candle.ClosePrice - distance;
if (!_stopLossPrice.HasValue || _stopLossPrice.Value < candidate)
_stopLossPrice = candidate;
}
}
else if (Position < 0)
{
var profit = entryPrice - candle.ClosePrice;
if (profit > distance)
{
var candidate = candle.ClosePrice + distance;
if (!_stopLossPrice.HasValue || _stopLossPrice.Value > candidate)
_stopLossPrice = candidate;
}
}
}
private void SetRiskLevels(decimal executionPrice, bool isLong)
{
if (_pointSize <= 0m)
{
ResetRiskLevels();
return;
}
_stopLossPrice = StopLossPoints > 0m
? executionPrice + (isLong ? -1m : 1m) * StopLossPoints * _pointSize
: null;
_takeProfitPrice = TakeProfitPoints > 0m
? executionPrice + (isLong ? 1m : -1m) * TakeProfitPoints * _pointSize
: null;
}
private void ResetRiskLevels()
{
_stopLossPrice = null;
_takeProfitPrice = null;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position != 0 && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0m)
_entryPrice = 0m;
}
private decimal CalculatePointSize()
{
var step = Security?.PriceStep ?? 0m;
return step > 0m ? step : 1m;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ema_cross_2_strategy(Strategy):
"""
Counter-trend EMA crossover strategy (EMA_CROSS_2 MetaTrader port).
Buys when long EMA rises above short EMA, sells on opposite.
Point-based stop-loss, take-profit, and trailing stop management.
"""
def __init__(self):
super(ema_cross_2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Time frame for EMA calculations", "General")
self._take_profit_points = self.Param("TakeProfitPoints", 500.0) \
.SetDisplay("Take Profit (points)", "Distance from entry to take-profit", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 500.0) \
.SetDisplay("Stop Loss (points)", "Distance from entry to stop-loss", "Risk")
self._trailing_stop_points = self.Param("TrailingStopPoints", 500.0) \
.SetDisplay("Trailing Stop (points)", "Trailing distance after entry", "Risk")
self._short_ema_period = self.Param("ShortEmaPeriod", 5) \
.SetDisplay("Short EMA", "Length of the fast EMA", "Indicators")
self._long_ema_period = self.Param("LongEmaPeriod", 60) \
.SetDisplay("Long EMA", "Length of the slow EMA", "Indicators")
self._skip_first_signal = True
self._last_direction = 0
self._stop_loss_price = None
self._take_profit_price = None
self._point_size = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ema_cross_2_strategy, self).OnReseted()
self._skip_first_signal = True
self._last_direction = 0
self._stop_loss_price = None
self._take_profit_price = None
self._point_size = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(ema_cross_2_strategy, self).OnStarted2(time)
self._point_size = self._calculate_point_size()
short_ema = ExponentialMovingAverage()
short_ema.Length = self._short_ema_period.Value
long_ema = ExponentialMovingAverage()
long_ema.Length = self._long_ema_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(short_ema, long_ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, short_ema)
self.DrawIndicator(area, long_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, short_val, long_val):
if candle.State != CandleStates.Finished:
return
short_val = float(short_val)
long_val = float(long_val)
if self._point_size <= 0.0:
self._point_size = self._calculate_point_size()
if self._check_risk(candle):
return
if self.Position != 0:
self._update_trailing_stop(candle)
elif self._stop_loss_price is not None or self._take_profit_price is not None:
self._reset_risk_levels()
signal = self._evaluate_cross(long_val, short_val)
if signal == 0:
return
if self.Position != 0:
return
if signal == 1:
self.BuyMarket()
self._set_risk_levels(float(candle.ClosePrice), True)
elif signal == 2:
self.SellMarket()
self._set_risk_levels(float(candle.ClosePrice), False)
def _evaluate_cross(self, long_val, short_val):
current_direction = 0
if long_val > short_val:
current_direction = 1
elif long_val < short_val:
current_direction = 2
if self._skip_first_signal:
self._skip_first_signal = False
return 0
if current_direction != 0 and current_direction != self._last_direction:
self._last_direction = current_direction
return self._last_direction
return 0
def _check_risk(self, candle):
if self.Position > 0:
if self._stop_loss_price is not None and float(candle.LowPrice) <= self._stop_loss_price:
self.SellMarket()
self._reset_risk_levels()
return True
if self._take_profit_price is not None and float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket()
self._reset_risk_levels()
return True
elif self.Position < 0:
if self._stop_loss_price is not None and float(candle.HighPrice) >= self._stop_loss_price:
self.BuyMarket()
self._reset_risk_levels()
return True
if self._take_profit_price is not None and float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket()
self._reset_risk_levels()
return True
elif self._stop_loss_price is not None or self._take_profit_price is not None:
self._reset_risk_levels()
return False
def _update_trailing_stop(self, candle):
trail_pts = self._trailing_stop_points.Value
if trail_pts <= 0 or self._point_size <= 0:
return
distance = trail_pts * self._point_size
if distance <= 0:
return
entry = self._entry_price if self._entry_price > 0 else float(candle.ClosePrice)
close = float(candle.ClosePrice)
if self.Position > 0:
profit = close - entry
if profit > distance:
candidate = close - distance
if self._stop_loss_price is None or self._stop_loss_price < candidate:
self._stop_loss_price = candidate
elif self.Position < 0:
profit = entry - close
if profit > distance:
candidate = close + distance
if self._stop_loss_price is None or self._stop_loss_price > candidate:
self._stop_loss_price = candidate
def _set_risk_levels(self, price, is_long):
if self._point_size <= 0:
self._reset_risk_levels()
return
self._entry_price = price
sl = self._stop_loss_points.Value
tp = self._take_profit_points.Value
direction = 1.0 if is_long else -1.0
self._stop_loss_price = price - direction * sl * self._point_size if sl > 0 else None
self._take_profit_price = price + direction * tp * self._point_size if tp > 0 else None
def _reset_risk_levels(self):
self._stop_loss_price = None
self._take_profit_price = None
def _calculate_point_size(self):
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
return step if step > 0 else 1.0
def CreateClone(self):
return ema_cross_2_strategy()