Auf GitHub ansehen

Geldverwaltungs-Strategie mit festem Margin

Diese Strategie repliziert das MetaTrader-Beispiel "Money Fixed Margin" mithilfe der High-Level-API von StockSharp. Sie zeigt, wie Positionen durch Risikierung eines festen Prozentsatzes des Portfolios bemessen werden, während die in Pips ausgedrückte Stop-Loss-Distanz in einen absoluten Preisversatz umgerechnet wird. Die Strategie handelt nur Long-Positionen und konzentriert sich auf die Demonstration der Geldverwaltungslogik anstelle eines prädiktiven Einstiegssignals.

Details

  • Einstiegskriterien:
    • Long: führt nach jeder abgeschlossenen Kerzenanzahl, die durch Check Interval angegeben wird (standardmäßig jede 980. Bar), einen Marktkauf aus. Die Order verwendet den Schlusskurs der auslösenden Kerze als Referenz für Risikoberechnungen.
  • Long/Short: Nur Long.
  • Ausstiegskriterien:
    • Ein schützender Stop-Loss wird automatisch über StartProtection in einer Entfernung angehängt, die aus dem Parameter Stop Loss (pips) abgeleitet wird.
    • Es wird kein Gewinnziel verwendet; Positionen werden nur durch den Stop-Loss oder manuellen Eingriff geschlossen.
  • Stops: Nur Stop Loss.
  • Standardwerte:
    • Stop Loss (pips) = 25
    • Risk Percent = 10
    • Check Interval = 980
    • Candle Type = 1-Minuten-Zeitrahmen
  • Filter:
    • Kategorie: Risikomanagement
    • Richtung: Long
    • Indikatoren: Keine
    • Stops: Ja (Stop-Loss)
    • Komplexität: Grundlegend
    • Zeitrahmen: Intraday (konfigurierbar über Candle Type)
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel (skaliert mit Risk Percent)

Positionsgrößenlogik

  1. Die Strategie liest Security.PriceStep und Security.Decimals um die Pip-Größe abzuleiten. Paare mit 3 oder 5 Dezimalstellen verwenden einen zehnfachen Multiplikator, um der MetaTrader-Definition eines Pips zu entsprechen.
  2. Stop Loss (pips) wird mit der Pip-Größe multipliziert, um eine absolute Preisdistanz (ExtStopLoss) zu erhalten, die mit dem MQL5-Code identisch ist.
  3. Der aktuelle Portfoliowert (bevorzugt Portfolio.CurrentValue, dann Portfolio.BeginValue) wird mit Risk Percent / 100 multipliziert, um das pro Trade exponierte Kapital zu bestimmen.
  4. Das Risiko pro Einzellot wird durch das Produkt aus der Stop-Loss-Distanz, der Anzahl der Preisschritte innerhalb dieser Distanz und Security.StepPrice (wenn verfügbar) berechnet. Wenn StepPrice unbekannt ist, wird die Preisdistanz selbst als Fallback verwendet.
  5. Die Division des Risikobetrags durch das Risiko pro Lot ergibt das gewünschte Volumen. Das Ergebnis wird auf den VolumeStep des Instruments normiert, auf Mindest- und Höchstvolumengrenzen begrenzt und zur Transparenz protokolliert. Ein Vergleichswert mit null Stop-Loss-Distanz wird ebenfalls protokolliert, um zu veranschaulichen, warum der Money Manager Trades ohne einen schützenden Stop ablehnt.

Arbeitsablauf

  1. Beim Start abonniert die Strategie die konfigurierte Kerzenserie, berechnet die Pip-Größe und aktiviert StartProtection mit der berechneten absoluten Stop-Loss-Distanz.
  2. Jede abgeschlossene Kerze erhöht einen internen Zähler. Wenn der Zähler das gewählte Check Interval erreicht, bewertet die Strategie die Positionsgröße, gibt Diagnoseinformationen aus und setzt den Zähler zurück.
  3. Wenn das berechnete Volumen positiv ist, wird eine Marktbuy-Order platziert. Der eingebaute Schutz hängt den Stop-Loss bei Close - ExtStopLoss an. Fehler (z. B. durch unzureichende Daten oder Instrumente mit Nullpreis) verhindern die Orderübermittlung.
  4. Es werden keine weiteren Trades durchgeführt, bis der Zähler ein weiteres Intervall abgeschlossen hat, wobei der Fokus auf dem Geldmanagement statt auf der Signalfrequenz liegt.

Verwendungshinweise

  • Setzen Sie Risk Percent auf einen konservativen Wert, wenn Sie sich mit einem Live-Konto verbinden; das Standard-Risiko von 10% spiegelt das MQL-Beispiel wider, ist aber für den echten Handel aggressiv.
  • Stellen Sie sicher, dass das Instrument aussagekräftige PriceStep- und StepPrice-Metadaten bereitstellt. Wenn nicht verfügbar, arbeitet die Strategie weiterhin, interpretiert das Risiko jedoch in rohen Preiseinheiten.
  • Die Strategie vermeidet absichtlich Short-Trades, um der originalen Demonstration treu zu bleiben. Passen Sie BuyMarket/SellMarket-Aufrufe an, wenn zweiseitiger Handel gewünscht wird.
  • Kombinieren Sie dieses Geldverwaltungsmodul mit anderen Signalgeneratoren durch Wiederverwendung des CalculateFixedMarginVolume-Helpers aus dem Strategiecode.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Recreates the MetaTrader Money Fixed Margin sample using StockSharp.
