Auf GitHub ansehen

Flachkanal-Strategie (2684)

Diese Strategie ist eine C#-Konvertierung des MetaTrader 5 Expert Advisors Flat Channel (barabashkakvn's edition). Sie erkennt Perioden niedriger Volatilität (einen "flachen" Kanal) mit dem Standardabweichungs-Indikator und platziert Ausbruchs-Stop-Orders an den Kanalgrenzen. Wenn der Preis aus dem flachen Bereich ausbricht, wird die entsprechende Stop-Order ausgelöst, während die entgegengesetzte Order storniert wird, um zu vermeiden, auf beiden Seiten des Markts gefangen zu sein.

Kernlogik

  1. Volatilitätsfilter – Die Strategie abonniert Kerzen und berechnet den Standardabweichungs-Indikator des Medianpreises. Eine flache Phase wird bestätigt, wenn der Wert mindestens FlatBars aufeinanderfolgende Kerzen lang weiter fällt.
  2. Kanalaufbau – Sobald die flache Phase bestätigt ist, werden das höchste Hoch und das niedrigste Tief des flachen Bereichs verfolgt. Die Kanalbreite muss zwischen ChannelMinPips und ChannelMaxPips bleiben (in Preiseinheiten über die Instrument-Tick-Größe umgerechnet).
  3. Einstiegsorders – Solange der Preis innerhalb des Kanals handelt, platziert die Strategie:
    • Einen Buy Stop am Kanalhoch mit Stop-Loss 2 × Kanalbreite unterhalb des Einstiegs und Take-Profit 1 × Kanalbreite oberhalb.
    • Einen Sell Stop am Kanaltief mit den symmetrischen Stop-Loss/Take-Profit-Abständen.
  4. Order-Lebensdauer – Ausstehende Stop-Orders laufen nach OrderLifetimeSeconds ab. Wenn das Timeout abläuft, werden sie storniert und können neu erstellt werden, wenn flache Bedingungen noch gelten.
  5. Positionsverwaltung – Nachdem eine Einstiegsorder ausgeführt wurde, wird die entgegengesetzte Stop-Order storniert und neue schützende Orders (Stop-Loss und Take-Profit) werden registriert. Optionale Breakeven-Logik bewegt den Stop-Loss auf den Einstiegspreis, sobald der Preis eine Fibonacci-Fraktion (FiboTrail) der Distanz zum Take-Profit-Ziel zurücklegt.
  6. Handelsfenster – Der UseTradingHours-Filter schränkt die Aktivität nach Wochentag und nach bestimmten Montag-/Freitag-Stunden ein und emuliert die Zeitplansteuerungen des ursprünglichen EAs.

Indikatoren

  • StandardDeviation (Medianpreis, Länge = StdDevPeriod) – erkennt fallende Volatilität.
  • DonchianChannels (Länge = FlatBars) – liefert die anfänglichen Hoch/Tief-Grenzen für den flachen Kanal.

Risiko & Geldmanagement

  • FixedVolume definiert die Losgröße, wenn UseMoneyManagement deaktiviert ist.
  • Wenn UseMoneyManagement aktiviert ist, wird die Positionsgröße aus RiskPercent des aktuellen Portfoliowerts geteilt durch den Stop-Loss-Abstand in Geld (unter Verwendung von PriceStep und StepPrice) geschätzt.
  • Nach einem Verlust-Trade verwendet die nächste Position FixedVolume × 4, was das Wiederherstellungsverhalten des ursprünglichen EAs repliziert.

Parameter

Parameter Beschreibung
UseTradingHours Den Wochentag/Stunden-Zeitplanfilter aktivieren oder deaktivieren.
TradeTuesday, TradeWednesday, TradeThursday Handel an einzelnen Wochenmittel-Tagen erlauben (Montag und Freitag sind immer erlaubt, aber durch die Stundengrenzen kontrolliert).
MondayStartHour, FridayStopHour Startzeit am Montag und Abbruchzeit am Freitag (24h-Uhr).
UseMoneyManagement, RiskPercent, FixedVolume Oben beschriebene Geldmanagement-Optionen.
OrderLifetimeSeconds Ablaufzeit für ausstehende Einstiegsorders (0 = kein Ablauf).
StdDevPeriod, FlatBars Indikatoreinstellungen, die die Flachphasen-Erkennung steuern.
ChannelMinPips, ChannelMaxPips Erlaubte Kanalbreite in Pips ausgedrückt (umgerechnet mit der Instrument-Tick-Größe).
UseBreakeven, FiboTrail Breakeven-Logik aktivieren und den Fibonacci-Multiplikator setzen, der zur Auslösung der Stop-Anpassung verwendet wird.
CandleType Kerzen-Datentyp oder Zeitrahmen für Berechnungen.

