Forex Fraus Slogger Strategy
This strategy replicates the MetaTrader envelope reversal system.
Logic
- Calculate a 1-period SMA as the base price.
- Upper and lower envelopes are set at
EnvelopePercentpercent from the base. - When price closes above the upper band and then returns below, enter a short position.
- When price closes below the lower band and then returns above, enter a long position.
- Positions are protected by a trailing stop.
Parameters
EnvelopePercent– percentage offset for envelopes (default 0.1).TrailingStop– trailing stop distance in price units (default 0.001).TrailingStep– minimum price move required to advance the trailing stop (default 0.0001).ProfitTrailing– enable trailing only after position becomes profitable.UseTimeFilter– trade only during specified hours.StartHour– start of the trading window.StopHour– end of the trading window.CandleType– candle timeframe used for calculations.
Notes
- The strategy uses market orders via
BuyMarketandSellMarket. - The trailing stop exits the position when price crosses the stop level.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Envelope reversion strategy with trailing stop.
/// </summary>
public class ForexFrausSloggerStrategy : Strategy
{
private readonly StrategyParam<decimal> _envelopePercent;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<decimal> _trailingStep;
private readonly StrategyParam<bool> _profitTrailing;
private readonly StrategyParam<bool> _useTimeFilter;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _stopHour;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _sma = null!;
private bool _wasAboveUpper;
private bool _wasBelowLower;
private decimal _entryPrice;
private decimal _peakPrice;
private decimal _troughPrice;
private decimal _currentStop;
/// <summary>
/// Envelope percent.
/// </summary>
public decimal EnvelopePercent
{
get => _envelopePercent.Value;
set => _envelopePercent.Value = value;
}
/// <summary>
/// Trailing stop distance.
/// </summary>
public decimal TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Trailing step for stop adjustment.
/// </summary>
public decimal TrailingStep
{
get => _trailingStep.Value;
set => _trailingStep.Value = value;
}
/// <summary>
/// Enable trailing only after profit.
/// </summary>
public bool ProfitTrailing
{
get => _profitTrailing.Value;
set => _profitTrailing.Value = value;
}
/// <summary>
/// Use time filter.
/// </summary>
public bool UseTimeFilter
{
get => _useTimeFilter.Value;
set => _useTimeFilter.Value = value;
}
/// <summary>
/// Trading start hour.
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Trading stop hour.
/// </summary>
public int StopHour
{
get => _stopHour.Value;
set => _stopHour.Value = value;
}
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ForexFrausSloggerStrategy"/>.
/// </summary>
public ForexFrausSloggerStrategy()
{
_envelopePercent = Param(nameof(EnvelopePercent), 1.0m)
.SetGreaterThanZero()
.SetDisplay("Envelope %", "Envelope percent", "Parameters");
_trailingStop = Param(nameof(TrailingStop), 500m)
.SetGreaterThanZero()
.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk");
_trailingStep = Param(nameof(TrailingStep), 100m)
.SetGreaterThanZero()
.SetDisplay("Trailing Step", "Minimum stop move", "Risk");
_profitTrailing = Param(nameof(ProfitTrailing), true)
.SetDisplay("Profit Trailing", "Trail only after profit", "Risk");
_useTimeFilter = Param(nameof(UseTimeFilter), false)
.SetDisplay("Use Time Filter", "Enable trading hours filter", "Parameters");
_startHour = Param(nameof(StartHour), 7)
.SetRange(0, 23)
.SetDisplay("Start Hour", "Trading start hour", "Parameters");
_stopHour = Param(nameof(StopHour), 17)
.SetRange(0, 23)
.SetDisplay("Stop Hour", "Trading stop hour", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Working candle timeframe", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null!;
_wasAboveUpper = false;
_wasBelowLower = false;
_entryPrice = 0m;
_peakPrice = 0m;
_troughPrice = 0m;
_currentStop = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma = new ExponentialMovingAverage { Length = 14 };
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 basis)
{
if (candle.State != CandleStates.Finished)
return;
if (!_sma.IsFormed || !IsFormedAndOnlineAndAllowTrading())
return;
if (UseTimeFilter && !IsWithinTradingHours(candle.OpenTime))
return;
var percent = EnvelopePercent / 100m;
var upper = basis * (1m + percent);
var lower = basis * (1m - percent);
var close = candle.ClosePrice;
if (close > upper)
_wasAboveUpper = true;
if (close < lower)
_wasBelowLower = true;
if (_wasAboveUpper && close <= upper)
{
// Sell signal - go short
if (Position >= 0)
SellMarket();
_entryPrice = close;
_troughPrice = close;
_currentStop = close + TrailingStop;
_wasAboveUpper = false;
return;
}
if (_wasBelowLower && close >= lower)
{
// Buy signal - go long
if (Position <= 0)
BuyMarket();
_entryPrice = close;
_peakPrice = close;
_currentStop = close - TrailingStop;
_wasBelowLower = false;
return;
}
if (Position > 0)
{
if (candle.HighPrice >= _peakPrice + TrailingStep)
{
_peakPrice = candle.HighPrice;
var newStop = _peakPrice - TrailingStop;
if (!ProfitTrailing || newStop > _entryPrice)
_currentStop = Math.Max(_currentStop, newStop);
}
if (close <= _currentStop)
SellMarket();
}
else if (Position < 0)
{
if (_troughPrice == 0m || candle.LowPrice <= _troughPrice - TrailingStep)
{
_troughPrice = _troughPrice == 0m ? candle.LowPrice : Math.Min(_troughPrice, candle.LowPrice);
var newStop = _troughPrice + TrailingStop;
if (!ProfitTrailing || newStop < _entryPrice)
_currentStop = _currentStop == 0m ? newStop : Math.Min(_currentStop, newStop);
}
if (_currentStop != 0m && close >= _currentStop)
BuyMarket();
}
}
private bool IsWithinTradingHours(DateTimeOffset time)
{
var hour = time.Hour;
if (StartHour < StopHour)
return hour >= StartHour && hour < StopHour;
return hour >= StartHour || hour < StopHour;
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class forex_fraus_slogger_strategy(Strategy):
def __init__(self):
super(forex_fraus_slogger_strategy, self).__init__()
self._envelope_percent = self.Param("EnvelopePercent", 1.0) \
.SetDisplay("Envelope %", "Envelope percent", "Parameters")
self._trailing_stop = self.Param("TrailingStop", 500.0) \
.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk")
self._trailing_step = self.Param("TrailingStep", 100.0) \
.SetDisplay("Trailing Step", "Minimum stop move", "Risk")
self._profit_trailing = self.Param("ProfitTrailing", True) \
.SetDisplay("Profit Trailing", "Trail only after profit", "Risk")
self._use_time_filter = self.Param("UseTimeFilter", False) \
.SetDisplay("Use Time Filter", "Enable trading hours filter", "Parameters")
self._start_hour = self.Param("StartHour", 7) \
.SetDisplay("Start Hour", "Trading start hour", "Parameters")
self._stop_hour = self.Param("StopHour", 17) \
.SetDisplay("Stop Hour", "Trading stop hour", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Working candle timeframe", "Parameters")
self._sma = None
self._was_above_upper = False
self._was_below_lower = False
self._entry_price = 0.0
self._peak_price = 0.0
self._trough_price = 0.0
self._current_stop = 0.0
@property
def envelope_percent(self):
return self._envelope_percent.Value
@property
def trailing_stop(self):
return self._trailing_stop.Value
@property
def trailing_step(self):
return self._trailing_step.Value
@property
def profit_trailing(self):
return self._profit_trailing.Value
@property
def use_time_filter(self):
return self._use_time_filter.Value
@property
def start_hour(self):
return self._start_hour.Value
@property
def stop_hour(self):
return self._stop_hour.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(forex_fraus_slogger_strategy, self).OnReseted()
self._sma = None
self._was_above_upper = False
self._was_below_lower = False
self._entry_price = 0.0
self._peak_price = 0.0
self._trough_price = 0.0
self._current_stop = 0.0
def OnStarted2(self, time):
super(forex_fraus_slogger_strategy, self).OnStarted2(time)
self._sma = ExponentialMovingAverage()
self._sma.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._sma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma)
self.DrawOwnTrades(area)
def process_candle(self, candle, basis):
if candle.State != CandleStates.Finished:
return
if not self._sma.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
if self.use_time_filter and not self._is_within_trading_hours(candle.OpenTime):
return
basis = float(basis)
pct = float(self.envelope_percent) / 100.0
upper = basis * (1.0 + pct)
lower = basis * (1.0 - pct)
close = float(candle.ClosePrice)
trailing_stop = float(self.trailing_stop)
trailing_step = float(self.trailing_step)
if close > upper:
self._was_above_upper = True
if close < lower:
self._was_below_lower = True
if self._was_above_upper and close <= upper:
if self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._trough_price = close
self._current_stop = close + trailing_stop
self._was_above_upper = False
return
if self._was_below_lower and close >= lower:
if self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._peak_price = close
self._current_stop = close - trailing_stop
self._was_below_lower = False
return
if self.Position > 0:
high = float(candle.HighPrice)
if high >= self._peak_price + trailing_step:
self._peak_price = high
new_stop = self._peak_price - trailing_stop
if not self.profit_trailing or new_stop > self._entry_price:
self._current_stop = max(self._current_stop, new_stop)
if close <= self._current_stop:
self.SellMarket()
elif self.Position < 0:
low = float(candle.LowPrice)
if self._trough_price == 0.0 or low <= self._trough_price - trailing_step:
self._trough_price = low if self._trough_price == 0.0 else min(self._trough_price, low)
new_stop = self._trough_price + trailing_stop
if not self.profit_trailing or new_stop < self._entry_price:
self._current_stop = new_stop if self._current_stop == 0.0 else min(self._current_stop, new_stop)
if self._current_stop != 0.0 and close >= self._current_stop:
self.BuyMarket()
def _is_within_trading_hours(self, time):
hour = time.Hour
sh = int(self.start_hour)
eh = int(self.stop_hour)
if sh < eh:
return hour >= sh and hour < eh
return hour >= sh or hour < eh
def CreateClone(self):
return forex_fraus_slogger_strategy()