Tuyul Uncensored Strategy
Overview
Tuyul Uncensored is a swing-following strategy that rebuilds the original MetaTrader 5 expert advisor with StockSharp's high-level API. The system observes ZigZag swings, aligns entries with a moving average trend filter, and places limit orders at the 57% Fibonacci retracement of the latest leg. When price revisits that level, the strategy attempts to join the dominant swing while protecting the trade with stop-loss and take-profit levels derived from the same leg.
Trading Logic
- Data preparation
- One candle subscription defined by the selected
Candle Typeparameter. - A ZigZag indicator (Depth/Deviation/Backstep) is used to track the latest confirmed swing high and swing low.
- Fast and slow EMAs (default 9/21) provide the directional filter.
- One candle subscription defined by the selected
- Signal detection
- When the ZigZag confirms a new pivot (either a new high or a new low), the strategy updates the most recent swing pair.
- If no orders are active and there is no open position, the previous EMA values determine the trend:
- Fast EMA above slow EMA → bullish context.
- Fast EMA below slow EMA → bearish context.
- Order placement
- In a bullish context the strategy places a buy limit order at the 57% retracement between the last swing low and swing high.
- In a bearish context it places a sell limit order at the symmetric 57% retracement from swing high to swing low.
- Stop-loss is anchored at the opposite ZigZag extreme, while take-profit equals the stop distance multiplied by
Take Profit Multiplier(default 1.2). - Orders remain active for
Wait Bars After Signalcandles; afterwards they are cancelled to wait for a fresh signal.
- Position management
- Once an order fills the strategy watches subsequent candles. A long position is closed when price reaches either the predefined stop-loss or take-profit. The same mirrored logic applies to short positions.
- Trading can be limited to specific weekdays. Outside the permitted days all pending orders are removed, but existing positions are left untouched, following the original advisor behavior.
Parameters
| Name | Description |
|---|---|
Volume Per Trade |
Order volume submitted with every entry. |
TP Multiplier |
Multiplier applied to the stop distance to compute the take-profit offset. |
ZigZag Depth |
Number of candles examined when confirming a swing. |
ZigZag Deviation |
Minimum deviation (in points) required before the ZigZag validates a new pivot. |
ZigZag Backstep |
Minimum number of candles between opposite ZigZag pivots. |
Wait Bars After Signal |
Maximum candles to keep the pending order alive before cancellation. |
Fast EMA |
Period of the fast exponential moving average used as trend filter. |
Slow EMA |
Period of the slow exponential moving average used as trend filter. |
Allow Monday … Allow Friday |
Toggles that enable or disable trading on individual weekdays. |
Candle Type |
Candle series used for all indicator calculations and trading decisions. |
Notes
- The Fibonacci retracement ratio is fixed at 57% as in the source EA.
- Stop-loss and take-profit levels are monitored on candle closes; intrabar spikes beyond the thresholds trigger market exits on the next evaluation.
- The strategy keeps a single pending order at a time, mirroring the original implementation.
namespace StockSharp.Samples.Strategies;
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;
/// <summary>
/// Strategy converted from the "Tuyul Uncensored" MetaTrader expert advisor.
/// It watches ZigZag swings, aligns with an EMA trend filter, and places Fibonacci retracement limit orders.
/// </summary>
public class TuyulUncensoredStrategy : Strategy
{
private readonly StrategyParam<decimal> _volume;
private readonly StrategyParam<decimal> _takeProfitMultiplier;
private readonly StrategyParam<int> _zigZagDepth;
private readonly StrategyParam<decimal> _zigZagDeviation;
private readonly StrategyParam<int> _zigZagBackstep;
private readonly StrategyParam<int> _waitBars;
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<bool> _allowMonday;
private readonly StrategyParam<bool> _allowTuesday;
private readonly StrategyParam<bool> _allowWednesday;
private readonly StrategyParam<bool> _allowThursday;
private readonly StrategyParam<bool> _allowFriday;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _fibLevel;
private List<(DateTimeOffset Time, decimal Price)> _pivots;
private ZigZag _zigZag;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private decimal _lastZigZagHigh;
private decimal _lastZigZagLow;
private decimal? _previousFast;
private decimal? _previousSlow;
private decimal? _activeStop;
private decimal? _activeTake;
private int _activeDirection;
/// <summary>
/// Initializes a new instance of the <see cref="TuyulUncensoredStrategy"/> class.
/// </summary>
public TuyulUncensoredStrategy()
{
_volume = Param(nameof(VolumePerTrade), 0.03m)
.SetDisplay("Volume", "Order volume per trade", "General")
.SetGreaterThanZero();
_takeProfitMultiplier = Param(nameof(TakeProfitMultiplier), 1.2m)
.SetDisplay("TP Multiplier", "Take profit distance relative to stop loss", "Risk")
.SetGreaterThanZero();
_zigZagDepth = Param(nameof(ZigZagDepth), 12)
.SetDisplay("ZigZag Depth", "Number of bars to evaluate for swings", "ZigZag")
.SetGreaterThanZero();
_zigZagDeviation = Param(nameof(ZigZagDeviation), 0.05m)
.SetDisplay("ZigZag Deviation", "Minimum deviation in points to confirm a swing", "ZigZag")
.SetNotNegative();
_zigZagBackstep = Param(nameof(ZigZagBackstep), 3)
.SetDisplay("ZigZag Backstep", "Bars required between opposite pivots", "ZigZag")
.SetNotNegative();
_waitBars = Param(nameof(WaitBarsAfterSignal), 12)
.SetDisplay("Wait Bars", "Candles to keep the pending order before cancelling", "Trading")
.SetNotNegative();
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 9)
.SetDisplay("Fast EMA", "Period of the fast EMA filter", "Trend")
.SetGreaterThanZero();
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 21)
.SetDisplay("Slow EMA", "Period of the slow EMA filter", "Trend")
.SetGreaterThanZero();
_allowMonday = Param(nameof(AllowMonday), true)
.SetDisplay("Allow Monday", "Enable trading on Monday", "Schedule");
_allowTuesday = Param(nameof(AllowTuesday), true)
.SetDisplay("Allow Tuesday", "Enable trading on Tuesday", "Schedule");
_allowWednesday = Param(nameof(AllowWednesday), true)
.SetDisplay("Allow Wednesday", "Enable trading on Wednesday", "Schedule");
_allowThursday = Param(nameof(AllowThursday), true)
.SetDisplay("Allow Thursday", "Enable trading on Thursday", "Schedule");
_allowFriday = Param(nameof(AllowFriday), true)
.SetDisplay("Allow Friday", "Enable trading on Friday", "Schedule");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for analysis", "General");
_fibLevel = Param(nameof(FibLevel), 0.57m)
.SetDisplay("Fibonacci Level", "Retracement level used to position pending orders", "Trading")
.SetRange(0m, 1m);
}
/// <summary>
/// Volume traded on each entry signal.
/// </summary>
public decimal VolumePerTrade
{
get => _volume.Value;
set => _volume.Value = value;
}
/// <summary>
/// Take profit multiplier relative to the stop distance.
/// </summary>
public decimal TakeProfitMultiplier
{
get => _takeProfitMultiplier.Value;
set => _takeProfitMultiplier.Value = value;
}
/// <summary>
/// ZigZag depth parameter.
/// </summary>
public int ZigZagDepth
{
get => _zigZagDepth.Value;
set => _zigZagDepth.Value = value;
}
/// <summary>
/// ZigZag deviation parameter expressed in points.
/// </summary>
public decimal ZigZagDeviation
{
get => _zigZagDeviation.Value;
set => _zigZagDeviation.Value = value;
}
/// <summary>
/// ZigZag backstep parameter.
/// </summary>
public int ZigZagBackstep
{
get => _zigZagBackstep.Value;
set => _zigZagBackstep.Value = value;
}
/// <summary>
/// Number of candles to keep a pending order active.
/// </summary>
public int WaitBarsAfterSignal
{
get => _waitBars.Value;
set => _waitBars.Value = value;
}
/// <summary>
/// Period of the fast EMA filter.
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// Period of the slow EMA filter.
/// </summary>
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
/// <summary>
/// Determines whether Monday is tradable.
/// </summary>
public bool AllowMonday
{
get => _allowMonday.Value;
set => _allowMonday.Value = value;
}
/// <summary>
/// Determines whether Tuesday is tradable.
/// </summary>
public bool AllowTuesday
{
get => _allowTuesday.Value;
set => _allowTuesday.Value = value;
}
/// <summary>
/// Determines whether Wednesday is tradable.
/// </summary>
public bool AllowWednesday
{
get => _allowWednesday.Value;
set => _allowWednesday.Value = value;
}
/// <summary>
/// Determines whether Thursday is tradable.
/// </summary>
public bool AllowThursday
{
get => _allowThursday.Value;
set => _allowThursday.Value = value;
}
/// <summary>
/// Determines whether Friday is tradable.
/// </summary>
public bool AllowFriday
{
get => _allowFriday.Value;
set => _allowFriday.Value = value;
}
/// <summary>
/// Candle type used for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Fibonacci retracement level used to place pending orders.
/// </summary>
public decimal FibLevel
{
get => _fibLevel.Value;
set => _fibLevel.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pivots = null;
_zigZag = null;
_fastEma = null;
_slowEma = null;
_lastZigZagHigh = 0m;
_lastZigZagLow = 0m;
_previousFast = null;
_previousSlow = null;
_activeStop = null;
_activeTake = null;
_activeDirection = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pivots = new List<(DateTimeOffset Time, decimal Price)>();
_zigZag = new ZigZag
{
Deviation = ZigZagDeviation
};
_fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
_slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_zigZag, _fastEma, _slowEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawIndicator(area, _zigZag);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal zigZagValue, decimal fastEmaValue, decimal slowEmaValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateActiveProtection(candle);
var (newHigh, newLow) = UpdateZigZagState(candle, zigZagValue);
if (Position == 0m && (newHigh || newLow) &&
_previousFast.HasValue && _previousSlow.HasValue)
{
TryEnterMarket(_previousFast.Value, _previousSlow.Value);
}
_previousFast = fastEmaValue;
_previousSlow = slowEmaValue;
if (Position == 0m && _activeDirection != 0)
ClearActiveProtection();
}
private void UpdateActiveProtection(ICandleMessage candle)
{
if (_activeDirection == 1 && Position > 0m && _activeStop.HasValue && _activeTake.HasValue)
{
if (candle.LowPrice <= _activeStop.Value || candle.HighPrice >= _activeTake.Value)
{
SellMarket(Position);
ClearActiveProtection();
}
}
else if (_activeDirection == -1 && Position < 0m && _activeStop.HasValue && _activeTake.HasValue)
{
if (candle.HighPrice >= _activeStop.Value || candle.LowPrice <= _activeTake.Value)
{
BuyMarket(-Position);
ClearActiveProtection();
}
}
}
private void TryEnterMarket(decimal previousFast, decimal previousSlow)
{
var high = _lastZigZagHigh;
var low = _lastZigZagLow;
if (high <= 0m || low <= 0m || high <= low)
return;
var volume = VolumePerTrade;
if (volume <= 0m)
volume = Volume;
if (volume <= 0m)
return;
if (previousFast > previousSlow)
{
var stopPrice = low;
var fibPrice = low + (high - low) * FibLevel;
var slDistance = fibPrice - stopPrice;
if (slDistance <= 0m)
return;
var takePrice = fibPrice + slDistance * TakeProfitMultiplier;
BuyMarket(volume);
_activeStop = stopPrice;
_activeTake = takePrice;
_activeDirection = 1;
}
else if (previousFast < previousSlow)
{
var stopPrice = high;
var fibPrice = high - (high - low) * FibLevel;
var slDistance = stopPrice - fibPrice;
if (slDistance <= 0m)
return;
var takePrice = fibPrice - slDistance * TakeProfitMultiplier;
SellMarket(volume);
_activeStop = stopPrice;
_activeTake = takePrice;
_activeDirection = -1;
}
}
private (bool newHigh, bool newLow) UpdateZigZagState(ICandleMessage candle, decimal zigZagValue)
{
var newHigh = false;
var newLow = false;
if (zigZagValue == 0m)
return (false, false);
var index = _pivots.FindIndex(p => p.Time == candle.OpenTime);
if (index >= 0)
{
if (_pivots[index].Price == zigZagValue)
return (false, false);
_pivots[index] = (candle.OpenTime, zigZagValue);
}
else
{
_pivots.Add((candle.OpenTime, zigZagValue));
if (_pivots.Count > 300)
_pivots.RemoveAt(0);
}
if (_pivots.Count < 2)
return (false, false);
var previous = _pivots[^2];
var last = _pivots[^1];
var isHigh = last.Price > previous.Price;
if (isHigh)
{
if (_lastZigZagHigh != last.Price)
{
_lastZigZagHigh = last.Price;
newHigh = true;
}
if (_lastZigZagLow != previous.Price)
{
_lastZigZagLow = previous.Price;
newLow = true;
}
}
else
{
if (_lastZigZagLow != last.Price)
{
_lastZigZagLow = last.Price;
newLow = true;
}
if (_lastZigZagHigh != previous.Price)
{
_lastZigZagHigh = previous.Price;
newHigh = true;
}
}
return (newHigh, newLow);
}
private bool IsTradingDayAllowed(DayOfWeek day)
{
return day switch
{
DayOfWeek.Monday => AllowMonday,
DayOfWeek.Tuesday => AllowTuesday,
DayOfWeek.Wednesday => AllowWednesday,
DayOfWeek.Thursday => AllowThursday,
DayOfWeek.Friday => AllowFriday,
_ => false,
};
}
private void ClearActiveProtection()
{
_activeStop = null;
_activeTake = null;
_activeDirection = 0;
}
}
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 ZigZag, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class tuyul_uncensored_strategy(Strategy):
"""ZigZag swings + EMA trend filter with Fibonacci retracement entries and virtual SL/TP."""
def __init__(self):
super(tuyul_uncensored_strategy, self).__init__()
self._volume_per_trade = self.Param("VolumePerTrade", 0.03) \
.SetGreaterThanZero() \
.SetDisplay("Volume", "Order volume per trade", "General")
self._take_profit_multiplier = self.Param("TakeProfitMultiplier", 1.2) \
.SetGreaterThanZero() \
.SetDisplay("TP Multiplier", "Take profit distance relative to stop loss", "Risk")
self._zigzag_depth = self.Param("ZigZagDepth", 12) \
.SetGreaterThanZero() \
.SetDisplay("ZigZag Depth", "Number of bars to evaluate for swings", "ZigZag")
self._zigzag_deviation = self.Param("ZigZagDeviation", 0.05) \
.SetDisplay("ZigZag Deviation", "Minimum deviation in points to confirm a swing", "ZigZag")
self._zigzag_backstep = self.Param("ZigZagBackstep", 3) \
.SetDisplay("ZigZag Backstep", "Bars required between opposite pivots", "ZigZag")
self._fast_ema_period = self.Param("FastEmaPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Fast EMA", "Period of the fast EMA filter", "Trend")
self._slow_ema_period = self.Param("SlowEmaPeriod", 21) \
.SetGreaterThanZero() \
.SetDisplay("Slow EMA", "Period of the slow EMA filter", "Trend")
self._fib_level = self.Param("FibLevel", 0.57) \
.SetDisplay("Fibonacci Level", "Retracement level used to position pending orders", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles used for analysis", "General")
self._pivots = None
self._last_zigzag_high = 0.0
self._last_zigzag_low = 0.0
self._previous_fast = None
self._previous_slow = None
self._active_stop = None
self._active_take = None
self._active_direction = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def VolumePerTrade(self):
return self._volume_per_trade.Value
@property
def TakeProfitMultiplier(self):
return self._take_profit_multiplier.Value
@property
def ZigZagDeviation(self):
return self._zigzag_deviation.Value
@property
def FastEmaPeriod(self):
return self._fast_ema_period.Value
@property
def SlowEmaPeriod(self):
return self._slow_ema_period.Value
@property
def FibLevel(self):
return self._fib_level.Value
def OnReseted(self):
super(tuyul_uncensored_strategy, self).OnReseted()
self._pivots = None
self._last_zigzag_high = 0.0
self._last_zigzag_low = 0.0
self._previous_fast = None
self._previous_slow = None
self._active_stop = None
self._active_take = None
self._active_direction = 0
def OnStarted2(self, time):
super(tuyul_uncensored_strategy, self).OnStarted2(time)
self._pivots = []
zigzag = ZigZag()
zigzag.Deviation = float(self.ZigZagDeviation)
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastEmaPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowEmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(zigzag, fast_ema, slow_ema, self._process_candle).Start()
def _process_candle(self, candle, zigzag_value, fast_ema_value, slow_ema_value):
if candle.State != CandleStates.Finished:
return
self._update_active_protection(candle)
zz_v = float(zigzag_value)
fast_v = float(fast_ema_value)
slow_v = float(slow_ema_value)
new_high, new_low = self._update_zigzag_state(candle, zz_v)
if self.Position == 0 and (new_high or new_low) and \
self._previous_fast is not None and self._previous_slow is not None:
self._try_enter_market(self._previous_fast, self._previous_slow)
self._previous_fast = fast_v
self._previous_slow = slow_v
if self.Position == 0 and self._active_direction != 0:
self._clear_active_protection()
def _update_active_protection(self, candle):
if self._active_direction == 1 and self.Position > 0 and \
self._active_stop is not None and self._active_take is not None:
if float(candle.LowPrice) <= self._active_stop or float(candle.HighPrice) >= self._active_take:
self.SellMarket(self.Position)
self._clear_active_protection()
elif self._active_direction == -1 and self.Position < 0 and \
self._active_stop is not None and self._active_take is not None:
if float(candle.HighPrice) >= self._active_stop or float(candle.LowPrice) <= self._active_take:
self.BuyMarket(abs(self.Position))
self._clear_active_protection()
def _try_enter_market(self, previous_fast, previous_slow):
high = self._last_zigzag_high
low = self._last_zigzag_low
if high <= 0 or low <= 0 or high <= low:
return
volume = float(self.VolumePerTrade)
if volume <= 0:
volume = float(self.Volume)
if volume <= 0:
return
fib = float(self.FibLevel)
tp_mult = float(self.TakeProfitMultiplier)
if previous_fast > previous_slow:
stop_price = low
fib_price = low + (high - low) * fib
sl_distance = fib_price - stop_price
if sl_distance <= 0:
return
take_price = fib_price + sl_distance * tp_mult
self.BuyMarket(volume)
self._active_stop = stop_price
self._active_take = take_price
self._active_direction = 1
elif previous_fast < previous_slow:
stop_price = high
fib_price = high - (high - low) * fib
sl_distance = stop_price - fib_price
if sl_distance <= 0:
return
take_price = fib_price - sl_distance * tp_mult
self.SellMarket(volume)
self._active_stop = stop_price
self._active_take = take_price
self._active_direction = -1
def _update_zigzag_state(self, candle, zigzag_value):
new_high = False
new_low = False
if zigzag_value == 0:
return False, False
found_index = -1
for i in range(len(self._pivots)):
if self._pivots[i][0] == candle.OpenTime:
found_index = i
break
if found_index >= 0:
if self._pivots[found_index][1] == zigzag_value:
return False, False
self._pivots[found_index] = (candle.OpenTime, zigzag_value)
else:
self._pivots.append((candle.OpenTime, zigzag_value))
if len(self._pivots) > 300:
self._pivots.pop(0)
if len(self._pivots) < 2:
return False, False
previous = self._pivots[-2]
last = self._pivots[-1]
is_high = last[1] > previous[1]
if is_high:
if self._last_zigzag_high != last[1]:
self._last_zigzag_high = last[1]
new_high = True
if self._last_zigzag_low != previous[1]:
self._last_zigzag_low = previous[1]
new_low = True
else:
if self._last_zigzag_low != last[1]:
self._last_zigzag_low = last[1]
new_low = True
if self._last_zigzag_high != previous[1]:
self._last_zigzag_high = previous[1]
new_high = True
return new_high, new_low
def _clear_active_protection(self):
self._active_stop = None
self._active_take = None
self._active_direction = 0
def CreateClone(self):
return tuyul_uncensored_strategy()