GitHub で見る

MA Trend 2 Strategy

Summary

  • Converted from MetaTrader 5 expert advisor MA Trend 2.mq5.
  • Uses a configurable moving average to detect whether price trades above or below the shifted average.
  • Positions are managed with optional stop-loss, take-profit, trailing stop, and money management features.

Strategy Logic

  1. Subscribe to the user-selected candle series and calculate the moving average with the chosen method, period, shift, and price source.
  2. On each finished candle store the latest moving average value so a shifted sample (previous bar plus MaShift) can be compared against the current close price.
  3. Generate buy signals when price crosses above the reference average and the direction filter allows long trades. Generate sell signals for the opposite condition. When ReverseSignals is enabled these rules are inverted.
  4. Before entering a trade check the OnlyOnePosition and CloseOppositePositions flags. The strategy can either skip entries when the opposite exposure exists or close it in the same order to flip the position.
  5. Position sizing uses either a fixed volume or a percent risk model derived from the original EA. The percent mode estimates the required volume so the loss at the configured stop distance matches the risk budget.
  6. A trailing stop replicates the original step logic: once profit exceeds TrailingStopPips + TrailingStepPips it moves the stop in steps while never loosening it. If price crosses the trailing stop the position is closed at market.
  7. Optional stop-loss and take-profit protections are attached through the high-level StartProtection helper so the broker model can exit positions between candle updates.

Parameters

Name Description Default
StopLossPips Stop-loss distance in pips. Set to 0 to disable. 50
TakeProfitPips Take-profit distance in pips. Set to 0 to disable. 140
TrailingStopPips Base distance for the trailing stop in pips. 15
TrailingStepPips Minimum additional profit before the trailing stop is tightened. 5
LotMode FixedVolume uses LotOrRiskValue directly. RiskPercent interprets it as account risk percent. RiskPercent
LotOrRiskValue Fixed order size or risk percent depending on LotMode. 3
MaPeriod Moving average period. 12
MaShift Number of completed candles between the current bar and the moving average sample used for signals. 3
MaMethod Moving average method (Simple, Exponential, Smoothed, LinearWeighted). LinearWeighted
MaPrice Candle price used by the moving average (close, open, weighted, etc.). Weighted
CandleType Candle data type subscribed by the strategy. 1 minute time frame
Direction Allowed direction (BuyOnly, SellOnly, Both). Both
OnlyOnePosition Allow only a single open position. false
ReverseSignals Invert buy/sell logic. false
CloseOppositePositions Close opposite exposure before opening a new trade. false

Money Management

  • When LotMode = RiskPercent, the strategy converts the stop-loss distance (in pips) into price units using security metadata (PriceStep, StepPrice).
  • Risk is calculated from the portfolio value (CurrentValue with a fallback to BeginValue).
  • The requested volume is rounded up to the nearest VolumeStep to avoid exchange rejections.

Trailing Stop

  • Trailing distance and step are expressed in pips; the code derives the actual price distance using the instrument pip size.
  • Long positions move the stop up once the close exceeds the entry by at least TrailingStopPips + TrailingStepPips. The stop remains fixed if profit retracts.
  • Short positions mirror the same logic with symmetrical price checks.

Conversion Notes

  • All trading actions use the high-level Strategy API (BuyMarket, SellMarket, StartProtection).
  • The strategy keeps only a short moving average history (shift + buffer) to replicate the previous-bar reference without storing large datasets.
  • Comments are provided in English to document each major block of logic.
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 MaTrend2Strategy : 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 MaTrend2Strategy()
	{
		_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;
	}
}