Die Multi-Paar-Schließer-Strategie spiegelt das ursprüngliche MetaTrader-Skript wider, das einen Korb von Währungspaaren überwacht und alle offenen Positionen liquidiert, sobald der kombinierte schwebende Gewinn ein Ziel trifft oder der angesammelte Verlust einen Sicherheitsschwellenwert überschreitet. Die Konvertierung nutzt StockSharp's High-Level-API, um Gewinne zu verfolgen, eine Mindesthaltedauer durchzusetzen und Positionen über mehrere Wertpapiere in einer Aktion zu schließen.
Logik
Die beobachteten Instrumente aus dem kommagetrennnten WatchedSymbols-Parameter auflösen. Wenn die Liste leer ist, wird das Haupt-Security verwendet.
Den ausgewählten Kerzentyp (Standard: 1-Minuten-Zeitrahmen) für jedes Instrument abonnieren. Jede fertige Kerze löst eine Gewinnauswertung aus.
Für jedes Instrument speichert die Strategie:
Den zuletzt berechneten Gewinn (Positions[i].PnL).
Den Timestamp, wann eine Position zuerst ungleich null wurde, um die MinAgeSeconds-Anforderung einzuhalten.
Nach jeder Aktualisierung wird der Nettogewinn über alle beobachteten Symbole berechnet:
Wenn ProfitTarget erreicht wird, werden alle Positionen, die älter als das Mindestalter sind, mit BuyMarket / SellMarket-Orders geglättet.
Wenn der Nettogewinn unter -MaxLoss fällt, wird dieselbe Liquidationslogik als Schutzstop angewendet.
Detaillierte Protokolle fassen den Gewinn pro Instrument und das aktuelle Korbergebnis nach jeder Auswertung zusammen.
Parameter
Parameter
Beschreibung
Standard
WatchedSymbols
Kommagetrennte Liste von Wertpapieridentifikatoren zur Überwachung. Wenn leer, greift die Strategie auf das zugewiesene Security zurück.
"GBPUSD,USDCAD,USDCHF,USDSEK"
ProfitTarget
Nettogewinn (in Portfoliowährung), der erforderlich ist, um einen globalen Abschluss aller beobachteten Positionen auszulösen.
60
MaxLoss
Maximaler akzeptabler Verlust (in Portfoliowährung), bevor die Strategie den Korb zwangsschließt.
60
Slippage
Kompatibilitätsparameter, der den erlaubten Slippage aus dem ursprünglichen Skript widerspiegelt. Marktorders werden für Ausstiege verwendet, daher ist der Wert informativ.
10
MinAgeSeconds
Mindestlebensdauer einer Position, bevor die Strategie sie schließen darf.
60
CandleType
Kerzentyp, der für die periodische Überwachung verwendet wird (Standard: 1-Minuten-Kerzen).
1 minute
Hinweise
Die Strategie verlässt sich auf Positions[i].PnL, bereitgestellt von StockSharp, um den schwebenden Gewinn zu messen. Sie ruft keine Handelshistorie ab und berechnet Preise nicht manuell.
Positionen, die vor dem Start der Strategie geöffnet wurden, erben die Startzeit als ihren ersten gesehenen Timestamp. Sie werden erst geschlossen, nachdem das MinAgeSeconds-Intervall seit dem Strategiestart vergangen ist.
Ausstiege werden mit Marktorders ausgeführt, um die Wahrscheinlichkeit sofortiger Liquidation zu maximieren. Slippage wird aus Gründen der Parität mit der MQL-Version protokolliert, wird aber nicht auf Preisberechnungen angewendet.
Die Protokollausgabe repliziert das MetaTrader "Comment"-Fenster, indem der Gewinn jedes Symbols gefolgt vom Gesamtkorb-Total ausgegeben wird.
Anforderungen
Einen gültigen SecurityProvider zuweisen oder sicherstellen, dass die angeforderten Identifikatoren über den Connector verfügbar sind.
Ausreichende Volumenkonfiguration pro Wertpapier bereitstellen, damit Marktorders die Position vollständig glätten können.
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()