Auf GitHub ansehen

Grid-Handel am volatilen Markt

Überblick

Diese Strategie repliziert den MetaTrader-Experten „Gridtrading_at_volatile_market.mq4“ unter Verwendung der StockSharp-Hochebene API. Es handelt um Donchian Kanalgrenzen, die in einem längeren Zeitrahmen erkannt wurden, und bestätigt gleichzeitig Einträge mit verschlingenden Mustern im Handelszeitrahmen. Sobald ein Raster aktiv ist, fügt die Strategie durchschnittliche Aufträge hinzu, wenn der Preis um ein Vielfaches des höheren Zeitrahmens ATR steigt, und endet, wenn die Portfoliogewinn- oder Drawdown-Ziele erreicht sind.

Wie es funktioniert

  1. Es werden zwei Candle-Streams verwendet: der vom Benutzer ausgewählte Handelszeitrahmen und ein automatisch daraus abgeleiteter höherer Zeitrahmen (M1→M5→M15→M30→H1→H4→D1).
  2. Auf dem höheren Zeitrahmen berechnet die Strategie:
    • ATR(20) zur Größe des Rasterabstands.
    • SMA(SlowMaLength), um den Trend zusammen mit RSI zu filtern.
    • DonchianChannels(20) für Unterstützungs- und Widerstandsniveaus.
  3. Im Handelszeitraum werden die letzten beiden abgeschlossenen Kerzen verfolgt, um bullische oder bärische Engulfing-Muster zu erkennen.
  4. Ein langes Raster beginnt, wenn die vorherige Kerze das untere Band von Donchian berührt, ein bullisches Engulfing-Muster bildet und RSI überverkaufte Bedingungen bestätigt (RSI < 35, während der Preis über dem höheren Zeitrahmen SMA liegt). Ein kurzes Gitter spiegelt diese Regeln im oberen Band mit RSI > 65 wider.
  5. Nach der ersten Marktorder behält die Strategie den Anfangspreis als Anker bei. Wenn sich der Preis im aktuellen Rasterschritt um 2 * ATR gegenüber der Position bewegt, wird eine weitere Order mit einem Volumen multipliziert mit GridMultiplier hinzugefügt.
  6. Das Raster wird geschlossen und alle Bestellungen werden storniert, wenn:
    • Der kombinierte (realisierte + nicht realisierte) PnL übersteigt TakeProfitFactor * total grid volume.
    • Der Drawdown fällt unter -MaxDrawdownFraction * initial portfolio value.

Parameter

  • TakeProfitFactor – Gewinnmultiplikator des gesamten Netzvolumens, das zum Schließen des Netzes erforderlich ist (Standard 0.1).
  • SlowMaLength – Zeitraum des höheren Zeitrahmens SMA, der zum Filtern verwendet wird (Standard 50).
  • GridMultiplier – geometrischer Faktor, der auf jede zusätzliche Mittelungsreihenfolge angewendet wird (Standard 1.5).
  • BaseOrderVolume – Volumen der ersten Ordnung im Raster (Standard 0.1).
  • MaxDrawdownFraction – maximaler Verlust im Verhältnis zum anfänglichen Portfoliowert, bevor das Raster zwangsweise geschlossen wird (Standard 0.8).
  • CandleType – Handelszeitraum. Der höhere Zeitrahmen wird automatisch abgeleitet.

Notizen

  • Es werden nur geschlossene Kerzen verarbeitet, um ein Nachlackieren zu vermeiden.
  • Die Strategie stützt sich auf verfügbare Geld-/Briefkurse, um den offenen PnL zu bewerten; Wenn nur die letzten Handelspreise angegeben werden, ist die Näherung möglicherweise weniger genau.
  • Wenn die Portfolioinformationen nicht verfügbar sind, wird der Drawdown-Schutz übersprungen, sodass das Raster weiterläuft, bis das Gewinnziel erreicht ist oder die Position manuell geschlossen wird.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Grid Trading at Volatile Market" MetaTrader expert.
/// Uses RSI + SMA trend filter for initial entry then manages a simple
/// grid of averaging orders based on ATR distance.
/// </summary>
public class GridTradingAtVolatileMarketStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _maxGridLevels;

	private RelativeStrengthIndex _rsi;
	private readonly Queue<decimal> _smaQueue = new();
	private decimal _smaSum;

	private Sides? _gridDirection;
	private int _gridLevel;
	private decimal _lastEntryPrice;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	public int MaxGridLevels
	{
		get => _maxGridLevels.Value;
		set => _maxGridLevels.Value = value;
	}

	public GridTradingAtVolatileMarketStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signal detection", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period for entry signals", "Indicators");

		_smaPeriod = Param(nameof(SmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "SMA period for trend filter", "Indicators");

		_maxGridLevels = Param(nameof(MaxGridLevels), 3)
			.SetGreaterThanZero()
			.SetDisplay("Max Grid Levels", "Maximum averaging levels", "Grid");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_smaQueue.Clear();
		_smaSum = 0;
		_gridDirection = null;
		_gridLevel = 0;
		_lastEntryPrice = 0;

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

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

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

		var close = candle.ClosePrice;

		// Manual SMA
		_smaQueue.Enqueue(close);
		_smaSum += close;
		while (_smaQueue.Count > SmaPeriod)
			_smaSum -= _smaQueue.Dequeue();

		if (!_rsi.IsFormed || _smaQueue.Count < SmaPeriod)
			return;

		var smaValue = _smaSum / _smaQueue.Count;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// If no grid active, look for entry signals
		if (_gridDirection is null)
		{
			// Buy: RSI oversold + price below SMA (mean reversion)
			if (rsiValue < 35 && close < smaValue)
			{
				if (Position < 0)
					BuyMarket(Math.Abs(Position));

				BuyMarket(volume);
				_gridDirection = Sides.Buy;
				_gridLevel = 1;
				_lastEntryPrice = close;
			}
			// Sell: RSI overbought + price above SMA
			else if (rsiValue > 65 && close > smaValue)
			{
				if (Position > 0)
					SellMarket(Position);

				SellMarket(volume);
				_gridDirection = Sides.Sell;
				_gridLevel = 1;
				_lastEntryPrice = close;
			}
		}
		else
		{
			// Grid management - add levels on adverse moves
			var distanceThreshold = _lastEntryPrice * 0.005m; // 0.5% grid step

			if (_gridDirection == Sides.Buy)
			{
				// Price moved further down - add to grid
				if (_gridLevel < MaxGridLevels && close < _lastEntryPrice - distanceThreshold)
				{
					BuyMarket(volume);
					_gridLevel++;
					_lastEntryPrice = close;
				}
				// Take profit - price recovered above SMA
				else if (close > smaValue && rsiValue > 50)
				{
					if (Position > 0)
						SellMarket(Position);
					_gridDirection = null;
					_gridLevel = 0;
				}
			}
			else if (_gridDirection == Sides.Sell)
			{
				// Price moved further up - add to grid
				if (_gridLevel < MaxGridLevels && close > _lastEntryPrice + distanceThreshold)
				{
					SellMarket(volume);
					_gridLevel++;
					_lastEntryPrice = close;
				}
				// Take profit - price fell below SMA
				else if (close < smaValue && rsiValue < 50)
				{
					if (Position < 0)
						BuyMarket(Math.Abs(Position));
					_gridDirection = null;
					_gridLevel = 0;
				}
			}
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_rsi = null;
		_smaQueue.Clear();
		_smaSum = 0;
		_gridDirection = null;
		_gridLevel = 0;
		_lastEntryPrice = 0;

		base.OnReseted();
	}
}