Auf GitHub ansehen

Strategie mit festem Risiko

Übersicht

Die Strategie mit festem Risiko ist ein direkter Port des MetaTrader 5 Expert Advisors Money Fixed Risk.mq5. Das ursprüngliche Skript berechnet periodisch die maximale Positionsgröße, die das Risiko unter einem festen Prozentsatz des Kontokapitals hält, und öffnet dann einen Marktkauf, der mit symmetrischen Stop-Loss- und Take-Profit-Orders abgesichert ist. Diese StockSharp-Version bewahrt dasselbe Verhalten mithilfe der High-Level-Tick-Abonnement-API und der vom Framework bereitgestellten Risikokontrollen.

Die Strategie hört auf jeden Trade (Tick) des ausgewählten Instruments. Nach einer konfigurierbaren Anzahl von Ticks wertet sie den aktuellen Portfoliowert aus, wandelt die konfigurierte Stop-Distanz in Pips in Preiseinheiten um und berechnet das größte Volumen, das das Risiko innerhalb des angegebenen Kapitalanteils hält. Wenn das berechnete Volumen gültig ist, öffnet die Strategie eine Long-Marktorder und weist Stop-Loss- und Take-Profit-Niveaus genau im Stop-Abstand vom Ausführungspreis zu. Stop und Ziel werden auf jedem folgenden Tick überwacht und die Position wird geschlossen, sobald eine der Grenzen berührt wird.

Datenanforderungen

  • Tick-(Trade-)Daten sind erforderlich, weil die Einstiegsbedingung einzelne Ticks zählt. Kerzendaten werden nicht verwendet.
  • PriceStep, StepPrice, VolumeStep, MinVolume und das optionale MaxVolume müssen für das Instrument korrekt konfiguriert sein, damit die Positionsgrößenformel den Broker-Kontraktspezifikationen entspricht.

Funktionsweise der Strategie

  1. Auf Tick-Updates über SubscribeTrades() warten.
  2. Den zuletzt gehandelten Preis verfolgen und einen internen Zähler erhöhen.
  3. Immer wenn der Tick-Zähler das Ticks Interval erreicht, den Zähler zurücksetzen und:
    • Die Pip-Größe aus PriceStep und Decimals bestimmen (5- und 3-stellige Kurse werden automatisch um 10 skaliert).
    • Die konfigurierte Stop-Loss-Distanz von Pips in Preiseinheiten umrechnen.
    • Das aktuelle Kontokapital bestimmen (versucht Portfolio.CurrentValue, fällt auf CurrentBalance zurück, dann BeginValue).
    • Das monetäre Risiko pro Kontrakt mithilfe der Stop-Distanz und StepPrice berechnen.
    • Das maximale Volumen ableiten, das das monetäre Risiko unter Risk % des Kapitals hält, und es auf den Exchange-Volumenschritt und -Grenzen normalisieren.
  4. Wenn das berechnete Volumen positiv ist, eine Kauf-Marktorder senden, die jedes bestehende Short-Engagement ausgleicht und eine neue Long-Position öffnet.
  5. Die Stop-Loss- und Take-Profit-Preise rund um den Einstiegspreis aufzeichnen. Auf jedem folgenden Tick den Handelspreis überwachen und die Position schließen, wenn ein Niveau verletzt wird.

Parameter

  • Stop Loss (pips) – Stop-Loss-Distanz in Pips. Der Take-Profit wird in entgegengesetzter Richtung im gleichen Abstand platziert.
  • Risk % – Prozentsatz des Portfolio-Kapitals, das pro Trade riskiert wird.
  • Ticks Interval – Anzahl der Ticks, die gewartet werden, bevor eine neue Position reevaluiert und potenziell geöffnet wird.

Alle Parameter unterstützen Optimierung und Validierung (müssen größer als null sein).

Details zum Money Management

  • Risikobetrag = Equity * (Risk % / 100).
  • Stop-Distanz in Preiseinheiten = Stop Loss (pips) * pip size, wobei pip size für 3- und 5-Dezimalinstrumente PriceStep * 10 entspricht, sonst PriceStep.
  • Monetäres Risiko pro Kontrakt = (stop distance / PriceStep) * StepPrice.
  • Positionsgröße = Risk amount / monetary risk per contract, abgerundet auf den nächsten VolumeStep und begrenzt durch MinVolume/MaxVolume. Orders werden übersprungen, wenn die normalisierte Größe unter dem Mindestvolumen liegt.

