The New FSCEA strategy is a MACD-based trend following system that was ported from the original MetaTrader 4 expert advisor new_fscea.mq4. The strategy combines a classic MACD crossover confirmation with an EMA slope filter, static take-profit targets, and a trailing stop to manage open positions. It trades a single symbol at a time and opens only one position in the market.
Trading Logic
Long Entry
MACD main line is below zero, but crosses above the signal line on the current closed candle.
The previous candle still had the MACD line below the signal line (confirms the crossover).
The absolute value of the MACD line exceeds the OpenLevelPoints threshold (scaled by price step).
The shifted EMA slope is positive (EMA_shifted_now > EMA_shifted_previous).
No position is currently open.
Short Entry
MACD main line is above zero, but crosses below the signal line on the current closed candle.
The previous candle still had the MACD line above the signal line.
The MACD main line exceeds the OpenLevelPoints threshold (scaled by price step).
The shifted EMA slope is negative (EMA_shifted_now < EMA_shifted_previous).
No position is currently open.
Long Exit
Triggered when MACD crosses below the signal line while staying above zero and the MACD value exceeds the CloseLevelPoints threshold.
Or when the candle high touches the virtual take-profit level (entry + TakeProfitPoints * priceStep).
Or when the candle low reaches the trailing-stop level (updated dynamically as price moves in favor).
Short Exit
Triggered when MACD crosses above the signal line while staying below zero and the absolute MACD value exceeds the CloseLevelPoints threshold.
Or when the candle low touches the virtual take-profit level (entry - TakeProfitPoints * priceStep).
Or when the candle high reaches the trailing-stop level (updated dynamically as price moves in favor).
Risk Management
Take profit is expressed in instrument points and converted into price by multiplying by Security.PriceStep.
Trailing stop works in points and tightens once the floating profit is larger than the trailing distance.
Only one position can be open at any time, mirroring the behaviour of the MT4 expert advisor.
Position protection is enabled through the built-in StartProtection() helper.
Indicators
MACD (12, 26, 9) – the main crossover engine. The histogram magnitude provides the entry and exit thresholds.
EMA (TrendPeriod) – applied to close prices. The slope comparison uses a configurable shift (TrendShift) to emulate the MT4 ma_shift parameter.
Parameters
Parameter
Default
Description
TakeProfitPoints
300
Distance to the profit target in points. Converted to price using the symbol price step.
TrailingStopPoints
20
Trailing stop size in points. Activated only after the trade moves in favor by more than this distance.
OpenLevelPoints
3
Minimum MACD magnitude (points) required before a new trade is allowed.
CloseLevelPoints
2
MACD magnitude (points) required to close a trade via MACD crossover.
TrendPeriod
10
Length of the EMA trend filter.
TrendShift
2
Horizontal shift (in bars) applied to the EMA when evaluating its slope. Higher values delay trend confirmation.
TradeVolume
0.1
Default order volume sent with market orders.
CandleType
1-hour time frame
Candle type used for indicator calculations; can be changed to match the desired timeframe.
Implementation Notes
The strategy processes only finished candles to keep the logic close to the MT4 version.
EMA shift is emulated by buffering indicator outputs and comparing values TrendShift bars apart.
Trailing stop and take profit are implemented virtually (no actual stop/limit orders) to stay within the high-level API requirements.
The code relies exclusively on the high-level candle subscription API (SubscribeCandles().BindEx(...)) to comply with repository guidelines.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// NewFSCEA: EMA trend with RSI momentum and ATR stops.
/// </summary>
public class NewFsceaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevClose;
private decimal _entryPrice;
public NewFsceaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI 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 EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 0;
_entryPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_prevClose == 0 || atrVal <= 0)
{
_prevClose = close;
return;
}
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 75)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 25)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close > emaVal && _prevClose <= emaVal && rsiVal > 50)
{
_entryPrice = close;
BuyMarket();
}
else if (close < emaVal && _prevClose >= emaVal && rsiVal < 50)
{
_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 ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class new_fscea_strategy(Strategy):
def __init__(self):
super(new_fscea_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_close = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(new_fscea_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
rv = float(rsi_val)
av = float(atr_val)
close = float(candle.ClosePrice)
if self._prev_close == 0 or av <= 0:
self._prev_close = close
return
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5 or rv > 75:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.5 or close >= self._entry_price + av * 1.5 or rv < 25:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > ev and self._prev_close <= ev and rv > 50:
self._entry_price = close
self.BuyMarket()
elif close < ev and self._prev_close >= ev and rv < 50:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(new_fscea_strategy, self).OnReseted()
self._prev_close = 0.0
self._entry_price = 0.0
def CreateClone(self):
return new_fscea_strategy()