This strategy is a C# conversion of the MetaTrader 4 expert advisor SVOS_EURJPY_D1. It operates on daily candles for EURJPY and
combines a regime classifier with pattern recognition and indicator filters. The Vertical Horizontal Filter (VHF) distinguishes
between trending and ranging market states. When the market is trending the strategy relies on the MACD histogram (OSMA) slope,
while in ranging conditions it falls back to the Stochastic oscillator. Candlestick patterns such as engulfing bars and
morning/evening stars are used to close positions aggressively against unfavourable price action.
Trading logic
Regime detection – the previous day's VHF value is compared with VhfThreshold. Values above the threshold activate the
trend-following block, otherwise the range block is used.
Trend confirmation – two EMAs (5 and 20 periods) are compared with a slow EMA (130 periods, matching the six-month filter of
the original EA) to scale position sizes. In up-trends buy volume is multiplied by RiskBoost; in down-trends sell volume is
multiplied.
Indicator filters:
Trend regime: go long when OSMA is positive and rising (OSMA[1] > 0 and OSMA[1] > OSMA[2]). Go short when OSMA is negative
and falling.
Range regime: go long when the Stochastic main line crosses above its signal, go short when it crosses below.
Volatility guard: the previous standard deviation must exceed StdDevMinimum before any signal is accepted.
Price action filters – the most recent completed candle must not form a doji (DojiDivisor ratio) and must confirm the
direction (bullish for longs, bearish for shorts). Opposite engulfing or star patterns trigger immediate liquidation of the
respective side.
Position limits – the total number of open orders is capped by MaxTrendOrders in trending markets and by MaxRangeOrders
in ranging markets.
Risk management – every order carries fixed stop-loss and take-profit levels (StopLossPips, TakeProfitPips). A trailing
stop is activated when the floating profit exceeds TrailingStopPips; it is recalculated using the candle extremes to mimic the
MetaTrader behaviour.
Indicator usage
Exponential Moving Average (5, 20, 130) – used for direction confirmation and volume scaling.
Vertical Horizontal Filter – custom indicator that measures the ratio between net movement and cumulative close-to-close
changes to detect trends versus ranges.
MACD (OSMA) – the difference between MACD and its signal line drives trending entries and exits.
Stochastic Oscillator – %K and %D values provide mean-reversion signals for ranging markets.
Standard Deviation – ensures volatility is high enough before allowing new trades.
Order management
Orders are executed with BuyMarket/SellMarket and stored internally so that individual stops and targets can be simulated in
StockSharp's netting environment.
When stop-loss or take-profit levels are touched within the candle range, the corresponding portion of the position is closed.
The trailing stop follows the candle high (for longs) or low (for shorts) while maintaining the configured distance.
Parameters
Parameter
Description
Default
LotSize
Base order size expressed in lots.
0.1
RiskBoost
Multiplier applied to the lot size when the EMA trend filter is aligned.
3
TakeProfitPips
Take-profit distance in pips.
350
StopLossPips
Stop-loss distance in pips.
90
TrailingStopPips
Trailing-stop distance in pips (always active).
150
StochKPeriod
%K length of the Stochastic oscillator.
8
StochDPeriod
%D length of the Stochastic oscillator.
3
StochSlowing
Smoothing factor applied to %K.
3
StdDevPeriod
Lookback window for the standard deviation filter.
20
StdDevMinimum
Minimal standard deviation required before new trades can open.
Maximum number of simultaneously open orders during trends.
4
MaxRangeOrders
Maximum number of simultaneously open orders during ranges.
2
MacdFastLength
Fast EMA length inside MACD.
10
MacdSlowLength
Slow EMA length inside MACD.
25
MacdSignalLength
Signal EMA length for MACD.
5
DojiDivisor
Ratio used to flag doji candles (body smaller than range / divisor).
8.5
CandleType
Candle type used for analysis (daily by default).
1 day
PipSizeOverride
Optional pip-size override; 0 enables automatic detection from Security.PriceStep.
0
Implementation notes
The original EA referenced a six-month EMA from a monthly timeframe. The port computes a 130-period EMA on daily closes to
reproduce the same smoothing while keeping a single data subscription.
Stops, targets and trailing logic are reproduced inside the strategy because StockSharp nets positions by default. Each entry is
tracked individually to honour the MetaTrader behaviour.
Trailing stop updates use candle highs/lows to approximate intraday price movements. Results may differ slightly from tick-based
trailing in MetaTrader when large intraday reversals occur.
Pip size is calculated from Security.PriceStep; use PipSizeOverride if the broker uses a non-standard step for JPY pairs.
Usage
Attach the strategy to EURJPY daily data or update CandleType if another timeframe is desired.
Verify that the pip size is detected correctly; adjust PipSizeOverride if necessary.
Configure money-management parameters (LotSize, RiskBoost) to match account constraints.
Run the strategy in the StockSharp Designer or API Runner to validate behaviour before trading live.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Regime-switching strategy that uses ADX to detect trend vs range.
/// In trends, follows EMA direction; in ranges, trades mean reversion.
/// </summary>
public class SvosEurJpyD1Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _adxLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _adxThreshold;
private decimal _entryPrice;
public SvosEurJpyD1Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_adxLength = Param(nameof(AdxLength), 14)
.SetDisplay("ADX Length", "Period for ADX.", "Indicators");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "Period for trend EMA.", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "ADX level to distinguish trend from range.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AdxLength
{
get => _adxLength.Value;
set => _adxLength.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
var atr = new AverageTrueRange { Length = AdxLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal adxValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var isTrending = adxValue > AdxThreshold;
// Position management
if (Position > 0)
{
if (isTrending && close < emaValue)
{
SellMarket(); // Trend reversed
}
else if (!isTrending && close >= emaValue)
{
SellMarket(); // Mean reversion target hit
}
}
else if (Position < 0)
{
if (isTrending && close > emaValue)
{
BuyMarket();
}
else if (!isTrending && close <= emaValue)
{
BuyMarket();
}
}
// Entry
if (Position == 0)
{
if (isTrending)
{
// Trend following
if (close > emaValue)
{
_entryPrice = close;
BuyMarket();
}
else if (close < emaValue)
{
_entryPrice = close;
SellMarket();
}
}
else
{
// Mean reversion
var deviation = Math.Abs(close - emaValue);
if (deviation > 0 && close < emaValue)
{
_entryPrice = close;
BuyMarket();
}
else if (deviation > 0 && close > emaValue)
{
_entryPrice = close;
SellMarket();
}
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage
class svos_eur_jpy_d1_strategy(Strategy):
def __init__(self):
super(svos_eur_jpy_d1_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._adx_length = self.Param("AdxLength", 14) \
.SetDisplay("ADX Length", "Period for ADX", "Indicators")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Period for trend EMA", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetDisplay("ADX Threshold", "ADX level to distinguish trend from range", "Indicators")
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AdxLength(self):
return self._adx_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AdxThreshold(self):
return self._adx_threshold.Value
def OnStarted2(self, time):
super(svos_eur_jpy_d1_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AdxLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self._ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, adx_value, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
adx_val = float(adx_value)
ema_val = float(ema_value)
threshold = float(self.AdxThreshold)
is_trending = adx_val > threshold
# Position management
if self.Position > 0:
if is_trending and close < ema_val:
self.SellMarket()
elif not is_trending and close >= ema_val:
self.SellMarket()
elif self.Position < 0:
if is_trending and close > ema_val:
self.BuyMarket()
elif not is_trending and close <= ema_val:
self.BuyMarket()
# Entry
if self.Position == 0:
if is_trending:
if close > ema_val:
self._entry_price = close
self.BuyMarket()
elif close < ema_val:
self._entry_price = close
self.SellMarket()
else:
deviation = abs(close - ema_val)
if deviation > 0 and close < ema_val:
self._entry_price = close
self.BuyMarket()
elif deviation > 0 and close > ema_val:
self._entry_price = close
self.SellMarket()
def OnReseted(self):
super(svos_eur_jpy_d1_strategy, self).OnReseted()
self._entry_price = 0.0
def CreateClone(self):
return svos_eur_jpy_d1_strategy()