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
Wait for the current time to fall within the [StartTime, StopTime) trading window. Overnight windows are supported by wrapping around midnight.
Process only completed candles. Feed the configured moving average with the chosen applied price.
Store the previous moving average value to emulate the iMA shift logic used in MetaTrader.
When the previous average is below the latest price and the new average is above the price, open (or reverse into) a long position.
When the previous average is above the latest price and the new average is below the price, open (or reverse into) a short position.
Before opening a new position on the opposite side, flatten any existing exposure to mirror the OrdersTotal() == 0 constraint of the original code.
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();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_price_cross_strategy(Strategy):
def __init__(self):
super(ma_price_cross_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._ma_period = self.Param("MaPeriod", 100)
self._prev_average = None
self._prev_close = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
def OnReseted(self):
super(ma_price_cross_strategy, self).OnReseted()
self._prev_average = None
self._prev_close = None
def OnStarted2(self, time):
super(ma_price_cross_strategy, self).OnStarted2(time)
self._prev_average = None
self._prev_close = None
sma = ExponentialMovingAverage()
sma.Length = self.MaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(sma, self._process_candle).Start()
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
sma_val = float(sma_value)
close = float(candle.ClosePrice)
if self._prev_average is None or self._prev_close is None:
self._prev_average = sma_val
self._prev_close = close
return
# Price crosses above MA -> buy
buy_signal = self._prev_close <= self._prev_average and close > sma_val
# Price crosses below MA -> sell
sell_signal = self._prev_close >= self._prev_average and close < sma_val
if buy_signal:
if self.Position <= 0:
self.BuyMarket()
elif sell_signal:
if self.Position >= 0:
self.SellMarket()
self._prev_average = sma_val
self._prev_close = close
def CreateClone(self):
return ma_price_cross_strategy()