La Estrategia Nueva Aleatoria emula el experto original de MetaTrader "New Random" ofreciendo tres modos distintos de selección de entrada. Abre solo una posición a la vez y espera hasta que la posición actual esté cerrada antes de generar la siguiente dirección de orden. Las entradas a mercado se activan en actualizaciones del mejor precio (datos Level 1) usando los mejores precios bid/ask como anclas de ejecución. La estrategia calcula automáticamente los offsets de stop-loss y take-profit en pips, adaptándose a cotizaciones forex de 3 y 5 dígitos de la misma manera que la versión MQL.
Modos de entrada
Generador – la siguiente dirección es elegida por un generador pseudoaleatorio sembrado al inicio de la estrategia. Cada oportunidad es un lanzamiento de moneda independiente entre comprar y vender.
Compra-Venta-Compra – las posiciones alternan estrictamente entre compra y venta. La primera orden es una compra, seguida de una venta, y así sucesivamente.
Venta-Compra-Venta – las posiciones alternan estrictamente comenzando desde una venta, seguida de una compra, y repitiendo.
Parámetros
Random Mode (Mode) – selecciona uno de los tres mecanismos de entrada descritos anteriormente. Por defecto el generador aleatorio.
Minimal Lot Count (MinimalLotCount) – multiplica el volumen mínimo negociable del instrumento. Un valor de 1 significa que la estrategia opera exactamente Security.VolumeMin, mientras que valores más altos escalan el tamaño de la orden por múltiplos enteros.
Stop Loss (pips) (StopLossPips) – distancia en pips por debajo/encima del precio de llenado donde la estrategia saldrá de la posición. Establecer en 0 para deshabilitar el stop-loss.
Take Profit (pips) (TakeProfitPips) – distancia en pips donde la estrategia realizará ganancias. Establecer en 0 para deshabilitar el take-profit.
Lógica de trading
Se suscribe a datos Level 1 para el instrumento configurado y almacena constantemente los últimos precios bid, ask y último trade.
Cuando no hay posición abierta ni orden pendiente, la estrategia evalúa el modo seleccionado para determinar la siguiente dirección.
Las órdenes se colocan a mercado usando la última instantánea de mejor bid/ask. Los objetivos de stop-loss y take-profit se calculan inmediatamente desde el precio de entrada usando los parámetros de distancia en pips.
Solo puede existir una posición a la vez. Las entradas posteriores se suprimen hasta que la posición activa esté completamente cerrada.
Gestión de posición
Las posiciones largas salen anticipadamente cuando el precio actual cae al stop-loss o por debajo, o sube al take-profit o por encima.
Las posiciones cortas salen cuando el precio actual sube al stop-loss o por encima, o cae al take-profit o por debajo.
Las comparaciones de precio siempre usan la información Level 1 más fresca: el último precio de trade si está disponible, de lo contrario el mejor bid/ask para el lado respectivo.
Después de cerrar un trade, la estrategia reinicia el estado interno, alterna opcionalmente la siguiente dirección (para modos secuencia), y espera la próxima actualización de cotización antes de volver a entrar.
Notas
La estrategia nunca piramidaliza posiciones y mantiene el comportamiento determinista para los modos basados en secuencia.
El modo aleatorio se siembra con el conteo de ticks actual por lo que cada ejecución produce un flujo de órdenes único.
Todos los comentarios internos y logs están en inglés para alinearse con las pautas del repositorio.
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>
/// Randomized entry strategy that mimics the MetaTrader "New Random" expert.
/// </summary>
public class NewRandomStrategy : Strategy
{
/// <summary>
/// Available direction selection modes.
/// </summary>
public enum RandomModes
{
/// <summary>Use a pseudo random generator for every entry decision.</summary>
Generator,
/// <summary>Alternate buy-sell-buy.</summary>
BuySellBuy,
/// <summary>Alternate sell-buy-sell.</summary>
SellBuySell
}
private readonly StrategyParam<RandomModes> _mode;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private Sides? _sequenceLastSide;
private Sides? _positionSide;
private decimal _entryPrice;
private int _candleCount;
/// <summary>Direction selection mode.</summary>
public RandomModes Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>Stop loss in price steps.</summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>Take profit in price steps.</summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>Candle type.</summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public NewRandomStrategy()
{
_mode = Param(nameof(Mode), RandomModes.Generator)
.SetDisplay("Random Mode", "Direction selection mode", "General");
_stopLossPoints = Param(nameof(StopLossPoints), 5)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pts)", "Stop loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pts)", "Take profit 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();
_sequenceLastSide = null;
_positionSide = null;
_entryPrice = 0m;
_candleCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sequenceLastSide = Mode switch
{
RandomModes.BuySellBuy => Sides.Sell,
RandomModes.SellBuySell => Sides.Buy,
_ => null
};
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;
_candleCount++;
if (_candleCount < 3)
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 (_positionSide == Sides.Buy)
{
if (stopDistance > 0 && candle.LowPrice <= _entryPrice - stopDistance)
hit = true;
if (takeDistance > 0 && candle.HighPrice >= _entryPrice + takeDistance)
hit = true;
}
else if (_positionSide == Sides.Sell)
{
if (stopDistance > 0 && candle.HighPrice >= _entryPrice + stopDistance)
hit = true;
if (takeDistance > 0 && candle.LowPrice <= _entryPrice - takeDistance)
hit = true;
}
if (hit)
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_positionSide = null;
_entryPrice = 0m;
}
}
// If flat, open new random position
if (Position == 0 && _positionSide == null)
{
var side = DetermineNextSide();
if (side == Sides.Buy)
BuyMarket();
else
SellMarket();
_positionSide = side;
_entryPrice = price;
_sequenceLastSide = side;
}
}
private Sides DetermineNextSide()
{
// All modes use deterministic alternating logic
return _sequenceLastSide == Sides.Buy ? Sides.Sell : Sides.Buy;
}
}
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 new_random_strategy(Strategy):
"""Alternating entry strategy with SL/TP that mimics the MetaTrader New Random expert."""
def __init__(self):
super(new_random_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 5) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (pts)", "Stop loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 5) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pts)", "Take profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._seq_last_side = 0 # 0=none, 1=buy, -1=sell
self._pos_side = 0
self._entry_price = 0.0
self._candle_count = 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(new_random_strategy, self).OnStarted2(time)
self._seq_last_side = -1 # start with sell, so first entry is buy
self._pos_side = 0
self._entry_price = 0.0
self._candle_count = 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
self._candle_count += 1
if self._candle_count < 3:
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
if self.Position != 0 and self._entry_price > 0:
hit = False
if self._pos_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._pos_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._pos_side = 0
self._entry_price = 0.0
# If flat, open new position (alternating)
if self.Position == 0 and self._pos_side == 0:
side = -1 if self._seq_last_side == 1 else 1
if side == 1:
self.BuyMarket()
else:
self.SellMarket()
self._pos_side = side
self._entry_price = price
self._seq_last_side = side
def OnReseted(self):
super(new_random_strategy, self).OnReseted()
self._seq_last_side = 0
self._pos_side = 0
self._entry_price = 0.0
self._candle_count = 0
def CreateClone(self):
return new_random_strategy()