Unterschiede zum Original-Expert-Advisor

  • Läuft vollständig innerhalb von StockSharp ohne MetaTrader-Bibliotheken aufzurufen.
  • Verwendet StartProtection(), damit plattformseitige Schutzmaßnahmen aktiv bleiben.
  • Stützt sich auf das Strategie-Portfolio für aktuelle Kapitalinformationen statt Terminal-Saldoobjekte abzufragen.
  • Nutzt kontinuierliche Tick-Überwachung zum Schließen von Positionen, was die Notwendigkeit expliziter Stop-Orders in diesem Lehrbeispiel entfernt.

Verwendungshinweise

  • Dieses Beispiel öffnet nur Long-Positionen genau wie die ursprüngliche Datei. ProcessTrade erweitern, wenn Short-Trades erforderlich sind.
  • Beim Backtesting sicherstellen, dass die Tick-Daten genug Tiefe haben, um das konfigurierte Tick-Intervall zu erreichen; andernfalls werden keine Trades ausgelöst.
  • Da die Positionsgrößenbestimmung von Broker-Metadaten abhängt, die Korrektheit von PriceStep, StepPrice und Volumenbeschränkungen vor dem Livebetrieb überprüfen.
  • Die Implementierung vermeidet die Verwendung von Indikator-Sammlungen, um die Konvertierungsrichtlinien einzuhalten, und hält die Logik durch private Felder zustandsbehaftet.
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>
/// Recreates the Money Fixed Risk expert advisor using StockSharp's high level API.
/// Uses ATR to determine position sizing via risk percentage and opens long positions
/// with symmetric stop-loss and take-profit levels based on ATR.
/// </summary>
public class MoneyFixedRiskStrategy : Strategy
{
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _candleInterval;
	private readonly StrategyParam<DataType> _candleType;

	private int _candleCounter;
	private decimal _stopPrice;
	private decimal _takeProfitPrice;
	private decimal _entryPrice;

	/// <summary>
	/// ATR multiplier for stop distance.
	/// </summary>
	public decimal AtrMultiplier
	{
		get => _atrMultiplier.Value;
		set => _atrMultiplier.Value = value;
	}

	/// <summary>
	/// ATR calculation period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Number of candles between position evaluations.
	/// </summary>
	public int CandleInterval
	{
		get => _candleInterval.Value;
		set => _candleInterval.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="MoneyFixedRiskStrategy"/>.
	/// </summary>
	public MoneyFixedRiskStrategy()
	{
		_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Multiplier", "Multiplier for ATR-based stop distance", "Risk")
			.SetOptimize(0.5m, 3m, 0.5m);

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
			.SetOptimize(7, 28, 7);

		_candleInterval = Param(nameof(CandleInterval), 10)
			.SetGreaterThanZero()
			.SetDisplay("Candle Interval", "Candles between position evaluations", "General")
			.SetOptimize(5, 30, 5);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for processing", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_candleCounter = 0;
		_stopPrice = 0m;
		_takeProfitPrice = 0m;
		_entryPrice = 0m;
	}

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

		var atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, atr);
			DrawOwnTrades(area);
		}
	}

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

		var price = candle.ClosePrice;

		// Manage existing long position
		if (Position > 0 && _stopPrice > 0m)
		{
			if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takeProfitPrice)
			{
				SellMarket(Position);
				_stopPrice = 0m;
				_takeProfitPrice = 0m;
				_entryPrice = 0m;
			}
		}

		// Manage existing short position
		if (Position < 0 && _stopPrice > 0m)
		{
			if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takeProfitPrice)
			{
				BuyMarket(Math.Abs(Position));
				_stopPrice = 0m;
				_takeProfitPrice = 0m;
				_entryPrice = 0m;
			}
		}

		_candleCounter++;

		if (_candleCounter < CandleInterval)
			return;

		_candleCounter = 0;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position != 0)
			return;

		if (atrValue <= 0m)
			return;

		var stopDistance = atrValue * AtrMultiplier;

		// Alternate between long and short based on price relative to previous entry
		var goLong = _entryPrice == 0m || price > _entryPrice;

		if (goLong)
		{
			BuyMarket(Volume);
			_entryPrice = price;
			_stopPrice = price - stopDistance;
			_takeProfitPrice = price + stopDistance;
		}
		else
		{
			SellMarket(Volume);
			_entryPrice = price;
			_stopPrice = price + stopDistance;
			_takeProfitPrice = price - stopDistance;
		}
	}
}