Ver no GitHub

EMA LWMA RSI Strategy

Overview

The EMA LWMA RSI Strategy reproduces the MetaTrader expert advisor "EMA LWMA RSI" in StockSharp. It compares two moving averages that use the same applied price and optionally a forward shift, while a Relative Strength Index filter confirms the momentum. The algorithm only reacts to newly finished candles from the configured timeframe and trades a single net position: it closes any opposite exposure before opening a new order in the signalled direction. Stop-loss and take-profit distances are configured in pips and automatically scaled to the instrument's tick size.

Trading Logic

  1. Compute an exponential moving average (EMA) and a linear weighted moving average (LWMA) with individual lengths but the same applied price. If MaShift is greater than zero, both averages are shifted forward by the specified number of bars to mirror the MetaTrader "shift" argument.
  2. Process an RSI with its own applied price. The strategy uses the classic 50 threshold to distinguish bullish and bearish momentum.
  3. When a finished candle arrives:
    • A buy signal is generated if the EMA crosses above the LWMA (the previous EMA was greater than the previous LWMA, but the current EMA is below the current LWMA) and the RSI value is above 50.
    • A sell signal is generated if the EMA crosses below the LWMA (the previous EMA was lower than the previous LWMA, but the current EMA is above the current LWMA) and the RSI value is below 50.
  4. Signals set internal pending flags. Before reversing, the strategy first closes the existing position with ClosePosition(). After the fill is confirmed, it immediately submits a market order in the requested direction. This mirrors the original expert advisor that waited for transaction confirmation before sending the next order.
  5. Protective orders are started via StartProtection. If a stop-loss or take-profit is disabled (set to zero), that leg is omitted, matching the MQL behaviour.

Implementation Notes

  • Applied price selection supports the MetaTrader options (Close, Open, High, Low, Median, Typical, Weighted, Average). Weighted price is calculated as (High + Low + 2 * Close) / 4, identical to PRICE_WEIGHTED.
  • Pip sizing automatically multiplies the instrument's PriceStep by 10 for 3/5-digit forex symbols, ensuring a pip equals 10 points on fractional quotes.
  • Indicator bindings rely on StockSharp's high-level candle subscription. Shift handling uses Shift indicators instead of manual buffer indexing.
  • The code keeps boolean flags for pending buy/sell requests. They prevent duplicate orders while the previous command is still pending and are cleared when fills arrive or when the position already matches the signal.
  • Chart helpers draw both moving averages on the price pane and the RSI on a separate area for visual inspection.

Parameters

Parameter Type Default Description
CandleType DataType 1h TimeFrame Candle series processed by the strategy.
StopLossPips int 150 Stop-loss distance in pips. 0 disables the stop.
TakeProfitPips int 150 Take-profit distance in pips. 0 disables the target.
EmaPeriod int 28 Period of the exponential moving average.
LwmaPeriod int 8 Period of the linear weighted moving average.
MaShift int 0 Forward shift (bars) applied to both moving averages.
RsiPeriod int 14 Averaging period of the RSI.
MaAppliedPrice AppliedPriceType Weighted Applied price forwarded to EMA and LWMA.
RsiAppliedPrice AppliedPriceType Weighted Applied price used by the RSI.

Usage

  1. Attach the strategy to the desired security and set the CandleType to match the timeframe used in MetaTrader.
  2. Adjust pip-based protections and indicator settings if the broker uses different defaults.
  3. Enable trading once the subscription is live. The strategy will manage one position at a time and use ClosePosition() before flipping direction.

No Python translation is provided for this strategy yet.

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;

public class EmaLwmaRsiStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public EmaLwmaRsiStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { 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;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}