Auf GitHub ansehen

Up3x1 Investor Strategie

Die Up3x1 Investor Strategie portiert den klassischen MetaTrader Expert Advisor, der auf starke Expansionskerzen reagiert. Sie beobachtet den letzten abgeschlossenen Balken im konfigurierten Zeitrahmen und eröffnet eine neue Position auf dem folgenden Balken, wenn der vorherige Kursbereich und der Kerzenkörper in Richtung des Schlusskurses weit genug waren.

Die Strategie ist für diskretionäre Märkte wie Forex-Majors auf dem H1-Chart ausgelegt, aber die Schwellenwerte können für andere Instrumente angepasst werden. Es wird immer nur eine Position gehalten und jede Order verwendet die Volume-Eigenschaft der Strategie als Handelsgröße.

Handelslogik

  • Signalquelle – abgeschlossene Zeitrahmenkerzen aus CandleType (Standard: 1 Stunde).
  • Einstiegskriterien
    • Berechnung des Hoch–Tief-Bereichs und des absoluten Kerzenkörpers des vorherigen Balkens.
    • Long-Einstieg, wenn die Kerze über der Eröffnung schloss und sowohl der Bereich als auch der Körper ihre jeweiligen Pip-Schwellenwerte überschreiten.
    • Short-Einstieg, wenn die Kerze unter der Eröffnung schloss und sowohl der Bereich als auch der Körper die Schwellenwerte überschreiten.
    • Neue Einstiege werden ignoriert, solange eine Position offen ist.
  • Positionsmanagement
    • Optionale Stop-Loss- und Take-Profit-Levels werden mit Security.PriceStep von Pips in Preiseinheiten umgerechnet.
    • Ein Trailing Stop aktiviert sich, sobald sich der Preis um TrailingStopPips + TrailingStepPips vom Einstieg entfernt hat.
    • Der Trailing Stop bewegt sich nur, wenn das neue Niveau mindestens TrailingStepPips näher am Preis liegt als das vorherige Trailing-Niveau.
    • Die Strategie schließt eine Position, wenn der Preis die Stop-Loss-, Take-Profit- oder Trailing-Stop-Niveaus berührt.

Parameter

Parameter Beschreibung
CandleType Datentyp der für Signale verwendeten Kerzen (Standard: 1-Stunden-Zeitrahmen).
RangeThresholdPips Mindest-Hoch-Tief-Abstand der vorherigen Kerze, in Pips ausgedrückt.
BodyThresholdPips Mindest-Eröffnungs-Schluss-Abstand der vorherigen Kerze, in Pips ausgedrückt.
StopLossPips Stop-Loss-Abstand in Pips. Auf 0 setzen zum Deaktivieren.
TakeProfitPips Take-Profit-Abstand in Pips. Auf 0 setzen zum Deaktivieren.
TrailingStopPips Hinter dem Preis gehaltener Abstand beim Trailing. Auf 0 setzen zum Deaktivieren des Trailing.
TrailingStepPips Zusätzliche Bewegung in Pips, die erforderlich ist, bevor der Trailing Stop enger gestellt wird.

Hinweis: Pip-Schwellenwerte werden mit Security.PriceStep multipliziert. Stellen Sie sicher, dass das Symbol einen gültigen PriceStep hat, damit Pip-Umrechnungen Ihr Instrument korrekt widerspiegeln.

Verwendungshinweise

  1. Weisen Sie die Ziel-Security und den Handels-Connector zu, bevor Sie die Strategie starten.
  2. Passen Sie die Pip-Schwellenwerte an die Volatilität Ihres Marktes an. Forex-Paare mit 5-stelligen Kursen verwenden typischerweise 10 Pips = 0.0010.
  3. Setzen Sie das Volume der Strategie auf die gewünschte Ordergröße. Die Positionsgrößenlogik des Original-EA ist absichtlich vereinfacht, um die StockSharp-Version transparent zu halten.
  4. Da Signale auf geschlossenen Kerzen ausgewertet werden, werden Einstiege unmittelbar nach Bestätigung der Expansionskerze gesendet.
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>
/// Range breakout strategy based on the Up3x1 Investor expert advisor.
/// </summary>
public class Up3x1InvestorStrategy : Strategy
{
	private readonly StrategyParam<decimal> _rangeThresholdPips;
	private readonly StrategyParam<decimal> _bodyThresholdPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevOpen;
	private decimal _prevClose;
	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _hasPreviousCandle;
	private decimal? _entryPrice;
	private decimal _highestPrice;
	private decimal _lowestPrice;
	private decimal? _trailingStopPrice;

