La estrategia Nevalyashka es una adaptación de C# del asesor experto original MetaTrader 4 Nevalyashka.mq4. El EA invierte repetidamente su dirección de negociación: abre una única orden de mercado, espera hasta que la posición se cierra mediante una acción manual, de limitación de pérdidas o de obtención de beneficios, y vuelve a entrar instantáneamente en la dirección opuesta con el mismo volumen. La implementación StockSharp reproduce este comportamiento al tiempo que expone todas las configuraciones críticas como parámetros de estrategia.
Lógica de trading
Inicialización
Cuando comienza la estrategia, calcula el tamaño del pip a partir del PriceStep del instrumento. Para símbolos Forex de 3 y 5 dígitos, el paso se multiplica por 10 para que coincida con la definición de punto MetaTrader.
StartProtection está configurado con distancias de stop-loss y take-profit convertidas de pips a puntos de precio. Se adjuntan órdenes de protección a cada puesto posterior.
Se envía una orden de mercado inicial en la dirección definida por InitialDirection (predeterminado: corto). El volumen solicitado se redondea al lote válido más cercano utilizando los valores VolumeStep, MinVolume y MaxVolume del valor.
Seguimiento de posición
OnPositionChanged captura cada cambio en la exposición neta. Cuando se abre una nueva posición, la estrategia almacena el volumen completado y recuerda el lado comercial.
Una vez que la posición vuelve completamente plana, la estrategia emite inmediatamente una nueva orden de mercado en la dirección opuesta, reutilizando el tamaño de lote previamente almacenado.
Manejo de fallas
Si el corredor rechaza el registro de una orden, el indicador de dirección pendiente se borra, lo que permite al operador de la plataforma volver a intentarlo manualmente o ajustar los parámetros sin un estado interno obsoleto.
El flujo de trabajo resultante refleja la idea "roly-poly" del guión original: el robot está siempre en el mercado, alternando entre posiciones largas y cortas con salidas fijas.
Parámetros
Nombre
Descripción
Predeterminado
Notas
StopLossPips
Distancia del tope de protección en pips.
50
Convertido a puntos de precio mediante el cálculo del tamaño del pip; configúrelo en 0 para desactivar la parada.
TakeProfitPips
Distancia de la toma de ganancias protectora en pips.
50
Se convierte de la misma forma que el stop-loss; configúrelo en 0 para deshabilitar la obtención de ganancias.
Volume
Tamaño de lote utilizado para la primera operación.
1
Después del primer llenado, la estrategia reutiliza el volumen realmente ejecutado para todas las entradas futuras.
InitialDirection
Lado de la orden de mercado inicial.
Sell
Elija entre Buy y Sell para que coincida con el sesgo inicial deseado.
Notas de implementación
No se requieren suscripciones a velas ni indicadores; la estrategia reacciona únicamente a eventos de posición y confirmaciones de pedidos.
Se consulta IsFormedAndOnlineAndAllowTrading() antes de cada entrada para garantizar que el conector esté listo para operar.
El redondeo de volumen utiliza MidpointRounding.AwayFromZero para que los lotes fraccionarios siempre alcancen un nivel negociable en lugar de cero.
La lógica de conversión de pips se basa en metadatos del instrumento en lugar de suposiciones codificadas, lo que hace que el puerto funcione con símbolos FX, CFD o futuros con diferentes formatos de precios.
Diferencias frente a la versión MQL
La variante StockSharp expone la dirección inicial como parámetro en lugar de forzar el corto inicial desde el script MT4.
Las órdenes de limitación de pérdidas y toma de ganancias se gestionan a través de StartProtection, que produce órdenes de protección nativas compatibles con cualquier conector StockSharp.
Los rechazos de pedidos borran el estado pendiente interno para evitar el envío repetido de solicitudes no válidas.
Estos ajustes mantienen el espíritu del asesor original y al mismo tiempo se integran perfectamente con la API de alto nivel de StockSharp.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Nevalyashka Direction: RSI reversal with ATR stops.
/// </summary>
public class NevalyashkaDirectionStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevRsi;
private decimal _entryPrice;
public NevalyashkaDirectionStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = 0;
_entryPrice = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevRsi == 0 || atrVal <= 0)
{
_prevRsi = rsiVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 70)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 30)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (rsiVal < 35 && _prevRsi >= 35)
{
_entryPrice = close;
BuyMarket();
}
else if (rsiVal > 65 && _prevRsi <= 65)
{
_entryPrice = close;
SellMarket();
}
}
_prevRsi = rsiVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import RelativeStrengthIndex, AverageTrueRange
class nevalyashka_direction_strategy(Strategy):
def __init__(self):
super(nevalyashka_direction_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_rsi = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(nevalyashka_direction_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._entry_price = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
av = float(atr_val)
if self._prev_rsi == 0 or av <= 0:
self._prev_rsi = rv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + av * 2.0 or close <= self._entry_price - av * 1.5 or rv > 70:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.0 or close >= self._entry_price + av * 1.5 or rv < 30:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
if self.Position == 0:
if rv < 35 and self._prev_rsi >= 35:
self._entry_price = close
self.BuyMarket()
elif rv > 65 and self._prev_rsi <= 65:
self._entry_price = close
self.SellMarket()
self._prev_rsi = rv
def OnReseted(self):
super(nevalyashka_direction_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._entry_price = 0.0
def CreateClone(self):
return nevalyashka_direction_strategy()