View on GitHub

Moving Average Money Strategy

Overview

The strategy is a StockSharp conversion of the MetaTrader expert advisor "Moving Average Money". It evaluates completed candles and reacts when the previous bar crosses a shifted simple moving average. The system supports both long and short trades and keeps every decision synchronized with the high-level candle subscription API.

Trading Logic

  • A simple moving average with configurable length and visual shift is calculated from close prices.
  • Only finished candles are processed to prevent duplicate orders within a bar.
  • Short entry: when the previous candle opens above the shifted moving average and closes below it.
  • Long entry: when the previous candle opens below the shifted moving average and closes above it.
  • The strategy does not pyramid positions; any open exposure in the opposite direction is closed before establishing a new trade.

Risk Management

  • The stop-loss distance in price units is derived from MaximumRiskPercent. The current portfolio value, the instrument price step and the step price are used to convert the chosen risk percentage into price steps.
  • The bid/ask spread is subtracted from the risk-based distance whenever best quotes are available.
  • Take-profit levels are defined as stopDistance * ProfitLossFactor.
  • Both stop and target levels are monitored on completed candles. When either level is reached the position is flattened with a market order.

Parameters

  • CandleType – time frame used for signal detection.
  • MovingPeriod – length of the simple moving average.
  • MovingShift – number of fully formed candles used to shift the moving average to the right.
  • MaximumRiskPercent – percentage of the current portfolio value that defines the maximum loss per trade.
  • ProfitLossFactor – multiplier applied to the stop distance to compute the take-profit distance.
  • TradeVolume – base order volume for new entries (volume step constraints are respected automatically).

Implementation Notes

  • The strategy keeps track of open positions via high-level event handlers (OnOwnTradeReceived) to reinitialise stops and targets after fills.
  • If market data lacks quotes or portfolio valuation, new entries are skipped to avoid orders without proper risk control.
  • The moving average shift is emulated with an internal buffer so that the logic matches the MetaTrader version.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Moving Average Money: EMA crossover with ATR stops.
/// </summary>
public class MovingAverageMoneyStrategy : 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 MovingAverageMoneyStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_fastEmaLength = Param(nameof(FastEmaLength), 12)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
			.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) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if ((fastVal > slowVal && _prevFast <= _prevSlow) || 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;
	}
}