Ver en GitHub

EMA WMA Risk Strategy

Overview

  • Conversion of the MetaTrader 4 "EMA WMA" expert advisor by Vladimir Hlystov.
  • Trades trend reversals detected from the relationship between an exponential moving average (EMA) and a weighted moving average (WMA) calculated on candle open prices.
  • Automatically attaches stop-loss and take-profit orders identical to the MT4 robot by using StockSharp's protection helper.
  • Supports risk-based position sizing that mirrors the original "risk" input while keeping an option for fixed volume trading.

Original Expert Advisor Logic

  • The MT4 version works on any symbol and timeframe, evaluating signals once on a new bar (guarded by TimeBar).
  • Indicators use PRICE_OPEN, so the averages react to the bar opening tick.
  • When EMA falls below WMA while previously being above it, all short positions are closed and a long trade is opened with predefined stop-loss and take-profit distances.
  • When EMA rises above WMA after being below it, all long positions are closed and a new short position is opened.
  • The risk input computes the lot size from available margin and the stop-loss distance.

Trading Rules in StockSharp

  1. Subscribe to the configured candle series (CandleType, default 30-minute). Only finished candles are processed to avoid repainting.
  2. Feed candle open prices into EMA and WMA indicators. Wait until both indicators are formed.
  3. Detect a bullish crossover when previous EMA > previous WMA and current EMA < current WMA.
    • Close any shorts and enter a long position sized by risk rules.
  4. Detect a bearish crossover when previous EMA < previous WMA and current EMA > current WMA.
    • Close any longs and enter a short position sized by risk rules.
  5. StartProtection creates market-protection orders so every new trade immediately receives stop-loss and take-profit levels expressed in price steps.

Position Sizing and Risk Control

  • RiskPercent emulates the MT4 risk parameter. Volume is computed from portfolio equity, stop-loss distance and security step/step-price values.
  • If exchange metadata is missing (no price step or step price) the algorithm falls back to using the absolute stop distance.
  • If RiskPercent is set to zero the strategy requires a positive OrderVolume (fixed volume override).
  • Existing opposite exposure is closed before new orders are sent, matching the MT4 behaviour of CLOSEORDER then OPENORDER.

Parameters

Name Description
EmaPeriod Period of the exponential moving average (default 28).
WmaPeriod Period of the weighted moving average (default 8).
StopLossPoints Stop-loss distance in instrument steps (default 50).
TakeProfitPoints Take-profit distance in instrument steps (default 50).
RiskPercent Percentage of equity to risk per trade (default 10%).
OrderVolume Fixed order volume; use 0 to enable risk-based sizing.
CandleType Candle data type/timeframe used for calculations.

Implementation Notes

  • EMA and WMA values are pushed manually via DecimalIndicatorValue to ensure the open price is used exactly like the MT4 indicator configuration.
  • The strategy relies on closed candles for signal confirmation; this can delay entries by one bar compared to MT4 but prevents look-ahead bias.
  • Protective orders are expressed in price steps to match the Point multiplier from MetaTrader.
  • Charts automatically plot candles, both moving averages and trade markers when a chart area is available.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// EMA WMA Crossover Risk: Dual EMA crossover with ATR stops.
/// </summary>
public class EmaWmaRiskStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public EmaWmaRiskStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 21)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}