Un port directo de StockSharp del experto de MetaTrader "Nevalyashka". La estrategia siempre alterna entre trades largos y cortos: comienza con una orden de venta a mercado, espera a que la posición se cierre por stop loss o take profit, y luego abre inmediatamente una orden a mercado en la dirección opuesta. Las órdenes protectoras se recrean para cada entrada usando los mismos offsets basados en pips que en el código original.
Lógica de la estrategia
Inicialización
Detecta el paso de precio del instrumento y los decimales para derivar un tamaño de pip idéntico a la versión MQL (los pares de 3/5 dígitos se multiplican por 10).
Multiplica el MinVolume del exchange por el parámetro LotMultiplier para obtener el tamaño de la orden y lo redondea al step de volumen si es necesario.
Manejo de cotizaciones
Se suscribe a actualizaciones del libro de órdenes para capturar los últimos precios bid/ask, imitando la llamada RefreshRates() del experto.
Flujo de órdenes
Coloca una orden de venta a mercado inicial una vez que las mejores cotizaciones bid/ask están disponibles.
Después de que una posición se cierra, invierte el lado (compra después de venta, venta después de compra) y emite una nueva orden a mercado con el mismo volumen.
Para cada entrada ejecutada la estrategia coloca órdenes separadas de stop-loss y take-profit usando los parámetros de distancia en pips.
Gestión de riesgos
Stop Loss: Opcional. Cuando StopLossPips es mayor que cero, la estrategia envía una orden stop protectora (SellStop para posiciones largas, BuyStop para posiciones cortas) en entrada ± StopLossPips * pip.
Take Profit: Opcional. Cuando TakeProfitPips es mayor que cero, la estrategia envía una orden límite protectora (SellLimit para posiciones largas, BuyLimit para posiciones cortas) en entrada ± TakeProfitPips * pip.
Ambas órdenes protectoras se cancelan siempre que la posición esté plana para evitar órdenes pendientes antes del siguiente flip.
Parámetros
Nombre
Descripción
Valor predeterminado
LotMultiplier
Multiplicador aplicado al volumen mínimo del instrumento. El resultado se redondea al step de volumen del exchange.
1
StopLossPips
Distancia de stop-loss en pips. Establecer en 0 para deshabilitar el stop.
50
TakeProfitPips
Distancia de take-profit en pips. Establecer en 0 para deshabilitar el objetivo.
50
Notas operativas
El enfoque alterna continuamente la exposición y por lo tanto se adapta a mercados de reversión a la media donde es probable que un movimiento completado se revierta.
Funciona con cualquier símbolo que proporcione cotizaciones del libro de órdenes; los cálculos de pips se adaptan automáticamente según la precisión del precio.
El manejo del deslizamiento se delega al exchange—las órdenes se envían a mercado sin verificaciones adicionales como en el experto original.
La estrategia no incluye filtros de horario de trading, filtros de noticias o trailing stops. Tal lógica puede añadirse extendiendo TryOpenNextPosition o RegisterProtectionOrders.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Alternating long-short strategy that mirrors the original Nevalyashka MQL logic.
/// Opens an initial sell position and flips direction each time the position is closed.
/// </summary>
public class NevalyashkaFlipStrategy : Strategy
{
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private Sides? _currentSide;
private Sides? _lastCompletedSide;
/// <summary>
/// Stop loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type for monitoring price.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="NevalyashkaFlipStrategy"/> class.
/// </summary>
public NevalyashkaFlipStrategy()
{
_stopLossPoints = Param(nameof(StopLossPoints), 5)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pts)", "Stop loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pts)", "Take profit distance in price steps", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_currentSide = null;
_lastCompletedSide = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var step = Security?.PriceStep ?? 1m;
var stopDistance = StopLossPoints * step;
var takeDistance = TakeProfitPoints * step;
var price = candle.ClosePrice;
// Check SL/TP for current position
if (Position != 0 && _entryPrice > 0)
{
var hit = false;
if (_currentSide == Sides.Buy)
{
if (stopDistance > 0 && candle.LowPrice <= _entryPrice - stopDistance)
hit = true;
if (takeDistance > 0 && candle.HighPrice >= _entryPrice + takeDistance)
hit = true;
}
else if (_currentSide == Sides.Sell)
{
if (stopDistance > 0 && candle.HighPrice >= _entryPrice + stopDistance)
hit = true;
if (takeDistance > 0 && candle.LowPrice <= _entryPrice - takeDistance)
hit = true;
}
if (hit)
{
// Close position
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_lastCompletedSide = _currentSide;
_currentSide = null;
_entryPrice = 0m;
}
}
// If flat, open next position
if (Position == 0 && _currentSide == null)
{
// Alternate direction: start with sell, then flip
var sideToOpen = _lastCompletedSide switch
{
Sides.Buy => Sides.Sell,
Sides.Sell => Sides.Buy,
_ => Sides.Sell,
};
if (sideToOpen == Sides.Buy)
BuyMarket();
else
SellMarket();
_currentSide = sideToOpen;
_entryPrice = price;
}
}
}
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
class nevalyashka_flip_strategy(Strategy):
"""Alternating long-short strategy that flips direction each time position is closed."""
def __init__(self):
super(nevalyashka_flip_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 5) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (pts)", "Stop loss distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 5) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pts)", "Take profit distance in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._entry_price = 0.0
self._current_side = 0 # 0=none, 1=buy, -1=sell
self._last_completed_side = 0
@property
def StopLossPoints(self):
return int(self._stop_loss_points.Value)
@property
def TakeProfitPoints(self):
return int(self._take_profit_points.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(nevalyashka_flip_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._current_side = 0
self._last_completed_side = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
stop_dist = self.StopLossPoints * step
take_dist = self.TakeProfitPoints * step
price = float(candle.ClosePrice)
# Check SL/TP for current position
if self.Position != 0 and self._entry_price > 0:
hit = False
if self._current_side == 1:
if stop_dist > 0 and float(candle.LowPrice) <= self._entry_price - stop_dist:
hit = True
if take_dist > 0 and float(candle.HighPrice) >= self._entry_price + take_dist:
hit = True
elif self._current_side == -1:
if stop_dist > 0 and float(candle.HighPrice) >= self._entry_price + stop_dist:
hit = True
if take_dist > 0 and float(candle.LowPrice) <= self._entry_price - take_dist:
hit = True
if hit:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._last_completed_side = self._current_side
self._current_side = 0
self._entry_price = 0.0
# If flat, open next position
if self.Position == 0 and self._current_side == 0:
# Alternate: start with sell, then flip
if self._last_completed_side == 1:
side_to_open = -1
elif self._last_completed_side == -1:
side_to_open = 1
else:
side_to_open = -1
if side_to_open == 1:
self.BuyMarket()
else:
self.SellMarket()
self._current_side = side_to_open
self._entry_price = price
def OnReseted(self):
super(nevalyashka_flip_strategy, self).OnReseted()
self._entry_price = 0.0
self._current_side = 0
self._last_completed_side = 0
def CreateClone(self):
return nevalyashka_flip_strategy()