Esta estratégia é uma recriação de alto nível no StockSharp do consultor especializado MetaTrader Tengri. O consultor original negocia EURUSD e USDCHF com uma abordagem de grade e escala impulsionada por RSI, filtros de volatilidade "Silence" personalizados e um medidor de tendência EMA. A versão em C# mantém o núcleo comportamental enquanto o adapta às convenções do StockSharp e à contabilização de posição líquida.
Ideias Principais
Viés direcional – compara o bid atual com o preço de abertura de um candle de período superior (padrão 30 minutos). Uma diferença positiva inclina a estratégia para comprado, uma diferença negativa para vendido.
Filtro de momentum – um RSI de 14 períodos calculado em candles horários deve permanecer abaixo de 70 para entradas compradas e acima de 30 para entradas vendidas, correspondendo à lógica do MetaTrader.
Filtros de mercado quieto – o indicador "Silence" personalizado original é emulado com valores ATR suavizados por EMAs em dois períodos diferentes. Ambos os filtros devem permanecer abaixo de limiares configuráveis para permitir entradas ou escalonamentos.
Confirmação de tendência – uma EMA em um período médio garante que adições compradas ocorram apenas acima da EMA e adições vendidas apenas abaixo.
Sizing de grade e martingale – o primeiro trade usa um lote fixo ou proporcional ao patrimônio. Trades adicionais multiplicam o volume anterior por fatores configuráveis (1.70 antes de StepX, 2.08 depois por padrão).
Espaçamento de pips – a distância entre ordens de grade segue dois passos base (10 pips e 20 pips por padrão) e pode crescer exponencialmente através de PipStepExponent.
Fluxo de Negociação
Avaliação de entrada (por EntryCandleType, padrão M1):
Determinar a direção a partir do candle DealCandleType.
Verificar RSI e o primeiro filtro de silêncio.
Garantir que não há trades ativos na mesma direção (posições na direção oposta são zeradas primeiro pois os portfólios do StockSharp são líquidos).
Enviar uma ordem de mercado com o tamanho de lote calculado. O primeiro trade armazena um alvo de take-profit baseado em pips.
Avaliação de escalonamento (por ScaleCandleType, padrão M1):
Confirmar a tendência EMA e o segundo filtro de silêncio.
Verificar se o último preço de execução está suficientemente distante do mercado atual usando as regras de pip-step.
Adicionar outra ordem de mercado com sizing martingale enquanto a direção permanecer válida e o número de trades estiver abaixo de MaxTrades.
Gestão de posição:
O alvo de lucro global opcional fecha a posição quando existem pilhas compradas e vendidas e o PnL não realizado combinado supera Equity / LimitDivisor.
O take-profit do primeiro trade atua como saída simples: quando o bid/ask atinge o alvo armazenado, toda a posição líquida é zerada.
Nenhum stop-loss automático é usado, espelhando o código MetaTrader.
Parâmetros
Parâmetro
Descrição
DealCandleType
Período cujo preço de abertura define o viés direcional.
EntryCandleType
Período para avaliar sinais de entrada.
ScaleCandleType
Período para verificar adições de grade.
MaCandleType
Período para o filtro de tendência EMA.
Silence1CandleType / Silence2CandleType
Períodos para filtros de volatilidade baseados em ATR.
Suavização ATR e limiares que controlam os dois filtros de silêncio.
MaPeriod
Período da EMA.
PipStep, PipStep2, PipStepExponent
Distâncias entre trades de escalonamento.
LotExponent1, LotExponent2, StepX
Fatores martingale para posições adicionais.
LotSize, FixLot, LotStep
Configurações de gestão monetária para a primeira posição.
SlTpPips
Distância em pips para definir um take-profit para o primeiro trade (0 desativa).
MaxTrades
Número máximo de entradas por direção.
UseLimit, LimitDivisor
Configuração de bloqueio de lucro global.
CloseFriday, CloseFridayHour
Bloqueio opcional de entrada no final de sexta-feira.
Diferenças da Versão MetaTrader
Substituição do indicador Silence – o indicador "Silence" proprietário é aproximado com valores ATR suavizados por EMAs. Os limiares mantêm a mesma escala numérica, mas podem ser ajustados se o proxy ATR se comportar de maneira diferente.
Contabilização de posição líquida – os portfólios do StockSharp são líquidos, portanto a estratégia zera a direção oposta antes de abrir uma nova pilha em vez de proteger ambos os lados simultaneamente.
Tratamento de take-profit – o MetaTrader anexa TP apenas à primeira ordem. O port fecha toda a posição líquida quando esse alvo é acionado. Ordens adicionais intencionalmente não têm TP, correspondendo ao modelo de risco original.
Escolha de símbolo – a estratégia usa o Security atribuído à instância da estratégia. Configure instâncias separadas para EURUSD, USDCHF ou qualquer outro instrumento.
Notas de Uso
Configure o passo de volume e os volumes mín/máx no instrumento alvo para que o arredondamento estilo LotCheck se alinhe com os requisitos do corretor.
A estratégia assume que as cotações do corretor fornecem atualizações do melhor bid/ask. Sem dados de Nível 1, as verificações de direção e TP não podem funcionar.
Como não há stop-loss, considere executar a estratégia com controles de risco externos (stop de patrimônio, supervisão manual, etc.).
Visualização
Para analisar o comportamento, conecte widgets de gráfico às séries de candles assinadas (períodos de direção, entrada e escalonamento) e sobreponha os indicadores EMA e ATR. Isso espelha as ferramentas de diagnóstico usadas com o consultor original.
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()