Ver en GitHub

Estrategia de promediación de Amstell SL

Conversión del MetaTrader asesor experto exp_Amstell-SL. El sistema abre inmediatamente tanto una posición larga como una corta, agrega nuevas órdenes cada vez que el precio se mueve contra la entrada más reciente por un número fijo de puntos, y se basa en niveles virtuales de toma de ganancias y stop-loss (administrados por software) para salir de cada boleto individualmente.

Lógica de la estrategia

  • Entradas iniciales: Cuando la estrategia comienza y no hay operaciones abiertas, envía una compra de mercado (a la demanda) y una venta de mercado (a la oferta).
  • Pirámide en reducción:
    • Lado largo: siempre que la demanda actual esté ReentryPoints (por defecto, 10 puntos) por debajo del último precio de entrada larga, se envía una nueva orden de compra del mismo volumen.
    • Lado corto: siempre que la oferta actual esté ReentryPoints por encima del último precio de entrada corta, se abre una nueva orden de venta del mismo volumen.
  • Reglas de Salida (gestión virtual):
    • Para cada ticket largo, la estrategia monitorea la mejor oferta y la mejor demanda. Si la oferta aumenta un TakeProfitPoints con respecto al precio de la orden o la demanda cae un StopLossPoints, la posición se cierra en el mercado.
    • Para cada ticket corto, verifica si la demanda es menor en TakeProfitPoints o la oferta es mayor en StopLossPoints; en cualquier caso, la orden de venta se cubre en el mercado.
  • Orden de procesamiento: las salidas se evalúan antes de cualquier entrada nueva, replicando el script MetaTrader que detiene acciones adicionales después de cerrar una posición en el tick actual.

Parámetros

  • TakeProfitPoints – distancia (en pasos de precio) utilizada para cerrar posiciones rentables. Predeterminado: 30.
  • StopLossPoints – distancia (en pasos de precio) para salidas protectoras. Predeterminado: 30.
  • Volume: tamaño de lote para cada pedido recién abierto. Predeterminado: 0.01.
  • ReentryPoints – movimiento adverso (en pasos de precio) necesario para acumular una orden adicional en el lado correspondiente. Predeterminado: 10.

Notas adicionales

  • El valor del punto se deriva de Security.PriceStep; si el intercambio no lo proporciona, se utiliza un valor de 1.
  • La estrategia puede ser simultáneamente larga y corta porque rastrea la compra y venta de boletos de forma independiente, coincidiendo con el comportamiento de cobertura del asesor experto original.
  • Los niveles de toma de ganancias y límite de pérdidas se ejecutan virtualmente mediante órdenes de mercado; no se colocan en el libro de órdenes de cambio.
  • El riesgo aumenta rápidamente cuando los precios tienen una fuerte tendencia en una dirección porque se abren órdenes adicionales sin reducir la exposición anterior.
  • Funciona mejor con símbolos donde la noción de "punto" equivale a un incremento mínimo de precio, por ejemplo, pares de divisas importantes con precios de estilo MetaTrader.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Amstell SL: Grid averaging strategy with ATR-based take profit and stop loss.
/// Adds positions on adverse moves and exits on profit/stop targets.
/// </summary>
public class AmstellSlStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<int> _emaLength;

	private decimal _entryPrice;
	private decimal _prevEma;
	private int _gridCount;
	private int _cooldown;

	public AmstellSlStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");

		_emaLength = Param(nameof(EmaLength), 50)
			.SetDisplay("EMA Length", "EMA trend filter.", "Indicators");
	}

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

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

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

		_entryPrice = 0;
		_prevEma = 0;
		_gridCount = 0;
		_cooldown = 0;
	}

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

		_entryPrice = 0;
		_prevEma = 0;
		_gridCount = 0;
		_cooldown = 0;

		var atr = new AverageTrueRange { Length = AtrLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };

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

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

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

		if (atrVal <= 0 || _prevEma == 0)
		{
			_prevEma = emaVal;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevEma = emaVal;
			if (Position == 0) return;
		}

		var close = candle.ClosePrice;

		// Position management with grid and stops
		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2.5m)
			{
				SellMarket();
				_entryPrice = 0;
				_gridCount = 0;
				_cooldown = 10;
			}
			else if (close <= _entryPrice - atrVal * 4m)
			{
				SellMarket();
				_entryPrice = 0;
				_gridCount = 0;
				_cooldown = 10;
			}
			else if (_gridCount < 1 && close <= _entryPrice - atrVal * 2m)
			{
				_entryPrice = (_entryPrice + close) / 2m;
				_gridCount++;
				BuyMarket();
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2.5m)
			{
				BuyMarket();
				_entryPrice = 0;
				_gridCount = 0;
				_cooldown = 10;
			}
			else if (close >= _entryPrice + atrVal * 4m)
			{
				BuyMarket();
				_entryPrice = 0;
				_gridCount = 0;
				_cooldown = 10;
			}
			else if (_gridCount < 1 && close >= _entryPrice + atrVal * 2m)
			{
				_entryPrice = (_entryPrice + close) / 2m;
				_gridCount++;
				SellMarket();
			}
		}

		// Entry on EMA trend
		if (Position == 0 && _cooldown == 0)
		{
			if (close > emaVal && emaVal > _prevEma)
			{
				_entryPrice = close;
				_gridCount = 0;
				BuyMarket();
			}
			else if (close < emaVal && emaVal < _prevEma)
			{
				_entryPrice = close;
				_gridCount = 0;
				SellMarket();
			}
		}

		_prevEma = emaVal;
	}
}