La Estrategia Cierre de Múltiples Pares refleja el script original de MetaTrader que supervisa una cesta de pares de divisas y liquida todas las posiciones abiertas una vez que el beneficio flotante combinado alcanza un objetivo o la pérdida acumulada supera un umbral de seguridad. La conversión aprovecha la API de alto nivel de StockSharp para rastrear beneficios, aplicar un tiempo mínimo de mantenimiento y cerrar posiciones en varios instrumentos en una acción.
Lógica
Resolver los instrumentos observados del parámetro WatchedSymbols separado por comas. Si la lista está vacía, se usa el Security principal.
Suscribirse al tipo de vela seleccionado (por defecto: marco temporal de 1 minuto) para cada instrumento. Cada vela terminada activa una evaluación de beneficios.
Para cada instrumento la estrategia almacena:
El último beneficio calculado (Positions[i].PnL).
El timestamp cuando una posición se volvió no nula por primera vez para respetar el requisito MinAgeSeconds.
Después de cada actualización se calcula el beneficio neto en todos los instrumentos observados:
Si se alcanza ProfitTarget, todas las posiciones más antiguas que la edad mínima se aplanan usando órdenes BuyMarket / SellMarket.
Si el beneficio neto cae por debajo de -MaxLoss, se aplica la misma lógica de liquidación como stop protector.
Los registros detallados resumen el beneficio por instrumento y el resultado actual de la cesta después de cada evaluación.
Parámetros
Parámetro
Descripción
Por defecto
WatchedSymbols
Lista de identificadores de instrumentos separada por comas para supervisar. Cuando está vacía, la estrategia recurre al Security asignado.
"GBPUSD,USDCAD,USDCHF,USDSEK"
ProfitTarget
Beneficio neto (en moneda de cartera) requerido para activar un cierre global de todas las posiciones observadas.
60
MaxLoss
Pérdida máxima aceptable (en moneda de cartera) antes de que la estrategia cierre forzosamente la cesta.
60
Slippage
Parámetro de compatibilidad que refleja el deslizamiento permitido del script original. Se usan órdenes de mercado para las salidas, por lo que el valor es informativo.
10
MinAgeSeconds
Tiempo de vida mínimo de una posición antes de que la estrategia pueda cerrarla.
60
CandleType
Tipo de vela usado para supervisión periódica (por defecto: velas de 1 minuto).
1 minute
Notas
La estrategia depende de Positions[i].PnL proporcionado por StockSharp para medir el beneficio flotante. No extrae historial de operaciones ni calcula precios manualmente.
Las posiciones abiertas antes de que la estrategia empiece heredan el tiempo de inicio como su primer timestamp visto. Se cerrarán solo después de que transcurra el intervalo MinAgeSeconds desde el inicio de la estrategia.
Las salidas se ejecutan con órdenes de mercado para maximizar la probabilidad de liquidación inmediata. Slippage se registra por paridad con la versión MQL pero no se aplica a los cálculos de precio.
La salida del registro replica la ventana "Comment" de MetaTrader imprimiendo el beneficio de cada símbolo seguido del total de la cesta general.
Requisitos
Asignar un SecurityProvider válido o asegurarse de que los identificadores solicitados estén disponibles a través del conector.
Proporcionar suficiente configuración de volumen por instrumento para que las órdenes de mercado puedan aplanar la posición completamente.
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>
/// Closes the current position when floating PnL reaches a profit target or maximum loss.
/// Simplified from the multi-pair closer utility to work with a single security.
/// </summary>
public class MultiPairCloserStrategy : Strategy
{
private readonly StrategyParam<decimal> _profitTarget;
private readonly StrategyParam<decimal> _maxLoss;
private readonly StrategyParam<int> _minAgeSeconds;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaPeriod;
private SimpleMovingAverage _sma;
private decimal _entryPrice;
private DateTimeOffset? _entryTime;
/// <summary>
/// Profit target in price units.
/// </summary>
public decimal ProfitTarget
{
get => _profitTarget.Value;
set => _profitTarget.Value = value;
}
/// <summary>
/// Maximum tolerated loss in price units.
/// </summary>
public decimal MaxLoss
{
get => _maxLoss.Value;
set => _maxLoss.Value = value;
}
/// <summary>
/// Minimum age of an open position in seconds before exit is permitted.
/// </summary>
public int MinAgeSeconds
{
get => _minAgeSeconds.Value;
set => _minAgeSeconds.Value = value;
}
/// <summary>
/// Candle type for price monitoring.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// SMA period for entry signals.
/// </summary>
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public MultiPairCloserStrategy()
{
_profitTarget = Param(nameof(ProfitTarget), 5m)
.SetNotNegative()
.SetDisplay("Profit Target", "Close position when floating profit reaches this value", "Risk Management");
_maxLoss = Param(nameof(MaxLoss), 10m)
.SetNotNegative()
.SetDisplay("Maximum Loss", "Close position when floating loss reaches this value", "Risk Management");
_minAgeSeconds = Param(nameof(MinAgeSeconds), 60)
.SetNotNegative()
.SetDisplay("Min Age (s)", "Minimum holding time before exit is allowed", "Execution");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle series for monitoring", "General");
_smaPeriod = Param(nameof(SmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "Moving average period for entry signal", "Indicators");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null;
_entryPrice = 0m;
_entryTime = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma = new SimpleMovingAverage { Length = SmaPeriod };
SubscribeCandles(CandleType)
.Bind(_sma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
var price = candle.ClosePrice;
var time = candle.CloseTime;
// Check exit conditions for open position
if (Position != 0 && _entryPrice > 0m)
{
var pnl = Position > 0
? price - _entryPrice
: _entryPrice - price;
var canClose = MinAgeSeconds <= 0 ||
(_entryTime.HasValue && (time - _entryTime.Value).TotalSeconds >= MinAgeSeconds);
if (canClose)
{
if ((ProfitTarget > 0m && pnl >= ProfitTarget) ||
(MaxLoss > 0m && pnl <= -MaxLoss))
{
if (Position > 0)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
_entryPrice = 0m;
_entryTime = null;
return;
}
}
}
// Entry logic: trend following with SMA
if (Position == 0)
{
if (price > smaValue)
{
BuyMarket();
_entryPrice = price;
_entryTime = time;
}
else if (price < smaValue)
{
SellMarket();
_entryPrice = price;
_entryTime = time;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class multi_pair_closer_strategy(Strategy):
def __init__(self):
super(multi_pair_closer_strategy, self).__init__()
self._profit_target = self.Param("ProfitTarget", 5.0)
self._max_loss = self.Param("MaxLoss", 10.0)
self._min_age_seconds = self.Param("MinAgeSeconds", 60)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._sma_period = self.Param("SmaPeriod", 20)
self._sma = None
self._entry_price = 0.0
self._entry_time = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(multi_pair_closer_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = self._sma_period.Value
self.SubscribeCandles(self.CandleType).Bind(self._sma, self._process_candle).Start()
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormed:
return
price = float(candle.ClosePrice)
time = candle.CloseTime
sma_value = float(sma_val)
if self.Position != 0 and self._entry_price > 0:
if self.Position > 0:
pnl = price - self._entry_price
else:
pnl = self._entry_price - price
can_close = self._min_age_seconds.Value <= 0 or (
self._entry_time is not None and (time - self._entry_time).TotalSeconds >= self._min_age_seconds.Value)
if can_close:
if (self._profit_target.Value > 0 and pnl >= self._profit_target.Value) or \
(self._max_loss.Value > 0 and pnl <= -self._max_loss.Value):
if self.Position > 0:
self.SellMarket(abs(self.Position))
else:
self.BuyMarket(abs(self.Position))
self._entry_price = 0.0
self._entry_time = None
return
if self.Position == 0:
if price > sma_value:
self.BuyMarket()
self._entry_price = price
self._entry_time = time
elif price < sma_value:
self.SellMarket()
self._entry_price = price
self._entry_time = time
def OnReseted(self):
super(multi_pair_closer_strategy, self).OnReseted()
self._sma = None
self._entry_price = 0.0
self._entry_time = None
def CreateClone(self):
return multi_pair_closer_strategy()