Ver no GitHub

Estratégia de Tengri (Port do StockSharp)

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

  1. 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.
  2. 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.
  3. 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.
RsiPeriod Comprimento do RSI (padrão 14).
SilencePeriod1/2, SilenceInterpolation1/2, SilenceLevel1/2 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;
	}
}