Ver en GitHub

Negociación en red en un mercado volátil

Descripción general

Esta estrategia replica el MetaTrader experto "Gridtrading_at_volatile_market.mq4" utilizando la API de alto nivel de StockSharp. Opera alrededor de Donchian límites de canal detectados en un período de tiempo más alto mientras confirma entradas con patrones envolventes en el período de tiempo de negociación. Una vez que una cuadrícula está activa, la estrategia agrega órdenes promedio cuando el precio se extiende en múltiplos del período de tiempo más alto ATR y sale cuando se alcanzan los objetivos de ganancias o reducción de la cartera.

Cómo funciona

  1. Se utilizan dos flujos de velas: el marco temporal de negociación seleccionado por el usuario y un marco temporal superior derivado automáticamente de él (M1→M5→M15→M30→H1→H4→D1).
  2. En el marco de tiempo más alto, la estrategia calcula:
    • ATR(20) para dimensionar el espacio de la cuadrícula.
    • SMA(SlowMaLength) para filtrar la tendencia junto con RSI.
    • DonchianChannels(20) para niveles de soporte y resistencia.
  3. En el período de negociación, rastrea las dos últimas velas completadas para detectar patrones envolventes alcistas o bajistas.
  4. Una grilla larga comienza cuando la vela anterior toca la banda inferior Donchian, forma un patrón envolvente alcista y RSI confirma condiciones de sobreventa (RSI < 35 mientras el precio está por encima del marco temporal superior SMA). Una cuadrícula corta refleja estas reglas en la banda superior con RSI > 65.
  5. Después de la primera orden de mercado, la estrategia mantiene el precio inicial como ancla. Si el precio se mueve contra la posición en 2 * ATR para el paso actual de la cuadrícula, agrega otra orden con el volumen multiplicado por GridMultiplier.
  6. La grilla se cierra y todos los pedidos se cancelan cuando:
    • El PnL combinado (realizado + no realizado) supera TakeProfitFactor * total grid volume.
    • La reducción cae por debajo de -MaxDrawdownFraction * initial portfolio value.

Parámetros

  • TakeProfitFactor: múltiplo de beneficio del volumen total de la red requerido para cerrar la red (predeterminado 0.1).
  • SlowMaLength: período del período de tiempo más alto SMA utilizado para el filtrado (predeterminado 50).
  • GridMultiplier: factor geométrico aplicado a cada orden de promedio adicional (predeterminado 1.5).
  • BaseOrderVolume: volumen del primer pedido en la cuadrícula (predeterminado 0.1).
  • MaxDrawdownFraction: pérdida máxima relativa al valor inicial de la cartera antes de que se cierre forzosamente la red (predeterminado 0.8).
  • CandleType – plazo de negociación. El plazo más alto se deduce automáticamente.

Notas

  • Sólo se procesan velas cerradas para evitar repintar.
  • La estrategia se basa en las cotizaciones de oferta y demanda disponibles para evaluar la PnL abierta; si sólo se proporcionan los últimos precios comerciales, la aproximación puede ser menos precisa.
  • Cuando la información de la cartera no está disponible, se omite la protección de reducción, lo que permite que la red funcione hasta que se alcance el objetivo de ganancias o se cierre la posición manualmente.
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();
	}
}