/// It demonstrates fixed percentage risk sizing for long trades.
/// </summary>
public class MoneyFixedMarginStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<int> _checkInterval;
	private readonly StrategyParam<DataType> _candleType;

	private int _barCount;
	private decimal _pipSize;

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Portfolio percentage risked on each trade.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Number of finished candles between trade attempts.
	/// </summary>
	public int CheckInterval
	{
		get => _checkInterval.Value;
		set => _checkInterval.Value = value;
	}

	/// <summary>
	/// Candle series used to time entries.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="MoneyFixedMarginStrategy"/>.
	/// </summary>
	public MoneyFixedMarginStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 25m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Stop loss distance in pips", "Risk");

		_riskPercent = Param(nameof(RiskPercent), 10m)
			.SetGreaterThanZero()
			.SetDisplay("Risk Percent", "Percent of equity risked per trade", "Risk");

		_checkInterval = Param(nameof(CheckInterval), 150)
			.SetGreaterThanZero()
			.SetDisplay("Check Interval", "Completed candles between trades", "Execution");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
		_barCount = 0;
		_pipSize = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var priceStep = Security?.PriceStep ?? 1m;
		var decimals = Security?.Decimals ?? 0;
		var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;

		_pipSize = priceStep * adjust;
		if (_pipSize <= 0m)
			_pipSize = priceStep > 0m ? priceStep : 1m;

		// Attach a protective stop using the pip-based distance converted to price units.
		StartProtection(
			new Unit(StopLossPips * _pipSize, UnitTypes.Absolute),
			new Unit(StopLossPips * _pipSize * 2, UnitTypes.Absolute));

		// Subscribe to the candle stream that emulates the tick counter from the MQL example.
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Count finished candles to mirror the tick counter from the original script.
		_barCount++;

		if (_barCount < CheckInterval)
			return;

		var entryPrice = candle.ClosePrice;

		if (entryPrice <= 0m)
		{
			LogWarning("Skip trade because entry price is not positive. Close={0}", entryPrice);
			return;
		}

		var riskAmount = CalculateRiskAmount();
		if (riskAmount <= 0m)
		{
			LogWarning("Skip trade because risk amount is not positive. Portfolio value={0}", riskAmount);
			return;
		}

		var stopDistance = StopLossPips * _pipSize;
		var stopPrice = entryPrice - stopDistance;

		var volumeWithoutStop = CalculateFixedMarginVolume(entryPrice, 0m, riskAmount);
		var volumeWithStop = CalculateFixedMarginVolume(entryPrice, stopPrice, riskAmount);

		this.LogInfo(
			"StopLoss=0 -> volume {0:0.####}; StopLoss={1:0.#####} -> volume {2:0.####}; Portfolio={3:0.##}",
			volumeWithoutStop,
			stopPrice,
			volumeWithStop,
			GetPortfolioValue());

		BuyMarket();

		// Reset the counter only after successfully sending an order.
		_barCount = 0;
	}

	private decimal CalculateRiskAmount()
	{
		var portfolioValue = GetPortfolioValue();
		return portfolioValue > 0m ? portfolioValue * RiskPercent / 100m : 0m;
	}

	private decimal GetPortfolioValue()
	{
		var current = Portfolio?.CurrentValue ?? 0m;
		if (current > 0m)
			return current;

		var begin = Portfolio?.BeginValue ?? 0m;
		return begin > 0m ? begin : current;
	}

	private decimal CalculateFixedMarginVolume(decimal entryPrice, decimal stopPrice, decimal riskAmount)
	{
		if (riskAmount <= 0m || entryPrice <= 0m || stopPrice <= 0m)
			return 0m;

		var stopDistance = entryPrice - stopPrice;
		if (stopDistance <= 0m)
			return 0m;

		var priceStep = Security?.PriceStep ?? 0m;
		if (priceStep <= 0m)
			priceStep = 1m;

		var stepPrice = 0m;
		if (stepPrice <= 0m)
			stepPrice = priceStep;

		var stepsCount = stopDistance / priceStep;
		if (stepsCount <= 0m)
			return 0m;

		var riskPerVolume = stepsCount * stepPrice;
		if (riskPerVolume <= 0m)
			return 0m;

		var rawVolume = riskAmount / riskPerVolume;
		return NormalizeVolume(rawVolume);
	}

	private decimal NormalizeVolume(decimal volume)
	{
		if (volume <= 0m)
			return 0m;

		if (Security?.VolumeStep is decimal step && step > 0m)
		{
			volume = Math.Ceiling(volume / step) * step;
		}

		return volume;
	}
}