	public decimal RangeThresholdPips { get => _rangeThresholdPips.Value; set => _rangeThresholdPips.Value = value; }
	public decimal BodyThresholdPips { get => _bodyThresholdPips.Value; set => _bodyThresholdPips.Value = value; }
	public decimal StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public decimal TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
	public decimal TrailingStopPips { get => _trailingStopPips.Value; set => _trailingStopPips.Value = value; }
	public decimal TrailingStepPips { get => _trailingStepPips.Value; set => _trailingStepPips.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public Up3x1InvestorStrategy()
	{
		_rangeThresholdPips = Param(nameof(RangeThresholdPips), 2m)
			.SetDisplay("Range Threshold (pips)", "Minimum previous candle range in pips", "Signals");

		_bodyThresholdPips = Param(nameof(BodyThresholdPips), 1m)
			.SetDisplay("Body Threshold (pips)", "Minimum previous candle body in pips", "Signals");

		_stopLossPips = Param(nameof(StopLossPips), 5m)
			.SetDisplay("Stop Loss (pips)", "Distance of protective stop in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 5m)
			.SetDisplay("Take Profit (pips)", "Distance of profit target in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 3m)
			.SetDisplay("Trailing Stop (pips)", "Distance kept behind price when trailing", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
			.SetDisplay("Trailing Step (pips)", "Increment required to move trailing stop", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for signals", "General");
	}

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

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

		_prevOpen = 0m;
		_prevClose = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		_hasPreviousCandle = false;
		ResetPositionTracking();
	}

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

		// Subscribe to the configured timeframe and process finished candles.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Work only with fully formed candles to keep logic aligned with the original EA.
		if (candle.State != CandleStates.Finished)
			return;

		// If position was closed externally, reset tracking.
		if (Position == 0 && _entryPrice != null)
			ResetPositionTracking();

		var pipSize = GetPipSize();
		var stopLossDistance = StopLossPips > 0 ? StopLossPips * pipSize : 0m;
		var takeProfitDistance = TakeProfitPips > 0 ? TakeProfitPips * pipSize : 0m;
		var trailingStopDistance = TrailingStopPips > 0 ? TrailingStopPips * pipSize : 0m;
		var trailingStepDistance = TrailingStepPips > 0 ? TrailingStepPips * pipSize : 0m;

		// Manage existing trades before searching for a new signal.
		if (Position != 0 && _entryPrice != null)
		{
			if (ManageOpenPosition(candle, stopLossDistance, takeProfitDistance, trailingStopDistance, trailingStepDistance))
			{
				_prevOpen = candle.OpenPrice;
				_prevClose = candle.ClosePrice;
				_prevHigh = candle.HighPrice;
				_prevLow = candle.LowPrice;
				_hasPreviousCandle = true;
				return;
			}
		}

		// no indicators bound, skip IsFormedAndOnlineAndAllowTrading

		if (Position != 0)
		{
			_prevOpen = candle.OpenPrice;
			_prevClose = candle.ClosePrice;
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			_hasPreviousCandle = true;
			return;
		}

		var refOpen = _hasPreviousCandle ? _prevOpen : candle.OpenPrice;
		var refClose = _hasPreviousCandle ? _prevClose : candle.ClosePrice;
		var refHigh = _hasPreviousCandle ? _prevHigh : candle.HighPrice;
		var refLow = _hasPreviousCandle ? _prevLow : candle.LowPrice;

		var range = refHigh - refLow;
		var body = Math.Abs(refClose - refOpen);
		var rangeThreshold = RangeThresholdPips * pipSize;
		var bodyThreshold = BodyThresholdPips * pipSize;

		// Bullish setup: strong bullish candle with large range and body.
		if (range > rangeThreshold && body > bodyThreshold && refClose > refOpen)
		{
			BuyMarket();
			InitializePositionTracking(candle.ClosePrice);
		}
		// Bearish setup: strong bearish candle with large range and body.
		else if (range > rangeThreshold && body > bodyThreshold && refClose < refOpen)
		{
			SellMarket();
			InitializePositionTracking(candle.ClosePrice);
		}

		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_hasPreviousCandle = true;
	}

	private bool ManageOpenPosition(ICandleMessage candle, decimal stopLossDistance, decimal takeProfitDistance, decimal trailingStopDistance, decimal trailingStepDistance)
	{
		if (_entryPrice == null)
			return false;

		if (Position > 0)
		{
			// Update the highest price reached by the long position.
			_highestPrice = Math.Max(_highestPrice, candle.HighPrice);

			// Check stop loss.
			if (stopLossDistance > 0m && candle.LowPrice <= _entryPrice.Value - stopLossDistance)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}

			// Check take profit.
			if (takeProfitDistance > 0m && candle.HighPrice >= _entryPrice.Value + takeProfitDistance)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}

			// Update trailing stop level when the move is large enough.
			if (trailingStopDistance > 0m && _highestPrice - _entryPrice.Value >= trailingStopDistance + trailingStepDistance)
			{
				var candidate = _highestPrice - trailingStopDistance;
				if (_trailingStopPrice == null || candidate - _trailingStopPrice.Value >= trailingStepDistance)
				_trailingStopPrice = candidate;
			}

			// Exit if price returned to the trailing stop.
			if (_trailingStopPrice != null && candle.LowPrice <= _trailingStopPrice.Value)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}
		}
		else if (Position < 0)
		{
			// Update the lowest price reached by the short position.
			_lowestPrice = Math.Min(_lowestPrice, candle.LowPrice);

			// Check stop loss for short trades.
			if (stopLossDistance > 0m && candle.HighPrice >= _entryPrice.Value + stopLossDistance)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}

			// Check take profit for short trades.
			if (takeProfitDistance > 0m && candle.LowPrice <= _entryPrice.Value - takeProfitDistance)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}

			// Update trailing stop for the short side.
			if (trailingStopDistance > 0m && _entryPrice.Value - _lowestPrice >= trailingStopDistance + trailingStepDistance)
			{
				var candidate = _lowestPrice + trailingStopDistance;
				if (_trailingStopPrice == null || _trailingStopPrice.Value - candidate >= trailingStepDistance)
				_trailingStopPrice = candidate;
			}

			// Exit once the trailing stop is touched.
			if (_trailingStopPrice != null && candle.HighPrice >= _trailingStopPrice.Value)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}
		}

		return false;
	}

	private void InitializePositionTracking(decimal entryPrice)
	{
		// Store entry information to evaluate stops and trailing logic.
		_entryPrice = entryPrice;
		_highestPrice = entryPrice;
		_lowestPrice = entryPrice;
		_trailingStopPrice = null;
	}

	private void ResetPositionTracking()
	{
		_entryPrice = null;
		_highestPrice = 0m;
		_lowestPrice = 0m;
		_trailingStopPrice = null;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep;
		if (step == null || step == 0m)
			return 1m;

		return step.Value;
	}
}