Conversion of the MetaTrader 5 expert advisor "Moving Average with Frames". The original system evaluates the relationship between each candle's open/close prices and a shifted simple moving average (SMA) while displaying multiple optimization "frames" on charts. This StockSharp port focuses on the trading logic: it reacts only once per completed bar, opens a single netting position, and mirrors the money-management rules from the source code.
Trading Logic
Data source – the strategy subscribes to the configured time frame (CandleType) and processes only finished candles, which reproduces the MetaTrader constraint if(rt[1].tick_volume>1) return;.
Indicator – a simple moving average with period MovingPeriod. The indicator output is shifted forward by MovingShift completed candles by keeping a buffer of past values.
Warm-up – trading is suspended until at least 100 completed candles are collected, matching the original Bars(_Symbol,_Period)>100 guard.
Entry conditions
Go long when the candle opens below the shifted SMA and closes above it.
Go short when the candle opens above the shifted SMA and closes below it.
The engine enforces a single position: opposing exposure is flattened before entering in the new direction.
Exit conditions – an existing long is closed when the open price is above and the close price is below the shifted SMA; shorts are closed on the opposite crossover. New trades are not opened on the same bar after an exit, just like the original expert.
Position Sizing and Risk
MaximumRisk – determines the raw order volume as Portfolio.CurrentValue * MaximumRisk / price when portfolio data are available. If the broker feed does not provide equity information, the strategy falls back to the manual Volume property.
DecreaseFactor – after more than one consecutive losing trade, the next position size is reduced by volume * losses / DecreaseFactor, mimicking MetaTrader's lot reduction logic. Any profitable trade resets the counter.
Volume alignment – the computed size is normalized to the instrument's VolumeStep, clamped between MinVolume and MaxVolume, and rounded to two decimals when the exchange does not publish a step.
Additional Notes
The MetaTrader "frames" visualization is not ported because StockSharp already provides rich optimization dashboards. The trading logic, signal timing, and sizing behaviour remain faithful to the source.
All indicator values are consumed directly from the Bind callback; no manual GetValue calls are used.
Consecutive loss tracking is implemented inside OnOwnTradeReceived, allowing the strategy to react correctly to partial fills and netting behavior.
Parameters
Parameter
Default
Description
MaximumRisk
0.02
Fraction of portfolio equity risked on each entry.
DecreaseFactor
3
Divisor used to shrink position size after two or more consecutive losses.
MovingPeriod
12
Length of the simple moving average applied to closing prices.
MovingShift
6
Number of completed candles used to offset the SMA forward in time.
CandleType
1h time frame
Primary candle series processed by the strategy.
Usage Tips
Attach the strategy to a security and portfolio in StockSharp Designer or code.
Adjust the candle type to match the desired MetaTrader chart period.
Tune MaximumRisk and DecreaseFactor to match your account size and desired risk tolerance.
Run backtests to validate that the crossover signals align with the original MetaTrader results.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Moving Average With Frames: SMA crossover with candle body confirmation.
/// Buys when close crosses above shifted SMA, sells when crosses below.
/// </summary>
public class MovingAverageWithFramesStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _entryPrice;
private decimal _prevClose;
public MovingAverageWithFramesStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_smaLength = Param(nameof(SmaLength), 12)
.SetDisplay("SMA Length", "SMA 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 SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_prevClose = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = SmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
if (_prevClose == 0)
{
_prevClose = close;
return;
}
if (Position > 0)
{
if (close < smaVal && _prevClose >= smaVal)
{
SellMarket();
_entryPrice = 0;
}
else if (close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close > smaVal && _prevClose <= smaVal)
{
BuyMarket();
_entryPrice = 0;
}
else if (close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close > smaVal && _prevClose <= smaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (close < smaVal && _prevClose >= smaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevClose = close;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
class moving_average_with_frames_strategy(Strategy):
def __init__(self):
super(moving_average_with_frames_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._sma_length = self.Param("SmaLength", 12) \
.SetDisplay("SMA Length", "SMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._entry_price = 0.0
self._prev_close = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def SmaLength(self):
return self._sma_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(moving_average_with_frames_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._prev_close = 0.0
self._sma = SimpleMovingAverage()
self._sma.Length = self.SmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
sv = float(sma_val)
av = float(atr_val)
if av <= 0:
return
close = float(candle.ClosePrice)
if self._prev_close == 0:
self._prev_close = close
return
if self.Position > 0:
if close < sv and self._prev_close >= sv:
self.SellMarket()
self._entry_price = 0.0
elif close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close > sv and self._prev_close <= sv:
self.BuyMarket()
self._entry_price = 0.0
elif close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > sv and self._prev_close <= sv:
self._entry_price = close
self.BuyMarket()
elif close < sv and self._prev_close >= sv:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(moving_average_with_frames_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_close = 0.0
def CreateClone(self):
return moving_average_with_frames_strategy()