This strategy is a high-level StockSharp recreation of the MetaTrader expert advisor Tengri. The original advisor trades EURUSD and USDCHF with a grid-and-scale approach driven by RSI, custom "Silence" volatility filters and an EMA trend gauge. The C# version keeps the behavioural core while adapting it to StockSharp conventions and net-position accounting.
Core Ideas
Directional bias – compare the current bid to the opening price of a higher timeframe candle (default 30 minutes). A positive difference biases the strategy long, a negative difference biases it short.
Momentum filter – a 14-period RSI calculated on hourly candles must stay below 70 for long entries and above 30 for short entries, matching the MetaTrader logic.
Quiet-market filters – the original custom "Silence" indicator is emulated with ATR values smoothed by EMAs on two different timeframes. Both filters must stay below configurable thresholds to permit entries or scale-ins.
Trend confirmation – an EMA on a medium timeframe ensures long additions only happen above the EMA and short additions only below it.
Grid and martingale sizing – the first trade uses either a fixed lot or an equity-proportional lot. Additional trades multiply the previous volume by configurable factors (1.70 before StepX, 2.08 afterwards by default).
Pip spacing – the distance between grid orders follows two base steps (10 pips and 20 pips by default) and can be grown exponentially through PipStepExponent.
ATR smoothing and thresholds controlling the two silence filters.
MaPeriod
EMA period.
PipStep, PipStep2, PipStepExponent
Distances between scale-in trades.
LotExponent1, LotExponent2, StepX
Martingale factors for additional positions.
LotSize, FixLot, LotStep
Money management settings for the first position.
SlTpPips
Pip distance used to set a take-profit for the first trade (0 disables it).
MaxTrades
Maximum number of entries per direction.
UseLimit, LimitDivisor
Global profit lock configuration.
CloseFriday, CloseFridayHour
Optional late-Friday entry lockout.
Differences from the MetaTrader Version
Silence indicator replacement – the proprietary "Silence" indicator is approximated with ATR values smoothed by EMAs. Thresholds retain the same numeric scale but can be tuned if the ATR proxy behaves differently.
Net position accounting – StockSharp portfolios are netted, so the strategy flattens the opposite direction before opening a new stack instead of hedging both sides simultaneously.
Take-profit handling – MetaTrader attaches TP only to the first order. The port closes the full net position when that target triggers. Additional orders intentionally have no TP, matching the original risk model.
Symbol choice – the strategy uses the Security assigned to the strategy instance. Configure separate instances for EURUSD, USDCHF or any other instrument.
Usage Notes
Configure the volume step and min/max volumes on the target security so that LotCheck style rounding aligns with broker requirements.
The strategy assumes the broker quotes provide best bid/ask updates. Without Level1 data the direction and TP checks cannot operate.
Because there is no stop-loss, consider running the strategy with external risk controls (equity stop, manual supervision, etc.).
Visualisation
To analyse the behaviour connect chart widgets to the subscribed candle series (direction, entry and scaling timeframes) plus overlay the EMA and ATR indicators. This mirrors the diagnostic tools used with the original advisor.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Tengri strategy using WMA crossover with EMA trend filter.
/// Buys when fast WMA crosses above slow WMA and price above EMA.
/// Sells on reverse conditions.
/// </summary>
public class TengriStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private ExponentialMovingAverage _ema;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public TengriStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15).SetGreaterThanZero().SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 60).SetGreaterThanZero().SetDisplay("Slow Period", "Slow WMA period", "Indicator");
_emaPeriod = Param(nameof(EmaPeriod), 200).SetGreaterThanZero().SetDisplay("EMA Period", "Trend EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null; _ema = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { Length = SlowPeriod };
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, _ema, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed || !_ema.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && close > emaValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 80; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && close < emaValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 80; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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.Indicators import ExponentialMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class tengri_strategy(Strategy):
def __init__(self):
super(tengri_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 60) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._ema_period = self.Param("EmaPeriod", 200) \
.SetDisplay("EMA Period", "EMA trend filter period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._ema = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def ema_period(self):
return self._ema_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(tengri_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._ema = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(tengri_strategy, self).OnStarted2(time)
self._fast = WeightedMovingAverage()
self._fast.Length = self.fast_period
self._slow = WeightedMovingAverage()
self._slow.Length = self.slow_period
self._ema = ExponentialMovingAverage()
self._ema.Length = self.ema_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._ema, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value, ema_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
ema_val = float(ema_value)
if not self._fast.IsFormed or not self._slow.IsFormed or not self._ema.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return tengri_strategy()