Esta estrategia es una recreación de alto nivel en StockSharp del asesor experto de MetaTrader Tengri. El asesor original opera EURUSD y USDCHF con un enfoque de grid y escalado impulsado por RSI, filtros de volatilidad "Silence" personalizados y un indicador de tendencia EMA. La versión en C# mantiene el núcleo de comportamiento mientras lo adapta a las convenciones de StockSharp y la contabilidad de posición neta.
Ideas Principales
Sesgo direccional – compara el bid actual con el precio de apertura de una vela de marco temporal superior (30 minutos por defecto). Una diferencia positiva sesga la estrategia hacia largo, una diferencia negativa hacia corto.
Filtro de momentum – un RSI de 14 períodos calculado en velas horarias debe mantenerse por debajo de 70 para entradas largas y por encima de 30 para entradas cortas, coincidiendo con la lógica de MetaTrader.
Filtros de mercado tranquilo – el indicador personalizado "Silence" original se emula con valores ATR suavizados por EMAs en dos marcos temporales diferentes. Ambos filtros deben mantenerse por debajo de umbrales configurables para permitir entradas o ampliaciones.
Confirmación de tendencia – una EMA en un marco temporal medio asegura que las adiciones largas ocurran solo por encima de la EMA y las adiciones cortas solo por debajo.
Sizing de grid y martingala – el primer trade usa un lote fijo o proporcional al patrimonio. Los trades adicionales multiplican el volumen anterior por factores configurables (1.70 antes de StepX, 2.08 después por defecto).
Espaciado de pips – la distancia entre órdenes de grid sigue dos pasos base (10 pips y 20 pips por defecto) y puede crecer exponencialmente a través de PipStepExponent.
Flujo de Trading
Evaluación de entrada (por EntryCandleType, por defecto M1):
Determinar la dirección desde la vela DealCandleType.
Verificar RSI y el primer filtro de silencio.
Asegurar que no haya trades activos en la misma dirección (las posiciones en dirección opuesta se aplanan primero porque las carteras de StockSharp son netas).
Enviar una orden de mercado con el tamaño de lote calculado. El primer trade almacena un objetivo de take-profit basado en pips.
Evaluación de ampliación (por ScaleCandleType, por defecto M1):
Confirmar la tendencia EMA y el segundo filtro de silencio.
Verificar que el último precio de ejecución esté suficientemente lejos del mercado actual usando las reglas de pip-step.
Añadir otra orden de mercado con sizing martingala mientras la dirección permanezca válida y el conteo de trades esté por debajo de MaxTrades.
Gestión de posición:
El objetivo de ganancia global opcional cierra la posición cuando existen pilas largas y cortas y el PnL no realizado combinado supera Equity / LimitDivisor.
El take-profit del primer trade actúa como salida simple: cuando el bid/ask alcanza el objetivo almacenado, toda la posición neta se aplana.
No se usa stop-loss automático, espejando el código MetaTrader.
Parámetros
Parámetro
Descripción
DealCandleType
Marco temporal cuyo precio de apertura define el sesgo direccional.
EntryCandleType
Marco temporal para evaluar señales de entrada.
ScaleCandleType
Marco temporal para verificar adiciones de grid.
MaCandleType
Marco temporal para el filtro de tendencia EMA.
Silence1CandleType / Silence2CandleType
Marcos temporales para filtros de volatilidad basados en ATR.
Suavizado ATR y umbrales que controlan los dos filtros de silencio.
MaPeriod
Período de la EMA.
PipStep, PipStep2, PipStepExponent
Distancias entre trades de ampliación.
LotExponent1, LotExponent2, StepX
Factores martingala para posiciones adicionales.
LotSize, FixLot, LotStep
Configuración de gestión monetaria para la primera posición.
SlTpPips
Distancia en pips para fijar un take-profit para el primer trade (0 lo deshabilita).
MaxTrades
Número máximo de entradas por dirección.
UseLimit, LimitDivisor
Configuración de bloqueo de ganancia global.
CloseFriday, CloseFridayHour
Bloqueo opcional de entrada tardía el viernes.
Diferencias con la Versión MetaTrader
Reemplazo del indicador Silence – el indicador "Silence" propietario se aproxima con valores ATR suavizados por EMAs. Los umbrales mantienen la misma escala numérica pero pueden ajustarse si el proxy ATR se comporta diferente.
Contabilidad de posición neta – las carteras de StockSharp son netas, por lo que la estrategia aplana la dirección opuesta antes de abrir una nueva pila en lugar de cubrir ambos lados simultáneamente.
Manejo de take-profit – MetaTrader adjunta TP solo a la primera orden. El port cierra la posición neta completa cuando se activa ese objetivo. Las órdenes adicionales intencionalmente no tienen TP, coincidiendo con el modelo de riesgo original.
Elección de símbolo – la estrategia usa el Security asignado a la instancia de estrategia. Configurar instancias separadas para EURUSD, USDCHF u otro instrumento.
Notas de Uso
Configurar el paso de volumen y los volúmenes mín/máx en el instrumento objetivo para que el redondeo estilo LotCheck se alinee con los requisitos del broker.
La estrategia asume que las cotizaciones del broker proporcionan actualizaciones del mejor bid/ask. Sin datos de Nivel 1 los controles de dirección y TP no pueden operar.
Como no hay stop-loss, considerar ejecutar la estrategia con controles de riesgo externos (stop de patrimonio, supervisión manual, etc.).
Visualización
Para analizar el comportamiento conectar widgets de gráfico a las series de velas suscritas (marcos temporales de dirección, entrada y escalado) más superponer los indicadores EMA y ATR. Esto refleja las herramientas de diagnóstico utilizadas con el asesor 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()