Auf GitHub ansehen

Kleine Inside-Bar-Strategie

Übersicht

Die Kleine Inside-Bar-Strategie sucht nach einem kompakten Inside-Bar-Muster, dem ein Momentum-Wechsel zwischen zwei aufeinanderfolgenden Kerzen folgt. Der ursprüngliche MetaTrader 5-Experte wurde in die StockSharp High-Level-API übersetzt und arbeitet jetzt ausschließlich auf abgeschlossenen Kerzen. Der Ansatz richtet sich an Trader, die Ausbruchs-Einstiege bevorzugen, die durch komprimierte Volatilitätsphasen ausgelöst werden.

Musterdefinition

Die Strategie bewertet die zwei jüngsten abgeschlossenen Kerzen:

  1. Inside-Bar-Bedingung – die zuletzt abgeschlossene Kerze muss vollständig im Bereich der vorherigen liegen.
  2. Range-Ratio-Filter – der Bereich der Mutterbar (zwei Bars zurück) muss mindestens einem konfigurierbaren Vielfachen des Inside-Bar-Bereichs entsprechen. Das Standard-Verhältnis ist 2:1.
  3. Richtungsfilter
    • Ein Long-Setup erfordert eine bullische Inside-Bar, die sich in der unteren Hälfte der Mutterbar bildet, zusammen mit einer bärischen Mutterbar.
    • Ein Short-Setup erfordert eine bärische Inside-Bar, die sich in der oberen Hälfte der Mutterbar bildet, zusammen mit einer bullischen Mutterbar.
  4. Optionale Umkehr tauscht die Long- und Short-Interpretationen, behält aber die gleichen geometrischen Anforderungen bei.

Positionsverwaltung

Der Parameter OpenMode spiegelt das Verhalten des ursprünglichen EA wider:

  • AnySignal – übermittelt bei jedem Signal eine neue Market-Order. Wenn eine entgegengesetzte Position vorhanden ist, wird sie teilweise ausgeglichen, da StockSharp Netting-Konten verwendet.
  • SwingWithRefill – flacht die entgegengesetzte Exposition vor dem Einstieg ab und erlaubt mehrere Ergänzungen in dieselbe Richtung.
  • SingleSwing – hält höchstens einen direktionalen Trade; neue Signale werden ignoriert, solange eine ausgerichtete Position offen ist.

Sowohl Long- als auch Short-Einstiege können unabhängig aktiviert werden. Reversal-Trading invertiert einfach, welches Setup Long- oder Short-Orders produziert.

Parameter

Name Standard Beschreibung
CandleType 1-Stunden-Zeitrahmen Kerzen-Subscription zur Mustererkennung.
RangeRatioThreshold 2.0 Minimales Mutter-zu-Inside-Range-Verhältnis.
EnableLong true Bullische Trades erlauben.
EnableShort true Bärische Trades erlauben.
ReverseSignals false Long- und Short-Musterrichtungen tauschen.
OpenMode SwingWithRefill Steuert, wie bestehende Exposition bei einem neuen Signal behandelt wird.

Handelslogik

  1. Die konfigurierte Kerzenserie abonnieren und auf abgeschlossene Bars warten.
  2. Die letzten zwei abgeschlossenen Kerzen zur Musterbewertung pflegen.
  3. Wenn Muster und Ratio-Filter übereinstimmen, das direktionale Signal bestimmen und optional Umkehr anwenden.
  4. Bestätigen, dass der Handel erlaubt ist (IsFormedAndOnlineAndAllowTrading) und dass die relevante Richtung aktiviert ist.
  5. Ordergröße basierend auf dem ausgewählten OpenMode berechnen und eine Market-Order mit dem Basis-Strategie-Volumen senden.
  6. Die interne Kerzenhistorie aktualisieren, damit die neueste Kerze Teil des nächsten Bewertungszyklus wird.

Implementierungshinweise

  • Die Strategie verwendet StartProtection(), um den eingebauten Risikomanager zu aktivieren (ohne vordefinierte Stop- oder Take-Profit-Werte). Zusätzliche Filter können bei Bedarf extern hinzugefügt werden.
  • Der Indikatorzustand wird nicht in Sammlungen gespeichert; es werden nur die zwei neuesten Kerzen wie für das Muster erforderlich gehalten.
  • Der Algorithmus stützt sich ausschließlich auf abgeschlossene Kerzen und vermeidet Intrabar-Berechnungen gemäß den Best Practices der High-Level-API.
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>
/// Implements the "Small Inside Bar" pattern strategy converted from MetaTrader 5.
/// The strategy searches for an inside bar with a small range compared to the mother bar
/// and opens positions following the direction of the pattern conditions.
/// </summary>
public class SmallInsideBarStrategy : Strategy
{
	/// <summary>
	/// Defines how the strategy manages simultaneous entries.
	/// </summary>
	public enum SmallInsideBarOpenModes
	{
		/// <summary>
		/// Open a new position on every signal without forcing opposite positions to close.
		/// </summary>
		AnySignal,

		/// <summary>
		/// Close opposite positions first and allow adding to the current swing direction.
		/// </summary>
		SwingWithRefill,

		/// <summary>
		/// Maintain a single position in the market and ignore additional entries while it is active.
		/// </summary>
		SingleSwing
	}

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _rangeRatioThreshold;
	private readonly StrategyParam<bool> _enableLong;
	private readonly StrategyParam<bool> _enableShort;
	private readonly StrategyParam<bool> _reverseSignals;
	private readonly StrategyParam<SmallInsideBarOpenModes> _openMode;