Hinweise

  • Die Strategie erwartet Symbole, die PriceStep und StepPrice bereitstellen, damit Pip-basierte Schwellenwerte in tatsächliche Preise umgerechnet werden können.
  • Ausstehende Orders werden nur neu erstellt, wenn die Volatilität weiter fällt. Wenn die Volatilität steigt, wird der flache Zustand zurückgesetzt und alle Einstiegsorders werden storniert.
  • Schützende Stop- und Take-Profit-Orders werden automatisch storniert, wenn die Position schließt.

Haftungsausschluss

Dieses Beispiel dient nur zu Bildungszwecken. Die vergangene Performance der ursprünglichen Strategie garantiert keine zukünftigen Ergebnisse. Teste und passe die Parameter gründlich an, bevor du auf Live-Märkten deployst.

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>
/// Flat channel breakout strategy converted from the MetaTrader 5 version.
/// Detects consolidation via falling standard deviation, then trades breakouts of the channel.
/// </summary>
public class FlatChannelStrategy : Strategy
{
	private readonly StrategyParam<int> _stdDevPeriod;
	private readonly StrategyParam<int> _flatBars;
	private readonly StrategyParam<decimal> _channelMinPips;
	private readonly StrategyParam<decimal> _channelMaxPips;
	private readonly StrategyParam<DataType> _candleType;

	private StandardDeviation _stdDev = null!;
	private DonchianChannels _donchian = null!;

	private decimal _previousStdDev;
	private int _flatBarCount;
	private decimal _channelHigh;
	private decimal _channelLow;

	private decimal? _pendingBuyPrice;
	private decimal? _pendingSellPrice;
	private decimal _entryPrice;
	private decimal _longStop;
	private decimal _longTake;
	private decimal _shortStop;
	private decimal _shortTake;

	/// <summary>
	/// Standard deviation indicator period.
	/// </summary>
	public int StdDevPeriod
	{
		get => _stdDevPeriod.Value;
		set => _stdDevPeriod.Value = value;
	}

	/// <summary>
	/// Minimum number of bars with falling volatility required to form a flat channel.
	/// </summary>
	public int FlatBars
	{
		get => _flatBars.Value;
		set => _flatBars.Value = value;
	}

	/// <summary>
	/// Minimum channel width expressed in pips.
	/// </summary>
	public decimal ChannelMinPips
	{
		get => _channelMinPips.Value;
		set => _channelMinPips.Value = value;
	}

	/// <summary>
	/// Maximum channel width expressed in pips.
	/// </summary>
	public decimal ChannelMaxPips
	{
		get => _channelMaxPips.Value;
		set => _channelMaxPips.Value = value;
	}

	/// <summary>
	/// Candle type to analyse.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public FlatChannelStrategy()
	{
		_stdDevPeriod = Param(nameof(StdDevPeriod), 37)
			.SetDisplay("StdDev Period", "Standard deviation indicator period", "Indicators")
			.SetGreaterThanZero();

		_flatBars = Param(nameof(FlatBars), 2)
			.SetDisplay("Flat Bars", "Minimum bars in flat state", "Indicators")
			.SetGreaterThanZero();

		_channelMinPips = Param(nameof(ChannelMinPips), 10m)
			.SetDisplay("Channel Min Pips", "Minimum channel width in pips", "Indicators")
			.SetGreaterThanZero();

		_channelMaxPips = Param(nameof(ChannelMaxPips), 100000m)
			.SetDisplay("Channel Max Pips", "Maximum channel width in pips", "Indicators")
			.SetGreaterThanZero();

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

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

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

		_previousStdDev = 0m;
		_flatBarCount = 0;
		_channelHigh = 0m;
		_channelLow = 0m;
		_pendingBuyPrice = null;
		_pendingSellPrice = null;
		_entryPrice = 0m;
		_longStop = 0m;
		_longTake = 0m;
		_shortStop = 0m;
		_shortTake = 0m;
	}

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

		_stdDev = new StandardDeviation { Length = StdDevPeriod };
		_donchian = new DonchianChannels { Length = FlatBars };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.BindEx(_donchian, ProcessCandle)
			.Start();

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

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

