Ver no GitHub

Universal Investor Strategy

This strategy is a direct port of the Universal Investor MetaTrader 4 expert advisor. It combines an exponential moving average (EMA) and a linear weighted moving average (LWMA) to confirm short-term trend direction and performs one-position trading with adaptive position sizing.

Trading logic

  1. Subscribe to the configured CandleType and compute both EMA and LWMA with the period defined by MovingPeriod.
  2. Store the two most recent values of each moving average so that the logic mimics the iMA(..., shift = 1/2) calls from the original EA.
  3. Generate a buy signal when the previous LWMA is above the previous EMA, both averages were rising, and there is no opposite signal on the same candle.
  4. Generate a sell signal when the previous LWMA is below the previous EMA, both averages were falling, and there is no opposite signal on the same candle.
  5. Close an open long position as soon as the LWMA drops below the EMA (mirror logic for shorts).
  6. Calculate the trade volume from the strategy Volume parameter, increase it to satisfy the MaximumRisk requirement when the portfolio value is large enough, and reduce it after consecutive losing trades according to DecreaseFactor.
  7. Submit market orders with BuyMarket/SellMarket and keep track of the entry price to detect winning or losing exits.

The strategy keeps only one position open at a time and immediately reverses only after a full close, reproducing the behaviour of the original MetaTrader script.

Parameters

Name Description
CandleType Candle series used for calculations.
MovingPeriod Period for both EMA and LWMA.
MaximumRisk Fraction of equity (0.05 = 5%) used to compute the minimum position volume.
DecreaseFactor Reduces the volume after consecutive losing trades (0 disables the feature).
Volume Base contract volume passed to BuyMarket/SellMarket.

Indicators

  • ExponentialMovingAverage
  • LinearWeightedMovingAverage

Notes

  • Orders are placed only on closed candles, matching the EA that relies on Time[0] checks.
  • The position size logic mirrors the MetaTrader LotsOptimized function, including the risk-based component and the loss streak multiplier.
using System;

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

namespace StockSharp.Samples.Strategies;

public class UniversalInvestor3920Strategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public UniversalInvestor3920Strategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12).SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 26).SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = default;
		_prevSlow = default;
		_hasPrev = default;
		_cooldown = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) return;

		if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }
		if (_cooldown > 0)
		{
			_cooldown--;
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket(volume);
			_cooldown = 2;
		}
		else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket(volume);
			_cooldown = 2;
		}

		_prevFast = fast; _prevSlow = slow;
	}
}