Convierte la utilidad MQL5 Close all positions en una estrategia de alto nivel de StockSharp.
Observa velas terminadas del marco temporal configurado y acumula el beneficio flotante de cada posición abierta en el portafolio asignado.
Cuando el beneficio flotante es igual o supera el umbral, se envían órdenes de mercado para aplanar todos los valores manejados por la estrategia (incluyendo estrategias hijas) hasta que el libro esté completamente cerrado.
El indicador _closeAllRequested refleja la variable MQL m_close_all para que las órdenes de salida continúen emitiéndose hasta que no queden posiciones.
Parámetros
Nombre
Tipo
Predeterminado
Descripción
ProfitThreshold
decimal
10
Beneficio flotante (en moneda de cuenta) requerido antes de que la estrategia aplana cada posición abierta. Refleja InpProfit del EA.
CandleType
DataType
Marco temporal 1m
Serie de velas que define los momentos de "nueva barra". La comprobación de beneficio se ejecuta solo cuando termina una vela, emulando la lógica PrevBars original.
Lógica de trading
La estrategia se suscribe a velas de CandleType y procesa solo barras terminadas, igual que el EA evaluaba el beneficio solo en una nueva barra.
En cada barra terminada el helper CalculateTotalProfit recupera Portfolio.CurrentProfit (PnL flotante incluyendo comisión y swap). Si el adaptador no puede proporcionar este valor recurre a sumar los valores individuales de PnL de posición.
Si el beneficio flotante calculado está por debajo de ProfitThreshold, no ocurre nada.
Tan pronto como el beneficio alcanza el umbral, _closeAllRequested se establece en true y CloseAllPositions() se ejecuta inmediatamente.
CloseAllPositions() recopila cada valor que tiene una exposición en el portafolio o en estrategias anidadas y envía órdenes de mercado en la dirección opuesta al volumen actual (largo → venta, corto → compra).
El indicador _closeAllRequested permanece establecido hasta que HasAnyOpenPosition() detecta que el portafolio está plano, coincidiendo con el comportamiento MQL donde m_close_all permanecía verdadero hasta que todos los tickets estaban cerrados.
Notas adicionales
Solo se proporciona la implementación en C#; la carpeta Python se deja intencionalmente vacía según los requisitos de la tarea.
La estrategia no cancela órdenes pendientes porque el script original solo cerraba posiciones de mercado.
Usar SetOptimize en ProfitThreshold para explorar objetivos de beneficio alternativos a través del optimizador de Designer si es necesario.
Archivos
CS/CloseAllPositionsStrategy.cs
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>
/// Opens positions based on SMA trend and closes when floating PnL reaches a profit threshold.
/// Simplified from the "Close all positions" utility expert.
/// </summary>
public class CloseAllPositionsStrategy : Strategy
{
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private SimpleMovingAverage _sma;
private decimal _entryPrice;
private decimal _prevSma;
private int _cooldown;
/// <summary>
/// SMA period for entry signals.
/// </summary>
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
/// <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>
/// Initializes strategy parameters.
/// </summary>
public CloseAllPositionsStrategy()
{
_smaPeriod = Param(nameof(SmaPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "Moving average period for entry signals", "Indicators");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 300)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null;
_entryPrice = 0;
_prevSma = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma = new SimpleMovingAverage { Length = SmaPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_sma, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_sma.IsFormed)
{
_prevSma = smaValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevSma = smaValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevSma = smaValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevSma = smaValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevSma = smaValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevSma = smaValue;
return;
}
}
// Crossover entry: price crosses above SMA -> buy
if (close > smaValue && candle.OpenPrice <= _prevSma && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 60;
}
// Price crosses below SMA -> sell
else if (close < smaValue && candle.OpenPrice >= _prevSma && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 60;
}
_prevSma = smaValue;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class close_all_positions_strategy(Strategy):
"""
Opens positions based on SMA trend and closes when SL/TP reached.
"""
def __init__(self):
super(close_all_positions_strategy, self).__init__()
self._sma_period = self.Param("SmaPeriod", 100) \
.SetDisplay("SMA Period", "Moving average period for entry signals", "Indicators")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 300) \
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk")
self._entry_price = 0.0
self._prev_sma = 0.0
self._cooldown = 0
@property
def candle_type(self):
return DataType.TimeFrame(TimeSpan.FromMinutes(5))
def OnReseted(self):
super(close_all_positions_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_sma = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(close_all_positions_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._sma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def on_process(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
if self._prev_sma == 0.0:
self._prev_sma = sma_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_sma = sma_val
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and float(self.Security.PriceStep) > 0:
step = float(self.Security.PriceStep)
if self.Position > 0 and self._entry_price > 0:
if self._stop_loss_points.Value > 0 and close <= self._entry_price - self._stop_loss_points.Value * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_sma = sma_val
return
if self._take_profit_points.Value > 0 and close >= self._entry_price + self._take_profit_points.Value * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_sma = sma_val
return
elif self.Position < 0 and self._entry_price > 0:
if self._stop_loss_points.Value > 0 and close >= self._entry_price + self._stop_loss_points.Value * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_sma = sma_val
return
if self._take_profit_points.Value > 0 and close <= self._entry_price - self._take_profit_points.Value * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_sma = sma_val
return
if close > sma_val and open_price <= self._prev_sma and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 60
elif close < sma_val and open_price >= self._prev_sma and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 60
self._prev_sma = sma_val
def CreateClone(self):
return close_all_positions_strategy()