EMA WMA Contrarian-Strategie
Kontrar-Kreuzungssystem, das einen exponentiellen gleitenden Durchschnitt (EMA) und einen gewichteten gleitenden Durchschnitt (WMA) auf Kerzen-Eröffnungspreisen vergleicht. Wenn die schnelle EMA unter die WMA fällt, kauft die Strategie und wettet auf einen Rückprall. Wenn die EMA wieder über die WMA steigt, geht sie Short. Die Handelsgröße wird aus dem konfigurierten Risikoprozentsatz und der Distanz zum Schutz-Stop abgeleitet, während optionale Stop-Loss-, Take-Profit- und Trailing-Stop-Niveaus die Exposition unter Kontrolle halten.
Details
- Einstiegskriterien:
- Long: EMA(Open) kreuzt von oben nach unten unter die WMA(Open)
- Short: EMA(Open) kreuzt von unten nach oben über die WMA(Open)
- Long/Short: Beide Richtungen
- Ausstiegskriterien:
- Fester Stop-Loss in Preisschritten
- Festes Take-Profit in Preisschritten
- Trailing-Stop, der nach einer Preisbewegung von
TrailingStopPoints + TrailingStepPointsvorrückt - Entgegengesetzter Kreuzung schließt die aktuelle Position und öffnet die neue
- Stops: Stop-Loss, Take-Profit und Trailing-Stop
- Standardwerte:
EmaPeriod= 28WmaPeriod= 8StopLossPoints= 50mTakeProfitPoints= 50mTrailingStopPoints= 50mTrailingStepPoints= 10mRiskPercent= 10mBaseVolume= 1mCandleType= TimeSpan.FromMinutes(1).TimeFrame()
- Filter:
- Kategorie: Gleitender Durchschnitt, Contrarian
- Richtung: Long & Short
- Indikatoren: EMA (Open), WMA (Open)
- Stops: Ja (harter Stop, Trailing)
- Komplexität: Mittel
- Zeitrahmen: Intraday (Standard 1 Minute)
- Saisonalität: Keine
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
Parameter
| Parameter | Beschreibung |
|---|---|
EmaPeriod, WmaPeriod |
Rückblickperioden für EMA und WMA, berechnet auf Kerzen-Eröffnungen. |
StopLossPoints, TakeProfitPoints |
Abstand in Preisschritten zum Platzieren des Schutz-Stops und Gewinnziels. |
TrailingStopPoints |
Abstand zwischen Preis und Trailing-Stop nach Aktivierung. |
TrailingStepPoints |
Zusätzliche günstige Bewegung erforderlich, bevor der Trailing-Stop hoch/runter gezogen wird. Muss positiv sein, wenn Trailing aktiviert ist. |
RiskPercent |
Prozentsatz des Portfolio-Kapitals, der pro Trade riskiert wird. Positionsgröße wird berechnet als RiskPercent / (StopLossPoints * PriceStep). |
BaseVolume |
Mindest-Handelsgröße, die verwendet wird, wenn risikobasiertes Sizing nicht bestimmt werden kann. |
CandleType |
Kerzendatentyp für Berechnungen (Standard 1 Minute). |
Hinweise
- Beide gleitenden Durchschnitte verbrauchen Kerzen-Eröffnungspreise und spiegeln den ursprünglichen MetaTrader-Expertenberater wider.
- Trailing-Stops greifen erst, nachdem der Preis mindestens
TrailingStopPoints + TrailingStepPointszugunsten des Trades bewegt hat, was die Legacy-Logik repliziert. - Wenn
TrailingStopPointsgesetzt ist, währendTrailingStepPointsnull oder negativ ist, stoppt die Strategie sofort, um inkonsistentes Trailing-Verhalten zu vermeiden. - Risikobasiertes Sizing fällt auf
BaseVolumezurück, wenn der Portfolio-Wert, Preisschritt oder Stop-Distanz nicht verfügbar sind.
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>
/// Contrarian crossover between EMA and WMA calculated on candle open prices.
/// Opens a long position when EMA crosses below WMA and a short position on the opposite cross.
/// Supports fixed stop-loss, take-profit, and trailing stop management plus risk-based position sizing.
/// </summary>
public class EmaWmaContrarianStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _wmaPeriod;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _ema;
private WeightedMovingAverage _wma;
private bool _hasPrevious;
private decimal _previousEma;
private decimal _previousWma;
private decimal? _entryPrice;
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
/// <summary>
/// EMA period.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// WMA period.
/// </summary>
public int WmaPeriod
{
get => _wmaPeriod.Value;
set => _wmaPeriod.Value = value;
}
/// <summary>
/// Stop-loss in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Trailing stop step in price steps.
/// </summary>
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Risk percentage used for position sizing.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Minimum contract volume used when risk sizing cannot be applied.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.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="EmaWmaContrarianStrategy"/>.
/// </summary>
public EmaWmaContrarianStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 28)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA length calculated on candle open prices", "Indicators")
.SetOptimize(10, 60, 2);
_wmaPeriod = Param(nameof(WmaPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("WMA Period", "WMA length calculated on candle open prices", "Indicators")
.SetOptimize(4, 40, 2);
_stopLossPoints = Param(nameof(StopLossPoints), 50m)
.SetDisplay("Stop Loss (points)", "Stop-loss distance expressed in price steps", "Risk")
.SetOptimize(10m, 150m, 10m);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
.SetDisplay("Take Profit (points)", "Take-profit distance expressed in price steps", "Risk")
.SetOptimize(10m, 200m, 10m);
_trailingStopPoints = Param(nameof(TrailingStopPoints), 50m)
.SetDisplay("Trailing Stop (points)", "Trailing stop distance expressed in price steps", "Risk")
.SetOptimize(10m, 150m, 10m);
_trailingStepPoints = Param(nameof(TrailingStepPoints), 10m)
.SetDisplay("Trailing Step (points)", "Minimal favorable move before the trailing stop is advanced", "Risk")
.SetOptimize(5m, 50m, 5m);
_riskPercent = Param(nameof(RiskPercent), 10m)
.SetDisplay("Risk Percent", "Portfolio percentage risked per trade", "Position Sizing")
.SetOptimize(2m, 20m, 2m);
_baseVolume = Param(nameof(BaseVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Fallback volume when risk sizing is unavailable", "Position Sizing");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle type used for indicators", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_wma = null;
_hasPrevious = false;
_previousEma = 0m;
_previousWma = 0m;
ClearPositionState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Validate trailing configuration to match original expert advisor behaviour.
if (TrailingStopPoints > 0 && TrailingStepPoints <= 0)
{
Stop();
return;
}
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
_wma = new WeightedMovingAverage { Length = WmaPeriod };
// Subscribe to candles and connect indicators.
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle);
subscription.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
// Process only finished candles to avoid intrabar noise.
if (candle.State != CandleStates.Finished)
return;
if (_ema == null || _wma == null)
return;
// Evaluate protective logic before generating new signals.
ManageActivePosition(candle);
var emaValue = _ema.Process(new DecimalIndicatorValue(_ema, candle.OpenPrice, candle.OpenTime) { IsFinal = true });
var wmaValue = _wma.Process(new DecimalIndicatorValue(_wma, candle.OpenPrice, candle.OpenTime) { IsFinal = true });
// Ensure indicators produced valid values.
if (emaValue.IsEmpty || wmaValue.IsEmpty || !_ema.IsFormed || !_wma.IsFormed)
return;
var ema = emaValue.ToDecimal();
var wma = wmaValue.ToDecimal();
if (!_hasPrevious)
{
_previousEma = ema;
_previousWma = wma;
_hasPrevious = true;
return;
}
// Detect crossovers on open-price moving averages.
var buySignal = ema < wma && _previousEma > _previousWma;
var sellSignal = ema > wma && _previousEma < _previousWma;
if (buySignal)
{
EnterLong(candle);
}
else if (sellSignal)
{
EnterShort(candle);
}
_previousEma = ema;
_previousWma = wma;
}
private void ManageActivePosition(ICandleMessage candle)
{
if (Position > 0)
{
// Manage long position exits.
var currentPrice = candle.ClosePrice;
// Exit long when take-profit is reached.
if (_takeProfitPrice is decimal tp && currentPrice >= tp)
{
SellMarket(Position);
ClearPositionState();
return;
}
// Exit long when stop-loss is hit.
if (_stopLossPrice is decimal sl && currentPrice <= sl)
{
SellMarket(Position);
ClearPositionState();
return;
}
// Advance trailing stop for long trades.
if (_entryPrice is decimal entry)
UpdateTrailingForLong(currentPrice, entry);
}
else if (Position < 0)
{
// Manage short position exits.
var currentPrice = candle.ClosePrice;
// Exit short when take-profit is reached.
if (_takeProfitPrice is decimal tp && currentPrice <= tp)
{
BuyMarket(Math.Abs(Position));
ClearPositionState();
return;
}
// Exit short when stop-loss is hit.
if (_stopLossPrice is decimal sl && currentPrice >= sl)
{
BuyMarket(Math.Abs(Position));
ClearPositionState();
return;
}
// Advance trailing stop for short trades.
if (_entryPrice is decimal entry)
UpdateTrailingForShort(currentPrice, entry);
}
else
{
ClearPositionState();
}
}
private void EnterLong(ICandleMessage candle)
{
var entryPrice = candle.ClosePrice;
var volume = CalculateTradeVolume();
if (volume <= 0)
return;
if (Position < 0)
{
// Close an existing short position before opening a new long.
BuyMarket(Math.Abs(Position));
ClearPositionState();
}
// Open the new long trade.
BuyMarket(volume);
SetupRiskLevels(entryPrice, true);
}
private void EnterShort(ICandleMessage candle)
{
var entryPrice = candle.ClosePrice;
var volume = CalculateTradeVolume();
if (volume <= 0)
return;
if (Position > 0)
{
// Close an existing long position before opening a new short.
SellMarket(Position);
ClearPositionState();
}
// Open the new short trade.
SellMarket(volume);
SetupRiskLevels(entryPrice, false);
}
private void SetupRiskLevels(decimal entryPrice, bool isLong)
{
var priceStep = Security?.PriceStep ?? 1m;
var stopDistance = StopLossPoints > 0 ? StopLossPoints * priceStep : (decimal?)null;
var takeProfitDistance = TakeProfitPoints > 0 ? TakeProfitPoints * priceStep : (decimal?)null;
// Remember entry price for managing exits.
_entryPrice = entryPrice;
_stopLossPrice = stopDistance.HasValue
? isLong ? entryPrice - stopDistance.Value : entryPrice + stopDistance.Value
: null;
_takeProfitPrice = takeProfitDistance.HasValue
? isLong ? entryPrice + takeProfitDistance.Value : entryPrice - takeProfitDistance.Value
: null;
}
private decimal CalculateTradeVolume()
{
// Default to configured base volume.
var volume = BaseVolume;
var portfolioValue = Portfolio?.CurrentValue ?? 0m;
var priceStep = Security?.PriceStep ?? 1m;
var stopDistance = StopLossPoints * priceStep;
// Risk-based sizing uses stop distance to allocate capital.
if (RiskPercent > 0 && portfolioValue > 0 && stopDistance > 0)
{
var riskCapital = portfolioValue * (RiskPercent / 100m);
if (riskCapital > 0)
{
var rawVolume = riskCapital / stopDistance;
var adjusted = AdjustVolume(rawVolume);
if (adjusted > 0)
volume = adjusted;
}
}
return volume;
}
private decimal AdjustVolume(decimal volume)
{
// Align volume with instrument volume step.
var step = Security?.VolumeStep ?? 1m;
if (step <= 0)
step = 1m;
var adjusted = Math.Floor(volume / step) * step;
if (adjusted <= 0)
adjusted = step;
var minVolume = Security?.VolumeStep ?? step;
if (minVolume > 0 && adjusted < minVolume)
adjusted = minVolume;
return adjusted;
}
private void UpdateTrailingForLong(decimal currentPrice, decimal entryPrice)
{
if (TrailingStopPoints <= 0)
return;
var priceStep = Security?.PriceStep ?? 1m;
var trailingDistance = TrailingStopPoints * priceStep;
var trailingStep = TrailingStepPoints * priceStep;
// Trailing stop only applies after sufficient favorable movement.
if (currentPrice - entryPrice <= trailingDistance + trailingStep)
return;
var comparisonLevel = currentPrice - (trailingDistance + trailingStep);
// Raise stop-loss closer to current price.
if (_stopLossPrice is not decimal existing || existing < comparisonLevel)
_stopLossPrice = currentPrice - trailingDistance;
}
private void UpdateTrailingForShort(decimal currentPrice, decimal entryPrice)
{
if (TrailingStopPoints <= 0)
return;
var priceStep = Security?.PriceStep ?? 1m;
var trailingDistance = TrailingStopPoints * priceStep;
var trailingStep = TrailingStepPoints * priceStep;
// Trailing stop only applies after sufficient favorable movement.
if (entryPrice - currentPrice <= trailingDistance + trailingStep)
return;
var comparisonLevel = currentPrice + trailingDistance + trailingStep;
// Lower stop-loss toward market for short trades.
if (_stopLossPrice is not decimal existing || existing > comparisonLevel)
_stopLossPrice = currentPrice + trailingDistance;
}
private void ClearPositionState()
{
_entryPrice = null;
_stopLossPrice = null;
_takeProfitPrice = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import ExponentialMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math, Decimal
from indicator_extensions import *
class ema_wma_contrarian_strategy(Strategy):
def __init__(self):
super(ema_wma_contrarian_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 28)
self._wma_period = self.Param("WmaPeriod", 8)
self._stop_loss_points = self.Param("StopLossPoints", 50.0)
self._take_profit_points = self.Param("TakeProfitPoints", 50.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 50.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 10.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._ema = None
self._wma = None
self._has_previous = False
self._previous_ema = 0.0
self._previous_wma = 0.0
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(ema_wma_contrarian_strategy, self).OnStarted2(time)
self._ema = ExponentialMovingAverage()
self._ema.Length = self._ema_period.Value
self._wma = WeightedMovingAverage()
self._wma.Length = self._wma_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._ema is None or self._wma is None:
return
self._manage_active_position(candle)
open_price = float(candle.OpenPrice)
ema_result = process_float(self._ema, Decimal(float(open_price)), candle.OpenTime, True)
wma_result = process_float(self._wma, Decimal(float(open_price)), candle.OpenTime, True)
if ema_result.IsEmpty or wma_result.IsEmpty or not self._ema.IsFormed or not self._wma.IsFormed:
return
ema = float(ema_result.Value)
wma = float(wma_result.Value)
if not self._has_previous:
self._previous_ema = ema
self._previous_wma = wma
self._has_previous = True
return
buy_signal = ema < wma and self._previous_ema > self._previous_wma
sell_signal = ema > wma and self._previous_ema < self._previous_wma
if buy_signal:
self._enter_long(candle)
elif sell_signal:
self._enter_short(candle)
self._previous_ema = ema
self._previous_wma = wma
def _manage_active_position(self, candle):
price = float(candle.ClosePrice)
if self.Position > 0:
if self._take_profit_price is not None and price >= self._take_profit_price:
self.SellMarket(self.Position)
self._clear_position_state()
return
if self._stop_loss_price is not None and price <= self._stop_loss_price:
self.SellMarket(self.Position)
self._clear_position_state()
return
if self._entry_price is not None:
self._update_trailing_for_long(price, self._entry_price)
elif self.Position < 0:
vol = abs(self.Position)
if self._take_profit_price is not None and price <= self._take_profit_price:
self.BuyMarket(vol)
self._clear_position_state()
return
if self._stop_loss_price is not None and price >= self._stop_loss_price:
self.BuyMarket(vol)
self._clear_position_state()
return
if self._entry_price is not None:
self._update_trailing_for_short(price, self._entry_price)
else:
self._clear_position_state()
def _enter_long(self, candle):
entry_price = float(candle.ClosePrice)
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self._clear_position_state()
self.BuyMarket()
self._setup_risk_levels(entry_price, True)
def _enter_short(self, candle):
entry_price = float(candle.ClosePrice)
if self.Position > 0:
self.SellMarket(self.Position)
self._clear_position_state()
self.SellMarket()
self._setup_risk_levels(entry_price, False)
def _setup_risk_levels(self, entry_price, is_long):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0:
step = 1.0
self._entry_price = entry_price
if self._stop_loss_points.Value > 0:
sd = self._stop_loss_points.Value * step
self._stop_loss_price = entry_price - sd if is_long else entry_price + sd
else:
self._stop_loss_price = None
if self._take_profit_points.Value > 0:
td = self._take_profit_points.Value * step
self._take_profit_price = entry_price + td if is_long else entry_price - td
else:
self._take_profit_price = None
def _update_trailing_for_long(self, current_price, entry_price):
if self._trailing_stop_points.Value <= 0:
return
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0:
step = 1.0
trailing_distance = self._trailing_stop_points.Value * step
trailing_step = self._trailing_step_points.Value * step
if current_price - entry_price <= trailing_distance + trailing_step:
return
comparison_level = current_price - (trailing_distance + trailing_step)
if self._stop_loss_price is None or self._stop_loss_price < comparison_level:
self._stop_loss_price = current_price - trailing_distance
def _update_trailing_for_short(self, current_price, entry_price):
if self._trailing_stop_points.Value <= 0:
return
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0:
step = 1.0
trailing_distance = self._trailing_stop_points.Value * step
trailing_step = self._trailing_step_points.Value * step
if entry_price - current_price <= trailing_distance + trailing_step:
return
comparison_level = current_price + trailing_distance + trailing_step
if self._stop_loss_price is None or self._stop_loss_price > comparison_level:
self._stop_loss_price = current_price + trailing_distance
def _clear_position_state(self):
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
def OnReseted(self):
super(ema_wma_contrarian_strategy, self).OnReseted()
self._ema = None
self._wma = None
self._has_previous = False
self._previous_ema = 0.0
self._previous_wma = 0.0
self._clear_position_state()
def CreateClone(self):
return ema_wma_contrarian_strategy()