A Estratégia de Fechamento de Múltiplos Pares espelha o script original do MetaTrader que supervisiona uma cesta de pares de moedas e liquida todas as posições abertas assim que o lucro flutuante combinado atinge um alvo ou a perda acumulada excede um limite de segurança. A conversão aproveita a API de alto nível do StockSharp para rastrear lucros, impor um tempo mínimo de manutenção e fechar posições em vários instrumentos em uma ação.
Lógica
Resolver os instrumentos observados do parâmetro WatchedSymbols separado por vírgulas. Se a lista estiver vazia, o Security principal é usado.
Inscrever-se no tipo de candle selecionado (padrão: período de 1 minuto) para cada instrumento. Cada candle terminado aciona uma avaliação de lucro.
Para cada instrumento a estratégia armazena:
O último lucro calculado (Positions[i].PnL).
O timestamp quando uma posição se tornou diferente de zero pela primeira vez para respeitar o requisito MinAgeSeconds.
Após cada atualização, o lucro líquido em todos os símbolos observados é calculado:
Se ProfitTarget for atingido, todas as posições com mais idade que o mínimo são achatadas usando ordens BuyMarket / SellMarket.
Se o lucro líquido cair abaixo de -MaxLoss, a mesma lógica de liquidação é aplicada como stop protetor.
Registros detalhados resumem o lucro por instrumento e o resultado atual da cesta após cada avaliação.
Parâmetros
Parâmetro
Descrição
Padrão
WatchedSymbols
Lista de identificadores de instrumentos separada por vírgulas para supervisionar. Quando vazia, a estratégia recorre ao Security atribuído.
"GBPUSD,USDCAD,USDCHF,USDSEK"
ProfitTarget
Lucro líquido (em moeda do portfólio) necessário para acionar um fechamento global de todas as posições observadas.
60
MaxLoss
Perda máxima aceitável (em moeda do portfólio) antes de a estratégia fechar forçosamente a cesta.
60
Slippage
Parâmetro de compatibilidade que reflete o slippage permitido do script original. Ordens de mercado são usadas para saídas, portanto o valor é informativo.
10
MinAgeSeconds
Tempo de vida mínimo de uma posição antes de a estratégia poder fechá-la.
60
CandleType
Tipo de candle usado para supervisão periódica (padrão: candles de 1 minuto).
1 minute
Notas
A estratégia depende de Positions[i].PnL fornecido pelo StockSharp para medir o lucro flutuante. Ela não busca histórico de trades nem calcula preços manualmente.
Posições abertas antes do início da estratégia herdam o tempo de início como seu primeiro timestamp visto. Elas serão fechadas apenas após o intervalo MinAgeSeconds decorrido desde o início da estratégia.
As saídas são executadas com ordens de mercado para maximizar a probabilidade de liquidação imediata. Slippage é registrado por paridade com a versão MQL, mas não é aplicado aos cálculos de preço.
A saída do registro replica a janela "Comment" do MetaTrader, imprimindo o lucro de cada símbolo seguido do total geral da cesta.
Requisitos
Atribuir um SecurityProvider válido ou garantir que os identificadores solicitados estejam disponíveis através do conector.
Fornecer configuração de volume suficiente por instrumento para que as ordens de mercado possam achatar completamente a posição.
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()