EMA (barabashkakvn Edition)-Strategie
Konvertiert aus dem MetaTrader-5-Expertenberater "EMA (barabashkakvn's edition)". Das System handelt den Kreuzungspunkt zweier exponentieller gleitender Durchschnitte, die auf dem Median-Preis berechnet werden, und verwendet virtuelle Take-Profit/Stop-Loss-Level in Pips. Positionen werden nur nach einer bestätigten Kreuzung und einem kleinen Rücksetzer zum vorigen Kerzenextrem eröffnet.
Kernidee
- Verfolgung von 5- und 10-Perioden-EMAs (Median-Preis) im gewählten Zeitrahmen.
- Wenn die schnelle EMA die langsame EMA kreuzt, ein ausstehiges Signal setzen statt sofort zu handeln.
- Warten, bis der Preis
MoveBackPips vom vorigen Kerzenextremum zurückgesetzt hat, während der EMA-Spread 2 * pipSize übersteigt.
- In Richtung der Kreuzung einsteigen, sobald der Rücksetzer eintritt.
- Die offene Position mit virtuellen Zielen und Stops in Pips vom Einstandspreis verwalten.
Dieses Verhalten spiegelt die ursprüngliche MQL-Implementierung wider: Der Expertenberater wartete auf das Kreuzungsflag (check) und erforderte dann einen EMA-Spread plus einen Preisrücksetzer relativ zur vorherigen Kerze, um den Trade auszulösen. Die Ausstiegsregeln folgen ebenfalls dem „virtuellen" Ansatz, indem Positionen geschlossen werden, wenn Bid/Ask die angegebenen Abstände berührt hätte.
Indikatoren & Daten
- 5-Perioden-EMA auf Median-Preis (high + low) / 2.
- 10-Perioden-EMA auf Median-Preis.
- Hoch/Tief der vorherigen fertigen Kerze für Rücksetzerprüfungen.
- Alle Verarbeitung verwendet fertige Kerzen aus dem konfigurierten
CandleType-Abonnement.
Parameter
| Parameter |
Standard |
Beschreibung |
OrderVolume |
0.1 |
Handelsvolumen in Lots/Kontrakten für jeden Einstieg. |
VirtualProfitPips |
5 |
Abstand (in Pips) zwischen Einstandspreis und virtuellem Take-Profit. |
MoveBackPips |
3 |
Nach der Kreuzung erforderlicher Rücksetzer, gemessen vom vorigen Kerzenextremum. |
StopLossPips |
20 |
Abstand (in Pips) zwischen Einstandspreis und virtuellem Stop-Loss. |
PipSize |
0.0001 |
Pip-Größe in Preiseinheiten. Überschreiben bei Handelssymbolen mit anderer Pip-Definition. |
FastLength |
5 |
Länge der schnellen EMA. |
SlowLength |
10 |
Länge der langsamen EMA. |
CandleType |
TimeFrame(1m) |
Für Berechnungen verwendete Kerzenquelle. |
Alle pip-basierten Werte werden mit pipValue = PipSize in Preisabstände umgewandelt. Wenn der Parameter auf null oder eine negative Zahl gesetzt wird, fällt die Strategie auf Security.PriceStep zurück (wenn vom Board bereitgestellt).
Handelslogik
Einstiegsbedingungen
- Signalanrüstung: ein ausstehiges Signal speichern, wann immer eine Kreuzung auftritt (
FastEMA kreuzt über SlowEMA oder umgekehrt). Es wird noch kein Trade platziert.
- Short-Einstieg: erfordert
- Ausstehendes Signal vorhanden.
SlowEMA - FastEMA > 2 * pipSize.
- Kerzenhoch aktuell ≥ Kerzentief vorherig +
MoveBackPips * pipSize (Preis hat sich vom vorigen Tief nach oben zurückgesetzt).
- Long-Einstieg: erfordert
- Ausstehendes Signal vorhanden.
FastEMA - SlowEMA > 2 * pipSize.
- Kerzentief aktuell ≤ Kerzenhoch vorherig -
MoveBackPips * pipSize (Preis hat sich vom vorigen Hoch nach unten zurückgesetzt).
Nach dem Öffnen einer Position setzt sich das ausstehige Flag zurück, um Doppeleinstiege zu vermeiden.
Ausstiegsbedingungen
Virtuelle Ziele emulieren das MQL-Verhalten, indem Kerzenextreme mit den voreingestellten Abständen verglichen werden:
- Long-Position:
- Schließen wenn Kerzenhoch ≥ Einstandspreis +
VirtualProfitPips * pipSize.
- Schließen wenn Kerzentief ≤ Einstandspreis -
StopLossPips * pipSize.
- Short-Position:
- Schließen wenn Kerzentief ≤ Einstandspreis -
VirtualProfitPips * pipSize.
- Schließen wenn Kerzenhoch ≥ Einstandspreis +
StopLossPips * pipSize.
Nach jedem Ausstieg setzen sich die virtuellen Level zurück und die Strategie wartet auf die nächste Kreuzung.
Implementierungshinweise
- Verwendet das High-Level-Kerzenabonnement (
SubscribeCandles) und zeichnet EMAs plus Trades im optionalen Chartbereich.
- Median-Preis wird direkt aus dem Kerzenhoch/-tief berechnet, um
PRICE_MEDIAN von MetaTrader abzugleichen.
- Das Kreuzungsflag (
_hasCrossSignal) reproduziert die ursprüngliche check-Variable und stellt sicher, dass Trades nur nach Kreuzungs- und Rücksetzerprüfungen stattfinden.
StartProtection() wird in OnStarted aufgerufen, um eingebaute Risikoüberwachung zu aktivieren, obwohl die Strategie Ausstiege manuell behandelt.
- Der Code behält alle Kommentare auf Englisch, wie angefordert, und stützt sich ausschließlich auf fertige Kerzen ohne direkten Zugriff auf Indikatorpuffer.
Verwendungstipps
PipSize anpassen bei Instrumenten mit nicht-standardmäßigen Pip-Definitionen (z.B. JPY-Paare, Indizes, Krypto-Kurse).
- Da Ausstiege auf Kerzenextremen basieren, hält die Verwendung kürzerer Zeitrahmen (1–5 Minuten) das Verhalten näher am ursprünglichen tick-basierten Expertenberater.
- Optimierung kann EMA-Längen, Pip-Abstände und Rücksetzer-Werte mit den bereitgestellten Parameter-Metadaten erkunden.
- Die Strategie handelt jeweils eine Position; externe Positionen auf demselben Instrument können das virtuelle Ausstiegs-Tracking beeinträchtigen.
Risiken
- Kerzenbasierte Simulation kann Intrabar-Berührungen der virtuellen Level verpassen; bei Präzisionsbedarf höher aufgelöste Daten in Betracht ziehen.
- Virtuelle Ausstiege platzieren keine echten Schutzorders, daher können Verbindungsunterbrechungen oder Slippage zu größeren Verlusten als erwartet im Live-Handel führen.
- Wie bei jedem Kreuzungssystem verschlechtert sich die Performance in Seitwärtsmärkten; bei Bedarf Filter kombinieren.
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>
/// EMA crossover strategy with virtual take profit and stop loss distances.
/// Converted from the MQL5 expert "EMA (barabashkakvn's edition)".
/// </summary>
public class EmaBarabashkakvnEditionStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _virtualProfitPips;
private readonly StrategyParam<int> _moveBackPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<decimal> _pipSize;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private bool _hasCrossSignal;
private decimal? _prevFast;
private decimal? _prevSlow;
private decimal? _prevHigh;
private decimal? _prevLow;
private decimal? _entryPrice;
private decimal? _virtualTarget;
private decimal? _virtualStop;
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Virtual take profit distance in pips.
/// </summary>
public int VirtualProfitPips
{
get => _virtualProfitPips.Value;
set => _virtualProfitPips.Value = value;
}
/// <summary>
/// Retracement distance after a crossover in pips.
/// </summary>
public int MoveBackPips
{
get => _moveBackPips.Value;
set => _moveBackPips.Value = value;
}
/// <summary>
/// Virtual stop loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Pip size in price units.
/// </summary>
public decimal PipSize
{
get => _pipSize.Value;
set => _pipSize.Value = value;
}
/// <summary>
/// Fast EMA length applied to median price.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA length applied to median price.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.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="EmaBarabashkakvnEditionStrategy"/>.
/// </summary>
public EmaBarabashkakvnEditionStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume in lots", "Trading")
.SetOptimize(0.05m, 1m, 0.05m);
_virtualProfitPips = Param(nameof(VirtualProfitPips), 5)
.SetGreaterThanZero()
.SetDisplay("Virtual Profit", "Take profit distance in pips", "Risk")
.SetOptimize(2, 20, 1);
_moveBackPips = Param(nameof(MoveBackPips), 3)
.SetGreaterThanZero()
.SetDisplay("Move Back", "Retracement after crossover in pips", "Entries")
.SetOptimize(1, 10, 1);
_stopLossPips = Param(nameof(StopLossPips), 20)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Virtual stop loss distance in pips", "Risk")
.SetOptimize(10, 60, 2);
_pipSize = Param(nameof(PipSize), 0.0001m)
.SetGreaterThanZero()
.SetDisplay("Pip Size", "Instrument pip size in price units", "General");
_fastLength = Param(nameof(FastLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length on median price", "Indicators")
.SetOptimize(3, 15, 1);
_slowLength = Param(nameof(SlowLength), 10)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length on median price", "Indicators")
.SetOptimize(8, 40, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Source candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = default;
_slowEma = default;
_hasCrossSignal = false;
_prevFast = default;
_prevSlow = default;
_prevHigh = default;
_prevLow = default;
_entryPrice = default;
_virtualTarget = default;
_virtualStop = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new EMA { Length = FastLength };
_slowEma = new EMA { Length = SlowLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Calculate median price as in the original expert (PRICE_MEDIAN).
var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
// Update EMA values using the median price.
var fastValue = _fastEma.Process(new DecimalIndicatorValue(_fastEma, medianPrice, candle.OpenTime) { IsFinal = true });
var slowValue = _slowEma.Process(new DecimalIndicatorValue(_slowEma, medianPrice, candle.OpenTime) { IsFinal = true });
if (!_fastEma.IsFormed || !_slowEma.IsFormed)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevFast = fastValue.ToDecimal();
_prevSlow = slowValue.ToDecimal();
return;
}
var fast = fastValue.ToDecimal();
var slow = slowValue.ToDecimal();
if (_prevFast is decimal prevFast && _prevSlow is decimal prevSlow)
{
var bullishCross = prevFast <= prevSlow && fast > slow;
var bearishCross = prevFast >= prevSlow && fast < slow;
if (bullishCross || bearishCross)
_hasCrossSignal = true;
}
_prevFast = fast;
_prevSlow = slow;
var pipValue = PipSize;
if (pipValue <= 0m)
pipValue = Security?.PriceStep ?? 0.0001m;
var moveBackPrice = MoveBackPips * pipValue;
var profitDistance = VirtualProfitPips * pipValue;
var stopDistance = StopLossPips * pipValue;
if (Position == 0 && _hasCrossSignal && _prevHigh is decimal prevHigh && _prevLow is decimal prevLow)
{
var bearishSpread = slow - fast;
var bullishSpread = fast - slow;
var bearishReady = bearishSpread > 2m * pipValue && candle.HighPrice >= prevLow + moveBackPrice;
var bullishReady = bullishSpread > 2m * pipValue && candle.LowPrice <= prevHigh - moveBackPrice;
if (bearishReady)
{
// Enter short after bearish cross and retracement above the previous low.
_entryPrice = candle.ClosePrice;
_virtualTarget = _entryPrice - profitDistance;
_virtualStop = _entryPrice + stopDistance;
SellMarket();
_hasCrossSignal = false;
}
else if (bullishReady)
{
// Enter long after bullish cross and retracement below the previous high.
_entryPrice = candle.ClosePrice;
_virtualTarget = _entryPrice + profitDistance;
_virtualStop = _entryPrice - stopDistance;
BuyMarket();
_hasCrossSignal = false;
}
}
else if (Position != 0 && _entryPrice is decimal && _virtualTarget is decimal target && _virtualStop is decimal stop)
{
if (Position > 0)
{
// Long position: use high for profit target and low for stop.
var hitTarget = candle.HighPrice >= target;
var hitStop = candle.LowPrice <= stop;
if (hitTarget || hitStop)
{
SellMarket();
_hasCrossSignal = false;
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
}
else if (Position < 0)
{
// Short position: use low for profit target and high for stop.
var hitTarget = candle.LowPrice <= target;
var hitStop = candle.HighPrice >= stop;
if (hitTarget || hitStop)
{
BuyMarket();
_hasCrossSignal = false;
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
}
}
if (Position == 0)
{
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class ema_barabashkakvn_edition_strategy(Strategy):
def __init__(self):
super(ema_barabashkakvn_edition_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1)
self._virtual_profit_pips = self.Param("VirtualProfitPips", 5)
self._move_back_pips = self.Param("MoveBackPips", 3)
self._stop_loss_pips = self.Param("StopLossPips", 20)
self._pip_size = self.Param("PipSize", 0.0001)
self._fast_length = self.Param("FastLength", 5)
self._slow_length = self.Param("SlowLength", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def VirtualProfitPips(self):
return self._virtual_profit_pips.Value
@VirtualProfitPips.setter
def VirtualProfitPips(self, value):
self._virtual_profit_pips.Value = value
@property
def MoveBackPips(self):
return self._move_back_pips.Value
@MoveBackPips.setter
def MoveBackPips(self, value):
self._move_back_pips.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def PipSize(self):
return self._pip_size.Value
@PipSize.setter
def PipSize(self, value):
self._pip_size.Value = value
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(ema_barabashkakvn_edition_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowLength
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
median_price = (high + low) / 2.0
fast_result = process_float(self._fast_ema, median_price, candle.OpenTime, True)
slow_result = process_float(self._slow_ema, median_price, candle.OpenTime, True)
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed:
self._prev_high = high
self._prev_low = low
self._prev_fast = float(fast_result)
self._prev_slow = float(slow_result)
return
fast = float(fast_result)
slow = float(slow_result)
if self._prev_fast is not None and self._prev_slow is not None:
bullish_cross = self._prev_fast <= self._prev_slow and fast > slow
bearish_cross = self._prev_fast >= self._prev_slow and fast < slow
if bullish_cross or bearish_cross:
self._has_cross_signal = True
self._prev_fast = fast
self._prev_slow = slow
pip_value = float(self.PipSize)
if pip_value <= 0.0:
pip_value = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
move_back_price = int(self.MoveBackPips) * pip_value
profit_distance = int(self.VirtualProfitPips) * pip_value
stop_distance = int(self.StopLossPips) * pip_value
if self.Position == 0 and self._has_cross_signal and self._prev_high is not None and self._prev_low is not None:
bearish_spread = slow - fast
bullish_spread = fast - slow
bearish_ready = bearish_spread > 2.0 * pip_value and high >= self._prev_low + move_back_price
bullish_ready = bullish_spread > 2.0 * pip_value and low <= self._prev_high - move_back_price
if bearish_ready:
self._entry_price = close
self._virtual_target = self._entry_price - profit_distance
self._virtual_stop = self._entry_price + stop_distance
self.SellMarket()
self._has_cross_signal = False
elif bullish_ready:
self._entry_price = close
self._virtual_target = self._entry_price + profit_distance
self._virtual_stop = self._entry_price - stop_distance
self.BuyMarket()
self._has_cross_signal = False
elif self.Position != 0 and self._entry_price is not None and self._virtual_target is not None and self._virtual_stop is not None:
if self.Position > 0:
hit_target = high >= self._virtual_target
hit_stop = low <= self._virtual_stop
if hit_target or hit_stop:
self.SellMarket()
self._has_cross_signal = False
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
elif self.Position < 0:
hit_target = low <= self._virtual_target
hit_stop = high >= self._virtual_stop
if hit_target or hit_stop:
self.BuyMarket()
self._has_cross_signal = False
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
if self.Position == 0:
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
self._prev_high = high
self._prev_low = low
def OnReseted(self):
super(ema_barabashkakvn_edition_strategy, self).OnReseted()
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
def CreateClone(self):
return ema_barabashkakvn_edition_strategy()