GitHub で見る

MA Price Cross Strategy

The MA Price Cross strategy is a direct conversion of the MetaTrader 4 expert advisor "MA Price Cross" to the StockSharp high-level API. It waits for the selected moving average to cross the current price while trading is allowed within a configurable time window. When the crossing happens from below, the algorithm opens a long position; when the crossing happens from above, it opens a short position. Protective stop-loss and take-profit distances are defined in MetaTrader points and automatically translated to absolute price offsets using the instrument's PriceStep.

Unlike the original MQL implementation, which reacts on every tick, the StockSharp version works with finished candles and uses the SubscribeCandles high-level subscription. This ensures that trading decisions are executed once per bar and remain compatible with the indicator binding pipeline. The moving average can be configured to match all four MetaTrader modes and accepts different price sources (close, open, high, low, median, typical, weighted).

Trading logic

  1. Wait for the current time to fall within the [StartTime, StopTime) trading window. Overnight windows are supported by wrapping around midnight.
  2. Process only completed candles. Feed the configured moving average with the chosen applied price.
  3. Store the previous moving average value to emulate the iMA shift logic used in MetaTrader.
  4. When the previous average is below the latest price and the new average is above the price, open (or reverse into) a long position.
  5. When the previous average is above the latest price and the new average is below the price, open (or reverse into) a short position.
  6. Before opening a new position on the opposite side, flatten any existing exposure to mirror the OrdersTotal() == 0 constraint of the original code.
  7. Start a virtual stop-loss and take-profit with distances expressed in MetaTrader points multiplied by the current instrument PriceStep.

Default parameters

Parameter Default Description
CandleType TimeFrame(1m) Candle series that drives all calculations.
MaPeriod 160 Number of bars used by the moving average.
MaMethod Simple Moving average type: Simple, Exponential, Smoothed, or LinearWeighted.
PriceType Close Price source forwarded to the moving average (close/open/high/low/median/typical/weighted).
StartTime 01:00 Time of day when trading becomes active.
StopTime 22:00 Time of day when new entries stop.
StopLossPoints 200 MetaTrader points converted into an absolute protective stop distance.
TakeProfitPoints 600 MetaTrader points converted into an absolute profit target distance.
OrderVolume 0.1 Default volume submitted with market orders.

Notes

  • If StartTime equals StopTime, the time filter is disabled and trading is allowed all day.
  • When StopLossPoints or TakeProfitPoints equals zero, the corresponding protection level is not registered.
  • The time filter uses the candle close time (candle.CloseTime.TimeOfDay) so it adapts to the exchange time zone supplied by MarketDataConnector.
  • If the security does not expose PriceStep, point-based distances are used directly without scaling.

Original strategy reference

  • Source: MQL/44133/MA Price Cross.mq4
  • Author: JBlanked (2023)
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "MA Price Cross" MetaTrader expert.
/// Enters when SMA crosses above/below the current close price.
/// </summary>
public class MaPriceCrossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maPeriod;

	private ExponentialMovingAverage _sma;
	private decimal? _prevAverage;
	private decimal? _prevClose;

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

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public MaPriceCrossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for MA cross detection", "General");

		_maPeriod = Param(nameof(MaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "SMA period", "Indicators");
	}

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

		_sma = new ExponentialMovingAverage { Length = MaPeriod };
		_prevAverage = null;
		_prevClose = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_sma, ProcessCandle)
			.Start();

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

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

		if (!_sma.IsFormed)
		{
			_prevAverage = smaValue;
			_prevClose = candle.ClosePrice;
			return;
		}

		if (_prevAverage is null || _prevClose is null)
		{
			_prevAverage = smaValue;
			_prevClose = candle.ClosePrice;
			return;
		}

		var close = candle.ClosePrice;
		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// MA was below price, now crosses above -> sell signal (price goes under MA)
		var sellSignal = _prevClose.Value >= _prevAverage.Value && close < smaValue;
		// MA was above price, now crosses below -> buy signal (price goes above MA)
		var buySignal = _prevClose.Value <= _prevAverage.Value && close > smaValue;

		if (buySignal)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (sellSignal)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevAverage = smaValue;
		_prevClose = close;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_sma = null;
		_prevAverage = null;
		_prevClose = null;

		base.OnReseted();
	}
}