		var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
		var stdDevValue = _stdDev.Process(new DecimalIndicatorValue(_stdDev, medianPrice, candle.CloseTime) { IsFinal = true }).ToDecimal();

		if (!_stdDev.IsFormed || channelValue is not DonchianChannelsValue donchianValue)
		{
			_previousStdDev = stdDevValue;
			return;
		}

		if (donchianValue.UpperBand is not decimal upper || donchianValue.LowerBand is not decimal lower)
		{
			_previousStdDev = stdDevValue;
			return;
		}

		// Update flat state based on StdDev direction.
		UpdateStdDevState(stdDevValue, upper, lower, candle);

		// Check simulated pending entries.
		CheckPendingEntries(candle);

		// Manage existing positions with SL/TP.
		ManagePosition(candle);

		// If flat and no position, set up pending breakout entries.
		if (Position == 0 && _flatBarCount >= FlatBars && _channelHigh > _channelLow)
		{
			var channelWidth = _channelHigh - _channelLow;
			var priceStep = Security?.PriceStep ?? 0.01m;
			if (priceStep <= 0m) priceStep = 0.01m;
			var minWidth = ChannelMinPips * priceStep;
			var maxWidth = ChannelMaxPips * priceStep;

			if (channelWidth >= minWidth && channelWidth <= maxWidth)
			{
				// Set pending breakout entries at channel boundaries.
				_pendingBuyPrice = _channelHigh;
				_pendingSellPrice = _channelLow;
				_longStop = _channelHigh - channelWidth * 2m;
				_longTake = _channelHigh + channelWidth;
				_shortStop = _channelLow + channelWidth * 2m;
				_shortTake = _channelLow - channelWidth;
			}
		}

		_previousStdDev = stdDevValue;
	}

	private void UpdateStdDevState(decimal stdDevValue, decimal upper, decimal lower, ICandleMessage candle)
	{
		if (_previousStdDev == 0m)
		{
			_previousStdDev = stdDevValue;
			return;
		}

		if (stdDevValue < _previousStdDev)
		{
			_flatBarCount++;

			if (_flatBarCount == FlatBars)
			{
				_channelHigh = upper;
				_channelLow = lower;
			}
			else if (_flatBarCount > FlatBars)
			{
				if (candle.HighPrice > _channelHigh)
					_channelHigh = candle.HighPrice;
				if (candle.LowPrice < _channelLow)
					_channelLow = candle.LowPrice;
			}
		}
		else if (stdDevValue > _previousStdDev)
		{
			_flatBarCount = 0;
			_channelHigh = 0m;
			_channelLow = 0m;
			_pendingBuyPrice = null;
			_pendingSellPrice = null;
		}
		else if (_flatBarCount >= FlatBars && _channelHigh <= _channelLow)
		{
			_channelHigh = upper;
			_channelLow = lower;
		}
	}

	private void CheckPendingEntries(ICandleMessage candle)
	{
		if (Position != 0)
			return;

		if (_pendingBuyPrice is decimal buyPrice && candle.HighPrice >= buyPrice)
		{
			BuyMarket();
			_entryPrice = buyPrice;
			_pendingBuyPrice = null;
			_pendingSellPrice = null;
			return;
		}

		if (_pendingSellPrice is decimal sellPrice && candle.LowPrice <= sellPrice)
		{
			SellMarket();
			_entryPrice = sellPrice;
			_pendingBuyPrice = null;
			_pendingSellPrice = null;
		}
	}

	private void ManagePosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longStop > 0m && candle.LowPrice <= _longStop)
			{
				SellMarket();
				ResetPositionState();
				return;
			}
			if (_longTake > 0m && candle.HighPrice >= _longTake)
			{
				SellMarket();
				ResetPositionState();
			}
		}
		else if (Position < 0)
		{
			if (_shortStop > 0m && candle.HighPrice >= _shortStop)
			{
				BuyMarket();
				ResetPositionState();
				return;
			}
			if (_shortTake > 0m && candle.LowPrice <= _shortTake)
			{
				BuyMarket();
				ResetPositionState();
			}
		}
	}

	private void ResetPositionState()
	{
		_entryPrice = 0m;
		_longStop = 0m;
		_longTake = 0m;
		_shortStop = 0m;
		_shortTake = 0m;
		_pendingBuyPrice = null;
		_pendingSellPrice = null;
	}
}