	private ICandleMessage _previousCandle;
	private ICandleMessage _twoBackCandle;

	/// <summary>
	/// Type of candles used for pattern detection.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Minimum ratio between the mother bar range and the inside bar range.
	/// </summary>
	public decimal RangeRatioThreshold
	{
		get => _rangeRatioThreshold.Value;
		set => _rangeRatioThreshold.Value = value;
	}

	/// <summary>
	/// Allow long trades.
	/// </summary>
	public bool EnableLong
	{
		get => _enableLong.Value;
		set => _enableLong.Value = value;
	}

	/// <summary>
	/// Allow short trades.
	/// </summary>
	public bool EnableShort
	{
		get => _enableShort.Value;
		set => _enableShort.Value = value;
	}

	/// <summary>
	/// Reverse long and short signals.
	/// </summary>
	public bool ReverseSignals
	{
		get => _reverseSignals.Value;
		set => _reverseSignals.Value = value;
	}

	/// <summary>
	/// Mode for handling position entries.
	/// </summary>
	public SmallInsideBarOpenModes OpenMode
	{
		get => _openMode.Value;
		set => _openMode.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public SmallInsideBarStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for pattern detection", "General");

		_rangeRatioThreshold = Param(nameof(RangeRatioThreshold), 2.25m)
			.SetGreaterThanZero()
			.SetDisplay("Range Ratio", "Minimum mother-to-inside bar range ratio", "Pattern")
			
			.SetOptimize(1.5m, 3m, 0.25m);

		_enableLong = Param(nameof(EnableLong), true)
			.SetDisplay("Enable Long", "Allow bullish trades", "Trading");

		_enableShort = Param(nameof(EnableShort), true)
			.SetDisplay("Enable Short", "Allow bearish trades", "Trading");

		_reverseSignals = Param(nameof(ReverseSignals), false)
			.SetDisplay("Reverse Signals", "Invert long and short signals", "Trading");

		_openMode = Param(nameof(OpenMode), SmallInsideBarOpenModes.SwingWithRefill)
			.SetDisplay("Open Mode", "Position management mode", "Trading");
	}

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

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

		_previousCandle = null;
		_twoBackCandle = null;
	}

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

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

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

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

		if (_previousCandle == null)
		{
			_previousCandle = candle;
			return;
		}

		if (_twoBackCandle == null)
		{
			_twoBackCandle = _previousCandle;
			_previousCandle = candle;
			return;
		}

		var insideHigh = _previousCandle.HighPrice;
		var insideLow = _previousCandle.LowPrice;
		var motherHigh = _twoBackCandle.HighPrice;
		var motherLow = _twoBackCandle.LowPrice;

		if (insideHigh <= insideLow || motherHigh <= motherLow)
		{
			ShiftHistory(candle);
			return;
		}

		if (!(insideHigh < motherHigh && insideLow > motherLow))
		{
			ShiftHistory(candle);
			return;
		}

		var insideRange = insideHigh - insideLow;
		var motherRange = motherHigh - motherLow;
		var ratio = insideRange == 0 ? decimal.MaxValue : motherRange / insideRange;

		if (ratio <= RangeRatioThreshold)
		{
			ShiftHistory(candle);
			return;
		}

		var midpoint = (motherHigh + motherLow) / 2m;

		var bullishInside = _previousCandle.ClosePrice > _previousCandle.OpenPrice && insideHigh < midpoint && _twoBackCandle.ClosePrice < _twoBackCandle.OpenPrice;
		var bearishInside = _previousCandle.ClosePrice < _previousCandle.OpenPrice && insideLow < midpoint && _twoBackCandle.ClosePrice > _twoBackCandle.OpenPrice;

		if (ReverseSignals)
		{
			(bullishInside, bearishInside) = (bearishInside, bullishInside);
		}

		var shouldOpenLong = bullishInside && EnableLong;
		var shouldOpenShort = bearishInside && EnableShort;

		if (shouldOpenLong)
		{
			var volume = CalculateOrderVolume(true);

			if (volume > 0)
				BuyMarket(volume);
		}

		if (shouldOpenShort)
		{
			var volume = CalculateOrderVolume(false);

			if (volume > 0)
				SellMarket(volume);
		}

		ShiftHistory(candle);
	}

	private decimal CalculateOrderVolume(bool isLong)
	{
		var baseVolume = Volume;

		if (baseVolume <= 0)
			return 0;

		var position = Position;

		if (isLong)
		{
			if (OpenMode == SmallInsideBarOpenModes.SingleSwing && position > 0)
				return 0;

			if (position < 0 && OpenMode != SmallInsideBarOpenModes.AnySignal)
				baseVolume += Math.Abs(position);
		}
		else
		{
			if (OpenMode == SmallInsideBarOpenModes.SingleSwing && position < 0)
				return 0;

			if (position > 0 && OpenMode != SmallInsideBarOpenModes.AnySignal)
				baseVolume += Math.Abs(position);
		}

		return baseVolume;
	}

	private void ShiftHistory(ICandleMessage candle)
	{
		// Keep track of the last two finished candles for pattern evaluation.
		_twoBackCandle = _previousCandle;
		_previousCandle = candle;
	}
}